@absolutejs/absolute 0.20.0-beta.100 → 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
@@ -19495,7 +19496,7 @@ var writeAbsoluteMobileUpdateRegistry = async (options) => {
19495
19496
 
19496
19497
  // src/mobile/releaseDoctor.ts
19497
19498
  var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1;
19498
- var HMR_ASSET_PATTERN = /(?:__HMR_WS__|hmr-timing|__absolute_target|absolutejs-error-overlay)/u;
19499
+ var HMR_ASSET_PATTERN = /(?:hmr-timing|__absolute_target|absolutejs-error-overlay)/u;
19499
19500
  var RELEASE_ASSET_EXTENSIONS = new Set([".html", ".js", ".mjs"]);
19500
19501
  var EXACT_VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/u;
19501
19502
  var NOT_FOUND3 = -1;
@@ -19641,12 +19642,48 @@ var expoVersionCheck = async (config) => {
19641
19642
  return fail("mobile.expo-versions", error instanceof Error ? error.message : "Generated Expo dependency versions could not be validated.", manifestPath, "Run `absolute mobile init --yes`, then rebuild the production Expo project.");
19642
19643
  }
19643
19644
  };
19645
+ var dependencyLockAt = async (directory) => (await Promise.all(LOCK_FILES.map(async (name) => ({
19646
+ exists: await pathExists2(join7(directory, name)),
19647
+ name,
19648
+ path: join7(directory, name)
19649
+ })))).find(({ exists: exists2 }) => exists2);
19650
+ var declaredWorkspacePatterns = (declared) => {
19651
+ if (Array.isArray(declared))
19652
+ return declared;
19653
+ if (isRecord3(declared) && Array.isArray(declared.packages))
19654
+ return declared.packages;
19655
+ return [];
19656
+ };
19657
+ var ownsWorkspace = async (directory, projectRoot) => {
19658
+ const manifest = await readJsonObject(join7(directory, "package.json")).catch(() => {
19659
+ return;
19660
+ });
19661
+ const workspacePath = relative3(directory, projectRoot).replace(/\\/gu, "/");
19662
+ return declaredWorkspacePatterns(manifest?.workspaces).some((pattern) => typeof pattern === "string" && new Bun.Glob(pattern).match(workspacePath));
19663
+ };
19664
+ var ancestorDirectories = (projectRoot) => {
19665
+ const directories = [];
19666
+ let directory = projectRoot;
19667
+ while (true) {
19668
+ directories.push(directory);
19669
+ const parent = dirname5(directory);
19670
+ if (parent === directory)
19671
+ return directories;
19672
+ directory = parent;
19673
+ }
19674
+ };
19675
+ var findAbsoluteDependencyLock = async (projectRoot) => {
19676
+ const candidates = await Promise.all(ancestorDirectories(projectRoot).map(async (directory, index) => {
19677
+ const present = await dependencyLockAt(directory);
19678
+ if (!present || index > 0 && !await ownsWorkspace(directory, projectRoot))
19679
+ return;
19680
+ return present;
19681
+ }));
19682
+ return candidates.find((candidate) => candidate !== undefined);
19683
+ };
19644
19684
  var dependencyLockCheck = async (projectRoot) => {
19645
- const present = (await Promise.all(LOCK_FILES.map(async (name) => ({
19646
- exists: await pathExists2(join7(projectRoot, name)),
19647
- name
19648
- })))).find(({ exists: exists2 }) => exists2);
19649
- return present ? pass("mobile.dependency-lock", `Dependency graph is locked by ${present.name}.`, join7(projectRoot, present.name)) : fail("mobile.dependency-lock", "No supported dependency lockfile is present.", projectRoot, "Install dependencies with the project package manager and commit its lockfile before release.");
19685
+ const present = await findAbsoluteDependencyLock(projectRoot);
19686
+ return present ? pass("mobile.dependency-lock", `Dependency graph is locked by ${present.name}.`, present.path) : fail("mobile.dependency-lock", "No supported dependency lockfile is present.", projectRoot, "Install dependencies with the project package manager and commit its lockfile before release.");
19650
19687
  };
19651
19688
  var productionOriginCheck = (config, projectRoot) => {
19652
19689
  const origin = new URL(config.productionOrigin);
@@ -19802,21 +19839,23 @@ var expoEmbeddedAssetsCheck = async (config) => {
19802
19839
  cwd: assetsRoot,
19803
19840
  onlyFiles: true
19804
19841
  }))).sort();
19805
- if (sourceFiles.length === 0 || sourceFiles.length !== embeddedFiles.length)
19842
+ if (sourceFiles.length === 0 || embeddedFiles.length !== 1)
19806
19843
  throw new TypeError("Generated Expo asset count does not match the prepared mobile bundle.");
19807
- const matches = await Promise.all(sourceFiles.map(async (path, index) => {
19808
- const embedded = embeddedFiles[index];
19809
- if (!embedded)
19810
- return false;
19811
- const [left, right] = await Promise.all([
19812
- readFile8(join7(config.bundleDirectory, path)),
19813
- readFile8(join7(assetsRoot, embedded))
19814
- ]);
19815
- return left.equals(right);
19816
- }));
19817
- 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))))
19818
19857
  throw new TypeError("Generated Expo asset bytes differ from the prepared mobile bundle.");
19819
- 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);
19820
19859
  } catch (error) {
19821
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.");
19822
19861
  }
@@ -20383,6 +20422,14 @@ var signAab = (artifactPath, capture, jarsigner, signing) => {
20383
20422
  if (result.exitCode !== 0)
20384
20423
  throw new TypeError("jarsigner could not sign the Android App Bundle with the configured CI identity.");
20385
20424
  };
20425
+ var windowsJarsignerPath = (path, capture) => {
20426
+ const result = capture(["wslpath", "-w", path]);
20427
+ const translated = result.stdout.trim();
20428
+ if (result.exitCode !== 0 || !translated) {
20429
+ throw new TypeError(`Could not translate an Android signing path for the Windows JDK: ${path}`);
20430
+ }
20431
+ return translated;
20432
+ };
20386
20433
  var sha256File = async (path) => createHash2("sha256").update(await readFile9(path)).digest("hex");
20387
20434
  var fingerprintExpoAndroidProject = async (nativeDirectory) => {
20388
20435
  const root = await realpath(nativeDirectory);
@@ -20500,13 +20547,19 @@ var buildAbsoluteAndroidRelease = async (options) => {
20500
20547
  throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
20501
20548
  }
20502
20549
  const capture = options.capture ?? defaultCapture;
20503
- const jarsigner = options.jarsigner === undefined ? Bun.which("jarsigner") : options.jarsigner;
20504
- let signed = verifyAabSignature(artifactPath, capture, jarsigner);
20505
- if (signed === false && options.signing) {
20550
+ const jarsigner = options.jarsigner === undefined ? Bun.which("jarsigner") ?? (host === "wsl" || host === "windows" ? Bun.which("jarsigner.exe") : null) : options.jarsigner;
20551
+ const usesWindowsJarsigner = host === "wsl" && jarsigner?.toLowerCase().endsWith(".exe");
20552
+ const signerArtifactPath = usesWindowsJarsigner ? windowsJarsignerPath(artifactPath, capture) : artifactPath;
20553
+ const signing = usesWindowsJarsigner && options.signing ? {
20554
+ ...options.signing,
20555
+ keystorePath: windowsJarsignerPath(options.signing.keystorePath, capture)
20556
+ } : options.signing;
20557
+ let signed = verifyAabSignature(signerArtifactPath, capture, jarsigner);
20558
+ if (signed === false && signing) {
20506
20559
  if (!jarsigner)
20507
20560
  throw new TypeError("Could not sign the Android App Bundle because jarsigner is unavailable.");
20508
- signAab(artifactPath, capture, jarsigner, options.signing);
20509
- signed = verifyAabSignature(artifactPath, capture, jarsigner);
20561
+ signAab(signerArtifactPath, capture, jarsigner, signing);
20562
+ signed = verifyAabSignature(signerArtifactPath, capture, jarsigner);
20510
20563
  if (!signed)
20511
20564
  throw new TypeError("Android App Bundle signature verification failed after CI signing.");
20512
20565
  }
@@ -20538,6 +20591,343 @@ var buildAbsoluteAndroidRelease = async (options) => {
20538
20591
  return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
20539
20592
  };
20540
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
+
20541
20931
  // src/mobile/iosDeviceAcceptance.ts
20542
20932
  var absoluteIosDeviceAcceptanceCommands = (options) => {
20543
20933
  const xcrun = options.xcrun ?? "/usr/bin/xcrun";
@@ -20556,20 +20946,20 @@ var absoluteIosDeviceAcceptanceCommands = (options) => {
20556
20946
  ]
20557
20947
  };
20558
20948
  };
20559
- var requireSuccess = (result, message) => {
20949
+ var requireSuccess2 = (result, message) => {
20560
20950
  if (result.exitCode !== 0)
20561
20951
  throw new Error(message);
20562
20952
  return result;
20563
20953
  };
20564
20954
  var testAbsoluteIosPhysicalDevice = async (options) => {
20565
20955
  const commands = absoluteIosDeviceAcceptanceCommands(options);
20566
- 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.");
20567
- 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.");
20568
20958
  if (!apps.stdout.includes(options.appId))
20569
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.");
20570
20960
  const now = options.now ?? performance.now.bind(performance);
20571
20961
  const startedAt = now();
20572
- 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.");
20573
20963
  await options.waitForHmr();
20574
20964
  return {
20575
20965
  hmrConnected: true,
@@ -20579,7 +20969,7 @@ var testAbsoluteIosPhysicalDevice = async (options) => {
20579
20969
  };
20580
20970
 
20581
20971
  // src/mobile/iosConformance.ts
20582
- import { readFile as readFile10, stat as stat3 } from "fs/promises";
20972
+ import { readFile as readFile11, stat as stat4 } from "fs/promises";
20583
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");
20584
20974
  var parseAbsoluteIosHmrLog = (line) => {
20585
20975
  const match = HMR_LINE.exec(line);
@@ -20611,13 +21001,13 @@ var waitForAbsoluteIosHmrLog = async (options) => {
20611
21001
  const sleep = options.sleep ?? Bun.sleep;
20612
21002
  const timeoutMs = options.timeoutMs ?? 30000;
20613
21003
  const deadline = Date.now() + timeoutMs;
20614
- 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);
20615
21005
  let buffered = "";
20616
21006
  const poll = async () => {
20617
21007
  if (Date.now() > deadline)
20618
21008
  throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
20619
21009
  options.signal?.throwIfAborted();
20620
- const contents = await readFile10(options.logPath).catch(() => Buffer.alloc(0));
21010
+ const contents = await readFile11(options.logPath).catch(() => Buffer.alloc(0));
20621
21011
  if (contents.byteLength < offset) {
20622
21012
  offset = 0;
20623
21013
  buffered = "";
@@ -20638,8 +21028,8 @@ var waitForAbsoluteIosHmrLog = async (options) => {
20638
21028
  };
20639
21029
 
20640
21030
  // src/mobile/nativeTestReport.ts
20641
- import { mkdir as mkdir7, readFile as readFile11, writeFile as writeFile9 } from "fs/promises";
20642
- 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";
20643
21033
  var secretPattern = /(authorization|access[_ -]?token|refresh[_ -]?token|socket[_ -]?ticket|password|cookie)(\s*[=:]\s*)([^\s,;]+)/giu;
20644
21034
  var bearerPattern = /bearer\s+[^\s,;]+/giu;
20645
21035
  var coordinatePattern = /\b(latitude|longitude|lat|lng)(\s*[=:]\s*)-?\d+(?:\.\d+)?/giu;
@@ -20663,6 +21053,14 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
20663
21053
  artifactDetails = "Physical-device screen capture was intentionally skipped; use Xcode Device Hub for manual visual evidence.";
20664
21054
  artifactResult = "SKIPPED";
20665
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)`}.`;
20666
21064
  const checks = [
20667
21065
  {
20668
21066
  details: `Captured host, toolchain, Bun, and AbsoluteJS metadata for ${target}.`,
@@ -20670,13 +21068,13 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
20670
21068
  result: "PASS"
20671
21069
  },
20672
21070
  {
20673
- 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,
20674
21072
  ...evidence ? { evidence } : {},
20675
21073
  id: "AUTO-DEV-01",
20676
21074
  result: run.status === "pass" ? "PASS" : "FAIL"
20677
21075
  },
20678
21076
  {
20679
- 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,
20680
21078
  id: "AUTO-HMR-01",
20681
21079
  result: hmrResult
20682
21080
  },
@@ -20687,6 +21085,17 @@ var createAbsoluteNativeAutomatedChecks = (run) => {
20687
21085
  result: artifactResult
20688
21086
  }
20689
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
+ }
20690
21099
  if (run.upgrade) {
20691
21100
  const { upgrade } = run;
20692
21101
  checks.push({
@@ -20749,7 +21158,7 @@ var createAbsoluteNativeTestReport = (options) => ({
20749
21158
  run: options.run
20750
21159
  });
20751
21160
  var readPackageVersionForNativeReport = async (packageJsonPath) => {
20752
- const manifest = JSON.parse(await readFile11(packageJsonPath, "utf8"));
21161
+ const manifest = JSON.parse(await readFile12(packageJsonPath, "utf8"));
20753
21162
  if (typeof manifest !== "object" || manifest === null)
20754
21163
  return "unknown";
20755
21164
  const version = Reflect.get(manifest, "version");
@@ -20787,13 +21196,13 @@ ${table(report.manualChecks)}
20787
21196
  `;
20788
21197
  };
20789
21198
  var writeAbsoluteNativeTestReport = async (directory, report) => {
20790
- await mkdir7(directory, { recursive: true });
20791
- const jsonPath = join9(directory, "report.json");
20792
- 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");
20793
21202
  await Promise.all([
20794
- writeFile9(jsonPath, `${JSON.stringify(report, null, 2)}
21203
+ writeFile10(jsonPath, `${JSON.stringify(report, null, 2)}
20795
21204
  `),
20796
- writeFile9(markdownPath, renderAbsoluteNativeTestReport(report))
21205
+ writeFile10(markdownPath, renderAbsoluteNativeTestReport(report))
20797
21206
  ]);
20798
21207
  return { directory, jsonPath, markdownPath };
20799
21208
  };
@@ -21021,6 +21430,8 @@ var MANUAL_CHECKS2 = [
21021
21430
  "Review this directory for sensitive content and complete every row."
21022
21431
  ]
21023
21432
  ];
21433
+ var portableAdbVersion = (version) => version.split(/\r?\n/u).filter((line) => !/^\s*(?:Installed as|Running on)\b/iu.test(line)).join(`
21434
+ `).trim();
21024
21435
  var createAbsoluteAndroidTestReport = (options) => {
21025
21436
  const { serial, ...run } = options.run;
21026
21437
  return createAbsoluteNativeTestReport({
@@ -21028,10 +21439,10 @@ var createAbsoluteAndroidTestReport = (options) => {
21028
21439
  manualChecks: MANUAL_CHECKS2,
21029
21440
  metadata: {
21030
21441
  absolutejsVersion: options.absolutejsVersion,
21031
- adbVersion: options.adbVersion,
21442
+ adbVersion: portableAdbVersion(options.adbVersion),
21032
21443
  bunVersion: options.bunVersion,
21033
21444
  host: options.host,
21034
- provider: "capacitor"
21445
+ provider: options.provider ?? "capacitor"
21035
21446
  },
21036
21447
  run: {
21037
21448
  ...run,
@@ -21043,8 +21454,8 @@ var createAbsoluteAndroidTestReport = (options) => {
21043
21454
  };
21044
21455
 
21045
21456
  // src/mobile/releasePublisher.ts
21046
- import { access as access6 } from "fs/promises";
21047
- 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";
21048
21459
  import { pathToFileURL as pathToFileURL2 } from "url";
21049
21460
  var prepareAbsoluteIosRelease = async (publisher, options) => {
21050
21461
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -21067,24 +21478,24 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
21067
21478
  }
21068
21479
  return versionCode;
21069
21480
  };
21070
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21071
- 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";
21072
21483
  var publisherModulePath = (projectRoot, requested) => {
21073
- const root = resolve6(projectRoot);
21074
- const path = resolve6(root, requested);
21075
- const projectRelative = relative5(root, path);
21076
- 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)) {
21077
21488
  throw new TypeError("mobile publish --registry must remain inside the project.");
21078
21489
  }
21079
21490
  return path;
21080
21491
  };
21081
21492
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
21082
21493
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
21083
- await access6(modulePath).catch(() => {
21494
+ await access7(modulePath).catch(() => {
21084
21495
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
21085
21496
  });
21086
21497
  const loaded = await import(pathToFileURL2(modulePath).href);
21087
- const publisher = isRecord5(loaded) ? loaded.default ?? loaded.registry : undefined;
21498
+ const publisher = isRecord6(loaded) ? loaded.default ?? loaded.registry : undefined;
21088
21499
  if (!isPublisher(publisher)) {
21089
21500
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
21090
21501
  }
@@ -21142,8 +21553,8 @@ var publishAbsoluteIosRelease = async (options) => {
21142
21553
  };
21143
21554
 
21144
21555
  // src/mobile/mobileInspect.ts
21145
- import { access as access7, readFile as readFile12 } from "fs/promises";
21146
- 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";
21147
21558
  var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1;
21148
21559
  var MOBILE_PACKAGE_NAMES = new Set([
21149
21560
  "@absolutejs/absolute",
@@ -21158,19 +21569,19 @@ var MOBILE_PACKAGE_NAMES = new Set([
21158
21569
  ]);
21159
21570
  var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21160
21571
  var portablePath2 = (projectRoot, path) => {
21161
- const value = relative6(resolve7(projectRoot), resolve7(path)).replaceAll("\\", "/");
21572
+ const value = relative7(resolve8(projectRoot), resolve8(path)).replaceAll("\\", "/");
21162
21573
  return value || ".";
21163
21574
  };
21164
21575
  var pathExists4 = async (path) => {
21165
21576
  try {
21166
- await access7(path);
21577
+ await access8(path);
21167
21578
  return true;
21168
21579
  } catch {
21169
21580
  return false;
21170
21581
  }
21171
21582
  };
21172
21583
  var readObject2 = async (path) => {
21173
- const value = JSON.parse(await readFile12(path, "utf8"));
21584
+ const value = JSON.parse(await readFile13(path, "utf8"));
21174
21585
  if (!isObject2(value))
21175
21586
  throw new TypeError("JSON root must be an object.");
21176
21587
  return value;
@@ -21182,13 +21593,13 @@ var addPackageDeclarations = (declarations, value) => {
21182
21593
  declarations.set(name, declared);
21183
21594
  };
21184
21595
  var packageInspections = async (projectRoot, additionalNames) => {
21185
- const project = await readObject2(join10(projectRoot, "package.json"));
21596
+ const project = await readObject2(join11(projectRoot, "package.json"));
21186
21597
  const declarations = new Map;
21187
- for (const field of ["dependencies", "devDependencies"])
21188
- addPackageDeclarations(declarations, project[field]);
21598
+ for (const field2 of ["dependencies", "devDependencies"])
21599
+ addPackageDeclarations(declarations, project[field2]);
21189
21600
  const names = [...new Set([...declarations.keys(), ...additionalNames])].filter((name) => MOBILE_PACKAGE_NAMES.has(name) || name.startsWith("@capacitor/") || additionalNames.includes(name)).sort();
21190
21601
  return Promise.all(names.map(async (name) => {
21191
- 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(() => {
21192
21603
  return;
21193
21604
  });
21194
21605
  const installed = installedManifest?.version;
@@ -21247,7 +21658,7 @@ var inspectAbsoluteMobileProject = async (config, projectRoot, options = {}) =>
21247
21658
  },
21248
21659
  format: ABSOLUTE_MOBILE_INSPECTION_FORMAT,
21249
21660
  nativeProjects: await Promise.all(config.platforms.map(async (platform) => {
21250
- const path = join10(config.nativeProjectDirectory, platform);
21661
+ const path = join11(config.nativeProjectDirectory, platform);
21251
21662
  return {
21252
21663
  initialized: await pathExists4(path),
21253
21664
  path: portablePath2(projectRoot, path),
@@ -21304,8 +21715,8 @@ var renderAbsoluteMobileProjectInspection = (report) => {
21304
21715
 
21305
21716
  // src/mobile/ciWorkflow.ts
21306
21717
  import { existsSync } from "fs";
21307
- import { access as access8, mkdir as mkdir8, readFile as readFile13, writeFile as writeFile10 } from "fs/promises";
21308
- 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";
21309
21720
  var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
21310
21721
  var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
21311
21722
  var CI_ENV_INDENTATION = 6;
@@ -21324,34 +21735,34 @@ var RESERVED_SECRET_NAMES = new Set([
21324
21735
  "APP_STORE_CONNECT_KEY_ID",
21325
21736
  "APP_STORE_CONNECT_PRIVATE_KEY_BASE64"
21326
21737
  ]);
21327
- var exists2 = async (path) => {
21738
+ var exists3 = async (path) => {
21328
21739
  try {
21329
- await access8(path);
21740
+ await access9(path);
21330
21741
  return true;
21331
21742
  } catch {
21332
21743
  return false;
21333
21744
  }
21334
21745
  };
21335
21746
  var yamlString = (value) => `'${value.replaceAll("'", "''")}'`;
21336
- var projectPath2 = (projectRoot, value, field, options = {}) => {
21337
- const root = resolve8(projectRoot);
21338
- const path = resolve8(root, value);
21339
- const portable = relative7(root, path).replaceAll("\\", "/");
21340
- if (portable === ".." || portable.startsWith(`..${sep4}`) || portable.startsWith("../") || portable === "") {
21341
- 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.`);
21342
21753
  }
21343
21754
  if (/\r|\n/u.test(portable) || portable.startsWith("-"))
21344
- throw new TypeError(`${field} contains an unsafe path.`);
21755
+ throw new TypeError(`${field2} contains an unsafe path.`);
21345
21756
  if (!options.allowMissing && !existsSync(path))
21346
- throw new TypeError(`${field} does not exist inside the project.`);
21757
+ throw new TypeError(`${field2} does not exist inside the project.`);
21347
21758
  return portable;
21348
21759
  };
21349
21760
  var workflowOutputPath = (projectRoot, value) => {
21350
- const root = resolve8(projectRoot);
21351
- const workflows = resolve8(root, ".github/workflows");
21352
- const path = resolve8(root, value ?? ".github/workflows/absolute-mobile.yml");
21353
- const portable = relative7(workflows, path);
21354
- 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") {
21355
21766
  throw new TypeError("mobile ci github --output must be a .yml or .yaml file inside .github/workflows.");
21356
21767
  }
21357
21768
  return path;
@@ -21780,12 +22191,12 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
21780
22191
  var writeAbsoluteMobileGithubWorkflow = async (options) => {
21781
22192
  const path = workflowOutputPath(options.projectRoot, options.outputPath);
21782
22193
  const generated = createAbsoluteMobileGithubWorkflow(options);
21783
- const previous = await exists2(path) ? await readFile13(path, "utf8") : undefined;
22194
+ const previous = await exists3(path) ? await readFile14(path, "utf8") : undefined;
21784
22195
  if (previous !== undefined && previous !== generated.workflow && !options.force)
21785
- 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.`);
21786
22197
  if (previous !== generated.workflow) {
21787
- await mkdir8(dirname7(path), { recursive: true });
21788
- await writeFile10(path, generated.workflow);
22198
+ await mkdir9(dirname8(path), { recursive: true });
22199
+ await writeFile11(path, generated.workflow);
21789
22200
  }
21790
22201
  return {
21791
22202
  changed: previous !== generated.workflow,
@@ -21798,46 +22209,46 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
21798
22209
  };
21799
22210
 
21800
22211
  // src/mobile/updateSigning.ts
21801
- import { createHash as createHash3, sign, verify } from "crypto";
22212
+ import { createHash as createHash4, sign, verify } from "crypto";
21802
22213
  import {
21803
22214
  cp,
21804
- mkdir as mkdir9,
21805
- mkdtemp as mkdtemp2,
22215
+ mkdir as mkdir10,
22216
+ mkdtemp as mkdtemp3,
21806
22217
  readdir as readdir4,
21807
- readFile as readFile14,
21808
- rename as rename8,
21809
- rm as rm5,
21810
- stat as stat4,
21811
- writeFile as writeFile11
22218
+ readFile as readFile15,
22219
+ rename as rename9,
22220
+ rm as rm6,
22221
+ stat as stat5,
22222
+ writeFile as writeFile12
21812
22223
  } from "fs/promises";
21813
- 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";
21814
22225
  var UPDATE_MANIFEST_FILE = "update.json";
21815
22226
  var UPDATE_FILES_DIRECTORY = "files";
21816
- var sha256 = (value) => createHash3("sha256").update(value).digest("hex");
22227
+ var sha2562 = (value) => createHash4("sha256").update(value).digest("hex");
21817
22228
  var listFiles = async (root, directory = root) => {
21818
22229
  const entries = await readdir4(directory, { withFileTypes: true });
21819
22230
  const paths = await Promise.all(entries.map(async (entry) => {
21820
- const path = join11(directory, entry.name);
22231
+ const path = join12(directory, entry.name);
21821
22232
  if (entry.isDirectory())
21822
22233
  return listFiles(root, path);
21823
22234
  if (!entry.isFile())
21824
22235
  throw new TypeError("Mobile updates cannot contain links or special files.");
21825
- return [relative8(root, path).replaceAll("\\", "/")];
22236
+ return [relative9(root, path).replaceAll("\\", "/")];
21826
22237
  }));
21827
22238
  return paths.flat().sort((left, right) => left.localeCompare(right));
21828
22239
  };
21829
22240
  var inspectFiles = async (root, paths) => Promise.all(paths.map(async (path) => {
21830
- const source = join11(root, path);
22241
+ const source = join12(root, path);
21831
22242
  const [metadata, contents] = await Promise.all([
21832
- stat4(source),
21833
- readFile14(source)
22243
+ stat5(source),
22244
+ readFile15(source)
21834
22245
  ]);
21835
- return { bytes: metadata.size, path, sha256: sha256(contents) };
22246
+ return { bytes: metadata.size, path, sha256: sha2562(contents) };
21836
22247
  }));
21837
- var releaseIdFor = (value) => `amu_${sha256(canonicalizeAbsoluteMobileUpdate(value))}`;
22248
+ var releaseIdFor = (value) => `amu_${sha2562(canonicalizeAbsoluteMobileUpdate(value))}`;
21838
22249
  var buildAbsoluteMobileUpdate = async (options) => {
21839
- const bundleDirectory = resolve9(options.bundleDirectory);
21840
- const outputRoot = resolve9(options.outputDirectory);
22250
+ const bundleDirectory = resolve10(options.bundleDirectory);
22251
+ const outputRoot = resolve10(options.outputDirectory);
21841
22252
  if (outputRoot === bundleDirectory || outputRoot.startsWith(`${bundleDirectory}/`))
21842
22253
  throw new TypeError("Mobile update output must be outside the embedded bundle.");
21843
22254
  const paths = await listFiles(bundleDirectory);
@@ -21865,30 +22276,30 @@ var buildAbsoluteMobileUpdate = async (options) => {
21865
22276
  value: signature.toString("base64")
21866
22277
  }
21867
22278
  });
21868
- await mkdir9(outputRoot, { recursive: true });
21869
- const outputDirectory = join11(outputRoot, manifest.releaseId);
21870
- 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-"));
21871
22282
  try {
21872
- await cp(bundleDirectory, join11(staging, UPDATE_FILES_DIRECTORY), {
22283
+ await cp(bundleDirectory, join12(staging, UPDATE_FILES_DIRECTORY), {
21873
22284
  force: true,
21874
22285
  recursive: true
21875
22286
  });
21876
- 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")}
21877
22288
  `);
21878
- await rename8(staging, outputDirectory);
22289
+ await rename9(staging, outputDirectory);
21879
22290
  } catch (error) {
21880
- await rm5(staging, { force: true, recursive: true });
22291
+ await rm6(staging, { force: true, recursive: true });
21881
22292
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "EEXIST")
21882
22293
  throw new TypeError(`Mobile update ${manifest.releaseId} already exists.`, { cause: error });
21883
22294
  throw error;
21884
22295
  }
21885
22296
  return {
21886
22297
  manifest,
21887
- manifestPath: join11(outputDirectory, UPDATE_MANIFEST_FILE),
22298
+ manifestPath: join12(outputDirectory, UPDATE_MANIFEST_FILE),
21888
22299
  outputDirectory
21889
22300
  };
21890
22301
  };
21891
- 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")));
21892
22303
  var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
21893
22304
  const manifest = parseAbsoluteMobileUpdateManifest(manifestValue);
21894
22305
  const valid = verify("sha256", absoluteMobileUpdateSigningPayload(unsignedAbsoluteMobileUpdate(manifest)), { dsaEncoding: "ieee-p1363", key: publicKey }, Buffer.from(manifest.signature.value, "base64"));
@@ -21898,17 +22309,17 @@ var verifyAbsoluteMobileUpdateSignature = (manifestValue, publicKey) => {
21898
22309
  };
21899
22310
 
21900
22311
  // src/mobile/expoUpdate.ts
21901
- import { access as access9, mkdir as mkdir10, readFile as readFile15, writeFile as writeFile12 } from "fs/promises";
21902
- 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";
21903
22314
  var ABSOLUTE_EXPO_UPDATE_DESCRIPTOR = "_absolute/expo-update.json";
21904
22315
  var ABSOLUTE_EXPO_UPDATE_FORMAT = 1;
21905
22316
  var object2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
21906
- var safePath = (value, field) => {
22317
+ var safePath = (value, field2) => {
21907
22318
  if (typeof value !== "string" || value.length === 0)
21908
- throw new TypeError(`Expo update ${field} is invalid.`);
22319
+ throw new TypeError(`Expo update ${field2} is invalid.`);
21909
22320
  const path = value.replaceAll("\\", "/");
21910
22321
  if (path.startsWith("/") || path.split("/").some((segment) => !segment || segment === "." || segment === ".."))
21911
- throw new TypeError(`Expo update ${field} must be a relative path.`);
22322
+ throw new TypeError(`Expo update ${field2} must be a relative path.`);
21912
22323
  return path;
21913
22324
  };
21914
22325
  var extension = (value, path) => {
@@ -21965,7 +22376,7 @@ var parseAbsoluteExpoUpdateDescriptor = (value) => {
21965
22376
  };
21966
22377
  };
21967
22378
  var finalizeAbsoluteExpoUpdateExport = async (options) => {
21968
- 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"));
21969
22380
  if (!object2(metadata) || !object2(metadata.fileMetadata))
21970
22381
  throw new TypeError("Expo export metadata.json is invalid.");
21971
22382
  const descriptor = parseAbsoluteExpoUpdateDescriptor({
@@ -21976,33 +22387,33 @@ var finalizeAbsoluteExpoUpdateExport = async (options) => {
21976
22387
  runtimeVersion: options.runtimeVersion
21977
22388
  });
21978
22389
  const referenced = Object.values(descriptor.platforms).flatMap((entry) => entry ? [entry.launchAsset, ...entry.assets] : []);
21979
- 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(() => {
21980
22391
  throw new TypeError(`Expo export metadata references missing asset ${path}.`);
21981
22392
  })));
21982
- const destination = join12(options.exportDirectory, ABSOLUTE_EXPO_UPDATE_DESCRIPTOR);
21983
- await mkdir10(dirname9(destination), { recursive: true });
21984
- 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")}
21985
22396
  `);
21986
22397
  return { descriptor, path: destination };
21987
22398
  };
21988
22399
 
21989
22400
  // src/mobile/expoCodeSigning.ts
21990
22401
  var import_code_signing_certificates = __toESM(require_main(), 1);
21991
- import { access as access10, mkdir as mkdir11, writeFile as writeFile13 } from "fs/promises";
21992
- import { dirname as dirname10, isAbsolute as isAbsolute4, relative as relative9, resolve as resolve10, sep as sep5 } from "path";
21993
- var exists3 = async (path) => access10(path).then(() => true).catch(() => false);
21994
- var inside = (root, path) => {
21995
- const location = relative9(root, path);
21996
- 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);
21997
22408
  };
21998
22409
  var generateAbsoluteExpoCodeSigning = async (options) => {
21999
- const root = resolve10(options.projectRoot);
22000
- const certificatePath = resolve10(root, options.certificatePath);
22001
- const privateKeyPath = resolve10(root, options.privateKeyPath);
22002
- const publicKeyPath = resolve10(root, options.publicKeyPath ?? resolve10(dirname10(privateKeyPath), "public-key.pem"));
22003
- 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))
22004
22415
  throw new TypeError("Expo code-signing certificate must be written inside the project so store builds can embed it.");
22005
- if (inside(root, privateKeyPath) || inside(root, publicKeyPath))
22416
+ if (inside2(root, privateKeyPath) || inside2(root, publicKeyPath))
22006
22417
  throw new TypeError("Expo code-signing private/public key output must remain outside the project and source control.");
22007
22418
  const commonName = options.commonName.trim();
22008
22419
  if (!commonName || commonName.length > 64)
@@ -22011,7 +22422,7 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
22011
22422
  if (!Number.isSafeInteger(validityYears) || validityYears < 1 || validityYears > 20)
22012
22423
  throw new TypeError("Expo code-signing validity must be an integer from 1 through 20 years.");
22013
22424
  const collisions = (await Promise.all([certificatePath, privateKeyPath, publicKeyPath].map(async (path) => ({
22014
- exists: await exists3(path),
22425
+ exists: await exists4(path),
22015
22426
  path
22016
22427
  })))).filter((entry) => entry.exists);
22017
22428
  if (collisions.length > 0)
@@ -22029,11 +22440,11 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
22029
22440
  import_code_signing_certificates.validateSelfSignedCertificate(certificate, keyPair);
22030
22441
  const { privateKeyPEM, publicKeyPEM } = import_code_signing_certificates.convertKeyPairToPEM(keyPair);
22031
22442
  const certificatePem = import_code_signing_certificates.convertCertificateToCertificatePEM(certificate);
22032
- 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 })));
22033
22444
  await Promise.all([
22034
- writeFile13(certificatePath, certificatePem, { flag: "wx", mode: 420 }),
22035
- writeFile13(privateKeyPath, privateKeyPEM, { flag: "wx", mode: 384 }),
22036
- 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 })
22037
22448
  ]);
22038
22449
  return {
22039
22450
  certificatePath,
@@ -22044,8 +22455,8 @@ var generateAbsoluteExpoCodeSigning = async (options) => {
22044
22455
  };
22045
22456
 
22046
22457
  // src/mobile/updatePublisher.ts
22047
- import { access as access11 } from "fs/promises";
22048
- 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";
22049
22460
  import { pathToFileURL as pathToFileURL3 } from "url";
22050
22461
  var lifecycleMethod = (publisher, name) => {
22051
22462
  const method = publisher[name];
@@ -22154,16 +22565,16 @@ var pruneAbsoluteMobileUpdates = async (options) => {
22154
22565
  var object3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
22155
22566
  var isPublisher2 = (value) => object3(value) && typeof value.publishUpdate === "function" && typeof value.promoteUpdate === "function" && typeof value.rollbackUpdate === "function";
22156
22567
  var projectPath3 = (projectRoot, requested, label) => {
22157
- const root = resolve11(projectRoot);
22158
- const path = resolve11(root, requested);
22159
- const projectRelative = relative10(root, path);
22160
- 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))
22161
22572
  throw new TypeError(`${label} must remain inside the project.`);
22162
22573
  return path;
22163
22574
  };
22164
22575
  var loadAbsoluteMobileUpdatePublisher = async (projectRoot, requestedModulePath) => {
22165
22576
  const modulePath = projectPath3(projectRoot, requestedModulePath, "mobile update registry");
22166
- await access11(modulePath).catch(() => {
22577
+ await access12(modulePath).catch(() => {
22167
22578
  throw new TypeError(`Mobile update registry does not exist: ${modulePath}`);
22168
22579
  });
22169
22580
  const loaded = await import(pathToFileURL3(modulePath).href);
@@ -22216,10 +22627,12 @@ var rollbackAbsoluteMobileUpdate = async (options) => {
22216
22627
 
22217
22628
  // src/cli/scripts/mobile.ts
22218
22629
  var NOT_FOUND4 = -1;
22219
- 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);
22220
22633
  var expoPublicConfig = (config) => {
22221
22634
  const result = { ...config };
22222
- if (!isRecord6(result.updates))
22635
+ if (!isRecord7(result.updates))
22223
22636
  return result;
22224
22637
  const {
22225
22638
  codeSigningCertificate: _codeSigningCertificate,
@@ -22259,13 +22672,13 @@ var CAPACITOR_SYNC_PACKAGE_SPECS = [
22259
22672
  ];
22260
22673
  var packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@"));
22261
22674
  var directProjectPackages = async (projectRoot) => {
22262
- const manifest = JSON.parse(await readFile16(join13(projectRoot, "package.json"), "utf8"));
22263
- if (!isRecord6(manifest))
22675
+ const manifest = JSON.parse(await readFile17(join14(projectRoot, "package.json"), "utf8"));
22676
+ if (!isRecord7(manifest))
22264
22677
  throw new TypeError("Application package.json must contain an object.");
22265
22678
  const names = new Set;
22266
- for (const field of ["dependencies", "devDependencies"]) {
22267
- const dependencies = Reflect.get(manifest, field);
22268
- if (isRecord6(dependencies))
22679
+ for (const field2 of ["dependencies", "devDependencies"]) {
22680
+ const dependencies = Reflect.get(manifest, field2);
22681
+ if (isRecord7(dependencies))
22269
22682
  for (const name of Object.keys(dependencies))
22270
22683
  names.add(name);
22271
22684
  }
@@ -22273,8 +22686,8 @@ var directProjectPackages = async (projectRoot) => {
22273
22686
  };
22274
22687
  var resolvedPackageVersion = async (projectRoot, packageName) => {
22275
22688
  try {
22276
- const manifest = JSON.parse(await readFile16(join13(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
22277
- 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;
22278
22691
  } catch {
22279
22692
  return;
22280
22693
  }
@@ -22324,9 +22737,9 @@ var requireMobileConfig = (value) => {
22324
22737
  return value;
22325
22738
  };
22326
22739
  var capacitorExecutable = async (projectRoot) => {
22327
- const executable = join13(projectRoot, "node_modules", ".bin", "cap");
22740
+ const executable = join14(projectRoot, "node_modules", ".bin", "cap");
22328
22741
  try {
22329
- await access12(executable);
22742
+ await access13(executable);
22330
22743
  return executable;
22331
22744
  } catch {
22332
22745
  throw new TypeError(`Capacitor is not installed in this app. Run: bun add ${CAPACITOR_PACKAGES.join(" ")}`);
@@ -22347,9 +22760,9 @@ var runCapacitor = async (projectRoot, args) => {
22347
22760
  };
22348
22761
  var runCapacitorForPlatforms = (projectRoot, command, platforms) => platforms.reduce((pending, platform2) => pending.then(() => runCapacitor(projectRoot, [command, platform2])), Promise.resolve());
22349
22762
  var expoExecutable = async (project) => {
22350
- const executable = join13(project, "node_modules", ".bin", "expo");
22763
+ const executable = join14(project, "node_modules", ".bin", "expo");
22351
22764
  try {
22352
- await access12(executable);
22765
+ await access13(executable);
22353
22766
  return executable;
22354
22767
  } catch {
22355
22768
  throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
@@ -22381,14 +22794,14 @@ var runExpo = async (project, args, options = {}) => {
22381
22794
  };
22382
22795
  var ensureExpoPackages = async (project, args) => {
22383
22796
  try {
22384
- const manifest = JSON.parse(await readFile16(join13(project, "package.json"), "utf8"));
22797
+ const manifest = JSON.parse(await readFile17(join14(project, "package.json"), "utf8"));
22385
22798
  const dependencies = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "dependencies") : undefined;
22386
22799
  if (typeof dependencies !== "object" || dependencies === null || Array.isArray(dependencies))
22387
22800
  throw new TypeError("Generated Expo dependencies are invalid.");
22388
22801
  await Promise.all(Object.entries(dependencies).map(async ([name, expected]) => {
22389
22802
  if (typeof expected !== "string")
22390
22803
  throw new TypeError("Generated Expo dependency version is invalid.");
22391
- 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"));
22392
22805
  const actual = typeof installed === "object" && installed !== null ? Reflect.get(installed, "version") : undefined;
22393
22806
  if (typeof actual !== "string")
22394
22807
  throw new TypeError("Installed Expo dependency version is invalid.");
@@ -22515,7 +22928,7 @@ var unpairRemoteMac = async (args) => {
22515
22928
  var initialize = async (args) => {
22516
22929
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
22517
22930
  if (mobile.engine === "expo") {
22518
- 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.");
22519
22932
  const generated2 = await writeAbsoluteExpoProject(mobile, {
22520
22933
  force: args.includes("--force"),
22521
22934
  projectRoot
@@ -22584,7 +22997,7 @@ var sync = async (args) => {
22584
22997
  };
22585
22998
  var associations = async (args) => {
22586
22999
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
22587
- const outputDirectory = resolve12(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
23000
+ const outputDirectory = resolve13(projectRoot, valueAfter(args, "--outdir") ?? ".absolutejs/mobile/associations");
22588
23001
  if (args.includes("--verify")) {
22589
23002
  const result2 = await verifyAbsoluteMobileAssociationFiles(mobile);
22590
23003
  console.log(`Verified ${result2.results.length} hosted association files`);
@@ -22636,7 +23049,7 @@ var generateGithubCi = async (args) => {
22636
23049
  const publicResult = {
22637
23050
  changed: result.changed,
22638
23051
  format: result.format,
22639
- path: relative11(projectRoot, result.path).replaceAll("\\", "/"),
23052
+ path: relative12(projectRoot, result.path).replaceAll("\\", "/"),
22640
23053
  platforms: result.platforms,
22641
23054
  publishing: result.publishing,
22642
23055
  requiredSecrets: result.requiredSecrets
@@ -22747,9 +23160,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
22747
23160
  temporaryDirectory: undefined
22748
23161
  };
22749
23162
  await ensureExpoPackages(mobile.nativeProjectDirectory, options.args);
22750
- const temporaryParent = join13(options.projectRoot, ".absolutejs", "mobile", "expo-update-exports");
22751
- await mkdir12(temporaryParent, { recursive: true });
22752
- 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-"));
22753
23166
  try {
22754
23167
  await runExpo(mobile.nativeProjectDirectory, [
22755
23168
  "export",
@@ -22761,9 +23174,9 @@ var prepareExpoMobileUpdateExport = async (options) => {
22761
23174
  temporaryDirectory,
22762
23175
  "--clear"
22763
23176
  ], { production: true });
22764
- const generatedApp = JSON.parse(await readFile16(join13(mobile.nativeProjectDirectory, "app.json"), "utf8"));
22765
- const expoConfig = isRecord6(generatedApp) ? generatedApp.expo : undefined;
22766
- 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))
22767
23180
  throw new TypeError("Generated Expo app configuration is invalid.");
22768
23181
  const publicExpoConfig = expoPublicConfig(expoConfig);
22769
23182
  const computedRuntime = resolveAbsoluteMobileUpdateRuntime(mobile, options.projectRoot).fingerprint;
@@ -22779,7 +23192,7 @@ var prepareExpoMobileUpdateExport = async (options) => {
22779
23192
  temporaryDirectory
22780
23193
  };
22781
23194
  } catch (error) {
22782
- await rm6(temporaryDirectory, { force: true, recursive: true });
23195
+ await rm7(temporaryDirectory, { force: true, recursive: true });
22783
23196
  throw error;
22784
23197
  }
22785
23198
  };
@@ -22804,11 +23217,11 @@ var buildMobileUpdate = async (args) => {
22804
23217
  let expoExportDirectory;
22805
23218
  try {
22806
23219
  await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath, { prepareOnly: true });
22807
- const embedded = JSON.parse(await readFile16(join13(mobile.bundleDirectory, "absolute-mobile-manifest.json"), "utf8"));
22808
- 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;
22809
23222
  if (typeof runtimeFingerprint !== "string" || !/^[a-f0-9]{64}$/u.test(runtimeFingerprint))
22810
23223
  throw new TypeError("Prepared mobile bundle is missing its native runtime fingerprint.");
22811
- const privateKey = await readFile16(resolve12(projectRoot, signingKeyPath));
23224
+ const privateKey = await readFile17(resolve13(projectRoot, signingKeyPath));
22812
23225
  const configuredPublicKey = Buffer.from(mobile.updates.publicKeys[keyId], "base64");
22813
23226
  const derivedPublicKey = createPublicKey2(privateKey).export({
22814
23227
  format: "der",
@@ -22829,7 +23242,7 @@ var buildMobileUpdate = async (args) => {
22829
23242
  channel: mobile.updates.channel,
22830
23243
  classification,
22831
23244
  keyId,
22832
- outputDirectory: valueAfter(args, "--outdir") ?? join13(projectRoot, ".absolutejs", "mobile", "updates"),
23245
+ outputDirectory: valueAfter(args, "--outdir") ?? join14(projectRoot, ".absolutejs", "mobile", "updates"),
22833
23246
  privateKey,
22834
23247
  runtimeFingerprint
22835
23248
  });
@@ -22841,7 +23254,7 @@ var buildMobileUpdate = async (args) => {
22841
23254
  return result;
22842
23255
  } finally {
22843
23256
  if (expoExportDirectory)
22844
- await rm6(expoExportDirectory, { force: true, recursive: true });
23257
+ await rm7(expoExportDirectory, { force: true, recursive: true });
22845
23258
  sendTelemetryEvent("mobile:update-build", {
22846
23259
  classification: classification ?? "invalid",
22847
23260
  durationMs: Math.round(performance.now() - startedAt),
@@ -22871,7 +23284,7 @@ var generateExpoUpdateSigning = async (args) => {
22871
23284
  ...valueAfter(args, "--public-key") ? { publicKeyPath: valueAfter(args, "--public-key") } : {},
22872
23285
  ...validityYears === undefined ? {} : { validityYears }
22873
23286
  });
22874
- const certificatePath = relative11(projectRoot, result.certificatePath).replaceAll("\\", "/");
23287
+ const certificatePath = relative12(projectRoot, result.certificatePath).replaceAll("\\", "/");
22875
23288
  sendTelemetryEvent("mobile:update-code-signing-generated", {
22876
23289
  engine: "expo",
22877
23290
  validityYears: validityYears ?? 10
@@ -22933,7 +23346,7 @@ var provisionMobileUpdate = async (args) => {
22933
23346
  publicKeys: mobile.updates.publicKeys,
22934
23347
  storage: requestedStorage
22935
23348
  });
22936
- 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("\\", "/")}.`);
22937
23350
  if (requestedStorage === "local")
22938
23351
  console.log("Production release checks will require durable storage. Re-run with --storage s3 --force before deploying.");
22939
23352
  else
@@ -23375,8 +23788,10 @@ var buildAndroid = async (args, prepareVersionCode) => {
23375
23788
  phaseSuccess = true;
23376
23789
  return result;
23377
23790
  } finally {
23791
+ const durationMs = Math.round(performance.now() - phaseStartedAt);
23792
+ console.log(`[mobile:android-release] ${phase} ${phaseSuccess ? "completed" : "failed"} in ${getDurationString(durationMs)}`);
23378
23793
  sendTelemetryEvent("mobile:android-release-phase", {
23379
- durationMs: Math.round(performance.now() - phaseStartedAt),
23794
+ durationMs,
23380
23795
  engine: mobile.engine,
23381
23796
  host,
23382
23797
  phase,
@@ -23406,7 +23821,7 @@ var buildAndroid = async (args, prepareVersionCode) => {
23406
23821
  const durationMs = Math.round(performance.now() - startedAt);
23407
23822
  console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} Android App Bundle in ${getDurationString(durationMs)}.`);
23408
23823
  console.log(`Artifact: ${release.artifactPath}`);
23409
- console.log(`Metadata: ${join13(release.releaseRoot, "release.json")}`);
23824
+ console.log(`Metadata: ${join14(release.releaseRoot, "release.json")}`);
23410
23825
  return release;
23411
23826
  } finally {
23412
23827
  sendTelemetryEvent("mobile:android-release-build", {
@@ -23562,7 +23977,7 @@ var buildIos = async (args, prepareBuildNumber) => {
23562
23977
  const durationMs = Math.round(performance.now() - startedAt);
23563
23978
  console.log(`Built ${release.metadata.signed ? "signed" : "unsigned"} iOS IPA ${release.metadata.marketingVersion}${release.metadata.buildNumber ? ` (${release.metadata.buildNumber})` : ""} in ${getDurationString(durationMs)}.`);
23564
23979
  console.log(`Artifact: ${release.artifactPath}`);
23565
- console.log(`Metadata: ${join13(release.releaseRoot, "release.json")}`);
23980
+ console.log(`Metadata: ${join14(release.releaseRoot, "release.json")}`);
23566
23981
  return release;
23567
23982
  } finally {
23568
23983
  stopListeningForCancellation();
@@ -23674,7 +24089,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
23674
24089
  checks.push({
23675
24090
  id: "sync.storage-schema",
23676
24091
  label: `Offline schema ${schema.components.map((component) => `${component.id}@${component.version}`).join(", ")}`,
23677
- path: join13(projectRoot, "package.json"),
24092
+ path: join14(projectRoot, "package.json"),
23678
24093
  platform: "host",
23679
24094
  status: "pass"
23680
24095
  });
@@ -23682,7 +24097,7 @@ var appendSyncSchemaDoctorCheck = (checks, projectRoot) => {
23682
24097
  checks.push({
23683
24098
  id: "sync.storage-schema",
23684
24099
  label: "Offline schema metadata is invalid",
23685
- path: join13(projectRoot, "package.json"),
24100
+ path: join14(projectRoot, "package.json"),
23686
24101
  platform: "host",
23687
24102
  remediation: error instanceof Error ? error.message : String(error),
23688
24103
  status: "fail"
@@ -23749,7 +24164,7 @@ AbsoluteJS can configure ${installPlatform} emulation:`);
23749
24164
  Emulator setup verification:`);
23750
24165
  printDoctorChecks(result.checks.filter((check) => check.platform === "host" || check.platform === installPlatform));
23751
24166
  };
23752
- var captureCommand2 = (command) => {
24167
+ var captureCommand3 = (command) => {
23753
24168
  const result = Bun.spawnSync(command, {
23754
24169
  stderr: "pipe",
23755
24170
  stdin: "ignore",
@@ -23770,7 +24185,7 @@ var requireAndroidTestPort = (args, projectRoot) => {
23770
24185
  }
23771
24186
  return { https: args.includes("--https"), port };
23772
24187
  }
23773
- 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);
23774
24189
  if (instances.length !== 1) {
23775
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>.");
23776
24191
  }
@@ -23792,14 +24207,14 @@ var androidTestTimeout = (args) => {
23792
24207
  };
23793
24208
  var requireAndroidAdb = async () => {
23794
24209
  const checks = await inspectAbsoluteMobileToolchain();
23795
- const adb = checks.find((check) => check.id === "android.adb" && check.status === "pass")?.path;
23796
- if (!adb) {
24210
+ const adb2 = checks.find((check) => check.id === "android.adb" && check.status === "pass")?.path;
24211
+ if (!adb2) {
23797
24212
  throw new TypeError("Android Debug Bridge is unavailable. Run `absolute mobile doctor android --fix`.");
23798
24213
  }
23799
- return adb;
24214
+ return adb2;
23800
24215
  };
23801
- var selectAndroidSerial = (adb, explicitSerial) => {
23802
- const devices = captureCommand2([adb, "devices"]);
24216
+ var selectAndroidSerial = (adb2, explicitSerial) => {
24217
+ const devices = captureCommand3([adb2, "devices"]);
23803
24218
  if (devices.exitCode !== 0) {
23804
24219
  throw new Error(`Could not list Android targets: ${devices.stderr.trim() || devices.stdout.trim()}`);
23805
24220
  }
@@ -23816,9 +24231,56 @@ var selectAndroidSerial = (adb, explicitSerial) => {
23816
24231
  }
23817
24232
  return selected;
23818
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
+ };
23819
24281
  var safeArtifactRoot = (projectRoot, value) => {
23820
- const root = resolve12(projectRoot, value ?? ".absolutejs/mobile/test-artifacts");
23821
- 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)}/`)) {
23822
24284
  throw new TypeError("mobile test --artifacts must remain inside the project.");
23823
24285
  }
23824
24286
  return root;
@@ -23846,12 +24308,12 @@ var waitForRequestedAndroidHmr = async (args, session, checks, timeoutMs) => {
23846
24308
  });
23847
24309
  };
23848
24310
  var writeAndroidFailureArtifacts = async (options) => {
23849
- await mkdir12(options.artifactRoot, { recursive: true });
23850
- 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(() => {
23851
24313
  return;
23852
24314
  }) : undefined;
23853
- const diagnosticPath = join13(options.artifactRoot, "android-failure.json");
23854
- await writeFile14(diagnosticPath, `${JSON.stringify({
24315
+ const diagnosticPath = join14(options.artifactRoot, "android-failure.json");
24316
+ await writeFile15(diagnosticPath, `${JSON.stringify({
23855
24317
  diagnostics: options.session?.diagnostics ?? [],
23856
24318
  error: options.error instanceof Error ? options.error.message : String(options.error),
23857
24319
  platform: "android",
@@ -23863,16 +24325,171 @@ var writeAndroidFailureArtifacts = async (options) => {
23863
24325
  `);
23864
24326
  return { diagnosticPath, screenshot };
23865
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
+ };
23866
24480
  var testAndroid = async (args) => {
23867
24481
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
24482
+ if (args.includes("--release")) {
24483
+ return testAndroidRelease(args, mobile, projectRoot);
24484
+ }
23868
24485
  requireCapacitorEngine(mobile, "mobile test android");
23869
24486
  if (!mobile.platforms.includes("android")) {
23870
24487
  throw new TypeError("mobile test android requires android in mobile.platforms.");
23871
24488
  }
23872
24489
  const { https, port } = requireAndroidTestPort(args, projectRoot);
23873
24490
  const timeoutMs = androidTestTimeout(args);
23874
- const adb = await requireAndroidAdb();
23875
- const serial = selectAndroidSerial(adb, valueAfter(args, "--serial"));
24491
+ const adb2 = await requireAndroidAdb();
24492
+ const serial = selectAndroidSerial(adb2, valueAfter(args, "--serial"));
23876
24493
  const routes = valuesAfter(args, "--route");
23877
24494
  if (routes.length === 0)
23878
24495
  routes.push(mobile.entry);
@@ -23882,7 +24499,7 @@ var testAndroid = async (args) => {
23882
24499
  let session;
23883
24500
  try {
23884
24501
  session = await attachAbsoluteAndroidWebView({
23885
- adb,
24502
+ adb: adb2,
23886
24503
  appId: mobile.appId,
23887
24504
  serial,
23888
24505
  timeoutMs
@@ -23916,9 +24533,9 @@ var testAndroid = async (args) => {
23916
24533
  console.log(JSON.stringify(report, null, 2));
23917
24534
  else
23918
24535
  printAndroidTestReport(report);
23919
- 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;
23920
24537
  await writeRequestedAndroidReport({
23921
- adb,
24538
+ adb: adb2,
23922
24539
  args,
23923
24540
  projectRoot,
23924
24541
  run: {
@@ -23959,7 +24576,7 @@ var testAndroid = async (args) => {
23959
24576
  session
23960
24577
  });
23961
24578
  await writeRequestedAndroidReport({
23962
- adb,
24579
+ adb: adb2,
23963
24580
  args,
23964
24581
  projectRoot,
23965
24582
  run: {
@@ -23985,14 +24602,14 @@ var requireIosTestContext = (args, projectRoot) => {
23985
24602
  const port = Number(explicit);
23986
24603
  if (!Number.isInteger(port) || port < 1 || port > 65535)
23987
24604
  throw new TypeError("mobile test --port must be a valid TCP port.");
23988
- 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);
23989
24606
  return {
23990
24607
  https: instance2?.https ?? args.includes("--https"),
23991
24608
  instance: instance2,
23992
24609
  port
23993
24610
  };
23994
24611
  }
23995
- 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);
23996
24613
  if (instances.length !== 1)
23997
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>.");
23998
24615
  const [instance] = instances;
@@ -24010,7 +24627,7 @@ var waitForIosHmrClient = async (options) => {
24010
24627
  return;
24011
24628
  });
24012
24629
  const status = response?.ok ? await response.json().catch(() => null) : null;
24013
- const targets = isRecord6(status) && isRecord6(status.connectedTargets) ? status.connectedTargets : undefined;
24630
+ const targets = isRecord7(status) && isRecord7(status.connectedTargets) ? status.connectedTargets : undefined;
24014
24631
  if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
24015
24632
  return;
24016
24633
  await Bun.sleep(100);
@@ -24082,11 +24699,11 @@ var physicalIosCapture = async (options) => {
24082
24699
  remote: false,
24083
24700
  xcodeVersion: requireCapturedCommand(["/usr/bin/xcodebuild", "-version"], "Xcode version inspection").stdout.trim(),
24084
24701
  xcrun: "/usr/bin/xcrun",
24085
- capture: async (command) => captureCommand2(command)
24702
+ capture: async (command) => captureCommand3(command)
24086
24703
  };
24087
24704
  };
24088
24705
  var selectIosSimulator = (xcrun, explicitUdid) => {
24089
- const result = captureCommand2([
24706
+ const result = captureCommand3([
24090
24707
  xcrun,
24091
24708
  "simctl",
24092
24709
  "list",
@@ -24109,15 +24726,15 @@ var selectIosSimulator = (xcrun, explicitUdid) => {
24109
24726
  return selected;
24110
24727
  };
24111
24728
  var requireCapturedCommand = (command, label) => {
24112
- const result = captureCommand2(command);
24729
+ const result = captureCommand3(command);
24113
24730
  if (result.exitCode !== 0)
24114
24731
  throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
24115
24732
  return result;
24116
24733
  };
24117
24734
  var writeIosFailureArtifacts = async (options) => {
24118
- await mkdir12(options.artifactRoot, { recursive: true });
24119
- const screenshot = join13(options.artifactRoot, "ios-failure.png");
24120
- const screenshotResult = captureCommand2([
24735
+ await mkdir13(options.artifactRoot, { recursive: true });
24736
+ const screenshot = join14(options.artifactRoot, "ios-failure.png");
24737
+ const screenshotResult = captureCommand3([
24121
24738
  options.xcrun,
24122
24739
  "simctl",
24123
24740
  "io",
@@ -24125,8 +24742,8 @@ var writeIosFailureArtifacts = async (options) => {
24125
24742
  "screenshot",
24126
24743
  screenshot
24127
24744
  ]);
24128
- const diagnosticPath = join13(options.artifactRoot, "ios-failure.json");
24129
- await writeFile14(diagnosticPath, `${JSON.stringify({
24745
+ const diagnosticPath = join14(options.artifactRoot, "ios-failure.json");
24746
+ await writeFile15(diagnosticPath, `${JSON.stringify({
24130
24747
  appId: options.appId,
24131
24748
  error: options.error instanceof Error ? options.error.message : String(options.error),
24132
24749
  platform: "ios",
@@ -24153,8 +24770,8 @@ var nativeReportRoot = (args, projectRoot, platform2) => {
24153
24770
  var absolutejsVersionForReport = async () => {
24154
24771
  let absolutejsVersion = process.env.ABSOLUTE_VERSION ?? "unknown";
24155
24772
  const versions = await Promise.all([
24156
- resolve12(import.meta.dir, "..", "..", "package.json"),
24157
- resolve12(import.meta.dir, "..", "..", "..", "package.json")
24773
+ resolve13(import.meta.dir, "..", "..", "package.json"),
24774
+ resolve13(import.meta.dir, "..", "..", "..", "package.json")
24158
24775
  ].map((candidate) => readPackageVersionForIosReport(candidate).catch(() => "unknown")));
24159
24776
  for (const version of versions) {
24160
24777
  if (version === "unknown")
@@ -24165,7 +24782,7 @@ var absolutejsVersionForReport = async () => {
24165
24782
  return absolutejsVersion;
24166
24783
  };
24167
24784
  var writeRequestedAndroidReport = async (options) => {
24168
- const reportRoot = nativeReportRoot(options.args, options.projectRoot, "android");
24785
+ const reportRoot = options.reportRoot ?? nativeReportRoot(options.args, options.projectRoot, "android");
24169
24786
  if (!reportRoot)
24170
24787
  return;
24171
24788
  const adbVersion = requireCapturedCommand([options.adb, "version"], "ADB version inspection").stdout.trim();
@@ -24174,6 +24791,7 @@ var writeRequestedAndroidReport = async (options) => {
24174
24791
  adbVersion,
24175
24792
  bunVersion: Bun.version,
24176
24793
  host: `${process.platform}-${process.arch}`,
24794
+ ...options.provider ? { provider: options.provider } : {},
24177
24795
  run: options.run
24178
24796
  });
24179
24797
  const paths = await writeAbsoluteNativeTestReport(reportRoot, report);
@@ -24378,8 +24996,8 @@ var testIos = async (args) => {
24378
24996
  mobile.appId
24379
24997
  ], "iOS app launch");
24380
24998
  await waitForIosHmrClient({ https, port, timeoutMs });
24381
- await mkdir12(artifactRoot, { recursive: true });
24382
- const screenshot = join13(artifactRoot, "ios-simulator.png");
24999
+ await mkdir13(artifactRoot, { recursive: true });
25000
+ const screenshot = join14(artifactRoot, "ios-simulator.png");
24383
25001
  requireCapturedCommand([xcrun, "simctl", "io", simulator.udid, "screenshot", screenshot], "iOS simulator screenshot");
24384
25002
  const hmrApply = await waitForRequestedIosHmr(args, instance, timeoutMs);
24385
25003
  const report = {
@@ -24565,7 +25183,7 @@ var runMobile = async (args) => {
24565
25183
  await publishIos(args.slice(2));
24566
25184
  return;
24567
25185
  }
24568
- 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]");
24569
25187
  };
24570
25188
  export {
24571
25189
  runMobile