@absolutejs/absolute 0.20.0-beta.1 → 0.20.0-beta.3

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.
@@ -615,9 +615,9 @@ var verifyAbsoluteMobileCompatibilityProducer = async (release, maxProducerBytes
615
615
  return release;
616
616
  };
617
617
  // src/mobile/androidRelease.ts
618
- import { createHash as createHash3 } from "crypto";
618
+ import { createHash as createHash4 } from "crypto";
619
619
  import {
620
- access as access3,
620
+ access as access4,
621
621
  copyFile as copyFile2,
622
622
  mkdir as mkdir3,
623
623
  mkdtemp as mkdtemp2,
@@ -630,6 +630,7 @@ import {
630
630
  import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
631
631
 
632
632
  // src/mobile/emulatorDoctor.ts
633
+ import { access } from "fs/promises";
633
634
  import { homedir } from "os";
634
635
  import { join as join2 } from "path";
635
636
 
@@ -647,6 +648,34 @@ var isWSLEnvironment = () => {
647
648
  };
648
649
 
649
650
  // src/mobile/emulatorDoctor.ts
651
+ var ABSOLUTE_ANDROID_AVD_NAME = "AbsoluteJS_API_36";
652
+ var captureCommand = (command) => {
653
+ try {
654
+ const result = Bun.spawnSync(command, {
655
+ stderr: "ignore",
656
+ stdout: "pipe"
657
+ });
658
+ return {
659
+ exitCode: result.exitCode,
660
+ stdout: result.stdout.toString()
661
+ };
662
+ } catch {
663
+ return { exitCode: 1, stdout: "" };
664
+ }
665
+ };
666
+ var hasAvailableIosRuntime = (output) => {
667
+ try {
668
+ const parsed = JSON.parse(output);
669
+ if (typeof parsed !== "object" || parsed === null)
670
+ return false;
671
+ const runtimes = Reflect.get(parsed, "runtimes");
672
+ if (!Array.isArray(runtimes))
673
+ return false;
674
+ return runtimes.some((runtime) => typeof runtime === "object" && runtime !== null && Reflect.get(runtime, "isAvailable") === true && typeof Reflect.get(runtime, "identifier") === "string" && String(Reflect.get(runtime, "identifier")).includes("iOS"));
675
+ } catch {
676
+ return false;
677
+ }
678
+ };
650
679
  var windowsPathToWsl = (path) => {
651
680
  const match = /^([a-z]):[\\/](.*)$/i.exec(path.trim());
652
681
  if (!match)
@@ -678,6 +707,14 @@ var absoluteManagedAndroidSdkRoot = (host, env = process.env) => {
678
707
  }
679
708
  return join2(homedir(), ".absolutejs", "android-sdk");
680
709
  };
710
+ var pathExists = async (path) => {
711
+ try {
712
+ await access(path);
713
+ return true;
714
+ } catch {
715
+ return false;
716
+ }
717
+ };
681
718
  var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironment()) => {
682
719
  if (platform === "darwin")
683
720
  return "macos";
@@ -687,10 +724,151 @@ var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironm
687
724
  return "wsl";
688
725
  return "linux";
689
726
  };
727
+ var executableNames = (host, name) => {
728
+ if (host === "wsl")
729
+ return [`${name}.exe`, `${name}.bat`, name];
730
+ if (host === "windows")
731
+ return [name, `${name}.exe`, `${name}.bat`];
732
+ return [name];
733
+ };
734
+ var findExecutable = async (name, paths, options, host) => {
735
+ const existing = await Promise.all(paths.map(async (path) => await options.exists(path) ? path : undefined));
736
+ const configured = existing.find((path) => path !== undefined);
737
+ if (configured)
738
+ return configured;
739
+ for (const candidate of executableNames(host, name)) {
740
+ const path = options.which(candidate);
741
+ if (path)
742
+ return path;
743
+ }
744
+ return;
745
+ };
746
+ var toolCheck = (id, label, platform, path, remediation) => path ? {
747
+ id,
748
+ label,
749
+ path,
750
+ platform,
751
+ status: "pass"
752
+ } : {
753
+ id,
754
+ label,
755
+ platform,
756
+ remediation,
757
+ status: "fail"
758
+ };
759
+ var inspectAbsoluteMobileToolchain = async (input = {}) => {
760
+ const env = input.env ?? process.env;
761
+ const host = input.host ?? detectAbsoluteMobileHost();
762
+ const exists = input.exists ?? pathExists;
763
+ const which = input.which ?? ((command) => Bun.which(command));
764
+ const capture = input.capture ?? captureCommand;
765
+ const androidRoot = input.androidRoot === null ? undefined : input.androidRoot ?? env.ANDROID_HOME ?? env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host, env);
766
+ const windowsAndroidTools = host === "windows" || host === "wsl";
767
+ const android = (segments) => androidRoot ? join2(androidRoot, ...segments) : undefined;
768
+ const paths = (values) => values.filter((value) => Boolean(value));
769
+ const adb = await findExecutable("adb", paths([
770
+ android(["platform-tools", windowsAndroidTools ? "adb.exe" : "adb"])
771
+ ]), { exists, which }, host);
772
+ const emulator = await findExecutable("emulator", paths([
773
+ android([
774
+ "emulator",
775
+ windowsAndroidTools ? "emulator.exe" : "emulator"
776
+ ])
777
+ ]), { exists, which }, host);
778
+ const sdkmanager = await findExecutable("sdkmanager", paths([
779
+ android([
780
+ "cmdline-tools",
781
+ "latest",
782
+ "bin",
783
+ windowsAndroidTools ? "sdkmanager.bat" : "sdkmanager"
784
+ ])
785
+ ]), { exists, which }, host);
786
+ const avdmanager = await findExecutable("avdmanager", paths([
787
+ android([
788
+ "cmdline-tools",
789
+ "latest",
790
+ "bin",
791
+ windowsAndroidTools ? "avdmanager.bat" : "avdmanager"
792
+ ])
793
+ ]), { exists, which }, host);
794
+ const java = await findExecutable("java", [], { exists, which }, host);
795
+ const checks = [
796
+ {
797
+ id: "host",
798
+ label: `Development host: ${host}`,
799
+ platform: "host",
800
+ status: "pass"
801
+ },
802
+ toolCheck("android.adb", "Android Debug Bridge", "android", adb, "Install Android SDK Platform Tools or expose adb on PATH."),
803
+ toolCheck("android.emulator", "Android Emulator", "android", emulator, "Install the Android Emulator from Android Studio SDK Manager."),
804
+ toolCheck("android.sdkmanager", "Android SDK Manager", "android", sdkmanager, "Install Android SDK Command-line Tools (latest)."),
805
+ toolCheck("android.avdmanager", "Android Virtual Device Manager", "android", avdmanager, "Install Android SDK Command-line Tools (latest)."),
806
+ toolCheck("android.java", "Java runtime", "android", java, "Install the JDK required by the configured Android Gradle plugin.")
807
+ ];
808
+ if (emulator) {
809
+ const avds = capture([emulator, "-list-avds"]);
810
+ const hasManagedAvd = avds.exitCode === 0 && avds.stdout.split(/\r?\n/).includes(ABSOLUTE_ANDROID_AVD_NAME);
811
+ checks.push({
812
+ id: "android.avd",
813
+ label: `AbsoluteJS Android emulator (${ABSOLUTE_ANDROID_AVD_NAME})`,
814
+ platform: "android",
815
+ remediation: hasManagedAvd ? undefined : "Run absolute mobile doctor android --fix to provision the managed emulator.",
816
+ status: hasManagedAvd ? "pass" : "fail"
817
+ });
818
+ }
819
+ if (host === "wsl") {
820
+ checks.push({
821
+ id: "android.virtualization",
822
+ label: adb?.endsWith(".exe") ? "Windows-host Android bridge available to WSL" : "WSL requires a Windows-host emulator bridge or Linux KVM",
823
+ platform: "android",
824
+ remediation: adb?.endsWith(".exe") ? undefined : "Expose the Windows Android SDK adb.exe to WSL, or enable /dev/kvm for a Linux SDK.",
825
+ status: adb?.endsWith(".exe") ? "pass" : "warn"
826
+ });
827
+ } else if (host === "linux") {
828
+ const hasKvm = await exists("/dev/kvm");
829
+ checks.push({
830
+ id: "android.virtualization",
831
+ label: "Linux KVM acceleration",
832
+ platform: "android",
833
+ remediation: hasKvm ? undefined : "Enable KVM and grant the current user access to /dev/kvm.",
834
+ status: hasKvm ? "pass" : "warn"
835
+ });
836
+ }
837
+ if (host !== "macos") {
838
+ checks.push({
839
+ id: "ios.simulator",
840
+ label: "iOS Simulator requires macOS and Xcode",
841
+ platform: "ios",
842
+ status: "skip"
843
+ });
844
+ return checks;
845
+ }
846
+ const xcrun = await findExecutable("xcrun", [], { exists, which }, host);
847
+ const xcodebuild = await findExecutable("xcodebuild", [], { exists, which }, host);
848
+ checks.push(toolCheck("ios.xcrun", "Xcode command runner", "ios", xcrun, "Install Xcode and select it with xcode-select."), toolCheck("ios.xcodebuild", "Xcode build system", "ios", xcodebuild, "Install Xcode and select it with xcode-select."));
849
+ if (xcrun) {
850
+ const runtimes = capture([
851
+ xcrun,
852
+ "simctl",
853
+ "list",
854
+ "runtimes",
855
+ "--json"
856
+ ]);
857
+ const hasRuntime = runtimes.exitCode === 0 && hasAvailableIosRuntime(runtimes.stdout);
858
+ checks.push({
859
+ id: "ios.runtime",
860
+ label: "iOS Simulator runtime",
861
+ platform: "ios",
862
+ remediation: hasRuntime ? undefined : "Run absolute mobile doctor ios --fix to download an iOS Simulator runtime.",
863
+ status: hasRuntime ? "pass" : "fail"
864
+ });
865
+ }
866
+ return checks;
867
+ };
690
868
 
691
869
  // src/mobile/androidEmulatorController.ts
692
870
  import {
693
- access as access2,
871
+ access as access3,
694
872
  copyFile,
695
873
  lstat,
696
874
  mkdir as mkdir2,
@@ -702,6 +880,7 @@ import {
702
880
  rm as rm2,
703
881
  writeFile as writeFile3
704
882
  } from "fs/promises";
883
+ import { createHash as createHash3, randomUUID } from "crypto";
705
884
  import {
706
885
  dirname,
707
886
  isAbsolute,
@@ -713,7 +892,7 @@ import {
713
892
  } from "path";
714
893
 
715
894
  // src/mobile/capacitorProject.ts
716
- import { access, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
895
+ import { access as access2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
717
896
  import { relative, resolve } from "path";
718
897
  var CONFIG_FILE = "capacitor.config.ts";
719
898
  var portableRelative = (root, path) => relative(root, path).replaceAll("\\", "/");
@@ -735,7 +914,7 @@ export default config;
735
914
  `;
736
915
  var exists = async (path) => {
737
916
  try {
738
- await access(path);
917
+ await access2(path);
739
918
  return true;
740
919
  } catch {
741
920
  return false;
@@ -762,10 +941,12 @@ var writeAbsoluteCapacitorConfig = async (config, options) => {
762
941
  // src/mobile/androidEmulatorController.ts
763
942
  init_getDurationString();
764
943
  var HASH_RADIX = 16;
944
+ var EXECUTABLE_MODE_MASK = 73;
945
+ var NATIVE_PUBLIC_PATH_SEGMENTS = 5;
765
946
  var CAPACITOR_PROJECT_DIRECTORY_PATTERN = /project\(['"](:[^'"]+)['"]\)\.projectDir\s*=\s*new File\(['"]([^'"]+)['"]\)/gu;
766
- var pathExists = async (path) => {
947
+ var pathExists2 = async (path) => {
767
948
  try {
768
- await access2(path);
949
+ await access3(path);
769
950
  return true;
770
951
  } catch {
771
952
  return false;
@@ -792,7 +973,7 @@ var runCommand = async (command, options = {}) => {
792
973
  ]);
793
974
  return exitCode;
794
975
  };
795
- var captureCommand = (command, options = {}) => {
976
+ var captureCommand2 = (command, options = {}) => {
796
977
  try {
797
978
  const result = Bun.spawnSync(command, {
798
979
  cwd: options.cwd,
@@ -826,6 +1007,75 @@ var throwIfAborted = (signal) => {
826
1007
  return;
827
1008
  throw new DOMException("Android development startup was cancelled.", "AbortError");
828
1009
  };
1010
+ var nativeDependencySources = async (nativeDirectory) => {
1011
+ const settings = await readFile3(join3(nativeDirectory, "capacitor.settings.gradle"), "utf8");
1012
+ const pattern = new RegExp(CAPACITOR_PROJECT_DIRECTORY_PATTERN.source, CAPACITOR_PROJECT_DIRECTORY_PATTERN.flags);
1013
+ const dependencies = [...settings.matchAll(pattern)].map((match) => ({
1014
+ name: (match[1] ?? "").slice(1).replaceAll(/[^a-zA-Z0-9_.-]/gu, "_"),
1015
+ source: resolve2(nativeDirectory, match[2] ?? "")
1016
+ }));
1017
+ if (dependencies.length === 0) {
1018
+ throw new Error("Capacitor Android settings did not declare any native dependencies.");
1019
+ }
1020
+ return { dependencies, settings };
1021
+ };
1022
+ var shouldIgnoreNativePath = (relativePath, ignorePublicBundle) => {
1023
+ const parts = relativePath.split(sep);
1024
+ if (parts.includes(".gradle") || parts.includes("build") || parts.includes(".absolutejs-dependencies")) {
1025
+ return true;
1026
+ }
1027
+ return ignorePublicBundle && parts.slice(0, NATIVE_PUBLIC_PATH_SEGMENTS).join("/") === "app/src/main/assets/public";
1028
+ };
1029
+ var collectNativePath = async (root, label, path, isDirectory, isFile, isSymbolicLink, ignorePublicBundle) => {
1030
+ const relativePath = relative2(root, path);
1031
+ if (shouldIgnoreNativePath(relativePath, ignorePublicBundle))
1032
+ return [];
1033
+ const identity = `${label}:${relativePath.split(sep).join("/")}\x00`;
1034
+ if (isDirectory) {
1035
+ return collectNativeDirectory(root, label, path, ignorePublicBundle);
1036
+ }
1037
+ if (isSymbolicLink) {
1038
+ return [`link\x00${identity}${await readlink(path)}\x00`];
1039
+ }
1040
+ if (!isFile)
1041
+ return [];
1042
+ const [metadata, contents] = await Promise.all([
1043
+ lstat(path),
1044
+ readFile3(path)
1045
+ ]);
1046
+ const contentDigest = createHash3("sha256").update(contents).digest("hex");
1047
+ return [
1048
+ `file\x00${identity}${metadata.mode & EXECUTABLE_MODE_MASK}\x00${contentDigest}\x00`
1049
+ ];
1050
+ };
1051
+ var collectNativeDirectory = async (root, label, directory, ignorePublicBundle) => {
1052
+ const entries = await readdir2(directory, { withFileTypes: true });
1053
+ entries.sort((left, right) => left.name.localeCompare(right.name));
1054
+ const records = await Promise.all(entries.map((entry) => collectNativePath(root, label, join3(directory, entry.name), entry.isDirectory(), entry.isFile(), entry.isSymbolicLink(), ignorePublicBundle)));
1055
+ return records.flat();
1056
+ };
1057
+ var hashNativeTree = async (root, label, ignorePublicBundle) => {
1058
+ const resolvedRoot = await realpath(root);
1059
+ const records = await collectNativeDirectory(resolvedRoot, label, resolvedRoot, ignorePublicBundle);
1060
+ return createHash3("sha256").update(records.join("")).digest("hex");
1061
+ };
1062
+ var fingerprintAbsoluteAndroidNativeProject = async (project) => {
1063
+ const { dependencies } = await nativeDependencySources(project.nativeDirectory);
1064
+ const roots = [
1065
+ {
1066
+ ignorePublicBundle: true,
1067
+ label: "android",
1068
+ source: project.nativeDirectory
1069
+ },
1070
+ ...dependencies.sort((left, right) => left.name.localeCompare(right.name)).map((dependency) => ({
1071
+ ignorePublicBundle: false,
1072
+ label: `dependency:${dependency.name}`,
1073
+ source: dependency.source
1074
+ }))
1075
+ ];
1076
+ const treeDigests = await Promise.all(roots.map((root) => hashNativeTree(root.source, root.label, root.ignorePublicBundle)));
1077
+ return createHash3("sha256").update(treeDigests.join("\x00")).digest("hex");
1078
+ };
829
1079
  var windowsPathFromWsl = (path, capture) => {
830
1080
  const result = capture(["wslpath", "-w", path]);
831
1081
  if (result.exitCode !== 0 || !result.stdout.trim()) {
@@ -851,12 +1101,13 @@ var mirroredCapacitorDependencies = async (project, capture) => {
851
1101
  }
852
1102
  return { dependencies, rewrittenSettings };
853
1103
  };
854
- var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task) => {
1104
+ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task, gradleArguments) => {
855
1105
  const sourceDirectory = Buffer.from(windowsSource, "utf8").toString("base64");
856
1106
  const buildDirectory = Buffer.from(windowsDirectory, "utf8").toString("base64");
857
1107
  const androidRoot = Buffer.from(windowsAndroidRoot, "utf8").toString("base64");
858
1108
  const dependencyData = Buffer.from(JSON.stringify(dependencies), "utf8").toString("base64");
859
1109
  const settingsData = Buffer.from(rewrittenSettings, "utf8").toString("base64");
1110
+ const argumentsData = Buffer.from(JSON.stringify(gradleArguments), "utf8").toString("base64");
860
1111
  const source = [
861
1112
  "$ErrorActionPreference = 'Stop'",
862
1113
  `$source = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${sourceDirectory}'))`,
@@ -864,6 +1115,7 @@ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndro
864
1115
  `$androidHome = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${androidRoot}'))`,
865
1116
  `$dependencies = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${dependencyData}')) | ConvertFrom-Json`,
866
1117
  `$settings = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${settingsData}'))`,
1118
+ `$gradleArguments = @([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${argumentsData}')) | ConvertFrom-Json)`,
867
1119
  "$env:ANDROID_HOME = $androidHome",
868
1120
  "$env:ANDROID_SDK_ROOT = $androidHome",
869
1121
  "New-Item -ItemType Directory -Force -Path $directory | Out-Null",
@@ -873,7 +1125,7 @@ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndro
873
1125
  "foreach ($dependency in @($dependencies)) { $target = Join-Path $directory ('.absolutejs-dependencies\\' + $dependency.name); New-Item -ItemType Directory -Force -Path $target | Out-Null; & robocopy.exe $dependency.windowsSource $target /MIR /XD .gradle build /NFL /NDL /NJH /NJS /NP; if ($LASTEXITCODE -ge 8) { exit $LASTEXITCODE } }",
874
1126
  "[IO.File]::WriteAllText((Join-Path $directory 'capacitor.settings.gradle'), $settings)",
875
1127
  "$wrapper = Join-Path $directory 'gradlew.bat'",
876
- `& $wrapper --no-daemon --console=plain -p $directory ${task}`,
1128
+ `& $wrapper --no-daemon --console=plain -p $directory @gradleArguments ${task}`,
877
1129
  "if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }",
878
1130
  "exit 0"
879
1131
  ].join("; ");
@@ -891,16 +1143,17 @@ var gradleArtifactPath = (nativeDirectory, task, windows = false) => {
891
1143
  };
892
1144
  var resolveGradleArtifactPath = async (nativeDirectory, task) => {
893
1145
  const primary = gradleArtifactPath(nativeDirectory, task);
894
- if (task !== "assembleRelease" || await pathExists(primary)) {
1146
+ if (task !== "assembleRelease" || await pathExists2(primary)) {
895
1147
  return primary;
896
1148
  }
897
1149
  return join3(nativeDirectory, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk");
898
1150
  };
899
1151
  var buildAbsoluteAndroidGradleArtifact = async (options) => {
900
1152
  const { project, task } = options;
901
- const capture = options.capture ?? captureCommand;
1153
+ const capture = options.capture ?? captureCommand2;
902
1154
  const run = options.run ?? runCommand;
903
1155
  const env = options.env ?? process.env;
1156
+ const gradleArguments = options.gradleArguments ?? [];
904
1157
  if (project.host === "wsl") {
905
1158
  const windowsSource = windowsPathFromWsl(project.nativeDirectory, capture);
906
1159
  const buildId = Bun.hash(project.projectRoot).toString(HASH_RADIX);
@@ -912,7 +1165,7 @@ var buildAbsoluteAndroidGradleArtifact = async (options) => {
912
1165
  "powershell.exe",
913
1166
  "-NoProfile",
914
1167
  "-EncodedCommand",
915
- encodedWindowsGradleCommand(windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task)
1168
+ encodedWindowsGradleCommand(windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task, gradleArguments)
916
1169
  ], "Android Gradle build", run, { env, signal: options.signal });
917
1170
  const artifactPath2 = await resolveGradleArtifactPath(managedBuildDirectory, task);
918
1171
  const unsigned = artifactPath2.endsWith("-unsigned.apk");
@@ -922,7 +1175,7 @@ var buildAbsoluteAndroidGradleArtifact = async (options) => {
922
1175
  };
923
1176
  }
924
1177
  const wrapper = project.host === "windows" ? "gradlew.bat" : "./gradlew";
925
- await requireSuccess([wrapper, "--no-daemon", "--console=plain", task], "Android Gradle build", run, { cwd: project.nativeDirectory, env, signal: options.signal });
1178
+ await requireSuccess([wrapper, "--no-daemon", "--console=plain", ...gradleArguments, task], "Android Gradle build", run, { cwd: project.nativeDirectory, env, signal: options.signal });
926
1179
  const artifactPath = await resolveGradleArtifactPath(project.nativeDirectory, task);
927
1180
  return { artifactPath, installPath: artifactPath };
928
1181
  };
@@ -940,9 +1193,9 @@ var requireManifest = (value) => {
940
1193
  runtime: value.runtime
941
1194
  };
942
1195
  };
943
- var pathExists2 = async (path) => {
1196
+ var pathExists3 = async (path) => {
944
1197
  try {
945
- await access3(path);
1198
+ await access4(path);
946
1199
  return true;
947
1200
  } catch {
948
1201
  return false;
@@ -983,7 +1236,7 @@ var verifyAabSignature = (artifactPath, capture, jarsigner) => {
983
1236
  ]);
984
1237
  return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
985
1238
  };
986
- var sha256File = async (path) => createHash3("sha256").update(await readFile4(path)).digest("hex");
1239
+ var sha256File = async (path) => createHash4("sha256").update(await readFile4(path)).digest("hex");
987
1240
  var safeOutputDirectory = (projectRoot, requested) => {
988
1241
  const root = resolve3(projectRoot);
989
1242
  const output = resolve3(root, requested ?? ".absolutejs/mobile/releases/android");
@@ -997,7 +1250,7 @@ var installRelease = async (artifactPath, metadata, outputRoot) => {
997
1250
  const releaseRoot = join4(outputRoot, metadata.releaseId);
998
1251
  const artifactName = "app-release.aab";
999
1252
  const destination = join4(releaseRoot, artifactName);
1000
- if (await pathExists2(releaseRoot)) {
1253
+ if (await pathExists3(releaseRoot)) {
1001
1254
  const existing = requireManifestIdentity(JSON.parse(await readFile4(join4(releaseRoot, "release.json"), "utf8")), metadata);
1002
1255
  const [installedBytes, installedSha256] = await Promise.all([
1003
1256
  stat(destination).then(({ size }) => size),
@@ -1037,12 +1290,32 @@ var requireManifestIdentity = (value, expected) => {
1037
1290
  return { ...expected, artifact };
1038
1291
  };
1039
1292
  var buildAbsoluteAndroidRelease = async (options) => {
1293
+ if (options.versionCode !== undefined && options.prepareVersionCode) {
1294
+ throw new TypeError("Android release versionCode and prepareVersionCode cannot be combined.");
1295
+ }
1296
+ if (options.versionCode !== undefined && (!Number.isSafeInteger(options.versionCode) || options.versionCode < 1 || options.versionCode > 2100000000)) {
1297
+ throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
1298
+ }
1040
1299
  const projectRoot = resolve3(options.projectRoot);
1041
1300
  const host = options.host ?? detectAbsoluteMobileHost();
1042
1301
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
1043
1302
  const nativeDirectory = join4(options.config.nativeProjectDirectory, "android");
1303
+ const manifest = requireManifest(JSON.parse(await readFile4(join4(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1304
+ if (manifest.appId !== options.config.appId) {
1305
+ throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1306
+ }
1307
+ let { versionCode } = options;
1308
+ if (options.prepareVersionCode) {
1309
+ const nativeFingerprint = await fingerprintAbsoluteAndroidNativeProject({ nativeDirectory });
1310
+ const buildIdentity = createHash4("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
1311
+ versionCode = await options.prepareVersionCode(buildIdentity);
1312
+ }
1313
+ if (versionCode !== undefined && (!Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000)) {
1314
+ throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
1315
+ }
1044
1316
  const { artifactPath } = await buildAbsoluteAndroidGradleArtifact({
1045
1317
  capture: options.capture,
1318
+ gradleArguments: versionCode === undefined ? [] : [`-Pandroid.injected.version.code=${versionCode}`],
1046
1319
  project: {
1047
1320
  androidRoot,
1048
1321
  config: options.config,
@@ -1053,7 +1326,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
1053
1326
  run: options.run,
1054
1327
  task: "bundleRelease"
1055
1328
  });
1056
- if (!await pathExists2(artifactPath)) {
1329
+ if (!await pathExists3(artifactPath)) {
1057
1330
  throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
1058
1331
  }
1059
1332
  const capture = options.capture ?? defaultCapture;
@@ -1064,10 +1337,6 @@ var buildAbsoluteAndroidRelease = async (options) => {
1064
1337
  if (!signed && !options.allowUnsigned) {
1065
1338
  throw new TypeError("Android Gradle produced an unsigned App Bundle. Configure the release signingConfig in the source-owned Android project (prefer external Gradle properties), or pass --unsigned only for a non-publishable build.");
1066
1339
  }
1067
- const manifest = requireManifest(JSON.parse(await readFile4(join4(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1068
- if (manifest.appId !== options.config.appId) {
1069
- throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1070
- }
1071
1340
  const [bytes, sha256] = await Promise.all([
1072
1341
  stat(artifactPath).then(({ size }) => size),
1073
1342
  sha256File(artifactPath)
@@ -1084,32 +1353,1208 @@ var buildAbsoluteAndroidRelease = async (options) => {
1084
1353
  runtime: manifest.runtime,
1085
1354
  sha256,
1086
1355
  signed: signed === true,
1087
- type: "aab"
1356
+ type: "aab",
1357
+ ...versionCode === undefined ? {} : { versionCode }
1088
1358
  };
1089
1359
  return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
1090
1360
  };
1091
- // src/mobile/associationFiles.ts
1361
+ // src/mobile/iosRelease.ts
1362
+ import { createHash as createHash5 } from "crypto";
1092
1363
  import {
1093
- access as access4,
1364
+ access as access5,
1365
+ copyFile as copyFile3,
1094
1366
  mkdir as mkdir4,
1367
+ mkdtemp as mkdtemp3,
1368
+ readdir as readdir3,
1095
1369
  readFile as readFile5,
1096
1370
  rename as rename5,
1097
1371
  rm as rm4,
1372
+ stat as stat2,
1098
1373
  writeFile as writeFile5
1099
1374
  } from "fs/promises";
1100
- import { resolve as resolve5 } from "path";
1375
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join5, relative as relative4, resolve as resolve4, sep as sep3 } from "path";
1376
+ var ABSOLUTE_IOS_RELEASE_FORMAT = 1;
1377
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1378
+ var requireManifest2 = (value) => {
1379
+ if (!isRecord2(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
1380
+ throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
1381
+ }
1382
+ return {
1383
+ appBuild: value.appBuild,
1384
+ appId: value.appId,
1385
+ runtime: value.runtime
1386
+ };
1387
+ };
1388
+ var pathExists4 = async (path) => {
1389
+ try {
1390
+ await access5(path);
1391
+ return true;
1392
+ } catch {
1393
+ return false;
1394
+ }
1395
+ };
1396
+ var defaultRun = async (command, options = {}) => {
1397
+ const process2 = Bun.spawn(command, {
1398
+ cwd: options.cwd,
1399
+ env: options.env,
1400
+ stderr: "inherit",
1401
+ stdin: "inherit",
1402
+ stdout: "inherit"
1403
+ });
1404
+ return process2.exited;
1405
+ };
1406
+ var defaultCapture2 = (command, options = {}) => {
1407
+ try {
1408
+ const result = Bun.spawnSync(command, {
1409
+ cwd: options.cwd,
1410
+ env: options.env,
1411
+ stderr: "pipe",
1412
+ stdin: "ignore",
1413
+ stdout: "pipe"
1414
+ });
1415
+ return {
1416
+ exitCode: result.exitCode,
1417
+ stderr: result.stderr.toString(),
1418
+ stdout: result.stdout.toString()
1419
+ };
1420
+ } catch (error) {
1421
+ return {
1422
+ exitCode: 1,
1423
+ stderr: error instanceof Error ? error.message : String(error),
1424
+ stdout: ""
1425
+ };
1426
+ }
1427
+ };
1428
+ var ignoredFingerprintDirectories = new Set([
1429
+ "Pods",
1430
+ "DerivedData",
1431
+ "build",
1432
+ "xcuserdata"
1433
+ ]);
1434
+ var fingerprintFiles = async (root, current = root) => {
1435
+ const entries = await readdir3(current, { withFileTypes: true });
1436
+ const nested = await Promise.all(entries.sort((left, right) => left.name.localeCompare(right.name)).map(async (entry) => {
1437
+ const path = join5(current, entry.name);
1438
+ const projectRelative = relative4(root, path).replaceAll("\\", "/");
1439
+ const ignored = entry.isDirectory() && (ignoredFingerprintDirectories.has(entry.name) || projectRelative === "App/App/public");
1440
+ if (ignored)
1441
+ return [];
1442
+ if (entry.isDirectory())
1443
+ return fingerprintFiles(root, path);
1444
+ return entry.isFile() ? [path] : [];
1445
+ }));
1446
+ return nested.flat();
1447
+ };
1448
+ var fingerprintAbsoluteIosNativeProject = async (nativeDirectory) => {
1449
+ const hasher = createHash5("sha256");
1450
+ const files = await fingerprintFiles(nativeDirectory);
1451
+ const contents = await Promise.all(files.map((file) => readFile5(file)));
1452
+ files.forEach((file, index) => {
1453
+ hasher.update(relative4(nativeDirectory, file).replaceAll("\\", "/"));
1454
+ hasher.update("\x00");
1455
+ hasher.update(contents[index] ?? new Uint8Array);
1456
+ hasher.update("\x00");
1457
+ });
1458
+ return hasher.digest("hex");
1459
+ };
1460
+ var safeOutputDirectory2 = (projectRoot, requested) => {
1461
+ const root = resolve4(projectRoot);
1462
+ const output = resolve4(root, requested ?? ".absolutejs/mobile/releases/ios");
1463
+ const projectRelative = relative4(root, output);
1464
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep3}`) || isAbsolute3(projectRelative)) {
1465
+ throw new TypeError("mobile build --outdir must remain inside the project.");
1466
+ }
1467
+ return output;
1468
+ };
1469
+ var sha256File2 = async (path) => createHash5("sha256").update(await readFile5(path)).digest("hex");
1470
+ var findByExtension = async (root, extension) => {
1471
+ if (!await pathExists4(root))
1472
+ return;
1473
+ const entries = await readdir3(root, { withFileTypes: true });
1474
+ const matches = await Promise.all(entries.map(async (entry) => {
1475
+ const path = join5(root, entry.name);
1476
+ if (entry.isDirectory() && entry.name.endsWith(extension))
1477
+ return path;
1478
+ if (entry.isFile() && entry.name.endsWith(extension))
1479
+ return path;
1480
+ return entry.isDirectory() ? findByExtension(path, extension) : undefined;
1481
+ }));
1482
+ return matches.find((match) => match !== undefined);
1483
+ };
1484
+ var exportOptions = () => `<?xml version="1.0" encoding="UTF-8"?>
1485
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1486
+ <plist version="1.0"><dict>
1487
+ <key>destination</key><string>export</string>
1488
+ <key>manageAppVersionAndBuildNumber</key><false/>
1489
+ <key>method</key><string>app-store-connect</string>
1490
+ <key>signingStyle</key><string>automatic</string>
1491
+ <key>stripSwiftSymbols</key><true/>
1492
+ <key>uploadSymbols</key><true/>
1493
+ </dict></plist>
1494
+ `;
1495
+ var requireBuildNumber = (value) => {
1496
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))
1497
+ throw new TypeError("iOS build number must be a positive integer.");
1498
+ return value;
1499
+ };
1500
+ var installRelease2 = async (artifactPath, metadata, outputRoot) => {
1501
+ const releaseRoot = join5(outputRoot, metadata.releaseId);
1502
+ const destination = join5(releaseRoot, "App.ipa");
1503
+ if (await pathExists4(releaseRoot)) {
1504
+ const value = JSON.parse(await readFile5(join5(releaseRoot, "release.json"), "utf8"));
1505
+ if (!isRecord2(value) || value.artifact !== "App.ipa" || Object.entries(metadata).some(([key, expected]) => Reflect.get(value, key) !== expected)) {
1506
+ throw new TypeError(`Immutable iOS release ${metadata.releaseId} does not match its content.`);
1507
+ }
1508
+ const [bytes, sha256] = await Promise.all([
1509
+ stat2(destination).then(({ size }) => size),
1510
+ sha256File2(destination)
1511
+ ]);
1512
+ if (bytes !== metadata.bytes || sha256 !== metadata.sha256)
1513
+ throw new TypeError(`Immutable iOS release ${metadata.releaseId} artifact is missing or modified.`);
1514
+ return {
1515
+ artifactPath: destination,
1516
+ metadata: {
1517
+ ...metadata,
1518
+ artifact: "App.ipa"
1519
+ },
1520
+ releaseRoot
1521
+ };
1522
+ }
1523
+ await mkdir4(dirname3(releaseRoot), { recursive: true });
1524
+ const staging = await mkdtemp3(join5(dirname3(releaseRoot), ".ios-stage-"));
1525
+ try {
1526
+ await copyFile3(artifactPath, join5(staging, "App.ipa"));
1527
+ const complete = {
1528
+ ...metadata,
1529
+ artifact: "App.ipa"
1530
+ };
1531
+ await writeFile5(join5(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
1532
+ `, { flag: "wx" });
1533
+ await rename5(staging, releaseRoot);
1534
+ return { artifactPath: destination, metadata: complete, releaseRoot };
1535
+ } finally {
1536
+ await rm4(staging, { force: true, recursive: true }).catch(() => {
1537
+ return;
1538
+ });
1539
+ }
1540
+ };
1541
+ var buildAbsoluteIosRelease = async (options) => {
1542
+ if (options.buildNumber !== undefined && options.prepareBuildNumber)
1543
+ throw new TypeError("iOS release buildNumber and prepareBuildNumber cannot be combined.");
1544
+ if ((options.host ?? detectAbsoluteMobileHost()) !== "macos")
1545
+ throw new TypeError("iOS release builds require macOS and Xcode.");
1546
+ const marketingVersion = options.config.iosVersion;
1547
+ if (!marketingVersion)
1548
+ throw new TypeError("iOS release builds require mobile.ios.version in absolutejs.config.ts.");
1549
+ const manifest = requireManifest2(JSON.parse(await readFile5(join5(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1550
+ if (manifest.appId !== options.config.appId)
1551
+ throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1552
+ const nativeDirectory = join5(options.config.nativeProjectDirectory, "ios");
1553
+ let buildNumber = requireBuildNumber(options.buildNumber);
1554
+ if (options.prepareBuildNumber) {
1555
+ const nativeFingerprint = await fingerprintAbsoluteIosNativeProject(nativeDirectory);
1556
+ const buildIdentity = createHash5("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}\x00${marketingVersion}`).digest("hex");
1557
+ buildNumber = requireBuildNumber(await options.prepareBuildNumber(buildIdentity));
1558
+ }
1559
+ const stagingParent = resolve4(options.projectRoot, ".absolutejs/mobile");
1560
+ await mkdir4(stagingParent, { recursive: true });
1561
+ const staging = await mkdtemp3(join5(stagingParent, ".ios-build-"));
1562
+ const archivePath = join5(staging, "App.xcarchive");
1563
+ const exportPath = join5(staging, "export");
1564
+ const exportPlist = join5(staging, "ExportOptions.plist");
1565
+ await mkdir4(exportPath, { recursive: true });
1566
+ await writeFile5(exportPlist, exportOptions());
1567
+ const run = options.run ?? defaultRun;
1568
+ try {
1569
+ const versionArguments = [
1570
+ `MARKETING_VERSION=${marketingVersion}`,
1571
+ ...buildNumber === undefined ? [] : [`CURRENT_PROJECT_VERSION=${buildNumber}`]
1572
+ ];
1573
+ const archiveExit = await run([
1574
+ "xcodebuild",
1575
+ "-workspace",
1576
+ join5(nativeDirectory, "App", "App.xcworkspace"),
1577
+ "-scheme",
1578
+ "App",
1579
+ "-configuration",
1580
+ "Release",
1581
+ "-destination",
1582
+ "generic/platform=iOS",
1583
+ "-archivePath",
1584
+ archivePath,
1585
+ ...versionArguments,
1586
+ "archive"
1587
+ ], { cwd: nativeDirectory });
1588
+ if (archiveExit !== 0)
1589
+ throw new TypeError("Xcode failed to archive the iOS app.");
1590
+ const archivedApp = await findByExtension(join5(archivePath, "Products", "Applications"), ".app");
1591
+ const capture = options.capture ?? defaultCapture2;
1592
+ const signed = archivedApp ? capture([
1593
+ "codesign",
1594
+ "--verify",
1595
+ "--deep",
1596
+ "--strict",
1597
+ archivedApp
1598
+ ]).exitCode === 0 : false;
1599
+ if (!signed && !options.allowUnsigned)
1600
+ throw new TypeError("Xcode produced an unsigned iOS archive. Configure signing in the source-owned Xcode project, or pass --unsigned only for a non-publishable build.");
1601
+ const exportExit = await run([
1602
+ "xcodebuild",
1603
+ "-exportArchive",
1604
+ "-archivePath",
1605
+ archivePath,
1606
+ "-exportPath",
1607
+ exportPath,
1608
+ "-exportOptionsPlist",
1609
+ exportPlist
1610
+ ], { cwd: nativeDirectory });
1611
+ if (exportExit !== 0)
1612
+ throw new TypeError("Xcode failed to export the App Store IPA.");
1613
+ const artifactPath = await findByExtension(exportPath, ".ipa");
1614
+ if (!artifactPath)
1615
+ throw new TypeError("Xcode did not produce an exported IPA.");
1616
+ const [bytes, sha256] = await Promise.all([
1617
+ stat2(artifactPath).then(({ size }) => size),
1618
+ sha256File2(artifactPath)
1619
+ ]);
1620
+ const releaseId = `amobile_ios_${sha256}`;
1621
+ return await installRelease2(artifactPath, {
1622
+ appBuild: manifest.appBuild,
1623
+ appId: manifest.appId,
1624
+ ...buildNumber === undefined ? {} : { buildNumber },
1625
+ bytes,
1626
+ engine: "capacitor",
1627
+ format: 1,
1628
+ marketingVersion,
1629
+ platform: "ios",
1630
+ releaseId,
1631
+ runtime: manifest.runtime,
1632
+ sha256,
1633
+ signed,
1634
+ type: "ipa"
1635
+ }, safeOutputDirectory2(options.projectRoot, options.outputDirectory));
1636
+ } finally {
1637
+ await rm4(staging, { force: true, recursive: true }).catch(() => {
1638
+ return;
1639
+ });
1640
+ }
1641
+ };
1642
+ // src/mobile/iosConformance.ts
1643
+ import { readFile as readFile6, stat as stat3 } from "fs/promises";
1644
+ 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");
1645
+ var parseAbsoluteIosHmrLog = (line) => {
1646
+ const match = HMR_LINE.exec(line);
1647
+ if (!match)
1648
+ return null;
1649
+ const [, , action, durationValue, serverValue, clientValue] = match;
1650
+ let outcome = "reloaded";
1651
+ if (action === "applied in")
1652
+ outcome = "applied";
1653
+ if (action === "failed after")
1654
+ outcome = "failed";
1655
+ const serverMs = serverValue === undefined ? undefined : Number(serverValue);
1656
+ const clientMs = clientValue === undefined ? undefined : Number(clientValue);
1657
+ return {
1658
+ ...clientMs === undefined ? {} : { clientMs },
1659
+ duration: Number(durationValue),
1660
+ line: match[0],
1661
+ outcome,
1662
+ ...serverMs === undefined ? {} : { serverMs }
1663
+ };
1664
+ };
1665
+ var findHmrApply = (lines) => {
1666
+ const apply = lines.map((line) => parseAbsoluteIosHmrLog(line)).find((candidate) => candidate !== null);
1667
+ if (apply?.outcome === "failed")
1668
+ throw new Error(`iOS HMR client reported a failed apply: ${apply.line}`);
1669
+ return apply;
1670
+ };
1671
+ var waitForAbsoluteIosHmrLog = async (options) => {
1672
+ const sleep = options.sleep ?? Bun.sleep;
1673
+ const timeoutMs = options.timeoutMs ?? 30000;
1674
+ const deadline = Date.now() + timeoutMs;
1675
+ let offset = options.startOffset ?? await stat3(options.logPath).then(({ size }) => size).catch(() => 0);
1676
+ let buffered = "";
1677
+ const poll = async () => {
1678
+ if (Date.now() > deadline)
1679
+ throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
1680
+ options.signal?.throwIfAborted();
1681
+ const contents = await readFile6(options.logPath).catch(() => Buffer.alloc(0));
1682
+ if (contents.byteLength < offset) {
1683
+ offset = 0;
1684
+ buffered = "";
1685
+ }
1686
+ if (contents.byteLength > offset) {
1687
+ buffered += contents.subarray(offset).toString("utf8");
1688
+ offset = contents.byteLength;
1689
+ const lines = buffered.split(/\r?\n/u);
1690
+ buffered = lines.pop() ?? "";
1691
+ const apply = findHmrApply(lines);
1692
+ if (apply)
1693
+ return apply;
1694
+ }
1695
+ await sleep(100);
1696
+ return poll();
1697
+ };
1698
+ return poll();
1699
+ };
1700
+ // src/mobile/iosSimulatorController.ts
1701
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "crypto";
1702
+ import {
1703
+ access as access6,
1704
+ copyFile as copyFile4,
1705
+ mkdir as mkdir5,
1706
+ readFile as readFile7,
1707
+ rename as rename6,
1708
+ rm as rm5,
1709
+ writeFile as writeFile6
1710
+ } from "fs/promises";
1711
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join6, relative as relative5, resolve as resolve5, sep as sep4 } from "path";
1712
+ init_getDurationString();
1713
+ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone";
1714
+ var BOOT_TIMEOUT_MS = 180000;
1715
+ var BOOT_POLL_MS = 1000;
1716
+ var DEV_JOURNAL_FORMAT = 1;
1717
+ var NATIVE_CACHE_FORMAT = 1;
1718
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1719
+ var pathExists5 = async (path) => {
1720
+ try {
1721
+ await access6(path);
1722
+ return true;
1723
+ } catch {
1724
+ return false;
1725
+ }
1726
+ };
1727
+ var throwIfAborted2 = (signal) => signal?.throwIfAborted();
1728
+ var defaultCapture3 = (command, options = {}) => {
1729
+ try {
1730
+ const result = Bun.spawnSync(command, {
1731
+ cwd: options.cwd,
1732
+ env: options.env,
1733
+ stderr: "pipe",
1734
+ stdin: "ignore",
1735
+ stdout: "pipe"
1736
+ });
1737
+ return {
1738
+ exitCode: result.exitCode,
1739
+ stderr: result.stderr.toString(),
1740
+ stdout: result.stdout.toString()
1741
+ };
1742
+ } catch (error) {
1743
+ return {
1744
+ exitCode: 1,
1745
+ stderr: error instanceof Error ? error.message : String(error),
1746
+ stdout: ""
1747
+ };
1748
+ }
1749
+ };
1750
+ var defaultRun2 = async (command, options = {}) => {
1751
+ const process2 = Bun.spawn(command, {
1752
+ cwd: options.cwd,
1753
+ env: options.env,
1754
+ signal: options.signal,
1755
+ stderr: "inherit",
1756
+ stdin: "inherit",
1757
+ stdout: "inherit"
1758
+ });
1759
+ return process2.exited;
1760
+ };
1761
+ var defaultSpawn = (command, options = {}) => {
1762
+ Bun.spawn(command, {
1763
+ cwd: options.cwd,
1764
+ env: options.env,
1765
+ signal: options.signal,
1766
+ stderr: "ignore",
1767
+ stdin: "ignore",
1768
+ stdout: "ignore"
1769
+ });
1770
+ };
1771
+ var consumeLines = async (stream, onLine) => {
1772
+ const reader = stream.getReader();
1773
+ const decoder = new TextDecoder;
1774
+ let buffered = "";
1775
+ const pump = async () => {
1776
+ const { done, value } = await reader.read();
1777
+ if (done)
1778
+ return;
1779
+ buffered += decoder.decode(value, { stream: true });
1780
+ const lines = buffered.split(/\r?\n/u);
1781
+ buffered = lines.pop() ?? "";
1782
+ lines.forEach(onLine);
1783
+ await pump();
1784
+ };
1785
+ try {
1786
+ await pump();
1787
+ buffered += decoder.decode();
1788
+ if (buffered)
1789
+ onLine(buffered);
1790
+ } finally {
1791
+ reader.releaseLock();
1792
+ }
1793
+ };
1794
+ var defaultStartNativeLogs = (command, options, onLine) => {
1795
+ const process2 = Bun.spawn(command, {
1796
+ cwd: options.cwd,
1797
+ env: options.env,
1798
+ signal: options.signal,
1799
+ stderr: "pipe",
1800
+ stdin: "ignore",
1801
+ stdout: "pipe"
1802
+ });
1803
+ consumeLines(process2.stdout, onLine);
1804
+ consumeLines(process2.stderr, onLine);
1805
+ return {
1806
+ close: async () => {
1807
+ try {
1808
+ process2.kill();
1809
+ } catch {}
1810
+ await process2.exited.catch(() => {
1811
+ return;
1812
+ });
1813
+ }
1814
+ };
1815
+ };
1816
+ var requireSuccess2 = async (command, label, run, options) => {
1817
+ const exitCode = await run(command, options);
1818
+ if (exitCode !== 0)
1819
+ throw new Error(`${label} failed with status ${exitCode}.`);
1820
+ };
1821
+ var requireCapturedSuccess = (result, label) => {
1822
+ if (result.exitCode !== 0) {
1823
+ throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
1824
+ }
1825
+ return result.stdout.trim();
1826
+ };
1827
+ var parseJson = (source, label) => {
1828
+ try {
1829
+ const parsed = JSON.parse(source);
1830
+ if (isRecord3(parsed))
1831
+ return parsed;
1832
+ } catch {}
1833
+ throw new Error(`Invalid ${label} JSON from simctl.`);
1834
+ };
1835
+ var parseIosDeviceTypes = (source) => {
1836
+ const parsed = parseJson(source, "device type");
1837
+ const types = parsed.devicetypes;
1838
+ if (!Array.isArray(types))
1839
+ return [];
1840
+ return types.flatMap((type) => {
1841
+ if (!isRecord3(type))
1842
+ return [];
1843
+ const { identifier } = type;
1844
+ const { name } = type;
1845
+ return typeof identifier === "string" && typeof name === "string" ? [{ identifier, name }] : [];
1846
+ });
1847
+ };
1848
+ var parseIosRuntimes = (source) => {
1849
+ const parsed = parseJson(source, "runtime");
1850
+ const { runtimes } = parsed;
1851
+ if (!Array.isArray(runtimes))
1852
+ return [];
1853
+ return runtimes.flatMap((runtime) => {
1854
+ if (!isRecord3(runtime))
1855
+ return [];
1856
+ const { identifier } = runtime;
1857
+ const { name } = runtime;
1858
+ const { version } = runtime;
1859
+ if (typeof identifier !== "string" || typeof name !== "string" || typeof version !== "string")
1860
+ return [];
1861
+ return [
1862
+ {
1863
+ identifier,
1864
+ isAvailable: runtime.isAvailable !== false,
1865
+ name,
1866
+ version
1867
+ }
1868
+ ];
1869
+ });
1870
+ };
1871
+ var parseIosSimulators = (source) => {
1872
+ const parsed = parseJson(source, "device");
1873
+ const { devices } = parsed;
1874
+ if (!isRecord3(devices))
1875
+ return [];
1876
+ return Object.entries(devices).flatMap(([runtime, values]) => {
1877
+ if (!Array.isArray(values))
1878
+ return [];
1879
+ return values.flatMap((device) => {
1880
+ if (!isRecord3(device))
1881
+ return [];
1882
+ const { name } = device;
1883
+ const { state } = device;
1884
+ const { udid } = device;
1885
+ if (typeof name !== "string" || typeof state !== "string" || typeof udid !== "string")
1886
+ return [];
1887
+ return [
1888
+ {
1889
+ isAvailable: device.isAvailable !== false,
1890
+ name,
1891
+ runtime,
1892
+ state,
1893
+ udid
1894
+ }
1895
+ ];
1896
+ });
1897
+ });
1898
+ };
1899
+ var versionParts = (version) => version.split(".").map((part) => Number(part));
1900
+ var compareVersions = (left, right) => {
1901
+ const leftParts = versionParts(left);
1902
+ const rightParts = versionParts(right);
1903
+ const length = Math.max(leftParts.length, rightParts.length);
1904
+ for (let index = 0;index < length; index++) {
1905
+ const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
1906
+ if (difference !== 0)
1907
+ return difference;
1908
+ }
1909
+ return 0;
1910
+ };
1911
+ var latestIosRuntime = (runtimes) => runtimes.filter((runtime) => runtime.isAvailable && runtime.identifier.includes("SimRuntime.iOS-")).sort((left, right) => compareVersions(right.version, left.version))[0];
1912
+ var iphoneGeneration = (name) => Number(/iPhone\s+(\d+)/u.exec(name)?.[1] ?? 0);
1913
+ var preferredIphoneType = (types) => types.filter((type) => type.name.startsWith("iPhone")).sort((left, right) => {
1914
+ const generation = iphoneGeneration(right.name) - iphoneGeneration(left.name);
1915
+ if (generation !== 0)
1916
+ return generation;
1917
+ const rightPro = right.name.includes("Pro") ? 1 : 0;
1918
+ const leftPro = left.name.includes("Pro") ? 1 : 0;
1919
+ return rightPro - leftPro;
1920
+ })[0];
1921
+ var journalPaths = (projectRoot) => {
1922
+ const root = join6(projectRoot, ".absolutejs", "mobile", "ios-dev-session");
1923
+ return {
1924
+ configBackup: join6(root, "capacitor-config.backup"),
1925
+ infoBackup: join6(root, "Info.plist.backup"),
1926
+ journal: join6(root, "journal.json"),
1927
+ root
1928
+ };
1929
+ };
1930
+ var nativeCachePath = (projectRoot) => join6(projectRoot, ".absolutejs", "mobile", "ios-native-cache.json");
1931
+ var isInside = (root, path) => {
1932
+ const value = relative5(resolve5(root), resolve5(path));
1933
+ return value === "" || !value.startsWith(`..${sep4}`) && value !== ".." && !isAbsolute4(value);
1934
+ };
1935
+ var parseJournal = (value) => {
1936
+ if (!isRecord3(value) || value.format !== DEV_JOURNAL_FORMAT)
1937
+ return null;
1938
+ const { configBackupPath } = value;
1939
+ const { infoBackupPath } = value;
1940
+ const { infoPath } = value;
1941
+ const { nativeConfigPath } = value;
1942
+ if (typeof configBackupPath !== "string" || typeof infoBackupPath !== "string" || typeof infoPath !== "string" || typeof nativeConfigPath !== "string")
1943
+ return null;
1944
+ return {
1945
+ configBackupPath,
1946
+ format: DEV_JOURNAL_FORMAT,
1947
+ infoBackupPath,
1948
+ infoPath,
1949
+ nativeConfigPath
1950
+ };
1951
+ };
1952
+ var repairAbsoluteIosDevSession = async (projectRoot) => {
1953
+ const paths = journalPaths(projectRoot);
1954
+ if (!await pathExists5(paths.journal)) {
1955
+ await rm5(paths.root, { force: true, recursive: true });
1956
+ return false;
1957
+ }
1958
+ const journal = await readFile7(paths.journal, "utf8").then((source) => parseJournal(JSON.parse(source))).catch(() => null);
1959
+ if (!journal || !isInside(projectRoot, journal.nativeConfigPath) || !isInside(projectRoot, journal.infoPath) || !isInside(paths.root, journal.configBackupPath) || !isInside(paths.root, journal.infoBackupPath)) {
1960
+ throw new Error(`Refusing unsafe or invalid iOS dev journal at ${paths.journal}.`);
1961
+ }
1962
+ if (await pathExists5(journal.configBackupPath))
1963
+ await copyFile4(journal.configBackupPath, journal.nativeConfigPath);
1964
+ if (await pathExists5(journal.infoBackupPath))
1965
+ await copyFile4(journal.infoBackupPath, journal.infoPath);
1966
+ await rm5(paths.root, { force: true, recursive: true });
1967
+ return true;
1968
+ };
1969
+ var iosDevelopmentInfoPlist = (source, cleartext) => {
1970
+ if (!cleartext)
1971
+ return source;
1972
+ const arbitraryLoads = /(<key>NSAllowsArbitraryLoads<\/key>\s*)<false\s*\/>/u;
1973
+ if (arbitraryLoads.test(source))
1974
+ return source.replace(arbitraryLoads, "$1<true/>");
1975
+ if (/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(source))
1976
+ return source;
1977
+ const transport = /(<key>NSAppTransportSecurity<\/key>\s*<dict>)/u;
1978
+ if (transport.test(source))
1979
+ return source.replace(transport, `$1
1980
+ <key>NSAllowsArbitraryLoads</key>
1981
+ <true/>`);
1982
+ return source.replace(/<dict>/u, `<dict>
1983
+ <key>NSAppTransportSecurity</key>
1984
+ <dict>
1985
+ <key>NSAllowsArbitraryLoads</key>
1986
+ <true/>
1987
+ </dict>`);
1988
+ };
1989
+ var writeDevProjection = async (project, port, https) => {
1990
+ const paths = journalPaths(project.projectRoot);
1991
+ await repairAbsoluteIosDevSession(project.projectRoot);
1992
+ const nativeConfigPath = join6(project.nativeDirectory, "App", "App", "capacitor.config.json");
1993
+ const infoPath = join6(project.nativeDirectory, "App", "App", "Info.plist");
1994
+ const [configSource, infoSource] = await Promise.all([
1995
+ readFile7(nativeConfigPath, "utf8"),
1996
+ readFile7(infoPath, "utf8")
1997
+ ]);
1998
+ const parsed = JSON.parse(configSource);
1999
+ if (!isRecord3(parsed))
2000
+ throw new Error(`Invalid Capacitor native config at ${nativeConfigPath}.`);
2001
+ await mkdir5(paths.root, { recursive: true });
2002
+ await Promise.all([
2003
+ writeFile6(paths.configBackup, configSource, { flag: "wx" }),
2004
+ writeFile6(paths.infoBackup, infoSource, { flag: "wx" })
2005
+ ]);
2006
+ const journal = {
2007
+ configBackupPath: paths.configBackup,
2008
+ format: DEV_JOURNAL_FORMAT,
2009
+ infoBackupPath: paths.infoBackup,
2010
+ infoPath,
2011
+ nativeConfigPath
2012
+ };
2013
+ await writeFile6(paths.journal, `${JSON.stringify(journal, null, "\t")}
2014
+ `, {
2015
+ flag: "wx"
2016
+ });
2017
+ const developmentUrl = new URL(`${https ? "https" : "http"}://localhost:${port}${project.config.entry}`);
2018
+ developmentUrl.searchParams.set("__absolute_target", "capacitor-ios");
2019
+ const existingServer = parsed.server;
2020
+ parsed.server = {
2021
+ ...isRecord3(existingServer) ? existingServer : {},
2022
+ cleartext: !https,
2023
+ url: developmentUrl.href
2024
+ };
2025
+ await Promise.all([
2026
+ writeFile6(nativeConfigPath, `${JSON.stringify(parsed, null, "\t")}
2027
+ `),
2028
+ writeFile6(infoPath, iosDevelopmentInfoPlist(infoSource, !https))
2029
+ ]);
2030
+ };
2031
+ var parseNativeCache = (value) => {
2032
+ if (!isRecord3(value))
2033
+ return null;
2034
+ const { appId, fingerprint, format, installations } = value;
2035
+ if (format !== NATIVE_CACHE_FORMAT || typeof appId !== "string" || typeof fingerprint !== "string" || !isRecord3(installations) || !Object.values(installations).every((identity) => typeof identity === "string"))
2036
+ return null;
2037
+ return {
2038
+ appId,
2039
+ fingerprint,
2040
+ format,
2041
+ installations: Object.fromEntries(Object.entries(installations).map(([udid, identity]) => [
2042
+ udid,
2043
+ String(identity)
2044
+ ]))
2045
+ };
2046
+ };
2047
+ var readNativeCache = (projectRoot) => readFile7(nativeCachePath(projectRoot), "utf8").then((source) => parseNativeCache(JSON.parse(source))).catch(() => null);
2048
+ var writeNativeCache = async (projectRoot, cache) => {
2049
+ const destination = nativeCachePath(projectRoot);
2050
+ const temporary = `${destination}.${process.pid}.${randomUUID2()}.tmp`;
2051
+ await mkdir5(dirname4(destination), { recursive: true });
2052
+ try {
2053
+ await writeFile6(temporary, `${JSON.stringify(cache, null, "\t")}
2054
+ `, {
2055
+ flag: "wx"
2056
+ });
2057
+ await rename6(temporary, destination);
2058
+ } finally {
2059
+ await rm5(temporary, { force: true }).catch(() => {
2060
+ return;
2061
+ });
2062
+ }
2063
+ };
2064
+ var fingerprintAbsoluteIosDevProject = async (project) => fingerprintAbsoluteIosNativeProject(project.nativeDirectory);
2065
+ var simulatorInventory = (xcrun, capture) => {
2066
+ const result = capture([
2067
+ xcrun,
2068
+ "simctl",
2069
+ "list",
2070
+ "devices",
2071
+ "available",
2072
+ "-j"
2073
+ ]);
2074
+ return parseIosSimulators(requireCapturedSuccess(result, "iOS simulator discovery"));
2075
+ };
2076
+ var ensureManagedSimulator = async (project, capture) => {
2077
+ const runtimes = parseIosRuntimes(requireCapturedSuccess(capture([project.xcrun, "simctl", "list", "runtimes", "-j"]), "iOS runtime discovery"));
2078
+ const runtime = latestIosRuntime(runtimes);
2079
+ if (!runtime)
2080
+ throw new Error("No available iOS Simulator runtime. Run absolute mobile doctor ios --fix.");
2081
+ const [existing] = simulatorInventory(project.xcrun, capture).filter((device) => device.isAvailable && device.name === ABSOLUTE_IOS_SIMULATOR_NAME && device.runtime === runtime.identifier).sort((left, right) => Number(right.state === "Booted") - Number(left.state === "Booted"));
2082
+ if (existing)
2083
+ return { created: false, device: existing };
2084
+ const types = parseIosDeviceTypes(requireCapturedSuccess(capture([project.xcrun, "simctl", "list", "devicetypes", "-j"]), "iOS device-type discovery"));
2085
+ const type = preferredIphoneType(types);
2086
+ if (!type)
2087
+ throw new Error("Xcode did not report an iPhone simulator type.");
2088
+ const [udid] = requireCapturedSuccess(capture([
2089
+ project.xcrun,
2090
+ "simctl",
2091
+ "create",
2092
+ ABSOLUTE_IOS_SIMULATOR_NAME,
2093
+ type.identifier,
2094
+ runtime.identifier
2095
+ ]), "iOS simulator creation").split(/\s/u);
2096
+ if (!udid)
2097
+ throw new Error("simctl did not return the created simulator UDID.");
2098
+ return {
2099
+ created: true,
2100
+ device: {
2101
+ isAvailable: true,
2102
+ name: ABSOLUTE_IOS_SIMULATOR_NAME,
2103
+ runtime: runtime.identifier,
2104
+ state: "Shutdown",
2105
+ udid
2106
+ }
2107
+ };
2108
+ };
2109
+ var waitForBootedSimulator = async (project, udid, capture, sleep, signal) => {
2110
+ const deadline = Date.now() + BOOT_TIMEOUT_MS;
2111
+ const poll = async () => {
2112
+ throwIfAborted2(signal);
2113
+ const device = simulatorInventory(project.xcrun, capture).find((candidate) => candidate.udid === udid);
2114
+ if (device?.state === "Booted")
2115
+ return;
2116
+ if (Date.now() > deadline)
2117
+ throw new Error(`iOS simulator ${udid} did not finish booting within ${BOOT_TIMEOUT_MS / 1000}s.`);
2118
+ await sleep(BOOT_POLL_MS);
2119
+ await poll();
2120
+ };
2121
+ return poll();
2122
+ };
2123
+ var bootSimulator = (project, device, capture) => {
2124
+ if (device.state === "Booted")
2125
+ return;
2126
+ requireCapturedSuccess(capture([project.xcrun, "simctl", "boot", device.udid]), "iOS simulator boot");
2127
+ };
2128
+ var installedAppIdentity = (project, udid, capture) => {
2129
+ const result = capture([
2130
+ project.xcrun,
2131
+ "simctl",
2132
+ "get_app_container",
2133
+ udid,
2134
+ project.config.appId,
2135
+ "app"
2136
+ ]);
2137
+ return result.exitCode === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
2138
+ };
2139
+ var buildIosDebugApp = async (project, udid, fingerprint, run, signal) => {
2140
+ const derivedDataPath = join6(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash6("sha256").update(project.config.appId).digest("hex").slice(0, 16));
2141
+ await mkdir5(derivedDataPath, { recursive: true });
2142
+ await requireSuccess2([
2143
+ project.xcodebuild,
2144
+ "-workspace",
2145
+ join6(project.nativeDirectory, "App", "App.xcworkspace"),
2146
+ "-scheme",
2147
+ "App",
2148
+ "-configuration",
2149
+ "Debug",
2150
+ "-destination",
2151
+ `platform=iOS Simulator,id=${udid}`,
2152
+ "-derivedDataPath",
2153
+ derivedDataPath,
2154
+ "build"
2155
+ ], "iOS simulator build", run, { cwd: project.nativeDirectory, signal });
2156
+ const appPath = join6(derivedDataPath, "Build", "Products", "Debug-iphonesimulator", "App.app");
2157
+ if (!await pathExists5(appPath))
2158
+ throw new Error(`Xcode did not produce the simulator app at ${appPath}.`);
2159
+ return appPath;
2160
+ };
2161
+ var ensureIosDebugApp = async (options) => {
2162
+ const installed = installedAppIdentity(options.project, options.udid, options.capture);
2163
+ const cacheHit = options.cache?.appId === options.project.config.appId && options.cache.fingerprint === options.fingerprint && installed !== undefined && options.cache.installations[options.udid] === installed;
2164
+ if (cacheHit) {
2165
+ options.log(`iOS native app is unchanged on ${options.udid}; skipped Xcode build and install.`);
2166
+ return true;
2167
+ }
2168
+ options.log("iOS native inputs changed or the installed app is stale; rebuilding.");
2169
+ options.transition("building");
2170
+ const appPath = await buildIosDebugApp(options.project, options.udid, options.fingerprint, options.run, options.signal);
2171
+ throwIfAborted2(options.signal);
2172
+ options.transition("installing");
2173
+ await requireSuccess2([options.project.xcrun, "simctl", "install", options.udid, appPath], "iOS simulator app installation", options.run, { signal: options.signal });
2174
+ const updated = installedAppIdentity(options.project, options.udid, options.capture);
2175
+ if (updated) {
2176
+ await writeNativeCache(options.project.projectRoot, {
2177
+ appId: options.project.config.appId,
2178
+ fingerprint: options.fingerprint,
2179
+ format: NATIVE_CACHE_FORMAT,
2180
+ installations: { [options.udid]: updated }
2181
+ }).catch((error) => options.log(`iOS native cache could not be saved: ${error instanceof Error ? error.message : String(error)}`));
2182
+ }
2183
+ return false;
2184
+ };
2185
+ var SECRET_VALUE = /((?:authorization|cookie|password|secret|token|oauth[_-]?code)\s*[:=]\s*)([^\s,;]+)/giu;
2186
+ var BEARER_VALUE = new RegExp(String.raw`\bBearer\s+[A-Za-z0-9._~+/-]+=*`, "giu");
2187
+ var JWT_VALUE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu;
2188
+ var IOS_LOG_PATTERN = new RegExp(String.raw`\s(Debug|Info|Notice|Error|Fault)\s+.*?\[[^\]]+\]\s+\[([^\]]+)\]\s*(.*)$`, "iu");
2189
+ var parseAbsoluteIosLogLine = (line) => {
2190
+ const sanitized = redactAbsoluteIosLog(line).trim();
2191
+ if (!sanitized)
2192
+ return null;
2193
+ const match = IOS_LOG_PATTERN.exec(sanitized);
2194
+ const candidate = match?.[1]?.toLowerCase();
2195
+ const level = candidate === "debug" || candidate === "error" || candidate === "fault" || candidate === "notice" ? candidate : "info";
2196
+ return {
2197
+ level,
2198
+ message: match?.[3]?.trim() || sanitized,
2199
+ tag: match?.[2]?.trim() || "App"
2200
+ };
2201
+ };
2202
+ var redactAbsoluteIosLog = (value) => value.replaceAll(BEARER_VALUE, "Bearer [REDACTED]").replaceAll(JWT_VALUE, "[REDACTED_JWT]").replaceAll(SECRET_VALUE, "$1[REDACTED]").replaceAll(/\p{C}/gu, "");
2203
+ var attachNativeLogs = (project, udid, options) => {
2204
+ if (!options.nativeLog)
2205
+ return null;
2206
+ const start = options.startNativeLogs ?? defaultStartNativeLogs;
2207
+ return start([
2208
+ project.xcrun,
2209
+ "simctl",
2210
+ "spawn",
2211
+ udid,
2212
+ "log",
2213
+ "stream",
2214
+ "--style",
2215
+ "compact",
2216
+ "--level",
2217
+ "debug",
2218
+ "--predicate",
2219
+ 'process == "App"'
2220
+ ], { signal: options.signal }, (line) => {
2221
+ const entry = parseAbsoluteIosLogLine(line);
2222
+ if (entry)
2223
+ options.nativeLog?.(entry);
2224
+ });
2225
+ };
2226
+ var IOS_TIMING_PHASES = [
2227
+ ["syncing", "Capacitor sync"],
2228
+ ["configuring", "dev config"],
2229
+ ["fingerprinting", "fingerprint"],
2230
+ ["booting", "simulator"],
2231
+ ["connecting", "device ready"],
2232
+ ["checking-native", "app check"],
2233
+ ["building", "Xcode"],
2234
+ ["installing", "install"],
2235
+ ["launching", "launch"],
2236
+ ["streaming-logs", "logs"]
2237
+ ];
2238
+ var timingSummary = (timings) => IOS_TIMING_PHASES.map(([phase, label]) => {
2239
+ const duration = timings[phase];
2240
+ return duration === undefined ? null : `${label} ${getDurationString(duration)}`;
2241
+ }).filter((value) => value !== null).join(", ");
2242
+ var prepareAbsoluteIosDevProject = async (config, options) => {
2243
+ if (detectAbsoluteMobileHost() !== "macos")
2244
+ throw new Error("iOS simulation requires macOS and Xcode.");
2245
+ const projectRoot = resolve5(options.projectRoot);
2246
+ const checks = await inspectAbsoluteMobileToolchain({ host: "macos" });
2247
+ const failed = checks.filter((check) => check.platform === "ios" && (check.status === "fail" || check.status === "warn"));
2248
+ if (failed.length > 0)
2249
+ throw new Error(`iOS simulation is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
2250
+ const xcrun = checks.find((check) => check.id === "ios.xcrun")?.path;
2251
+ const xcodebuild = checks.find((check) => check.id === "ios.xcodebuild")?.path;
2252
+ if (!xcrun || !xcodebuild)
2253
+ throw new Error("Xcode tools disappeared after readiness checks.");
2254
+ const cap = join6(projectRoot, "node_modules", ".bin", "cap");
2255
+ if (!await pathExists5(cap))
2256
+ throw new Error("Capacitor CLI is not installed. Run absolute mobile init first.");
2257
+ await writeAbsoluteCapacitorConfig(config, { projectRoot });
2258
+ await mkdir5(config.bundleDirectory, { recursive: true });
2259
+ const placeholder = join6(config.bundleDirectory, "index.html");
2260
+ if (!await pathExists5(placeholder))
2261
+ await writeFile6(placeholder, `<!doctype html><title>AbsoluteJS mobile development</title>
2262
+ `);
2263
+ const nativeDirectory = join6(config.nativeProjectDirectory, "ios");
2264
+ if (!await pathExists5(nativeDirectory)) {
2265
+ if (!options.createNativeProject)
2266
+ throw new Error("iOS native project has not been created.");
2267
+ const run = options.run ?? defaultRun2;
2268
+ if (await run([cap, "add", "ios"], { cwd: projectRoot }) !== 0)
2269
+ throw new Error("Capacitor iOS project creation failed.");
2270
+ }
2271
+ return {
2272
+ cap,
2273
+ config,
2274
+ nativeDirectory,
2275
+ projectRoot,
2276
+ xcodebuild,
2277
+ xcrun
2278
+ };
2279
+ };
2280
+ var startAbsoluteIosDevSession = async (options) => {
2281
+ const { project } = options;
2282
+ const capture = options.capture ?? defaultCapture3;
2283
+ const run = options.run ?? defaultRun2;
2284
+ const sleep = options.sleep ?? Bun.sleep;
2285
+ const spawn = options.spawn ?? defaultSpawn;
2286
+ const log = options.log ?? console.log;
2287
+ const startedAt = performance.now();
2288
+ let phaseStartedAt = performance.now();
2289
+ const timings = {};
2290
+ let state = "syncing";
2291
+ const transition = (next) => {
2292
+ if (next === state) {
2293
+ options.onStateChange?.(next);
2294
+ return;
2295
+ }
2296
+ const now = performance.now();
2297
+ const durationMs = now - phaseStartedAt;
2298
+ timings[state] = (timings[state] ?? 0) + durationMs;
2299
+ options.onPhaseTiming?.({
2300
+ durationMs,
2301
+ phase: state,
2302
+ totalMs: now - startedAt
2303
+ });
2304
+ state = next;
2305
+ phaseStartedAt = now;
2306
+ options.onStateChange?.(next);
2307
+ };
2308
+ let nativeLogs = null;
2309
+ const closeLogs = async () => {
2310
+ const stream = nativeLogs;
2311
+ nativeLogs = null;
2312
+ await stream?.close().catch(() => {
2313
+ return;
2314
+ });
2315
+ };
2316
+ try {
2317
+ await repairAbsoluteIosDevSession(project.projectRoot);
2318
+ throwIfAborted2(options.signal);
2319
+ transition("syncing");
2320
+ await requireSuccess2([project.cap, "sync", "ios"], "Capacitor iOS synchronization", run, { cwd: project.projectRoot, signal: options.signal });
2321
+ transition("configuring");
2322
+ await writeDevProjection(project, options.port, options.https === true);
2323
+ throwIfAborted2(options.signal);
2324
+ const fingerprintStartedAt = performance.now();
2325
+ const fingerprintPromise = fingerprintAbsoluteIosDevProject(project).then((fingerprint2) => {
2326
+ timings.fingerprinting = performance.now() - fingerprintStartedAt;
2327
+ return fingerprint2;
2328
+ });
2329
+ transition("booting");
2330
+ const { created, device } = await ensureManagedSimulator(project, capture);
2331
+ const startedSimulator = created || device.state !== "Booted";
2332
+ bootSimulator(project, device, capture);
2333
+ spawn([
2334
+ "open",
2335
+ "-a",
2336
+ "Simulator",
2337
+ "--args",
2338
+ "-CurrentDeviceUDID",
2339
+ device.udid
2340
+ ]);
2341
+ transition("connecting");
2342
+ await waitForBootedSimulator(project, device.udid, capture, sleep, options.signal);
2343
+ await requireSuccess2([project.xcrun, "simctl", "bootstatus", device.udid, "-b"], "iOS simulator boot readiness", run, { signal: options.signal });
2344
+ const fingerprint = await fingerprintPromise;
2345
+ transition("checking-native");
2346
+ const nativeCacheHit = await ensureIosDebugApp({
2347
+ cache: await readNativeCache(project.projectRoot),
2348
+ capture,
2349
+ fingerprint,
2350
+ log,
2351
+ project,
2352
+ run,
2353
+ signal: options.signal,
2354
+ transition,
2355
+ udid: device.udid
2356
+ });
2357
+ throwIfAborted2(options.signal);
2358
+ if (options.nativeLog)
2359
+ transition("streaming-logs");
2360
+ nativeLogs = attachNativeLogs(project, device.udid, options);
2361
+ transition("launching");
2362
+ await requireSuccess2([
2363
+ project.xcrun,
2364
+ "simctl",
2365
+ "launch",
2366
+ "--terminate-running-process",
2367
+ device.udid,
2368
+ project.config.appId
2369
+ ], "iOS app launch", run, { signal: options.signal });
2370
+ transition("ready");
2371
+ timings.total = performance.now() - startedAt;
2372
+ log(`iOS simulator connected (${device.udid}) with HMR on port ${options.port} in ${getDurationString(timings.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
2373
+ log(`iOS startup: ${timingSummary(timings)}.`);
2374
+ let closed = false;
2375
+ const close = async () => {
2376
+ if (closed)
2377
+ return;
2378
+ closed = true;
2379
+ transition("closing");
2380
+ await closeLogs();
2381
+ await repairAbsoluteIosDevSession(project.projectRoot);
2382
+ transition("closed");
2383
+ };
2384
+ return {
2385
+ close,
2386
+ nativeCacheHit,
2387
+ startedSimulator,
2388
+ timings: { ...timings },
2389
+ udid: device.udid,
2390
+ rebuild: async () => {
2391
+ if (closed)
2392
+ throw new Error("iOS development session is closed.");
2393
+ log("iOS native inputs changed; rebuilding without restarting the dev server.");
2394
+ await close();
2395
+ return startAbsoluteIosDevSession(options);
2396
+ },
2397
+ relaunch: async () => {
2398
+ if (closed)
2399
+ throw new Error("iOS development session is closed.");
2400
+ transition("launching");
2401
+ try {
2402
+ await requireSuccess2([
2403
+ project.xcrun,
2404
+ "simctl",
2405
+ "launch",
2406
+ "--terminate-running-process",
2407
+ device.udid,
2408
+ project.config.appId
2409
+ ], "iOS app relaunch", run, { signal: options.signal });
2410
+ transition("ready");
2411
+ log(`iOS app relaunched on ${device.udid}.`);
2412
+ } catch (error) {
2413
+ transition("failed");
2414
+ throw error;
2415
+ }
2416
+ },
2417
+ screenshot: async (destination) => {
2418
+ const resolved = resolve5(project.projectRoot, destination);
2419
+ if (!isInside(project.projectRoot, resolved))
2420
+ throw new Error("iOS screenshot destination must remain inside the project.");
2421
+ await mkdir5(dirname4(resolved), { recursive: true });
2422
+ await requireSuccess2([
2423
+ project.xcrun,
2424
+ "simctl",
2425
+ "io",
2426
+ device.udid,
2427
+ "screenshot",
2428
+ resolved
2429
+ ], "iOS simulator screenshot", run, { signal: options.signal });
2430
+ return resolved;
2431
+ },
2432
+ get state() {
2433
+ return state;
2434
+ }
2435
+ };
2436
+ } catch (error) {
2437
+ transition("failed");
2438
+ await closeLogs();
2439
+ await repairAbsoluteIosDevSession(project.projectRoot);
2440
+ throw error;
2441
+ }
2442
+ };
2443
+ // src/mobile/iosNativeWatcher.ts
2444
+ import { watch } from "fs";
2445
+ import { basename } from "path";
2446
+ var NATIVE_CHANGE_DEBOUNCE_MS = 500;
2447
+ var ROOT_NATIVE_INPUTS = new Set([
2448
+ "absolute.config.js",
2449
+ "absolute.config.mjs",
2450
+ "absolute.config.ts",
2451
+ "absolutejs.config.js",
2452
+ "absolutejs.config.mjs",
2453
+ "absolutejs.config.ts",
2454
+ "bun.lock",
2455
+ "bun.lockb",
2456
+ "capacitor.config.js",
2457
+ "capacitor.config.ts",
2458
+ "package.json"
2459
+ ]);
2460
+ var createAbsoluteIosNativeWatcher = async (options) => {
2461
+ let fingerprint = await fingerprintAbsoluteIosDevProject(options.project);
2462
+ let closed = false;
2463
+ let running = false;
2464
+ let timer;
2465
+ let rootInputChanged = false;
2466
+ const changedPaths = new Set;
2467
+ const watchers = [];
2468
+ const debounceMs = options.debounceMs ?? NATIVE_CHANGE_DEBOUNCE_MS;
2469
+ const close = () => {
2470
+ if (closed)
2471
+ return;
2472
+ closed = true;
2473
+ if (timer)
2474
+ clearTimeout(timer);
2475
+ watchers.forEach((watcher) => watcher.close());
2476
+ options.signal?.removeEventListener("abort", close);
2477
+ };
2478
+ const schedule = () => {
2479
+ if (closed || running)
2480
+ return;
2481
+ if (timer)
2482
+ clearTimeout(timer);
2483
+ timer = setTimeout(() => void flush(), debounceMs);
2484
+ };
2485
+ const flush = async () => {
2486
+ timer = undefined;
2487
+ if (closed || running || changedPaths.size === 0)
2488
+ return;
2489
+ running = true;
2490
+ const paths = [...changedPaths].sort();
2491
+ const forced = rootInputChanged;
2492
+ changedPaths.clear();
2493
+ rootInputChanged = false;
2494
+ try {
2495
+ const next = await fingerprintAbsoluteIosDevProject(options.project);
2496
+ if (!forced && next === fingerprint)
2497
+ return;
2498
+ await options.onChange({
2499
+ afterFingerprint: next,
2500
+ beforeFingerprint: fingerprint,
2501
+ paths,
2502
+ rootInputChanged: forced
2503
+ });
2504
+ fingerprint = await fingerprintAbsoluteIosDevProject(options.project);
2505
+ } catch (error) {
2506
+ options.onError?.(error);
2507
+ } finally {
2508
+ running = false;
2509
+ if (changedPaths.size > 0)
2510
+ schedule();
2511
+ }
2512
+ };
2513
+ const record = (path, force) => {
2514
+ if (closed)
2515
+ return;
2516
+ changedPaths.add(path);
2517
+ rootInputChanged ||= force;
2518
+ schedule();
2519
+ };
2520
+ watchers.push(watch(options.project.nativeDirectory, { recursive: true }, (_event, filename) => {
2521
+ if (filename)
2522
+ record(String(filename), false);
2523
+ }));
2524
+ watchers.push(watch(options.project.projectRoot, (_event, filename) => {
2525
+ if (!filename)
2526
+ return;
2527
+ const path = String(filename);
2528
+ if (isAbsoluteIosNativeRootInput(path))
2529
+ record(path, true);
2530
+ }));
2531
+ watchers.forEach((watcher) => watcher.on("error", (error) => options.onError?.(error)));
2532
+ options.signal?.addEventListener("abort", close, { once: true });
2533
+ return { close };
2534
+ };
2535
+ var isAbsoluteIosNativeRootInput = (path) => ROOT_NATIVE_INPUTS.has(basename(path));
2536
+ // src/mobile/associationFiles.ts
2537
+ import {
2538
+ access as access7,
2539
+ mkdir as mkdir6,
2540
+ readFile as readFile8,
2541
+ rename as rename7,
2542
+ rm as rm6,
2543
+ writeFile as writeFile7
2544
+ } from "fs/promises";
2545
+ import { resolve as resolve7 } from "path";
1101
2546
  import { Elysia } from "elysia";
1102
2547
 
1103
2548
  // src/mobile/config.ts
1104
- import { resolve as resolve4 } from "path";
2549
+ import { resolve as resolve6 } from "path";
1105
2550
  var APP_ID_PATTERN = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
1106
2551
  var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
1107
2552
  var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
1108
2553
  var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
1109
2554
  var HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
1110
2555
  var resolveProjectPath = (projectRoot, value, field) => {
1111
- const root = resolve4(projectRoot);
1112
- const path = resolve4(root, value);
2556
+ const root = resolve6(projectRoot);
2557
+ const path = resolve6(root, value);
1113
2558
  if (path !== root && !path.startsWith(`${root}/`)) {
1114
2559
  throw new TypeError(`${field} must remain inside the project root.`);
1115
2560
  }
@@ -1172,6 +2617,15 @@ var normalizeAppleAppIdPrefix = (value) => {
1172
2617
  }
1173
2618
  return normalized;
1174
2619
  };
2620
+ var normalizeIosVersion = (value) => {
2621
+ if (value === undefined)
2622
+ return;
2623
+ const normalized = requireText(value, "mobile.ios.version");
2624
+ if (!/^\d+(?:\.\d+){0,2}$/u.test(normalized)) {
2625
+ throw new TypeError("mobile.ios.version must contain one to three dot-separated integer components, for example 1.4.0.");
2626
+ }
2627
+ return normalized;
2628
+ };
1175
2629
  var normalizeCertificateFingerprints = (values) => [
1176
2630
  ...new Set((values ?? []).map((value) => requireText(value, "mobile.deepLinks.android.sha256CertificateFingerprints").replaceAll(":", "").toUpperCase()).map((value) => {
1177
2631
  if (!CERTIFICATE_FINGERPRINT_PATTERN.test(value)) {
@@ -1200,6 +2654,7 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
1200
2654
  deepLinkScheme,
1201
2655
  engine: "capacitor",
1202
2656
  entry: normalizeEntry(config.entry),
2657
+ iosVersion: normalizeIosVersion(config.ios?.version),
1203
2658
  nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? "mobile", "mobile.nativeProject.directory"),
1204
2659
  platforms: normalizePlatforms(config.platforms),
1205
2660
  productionOrigin
@@ -1287,7 +2742,7 @@ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {})
1287
2742
  var writeAtomic = async (path, source) => {
1288
2743
  let current;
1289
2744
  try {
1290
- current = await readFile5(path, "utf8");
2745
+ current = await readFile8(path, "utf8");
1291
2746
  } catch (error) {
1292
2747
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
1293
2748
  throw error;
@@ -1296,23 +2751,23 @@ var writeAtomic = async (path, source) => {
1296
2751
  if (current === source)
1297
2752
  return false;
1298
2753
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
1299
- await writeFile5(temporary, source, { flag: "wx" });
1300
- await rename5(temporary, path);
2754
+ await writeFile7(temporary, source, { flag: "wx" });
2755
+ await rename7(temporary, path);
1301
2756
  return true;
1302
2757
  };
1303
2758
  var exists2 = async (path) => {
1304
2759
  try {
1305
- await access4(path);
2760
+ await access7(path);
1306
2761
  return true;
1307
2762
  } catch {
1308
2763
  return false;
1309
2764
  }
1310
2765
  };
1311
2766
  var assertOwnedOutput = async (root) => {
1312
- const path = resolve5(root, OWNERSHIP_FILE);
2767
+ const path = resolve7(root, OWNERSHIP_FILE);
1313
2768
  let ownership;
1314
2769
  try {
1315
- ownership = JSON.parse(await readFile5(path, "utf8"));
2770
+ ownership = JSON.parse(await readFile8(path, "utf8"));
1316
2771
  } catch {
1317
2772
  throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
1318
2773
  }
@@ -1326,22 +2781,22 @@ var publishGeneratedDirectory = async (temporary, root) => {
1326
2781
  await assertOwnedOutput(root);
1327
2782
  const backup = `${root}.${crypto.randomUUID()}.previous`;
1328
2783
  if (hasCurrent)
1329
- await rename5(root, backup);
2784
+ await rename7(root, backup);
1330
2785
  try {
1331
- await rename5(temporary, root);
2786
+ await rename7(temporary, root);
1332
2787
  } catch (error) {
1333
2788
  if (hasCurrent)
1334
- await rename5(backup, root);
2789
+ await rename7(backup, root);
1335
2790
  throw error;
1336
2791
  }
1337
2792
  if (hasCurrent)
1338
- await rm4(backup, { force: true, recursive: true });
2793
+ await rm6(backup, { force: true, recursive: true });
1339
2794
  };
1340
2795
  var materializeHost = async (root, host, files) => {
1341
- const directory = resolve5(root, host, ".well-known");
1342
- await mkdir4(directory, { recursive: true });
2796
+ const directory = resolve7(root, host, ".well-known");
2797
+ await mkdir6(directory, { recursive: true });
1343
2798
  return Promise.all(files.map(async ([name, document]) => {
1344
- const path = resolve5(directory, name);
2799
+ const path = resolve7(directory, name);
1345
2800
  await writeAtomic(path, `${JSON.stringify(document, null, 2)}
1346
2801
  `);
1347
2802
  return path;
@@ -1366,7 +2821,7 @@ var associationEndpoints = (config, documents) => config.deepLinkHosts.flatMap((
1366
2821
  return endpoints;
1367
2822
  });
1368
2823
  var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory) => {
1369
- const root = resolve5(outputDirectory);
2824
+ const root = resolve7(outputDirectory);
1370
2825
  const temporary = `${root}.${crypto.randomUUID()}.tmp`;
1371
2826
  const documents = createAbsoluteMobileAssociationDocuments(config, {
1372
2827
  requireAll: true
@@ -1377,16 +2832,16 @@ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory)
1377
2832
  if (documents.apple) {
1378
2833
  files.push(["apple-app-site-association", documents.apple]);
1379
2834
  }
1380
- await mkdir4(temporary, { recursive: true });
2835
+ await mkdir6(temporary, { recursive: true });
1381
2836
  try {
1382
2837
  const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
1383
- await writeAtomic(resolve5(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
2838
+ await writeAtomic(resolve7(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
1384
2839
  `);
1385
2840
  await publishGeneratedDirectory(temporary, root);
1386
- const written = temporaryPaths.map((path) => resolve5(root, path.slice(temporary.length + 1)));
2841
+ const written = temporaryPaths.map((path) => resolve7(root, path.slice(temporary.length + 1)));
1387
2842
  return { root, written };
1388
2843
  } catch (error) {
1389
- await rm4(temporary, { force: true, recursive: true });
2844
+ await rm6(temporary, { force: true, recursive: true });
1390
2845
  throw error;
1391
2846
  }
1392
2847
  };
@@ -1431,10 +2886,10 @@ var frameworks2 = new Set([
1431
2886
  "svelte",
1432
2887
  "vue"
1433
2888
  ]);
1434
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2889
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1435
2890
  var isPageFramework2 = (value) => typeof value === "string" && frameworks2.has(value);
1436
2891
  var parseAbsoluteMobileBuildPageMetadata = (value) => {
1437
- if (!isRecord2(value))
2892
+ if (!isRecord4(value))
1438
2893
  return;
1439
2894
  if (typeof value.bundleKey !== "string" || typeof value.contract !== "string" || !isPageFramework2(value.framework) || typeof value.pageId !== "string" || typeof value.propsSchemaHash !== "string") {
1440
2895
  return;
@@ -1448,23 +2903,23 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
1448
2903
  };
1449
2904
  };
1450
2905
  // src/mobile/buildPipeline.ts
1451
- import { readFile as readFile9 } from "fs/promises";
1452
- import { join as join8, resolve as resolve8 } from "path";
2906
+ import { readFile as readFile12 } from "fs/promises";
2907
+ import { join as join10, resolve as resolve10 } from "path";
1453
2908
  import { pathToFileURL as pathToFileURL2 } from "url";
1454
2909
 
1455
2910
  // src/mobile/buildRelease.ts
1456
- import { createHash as createHash4 } from "crypto";
1457
- import { readFile as readFile6 } from "fs/promises";
1458
- import { join as join5, relative as relative4, resolve as resolve6 } from "path";
1459
- var sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
2911
+ import { createHash as createHash7 } from "crypto";
2912
+ import { readFile as readFile9 } from "fs/promises";
2913
+ import { join as join7, relative as relative6, resolve as resolve8 } from "path";
2914
+ var sha256 = (bytes) => createHash7("sha256").update(bytes).digest("hex");
1460
2915
  var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
1461
2916
  var resolveAssetPath = (buildDirectory, assetPath) => {
1462
- const resolvedBuildDirectory = resolve6(buildDirectory);
1463
- const resolvedAsset = resolve6(assetPath);
2917
+ const resolvedBuildDirectory = resolve8(buildDirectory);
2918
+ const resolvedAsset = resolve8(assetPath);
1464
2919
  if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
1465
2920
  return resolvedAsset;
1466
2921
  }
1467
- return join5(buildDirectory, assetPath.replace(/^\/+/, ""));
2922
+ return join7(buildDirectory, assetPath.replace(/^\/+/, ""));
1468
2923
  };
1469
2924
  var pageFor = async (metadata, manifest, buildDirectory) => {
1470
2925
  const assetPath = manifest[metadata.bundleKey];
@@ -1472,8 +2927,8 @@ var pageFor = async (metadata, manifest, buildDirectory) => {
1472
2927
  throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
1473
2928
  }
1474
2929
  const resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
1475
- const bytes = await readFile6(resolvedAssetPath);
1476
- const bundlePath = `/${relative4(resolve6(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
2930
+ const bytes = await readFile9(resolvedAssetPath);
2931
+ const bundlePath = `/${relative6(resolve8(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
1477
2932
  return {
1478
2933
  bundleHash: sha256(bytes),
1479
2934
  bundlePath,
@@ -1486,7 +2941,7 @@ var pageFor = async (metadata, manifest, buildDirectory) => {
1486
2941
  var buildAbsoluteMobileCompatibilityRelease = async (options) => {
1487
2942
  const [captured, producerBytes] = await Promise.all([
1488
2943
  captureAbsoluteMobileRouteGraph(options.app),
1489
- readFile6(options.producerPath)
2944
+ readFile9(options.producerPath)
1490
2945
  ]);
1491
2946
  if (captured.length === 0) {
1492
2947
  throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
@@ -1566,16 +3021,16 @@ var captureAbsoluteMobileRouteGraph = async (app) => {
1566
3021
 
1567
3022
  // src/mobile/capacitorBundle.ts
1568
3023
  import {
1569
- copyFile as copyFile3,
1570
- mkdir as mkdir5,
1571
- mkdtemp as mkdtemp3,
1572
- readFile as readFile7,
1573
- rename as rename6,
1574
- rm as rm5,
1575
- writeFile as writeFile6
3024
+ copyFile as copyFile5,
3025
+ mkdir as mkdir7,
3026
+ mkdtemp as mkdtemp4,
3027
+ readFile as readFile10,
3028
+ rename as rename8,
3029
+ rm as rm7,
3030
+ writeFile as writeFile8
1576
3031
  } from "fs/promises";
1577
3032
  import { existsSync as existsSync2 } from "fs";
1578
- import { basename, dirname as dirname3, extname, join as join6, resolve as resolve7 } from "path";
3033
+ import { basename as basename2, dirname as dirname5, extname, join as join8, resolve as resolve9 } from "path";
1579
3034
 
1580
3035
  // src/mobile/routeMatcher.ts
1581
3036
  var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
@@ -1731,14 +3186,14 @@ var envelopeResponse = (response, status) => new Response(JSON.stringify({
1731
3186
  protocol: ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
1732
3187
  response
1733
3188
  }), { headers: responseHeaders(), status });
1734
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3189
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1735
3190
  var normalizeJsonValue = (value) => {
1736
3191
  const serialized = JSON.stringify(value);
1737
3192
  if (serialized === undefined) {
1738
3193
  throw new TypeError("Mobile page props must be JSON-serializable.");
1739
3194
  }
1740
3195
  const parsed = JSON.parse(serialized);
1741
- if (!isRecord3(parsed)) {
3196
+ if (!isRecord5(parsed)) {
1742
3197
  throw new TypeError("Mobile page props must serialize to an object.");
1743
3198
  }
1744
3199
  return parsed;
@@ -1863,14 +3318,14 @@ var upgradeReasons = new Set([
1863
3318
  "protocol",
1864
3319
  "runtime"
1865
3320
  ]);
1866
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3321
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1867
3322
  var isFramework = (value) => typeof value === "string" && frameworks4.has(value);
1868
3323
  var isUpgradeReason = (value) => typeof value === "string" && upgradeReasons.has(value);
1869
3324
  var parsePageResult = (value) => {
1870
3325
  if (typeof value.contract !== "string" || !isFramework(value.framework) || typeof value.pageId !== "string" || typeof value.status !== "number") {
1871
3326
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The mobile page response is missing required page metadata.");
1872
3327
  }
1873
- if (!isRecord4(value.props)) {
3328
+ if (!isRecord6(value.props)) {
1874
3329
  throw new AbsoluteMobilePageProtocolError("invalid-props", "The mobile page response must contain an object props value.");
1875
3330
  }
1876
3331
  return {
@@ -1916,7 +3371,7 @@ var activateAbsoluteMobilePage = async (value, options) => {
1916
3371
  };
1917
3372
  };
1918
3373
  var parseAbsoluteMobilePageEnvelope = (value) => {
1919
- if (!isRecord4(value) || !isRecord4(value.response)) {
3374
+ if (!isRecord6(value) || !isRecord6(value.response)) {
1920
3375
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The server did not return an AbsoluteJS mobile page envelope.");
1921
3376
  }
1922
3377
  if (value.protocol !== ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION) {
@@ -2009,7 +3464,7 @@ var INDEX_FILE = "index.html";
2009
3464
  var CLIENT_IMPORT_PATTERN = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s*)["'](\/[^"']+)["']/gu;
2010
3465
  var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2011
3466
  var shellBootstrapModule = () => {
2012
- const candidate = ["js", "ts"].map((extension) => join6(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
3467
+ const candidate = ["js", "ts"].map((extension) => join8(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
2013
3468
  if (candidate)
2014
3469
  return candidate;
2015
3470
  throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
@@ -2030,8 +3485,8 @@ var indexHtml = (appName) => `<!doctype html>
2030
3485
  </html>
2031
3486
  `;
2032
3487
  var sourceAssetPath = (buildDirectory, bundlePath) => {
2033
- const root = resolve7(buildDirectory);
2034
- const asset = resolve7(root, bundlePath.replace(/^\/+/, ""));
3488
+ const root = resolve9(buildDirectory);
3489
+ const asset = resolve9(root, bundlePath.replace(/^\/+/, ""));
2035
3490
  if (!asset.startsWith(`${root}/`)) {
2036
3491
  throw new TypeError("Mobile page bundle escaped the build directory.");
2037
3492
  }
@@ -2039,8 +3494,8 @@ var sourceAssetPath = (buildDirectory, bundlePath) => {
2039
3494
  };
2040
3495
  var buildShellBootstrap = async (staging) => {
2041
3496
  const modulePath = shellBootstrapModule();
2042
- const entryPath = join6(staging, ".absolute-mobile-entry.ts");
2043
- await writeFile6(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
3497
+ const entryPath = join8(staging, ".absolute-mobile-entry.ts");
3498
+ await writeFile8(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
2044
3499
  void startAbsoluteMobileShell();
2045
3500
  `);
2046
3501
  const build = await Bun.build({
@@ -2052,31 +3507,31 @@ void startAbsoluteMobileShell();
2052
3507
  if (!build.success || build.outputs.length !== 1) {
2053
3508
  throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
2054
3509
  }
2055
- await rename6(build.outputs[0]?.path ?? "", join6(staging, BOOTSTRAP_FILE));
2056
- await rm5(entryPath, { force: true });
3510
+ await rename8(build.outputs[0]?.path ?? "", join8(staging, BOOTSTRAP_FILE));
3511
+ await rm7(entryPath, { force: true });
2057
3512
  };
2058
3513
  var removePreviousBundle = async (backup, moved) => {
2059
3514
  if (!moved)
2060
3515
  return;
2061
- await rm5(backup, { force: true, recursive: true });
3516
+ await rm7(backup, { force: true, recursive: true });
2062
3517
  };
2063
3518
  var restorePreviousBundle = async (backup, destination, moved) => {
2064
3519
  if (!moved)
2065
3520
  return;
2066
- await rename6(backup, destination);
3521
+ await rename8(backup, destination);
2067
3522
  };
2068
3523
  var installBundle = async (staging, destination) => {
2069
3524
  const backup = `${destination}.previous-${crypto.randomUUID()}`;
2070
3525
  let movedPrevious = false;
2071
3526
  try {
2072
- await rename6(destination, backup);
3527
+ await rename8(destination, backup);
2073
3528
  movedPrevious = true;
2074
3529
  } catch (error) {
2075
3530
  if (!errorHasCode2(error, "ENOENT"))
2076
3531
  throw error;
2077
3532
  }
2078
3533
  try {
2079
- await rename6(staging, destination);
3534
+ await rename8(staging, destination);
2080
3535
  await removePreviousBundle(backup, movedPrevious);
2081
3536
  } catch (error) {
2082
3537
  await restorePreviousBundle(backup, destination, movedPrevious);
@@ -2090,12 +3545,12 @@ var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) =
2090
3545
  const extension = extname(page.bundlePath) || ".js";
2091
3546
  const localBundlePath = `./pages/${page.bundleHash}${extension}`;
2092
3547
  const source = sourceAssetPath(buildDirectory, page.bundlePath);
2093
- await copyFile3(source, join6(staging, localBundlePath));
3548
+ await copyFile5(source, join8(staging, localBundlePath));
2094
3549
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
2095
3550
  return { ...page, localBundlePath };
2096
3551
  };
2097
3552
  var absoluteClientImports = async (sourcePath) => {
2098
- const source = await readFile7(sourcePath, "utf8");
3553
+ const source = await readFile10(sourcePath, "utf8");
2099
3554
  return [...source.matchAll(CLIENT_IMPORT_PATTERN)].flatMap((match) => {
2100
3555
  const [specifier] = match.slice(1);
2101
3556
  return specifier ? [specifier.split(/[?#]/u, 1)[0] ?? specifier] : [];
@@ -2106,9 +3561,9 @@ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, co
2106
3561
  return;
2107
3562
  copied.add(specifier);
2108
3563
  const source = sourceAssetPath(buildDirectory, specifier);
2109
- const destination = join6(staging, specifier.replace(/^\/+/, ""));
2110
- await mkdir5(dirname3(destination), { recursive: true });
2111
- await copyFile3(source, destination);
3564
+ const destination = join8(staging, specifier.replace(/^\/+/, ""));
3565
+ await mkdir7(dirname5(destination), { recursive: true });
3566
+ await copyFile5(source, destination);
2112
3567
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
2113
3568
  };
2114
3569
  var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
@@ -2120,11 +3575,11 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
2120
3575
  throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
2121
3576
  }
2122
3577
  const destination = options.config.bundleDirectory;
2123
- await mkdir5(dirname3(destination), { recursive: true });
2124
- const staging = await mkdtemp3(join6(dirname3(destination), `.${basename(destination)}.stage-`));
3578
+ await mkdir7(dirname5(destination), { recursive: true });
3579
+ const staging = await mkdtemp4(join8(dirname5(destination), `.${basename2(destination)}.stage-`));
2125
3580
  try {
2126
- const pageDirectory = join6(staging, "pages");
2127
- await mkdir5(pageDirectory, { recursive: true });
3581
+ const pageDirectory = join8(staging, "pages");
3582
+ await mkdir7(pageDirectory, { recursive: true });
2128
3583
  const copiedDependencies = new Set;
2129
3584
  const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
2130
3585
  const manifest = {
@@ -2141,48 +3596,48 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
2141
3596
  runtime: options.artifact.runtime
2142
3597
  };
2143
3598
  await Promise.all([
2144
- writeFile6(join6(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
3599
+ writeFile8(join8(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
2145
3600
  `),
2146
- writeFile6(join6(staging, INDEX_FILE), indexHtml(options.config.appName)),
3601
+ writeFile8(join8(staging, INDEX_FILE), indexHtml(options.config.appName)),
2147
3602
  buildShellBootstrap(staging)
2148
3603
  ]);
2149
3604
  await installBundle(staging, destination);
2150
3605
  return manifest;
2151
3606
  } catch (error) {
2152
- await rm5(staging, { force: true, recursive: true });
3607
+ await rm7(staging, { force: true, recursive: true });
2153
3608
  throw error;
2154
3609
  }
2155
3610
  };
2156
3611
 
2157
3612
  // src/mobile/materializedBundle.ts
2158
- import { createHash as createHash5 } from "crypto";
3613
+ import { createHash as createHash8 } from "crypto";
2159
3614
  import {
2160
- access as access5,
2161
- mkdir as mkdir6,
2162
- mkdtemp as mkdtemp4,
2163
- readFile as readFile8,
2164
- rename as rename7,
2165
- rm as rm6,
2166
- writeFile as writeFile7
3615
+ access as access8,
3616
+ mkdir as mkdir8,
3617
+ mkdtemp as mkdtemp5,
3618
+ readFile as readFile11,
3619
+ rename as rename9,
3620
+ rm as rm8,
3621
+ writeFile as writeFile9
2167
3622
  } from "fs/promises";
2168
- import { dirname as dirname4, join as join7, resolve as resolvePath2 } from "path";
3623
+ import { dirname as dirname6, join as join9, resolve as resolvePath2 } from "path";
2169
3624
  import { pathToFileURL } from "url";
2170
3625
  var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
2171
3626
  var CURRENT_BUNDLE_FILE = "current.json";
2172
3627
  var BUNDLES_DIRECTORY = "bundles";
2173
3628
  var ARTIFACT_FILE2 = "artifact.json";
2174
3629
  var BUNDLE_ID_PATTERN = /^amb_[a-f0-9]{64}$/;
2175
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3630
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2176
3631
  var errorHasCode3 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2177
3632
  var bundleIdFor = (currentReleaseId, releases) => {
2178
3633
  const identity = JSON.stringify({
2179
3634
  currentReleaseId,
2180
3635
  releases: releases.map(({ releaseId }) => releaseId)
2181
3636
  });
2182
- return `amb_${createHash5("sha256").update(identity).digest("hex")}`;
3637
+ return `amb_${createHash8("sha256").update(identity).digest("hex")}`;
2183
3638
  };
2184
3639
  var parseBundleIndex = (value) => {
2185
- if (!isRecord5(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
3640
+ if (!isRecord7(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
2186
3641
  throw new TypeError("Invalid materialized mobile compatibility bundle.");
2187
3642
  }
2188
3643
  const releases = value.releases.map(parseAbsoluteMobileCompatibilityArtifact);
@@ -2205,30 +3660,30 @@ var parseBundleIndex = (value) => {
2205
3660
  };
2206
3661
  };
2207
3662
  var writeRelease = async (root, release) => {
2208
- const directory = join7(root, release.artifact.releaseId);
2209
- const producerPath = join7(directory, release.artifact.producer.module);
2210
- await mkdir6(dirname4(producerPath), { recursive: true });
3663
+ const directory = join9(root, release.artifact.releaseId);
3664
+ const producerPath = join9(directory, release.artifact.producer.module);
3665
+ await mkdir8(dirname6(producerPath), { recursive: true });
2211
3666
  await Promise.all([
2212
- writeFile7(join7(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
3667
+ writeFile9(join9(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
2213
3668
  `),
2214
- writeFile7(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
3669
+ writeFile9(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
2215
3670
  ]);
2216
3671
  };
2217
3672
  var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
2218
- const destination = join7(bundlesRoot, bundleId);
3673
+ const destination = join9(bundlesRoot, bundleId);
2219
3674
  try {
2220
- await access5(destination);
3675
+ await access8(destination);
2221
3676
  return destination;
2222
3677
  } catch (error) {
2223
3678
  if (!errorHasCode3(error, "ENOENT"))
2224
3679
  throw error;
2225
3680
  }
2226
- const staging = await mkdtemp4(join7(bundlesRoot, ".stage-"));
3681
+ const staging = await mkdtemp5(join9(bundlesRoot, ".stage-"));
2227
3682
  try {
2228
3683
  await Promise.all(releases.map((release) => writeRelease(staging, release)));
2229
- await rename7(staging, destination);
3684
+ await rename9(staging, destination);
2230
3685
  } catch (error) {
2231
- await rm6(staging, { force: true, recursive: true });
3686
+ await rm8(staging, { force: true, recursive: true });
2232
3687
  if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
2233
3688
  return destination;
2234
3689
  }
@@ -2239,7 +3694,7 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
2239
3694
  var readCompatibilityModule = (modulePath) => import(pathToFileURL(modulePath).href);
2240
3695
  var resolveProducerHandler = (loaded, exportName) => {
2241
3696
  const value = loaded[exportName];
2242
- if (!isRecord5(value) || typeof value.handle !== "function") {
3697
+ if (!isRecord7(value) || typeof value.handle !== "function") {
2243
3698
  throw new TypeError(`Compatibility producer export ${exportName} must expose handle(request).`);
2244
3699
  }
2245
3700
  const { handle } = value;
@@ -2257,15 +3712,15 @@ var resolveProducerHandler = (loaded, exportName) => {
2257
3712
  };
2258
3713
  var loadAbsoluteMobileMaterializedBundle = async (root) => {
2259
3714
  const resolvedRoot = resolvePath2(root);
2260
- const serialized = await readFile8(join7(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
3715
+ const serialized = await readFile11(join9(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2261
3716
  const parsed = JSON.parse(serialized);
2262
3717
  const index = parseBundleIndex(parsed);
2263
- const bundleRoot = join7(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
3718
+ const bundleRoot = join9(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2264
3719
  return {
2265
3720
  artifacts: index.releases,
2266
3721
  currentReleaseId: index.currentReleaseId,
2267
3722
  loadProducer: async (artifact) => {
2268
- const modulePath = join7(bundleRoot, artifact.releaseId, artifact.producer.module);
3723
+ const modulePath = join9(bundleRoot, artifact.releaseId, artifact.producer.module);
2269
3724
  await verifyAbsoluteMobileCompatibilityProducer({
2270
3725
  artifact,
2271
3726
  producer: Bun.file(modulePath)
@@ -2292,8 +3747,8 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
2292
3747
  return release;
2293
3748
  });
2294
3749
  const root = resolvePath2(input.root);
2295
- const bundlesRoot = join7(root, BUNDLES_DIRECTORY);
2296
- await mkdir6(bundlesRoot, { recursive: true });
3750
+ const bundlesRoot = join9(root, BUNDLES_DIRECTORY);
3751
+ await mkdir8(bundlesRoot, { recursive: true });
2297
3752
  const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
2298
3753
  await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
2299
3754
  const index = {
@@ -2302,22 +3757,22 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
2302
3757
  format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
2303
3758
  releases: artifacts
2304
3759
  };
2305
- const pointerPath = join7(root, CURRENT_BUNDLE_FILE);
2306
- const temporaryPointerPath = join7(root, `.current-${crypto.randomUUID()}.json`);
2307
- await writeFile7(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
3760
+ const pointerPath = join9(root, CURRENT_BUNDLE_FILE);
3761
+ const temporaryPointerPath = join9(root, `.current-${crypto.randomUUID()}.json`);
3762
+ await writeFile9(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
2308
3763
  `, { flag: "wx" });
2309
- await rename7(temporaryPointerPath, pointerPath);
3764
+ await rename9(temporaryPointerPath, pointerPath);
2310
3765
  return index;
2311
3766
  };
2312
3767
  var readAbsoluteMobileMaterializedReleases = async (root) => {
2313
3768
  const resolvedRoot = resolvePath2(root);
2314
3769
  try {
2315
- const serialized = await readFile8(join7(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
3770
+ const serialized = await readFile11(join9(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2316
3771
  const parsed = JSON.parse(serialized);
2317
3772
  const index = parseBundleIndex(parsed);
2318
- const bundleRoot = join7(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
3773
+ const bundleRoot = join9(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2319
3774
  return Promise.all(index.releases.map(async (artifact) => {
2320
- const producer = Bun.file(join7(bundleRoot, artifact.releaseId, artifact.producer.module));
3775
+ const producer = Bun.file(join9(bundleRoot, artifact.releaseId, artifact.producer.module));
2321
3776
  await verifyAbsoluteMobileCompatibilityProducer({
2322
3777
  artifact,
2323
3778
  producer
@@ -2366,11 +3821,11 @@ var loadServerApp = async (producerPath) => {
2366
3821
  return { app, exportName };
2367
3822
  };
2368
3823
  var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2369
- const buildDirectory = resolve8(options.buildDirectory);
3824
+ const buildDirectory = resolve10(options.buildDirectory);
2370
3825
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
2371
- const root = join8(buildDirectory, ".absolutejs", "mobile-compatibility");
3826
+ const root = join10(buildDirectory, ".absolutejs", "mobile-compatibility");
2372
3827
  const [manifestSource, previous] = await Promise.all([
2373
- readFile9(join8(buildDirectory, "manifest.json"), "utf8"),
3828
+ readFile12(join10(buildDirectory, "manifest.json"), "utf8"),
2374
3829
  readAbsoluteMobileMaterializedReleases(root)
2375
3830
  ]);
2376
3831
  const manifest = JSON.parse(manifestSource);
@@ -2381,7 +3836,7 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2381
3836
  process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
2382
3837
  let loaded;
2383
3838
  try {
2384
- loaded = await loadServerApp(resolve8(options.producerPath));
3839
+ loaded = await loadServerApp(resolve10(options.producerPath));
2385
3840
  } finally {
2386
3841
  restoreBuildDirectory(previousBuildDirectory);
2387
3842
  }
@@ -2392,7 +3847,7 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2392
3847
  manifest,
2393
3848
  previousArtifacts: previous.map(({ artifact }) => artifact),
2394
3849
  producerExport: loaded.exportName,
2395
- producerPath: resolve8(options.producerPath),
3850
+ producerPath: resolve10(options.producerPath),
2396
3851
  runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
2397
3852
  });
2398
3853
  const releasesById = new Map([current, ...previous].map((release) => [
@@ -2486,20 +3941,20 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2486
3941
  }).as("global");
2487
3942
  };
2488
3943
  // src/mobile/nativeDeepLinks.ts
2489
- import { readFile as readFile10, rename as rename8, writeFile as writeFile8 } from "fs/promises";
2490
- import { join as join9 } from "path";
3944
+ import { readFile as readFile13, rename as rename10, writeFile as writeFile10 } from "fs/promises";
3945
+ import { join as join11 } from "path";
2491
3946
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
2492
3947
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
2493
3948
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
2494
3949
  var NOT_FOUND = -1;
2495
3950
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2496
3951
  var writeChangedFile = async (path, source) => {
2497
- const current = await readFile10(path, "utf8");
3952
+ const current = await readFile13(path, "utf8");
2498
3953
  if (current === source)
2499
3954
  return false;
2500
3955
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2501
- await writeFile8(temporary, source, { flag: "wx" });
2502
- await rename8(temporary, path);
3956
+ await writeFile10(temporary, source, { flag: "wx" });
3957
+ await rename10(temporary, path);
2503
3958
  return true;
2504
3959
  };
2505
3960
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -2544,8 +3999,8 @@ ${hosts}
2544
3999
  `;
2545
4000
  };
2546
4001
  var configureAndroid = async (config) => {
2547
- const path = join9(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
2548
- const source = await readFile10(path, "utf8");
4002
+ const path = join11(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
4003
+ const source = await readFile13(path, "utf8");
2549
4004
  const mainActivity = source.indexOf('android:name=".MainActivity"');
2550
4005
  if (mainActivity === NOT_FOUND) {
2551
4006
  throw new TypeError("Android MainActivity was not found.");
@@ -2570,8 +4025,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
2570
4025
  ${END_MARKER}
2571
4026
  `;
2572
4027
  var configureIosInfo = async (config) => {
2573
- const path = join9(config.nativeProjectDirectory, "ios/App/App/Info.plist");
2574
- const source = await readFile10(path, "utf8");
4028
+ const path = join11(config.nativeProjectDirectory, "ios/App/App/Info.plist");
4029
+ const source = await readFile13(path, "utf8");
2575
4030
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
2576
4031
  ${END_MARKER}
2577
4032
  `;
@@ -2594,10 +4049,10 @@ ${domains}
2594
4049
  `;
2595
4050
  };
2596
4051
  var configureIosEntitlements = async (config) => {
2597
- const path = join9(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
4052
+ const path = join11(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
2598
4053
  let current = "";
2599
4054
  try {
2600
- current = await readFile10(path, "utf8");
4055
+ current = await readFile13(path, "utf8");
2601
4056
  } catch (error) {
2602
4057
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
2603
4058
  throw error;
@@ -2607,13 +4062,13 @@ var configureIosEntitlements = async (config) => {
2607
4062
  if (current === source)
2608
4063
  return false;
2609
4064
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2610
- await writeFile8(temporary, source, { flag: "wx" });
2611
- await rename8(temporary, path);
4065
+ await writeFile10(temporary, source, { flag: "wx" });
4066
+ await rename10(temporary, path);
2612
4067
  return true;
2613
4068
  };
2614
4069
  var configureIosProject = async (config) => {
2615
- const path = join9(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
2616
- const source = await readFile10(path, "utf8");
4070
+ const path = join11(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
4071
+ const source = await readFile13(path, "utf8");
2617
4072
  const declarations = [
2618
4073
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
2619
4074
  ].map((match) => match[1]);
@@ -2650,15 +4105,113 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
2650
4105
  changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
2651
4106
  };
2652
4107
  };
4108
+ // src/mobile/releasePublisher.ts
4109
+ import { access as access9 } from "fs/promises";
4110
+ import { isAbsolute as isAbsolute5, relative as relative7, resolve as resolve11, sep as sep5 } from "path";
4111
+ import { pathToFileURL as pathToFileURL3 } from "url";
4112
+ var prepareAbsoluteIosRelease = async (publisher, options) => {
4113
+ if (typeof publisher.prepareIosRelease !== "function") {
4114
+ throw new TypeError("App Store Connect publishing requires a registry module created with @absolutejs/deploy/app-store-connect.");
4115
+ }
4116
+ const { buildNumber } = await publisher.prepareIosRelease(options);
4117
+ if (!Number.isSafeInteger(buildNumber) || buildNumber < 1) {
4118
+ throw new TypeError("App Store Connect publisher returned an invalid iOS build number.");
4119
+ }
4120
+ return buildNumber;
4121
+ };
4122
+ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
4123
+ if (typeof publisher.prepareAndroidRelease !== "function") {
4124
+ throw new TypeError("Google Play publishing requires a registry module created with @absolutejs/deploy/google-play.");
4125
+ }
4126
+ const prepared = await publisher.prepareAndroidRelease(options);
4127
+ const { versionCode } = prepared;
4128
+ if (typeof versionCode !== "number" || !Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000) {
4129
+ throw new TypeError("Google Play publisher returned an invalid Android versionCode.");
4130
+ }
4131
+ return versionCode;
4132
+ };
4133
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4134
+ var isPublisher = (value) => isRecord8(value) && typeof value.publish === "function";
4135
+ var publisherModulePath = (projectRoot, requested) => {
4136
+ const root = resolve11(projectRoot);
4137
+ const path = resolve11(root, requested);
4138
+ const projectRelative = relative7(root, path);
4139
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep5}`) || isAbsolute5(projectRelative)) {
4140
+ throw new TypeError("mobile publish --registry must remain inside the project.");
4141
+ }
4142
+ return path;
4143
+ };
4144
+ var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
4145
+ const modulePath = publisherModulePath(projectRoot, requestedModulePath);
4146
+ await access9(modulePath).catch(() => {
4147
+ throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
4148
+ });
4149
+ const loaded = await import(pathToFileURL3(modulePath).href);
4150
+ const publisher = isRecord8(loaded) ? loaded.default ?? loaded.registry : undefined;
4151
+ if (!isPublisher(publisher)) {
4152
+ throw new TypeError("Native release registry module must default-export a registry with publish(options).");
4153
+ }
4154
+ return publisher;
4155
+ };
4156
+ var publishAbsoluteAndroidRelease = async (options) => {
4157
+ const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
4158
+ const publication = await publisher.publish({
4159
+ allowUnsigned: options.allowUnsigned,
4160
+ channel: options.channel,
4161
+ googlePlay: options.googlePlay,
4162
+ releaseRoot: options.release.releaseRoot,
4163
+ signal: options.signal
4164
+ });
4165
+ const expected = options.release.metadata;
4166
+ const actual = publication.record?.metadata;
4167
+ if (!actual || actual.appId !== expected.appId || actual.platform !== "android" || actual.releaseId !== expected.releaseId || actual.sha256 !== expected.sha256 || actual.signed !== expected.signed || actual.versionCode !== expected.versionCode || typeof publication.reused !== "boolean") {
4168
+ throw new TypeError("Native release registry returned a different Android release identity.");
4169
+ }
4170
+ if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
4171
+ throw new TypeError("Native release registry did not promote the requested channel.");
4172
+ }
4173
+ const { googlePlay } = publication;
4174
+ if (options.googlePlay) {
4175
+ if (!expected.versionCode || !googlePlay || googlePlay.receipt.provider !== "google-play" || googlePlay.receipt.packageName !== expected.appId || googlePlay.receipt.releaseId !== expected.releaseId || googlePlay.receipt.sha256 !== expected.sha256 || googlePlay.receipt.stage !== "committed" || googlePlay.receipt.intent.track !== options.googlePlay.track || typeof googlePlay.receipt.versionCode !== "string" || !/^\d+$/.test(googlePlay.receipt.versionCode) || Number(googlePlay.receipt.versionCode) !== expected.versionCode || typeof googlePlay.reused !== "boolean") {
4176
+ throw new TypeError("Native release publisher did not commit the requested Google Play release.");
4177
+ }
4178
+ }
4179
+ return publication;
4180
+ };
4181
+ var publishAbsoluteIosRelease = async (options) => {
4182
+ const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
4183
+ const publication = await publisher.publish({
4184
+ allowUnsigned: options.allowUnsigned,
4185
+ appStoreConnect: options.appStoreConnect,
4186
+ channel: options.channel,
4187
+ releaseRoot: options.release.releaseRoot,
4188
+ signal: options.signal
4189
+ });
4190
+ const expected = options.release.metadata;
4191
+ const actual = publication.record?.metadata;
4192
+ if (!actual || actual.appId !== expected.appId || actual.platform !== "ios" || actual.releaseId !== expected.releaseId || actual.sha256 !== expected.sha256 || actual.signed !== expected.signed || actual.buildNumber !== expected.buildNumber || actual.marketingVersion !== expected.marketingVersion || typeof publication.reused !== "boolean") {
4193
+ throw new TypeError("Native release registry returned a different iOS release identity.");
4194
+ }
4195
+ if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
4196
+ throw new TypeError("Native release registry did not promote the requested channel.");
4197
+ }
4198
+ if (options.appStoreConnect) {
4199
+ const distributed = publication.appStoreConnect;
4200
+ if (!expected.buildNumber || !distributed || distributed.receipt.provider !== "app-store-connect" || distributed.receipt.releaseId !== expected.releaseId || distributed.receipt.sha256 !== expected.sha256 || distributed.receipt.buildNumber !== expected.buildNumber || distributed.receipt.marketingVersion !== expected.marketingVersion || !["distributed", "review-submitted"].includes(distributed.receipt.stage) || JSON.stringify([...distributed.receipt.intent.groups].sort()) !== JSON.stringify([...options.appStoreConnect.groups ?? []].sort()) || distributed.receipt.intent.submitForReview !== (options.appStoreConnect.submitForReview ?? false) || typeof distributed.reused !== "boolean") {
4201
+ throw new TypeError("Native release publisher did not complete the requested App Store Connect release.");
4202
+ }
4203
+ }
4204
+ return publication;
4205
+ };
2653
4206
  // src/mobile/routeMetadataTransform.ts
2654
4207
  import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2655
- import { dirname as dirname5, extname as extname2, relative as relative5, resolve as resolve9 } from "path";
4208
+ import { dirname as dirname7, extname as extname2, relative as relative8, resolve as resolve12 } from "path";
2656
4209
  import ts from "typescript";
2657
4210
  var ROUTE_METHODS = new Set(["get", "head"]);
2658
4211
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
2659
4212
  var PAGE_HANDLER = "handleReactPageRequest";
2660
4213
  var posixPath = (value) => value.replace(/\\/g, "/");
2661
- var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname5(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
4214
+ var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname7(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
2662
4215
  var createProgram = (entry, projectRoot) => {
2663
4216
  const configPath = findTsconfig(entry, projectRoot);
2664
4217
  if (!configPath) {
@@ -2670,7 +4223,7 @@ var createProgram = (entry, projectRoot) => {
2670
4223
  target: ts.ScriptTarget.ESNext
2671
4224
  });
2672
4225
  }
2673
- const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname5(configPath));
4226
+ const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname7(configPath));
2674
4227
  if (!parsed.fileNames.includes(entry))
2675
4228
  parsed.fileNames.push(entry);
2676
4229
  return ts.createProgram(parsed.fileNames, parsed.options);
@@ -2776,7 +4329,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
2776
4329
  const declaration = symbol?.declarations?.[0];
2777
4330
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
2778
4331
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
2779
- const source = posixPath(relative5(projectRoot, file));
4332
+ const source = posixPath(relative8(projectRoot, file));
2780
4333
  return `${source}#${exportedName}`;
2781
4334
  };
2782
4335
  var resolveAlias = (symbol, checker) => {
@@ -2883,7 +4436,7 @@ var analyzeProgram = (program, projectRoot) => {
2883
4436
  const checker = program.getTypeChecker();
2884
4437
  const analyzed = new Map;
2885
4438
  for (const sourceFile of program.getSourceFiles()) {
2886
- const resolvedFile = resolve9(sourceFile.fileName);
4439
+ const resolvedFile = resolve12(sourceFile.fileName);
2887
4440
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
2888
4441
  continue;
2889
4442
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -2966,14 +4519,14 @@ var transformFile = (source, fileName, analysis) => {
2966
4519
  };
2967
4520
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
2968
4521
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
2969
- const projectRoot = resolve9(options.projectRoot ?? process.cwd());
2970
- const entry = resolve9(options.entry);
4522
+ const projectRoot = resolve12(options.projectRoot ?? process.cwd());
4523
+ const entry = resolve12(options.entry);
2971
4524
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
2972
4525
  return {
2973
4526
  name: "absolute-mobile-route-metadata",
2974
4527
  setup(build) {
2975
4528
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
2976
- const analysis = analyzed.get(resolve9(path));
4529
+ const analysis = analyzed.get(resolve12(path));
2977
4530
  if (!analysis)
2978
4531
  return;
2979
4532
  const source = await Bun.file(path).text();
@@ -2986,38 +4539,56 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
2986
4539
  };
2987
4540
  };
2988
4541
  var inspectAbsoluteMobileRouteMetadata = (options) => {
2989
- const projectRoot = resolve9(options.projectRoot ?? process.cwd());
2990
- const entry = resolve9(options.entry);
4542
+ const projectRoot = resolve12(options.projectRoot ?? process.cwd());
4543
+ const entry = resolve12(options.entry);
2991
4544
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
2992
4545
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
2993
- file: posixPath(relative5(projectRoot, file)),
4546
+ file: posixPath(relative8(projectRoot, file)),
2994
4547
  metadata
2995
4548
  })));
2996
4549
  };
2997
4550
  export {
2998
4551
  writeAbsoluteCapacitorConfig,
4552
+ waitForAbsoluteIosHmrLog,
2999
4553
  verifyAbsoluteMobileCompatibilityProducer,
3000
4554
  verifyAbsoluteMobileAssociationFiles,
4555
+ startAbsoluteIosDevSession,
3001
4556
  runWithAbsoluteMobileProducer,
3002
4557
  retainAbsoluteMobileCompatibilityArtifacts,
3003
4558
  resolveAbsoluteMobileRoute,
3004
4559
  resolveAbsoluteMobileDeepLink,
3005
4560
  resolveAbsoluteMobileCompatibilityRelease,
4561
+ repairAbsoluteIosDevSession,
4562
+ redactAbsoluteIosLog,
3006
4563
  readAbsoluteMobileMaterializedReleases,
4564
+ publishAbsoluteIosRelease,
4565
+ publishAbsoluteAndroidRelease,
4566
+ prepareAbsoluteIosRelease,
4567
+ prepareAbsoluteIosDevProject,
4568
+ prepareAbsoluteAndroidRelease,
4569
+ parseIosSimulators,
4570
+ parseIosRuntimes,
4571
+ parseIosDeviceTypes,
3007
4572
  parseAbsoluteMobilePageRequest,
3008
4573
  parseAbsoluteMobilePageEnvelope,
3009
4574
  parseAbsoluteMobileCompatibilityArtifact,
3010
4575
  parseAbsoluteMobileBuildPageMetadata,
4576
+ parseAbsoluteIosLogLine,
4577
+ parseAbsoluteIosHmrLog,
3011
4578
  normalizeAbsoluteMobileConfig,
3012
4579
  navigateAbsoluteMobilePage,
3013
4580
  materializeAbsoluteMobileCompatibilityBundle,
3014
4581
  materializeAbsoluteMobileAssociationFiles,
3015
4582
  materializeAbsoluteCapacitorWebBundle,
3016
4583
  matchesAbsoluteMobileRoutePattern,
4584
+ loadAbsoluteNativeReleasePublisher,
3017
4585
  loadAbsoluteMobileMaterializedBundle,
4586
+ isAbsoluteIosNativeRootInput,
3018
4587
  inspectAbsoluteMobileRouteMetadata,
3019
4588
  hashAbsoluteMobilePropsSchema,
3020
4589
  getCurrentAbsoluteMobileProducerContext,
4590
+ fingerprintAbsoluteIosNativeProject,
4591
+ fingerprintAbsoluteIosDevProject,
3021
4592
  finalizeAbsoluteMobilePage,
3022
4593
  finalizeAbsoluteMobileCompatibilityBuild,
3023
4594
  fetchAbsoluteMobilePage,
@@ -3032,9 +4603,11 @@ export {
3032
4603
  createAbsoluteMobileBlobArtifactStore,
3033
4604
  createAbsoluteMobileAssociationPlugin,
3034
4605
  createAbsoluteMobileAssociationDocuments,
4606
+ createAbsoluteIosNativeWatcher,
3035
4607
  carryForwardAbsoluteMobileCompatibilityReleases,
3036
4608
  captureAbsoluteMobileRouteGraph,
3037
4609
  buildAbsoluteMobileCompatibilityRelease,
4610
+ buildAbsoluteIosRelease,
3038
4611
  buildAbsoluteAndroidRelease,
3039
4612
  applyAbsoluteNativeDeepLinks,
3040
4613
  activateAbsoluteMobilePage,
@@ -3051,8 +4624,10 @@ export {
3051
4624
  ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
3052
4625
  ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
3053
4626
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
4627
+ ABSOLUTE_IOS_SIMULATOR_NAME,
4628
+ ABSOLUTE_IOS_RELEASE_FORMAT,
3054
4629
  ABSOLUTE_ANDROID_RELEASE_FORMAT
3055
4630
  };
3056
4631
 
3057
- //# debugId=EC6A042BA6D550B864756E2164756E21
4632
+ //# debugId=048422942BB9C32864756E2164756E21
3058
4633
  //# sourceMappingURL=index.js.map