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

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.
Files changed (68) hide show
  1. package/README.md +45 -0
  2. package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
  3. package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
  4. package/dist/angular/index.js +341 -22
  5. package/dist/angular/index.js.map +8 -5
  6. package/dist/angular/server.js +341 -22
  7. package/dist/angular/server.js.map +8 -5
  8. package/dist/build.js +537 -313
  9. package/dist/build.js.map +14 -13
  10. package/dist/cli/index.js +4171 -970
  11. package/dist/dev/client/cssUtils.ts +16 -2
  12. package/dist/dev/client/handlers/rebuild.ts +11 -1
  13. package/dist/dev/client/hmrTiming.ts +14 -7
  14. package/dist/index.js +857 -513
  15. package/dist/index.js.map +22 -21
  16. package/dist/mobile/browser.js +14 -1
  17. package/dist/mobile/browser.js.map +3 -3
  18. package/dist/mobile/index.js +2900 -244
  19. package/dist/mobile/index.js.map +23 -14
  20. package/dist/mobile/remoteMacAgentEntry.js +29 -0
  21. package/dist/mobile/shellAuth.js +43 -0
  22. package/dist/mobile/shellBootstrap.js +581 -0
  23. package/dist/mobile/shellSync.js +74 -0
  24. package/dist/src/angular/pageHandler.d.ts +3 -0
  25. package/dist/src/build/pwa.d.ts +15 -0
  26. package/dist/src/cli/config/server.d.ts +1 -1
  27. package/dist/src/core/pageHandlers.d.ts +11 -2
  28. package/dist/src/core/prepare.d.ts +6 -0
  29. package/dist/src/dev/clientManager.d.ts +2 -0
  30. package/dist/src/mobile/androidEmulatorController.d.ts +7 -1
  31. package/dist/src/mobile/androidRelease.d.ts +4 -0
  32. package/dist/src/mobile/buildPipeline.d.ts +1 -0
  33. package/dist/src/mobile/capacitorBundle.d.ts +15 -1
  34. package/dist/src/mobile/client.d.ts +4 -0
  35. package/dist/src/mobile/config.d.ts +1 -0
  36. package/dist/src/mobile/index.d.ts +7 -0
  37. package/dist/src/mobile/iosConformance.d.ts +15 -0
  38. package/dist/src/mobile/iosNativeWatcher.d.ts +19 -0
  39. package/dist/src/mobile/iosRelease.d.ts +61 -0
  40. package/dist/src/mobile/iosSimulatorController.d.ts +89 -0
  41. package/dist/src/mobile/nativeAuth.d.ts +17 -0
  42. package/dist/src/mobile/nativeBackgroundSync.d.ts +4 -0
  43. package/dist/src/mobile/releaseArtifact.d.ts +2 -0
  44. package/dist/src/mobile/releasePublisher.d.ts +130 -0
  45. package/dist/src/mobile/remoteMacAgent.d.ts +2 -0
  46. package/dist/src/mobile/remoteMacAgentEntry.d.ts +1 -0
  47. package/dist/src/mobile/remoteMacProtocol.d.ts +114 -0
  48. package/dist/src/mobile/remoteMacWire.d.ts +2 -0
  49. package/dist/src/mobile/shellAuth.d.ts +13 -0
  50. package/dist/src/mobile/shellBootstrap.d.ts +18 -1
  51. package/dist/src/mobile/shellSync.d.ts +19 -0
  52. package/dist/src/mobile/staticDocument.d.ts +5 -0
  53. package/dist/src/mobile/transport.d.ts +13 -1
  54. package/dist/src/plugins/hmr.d.ts +3 -0
  55. package/dist/src/plugins/imageOptimizer.d.ts +1 -1
  56. package/dist/src/svelte/pageHandler.d.ts +3 -0
  57. package/dist/src/utils/loadConfig.d.ts +1 -0
  58. package/dist/src/vue/pageHandler.d.ts +3 -0
  59. package/dist/svelte/index.js +312 -23
  60. package/dist/svelte/index.js.map +7 -4
  61. package/dist/svelte/server.js +307 -18
  62. package/dist/svelte/server.js.map +7 -4
  63. package/dist/types/build.d.ts +18 -0
  64. package/dist/vue/index.js +312 -23
  65. package/dist/vue/index.js.map +7 -4
  66. package/dist/vue/server.js +307 -18
  67. package/dist/vue/server.js.map +7 -4
  68. package/package.json +25 -9
@@ -159,6 +159,14 @@ var init_startupBanner = __esm(() => {
159
159
  ];
160
160
  });
161
161
 
162
+ // src/utils/stringModifiers.ts
163
+ var normalizeSlug = (str) => str.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9\-_]+/g, "").replace(/[-_]{2,}/g, "-"), toKebab = (str) => normalizeSlug(str).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), toPascal = (str) => {
164
+ if (!str.includes("-") && !str.includes("_")) {
165
+ return str.charAt(0).toUpperCase() + str.slice(1);
166
+ }
167
+ return normalizeSlug(str).split(/[-_]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()).join("");
168
+ };
169
+
162
170
  // src/mobile/artifactStore.ts
163
171
  import { createHash as createHash2 } from "crypto";
164
172
  import {
@@ -249,13 +257,19 @@ var parseCompatibilityPage = (value) => {
249
257
  if (!isCanonicalRecord(value) || !isPageFramework(value.framework)) {
250
258
  throw new TypeError("Compatibility artifact contains an invalid page.");
251
259
  }
260
+ const styleBundleHash = typeof value.styleBundleHash === "string" ? value.styleBundleHash : undefined;
261
+ const styleBundlePath = typeof value.styleBundlePath === "string" ? value.styleBundlePath : undefined;
262
+ if (Boolean(styleBundleHash) !== Boolean(styleBundlePath)) {
263
+ throw new TypeError("Compatibility page style hash and path must be provided together.");
264
+ }
252
265
  return {
253
266
  bundleHash: readString(value.bundleHash, "page.bundleHash"),
254
267
  bundlePath: readString(value.bundlePath, "page.bundlePath"),
255
268
  contract: readString(value.contract, "page.contract"),
256
269
  framework: value.framework,
257
270
  pageId: readString(value.pageId, "page.pageId"),
258
- propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash")
271
+ propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash"),
272
+ ...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
259
273
  };
260
274
  };
261
275
  var parseCompatibilityRoute = (value) => {
@@ -287,14 +301,23 @@ var validateProducerModule = (module) => {
287
301
  }
288
302
  return module;
289
303
  };
290
- var normalizePage = (page) => ({
291
- bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
292
- bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
293
- contract: requireNonEmpty(page.contract, "page.contract"),
294
- framework: page.framework,
295
- pageId: requireNonEmpty(page.pageId, "page.pageId"),
296
- propsSchemaHash: requireNonEmpty(page.propsSchemaHash, "page.propsSchemaHash")
297
- });
304
+ var normalizePage = (page) => {
305
+ if (Boolean(page.styleBundleHash) !== Boolean(page.styleBundlePath)) {
306
+ throw new TypeError("Compatibility page style hash and path must be provided together.");
307
+ }
308
+ return {
309
+ bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
310
+ bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
311
+ contract: requireNonEmpty(page.contract, "page.contract"),
312
+ framework: page.framework,
313
+ pageId: requireNonEmpty(page.pageId, "page.pageId"),
314
+ propsSchemaHash: requireNonEmpty(page.propsSchemaHash, "page.propsSchemaHash"),
315
+ ...page.styleBundleHash && page.styleBundlePath ? {
316
+ styleBundleHash: requireNonEmpty(page.styleBundleHash, "page.styleBundleHash"),
317
+ styleBundlePath: requireNonEmpty(page.styleBundlePath, "page.styleBundlePath")
318
+ } : {}
319
+ };
320
+ };
298
321
  var normalizeRoute = (route) => {
299
322
  if (!route.pattern.startsWith("/")) {
300
323
  throw new TypeError("route.pattern must start with /.");
@@ -615,9 +638,9 @@ var verifyAbsoluteMobileCompatibilityProducer = async (release, maxProducerBytes
615
638
  return release;
616
639
  };
617
640
  // src/mobile/androidRelease.ts
618
- import { createHash as createHash3 } from "crypto";
641
+ import { createHash as createHash4 } from "crypto";
619
642
  import {
620
- access as access3,
643
+ access as access4,
621
644
  copyFile as copyFile2,
622
645
  mkdir as mkdir3,
623
646
  mkdtemp as mkdtemp2,
@@ -630,6 +653,7 @@ import {
630
653
  import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
631
654
 
632
655
  // src/mobile/emulatorDoctor.ts
656
+ import { access } from "fs/promises";
633
657
  import { homedir } from "os";
634
658
  import { join as join2 } from "path";
635
659
 
@@ -647,6 +671,34 @@ var isWSLEnvironment = () => {
647
671
  };
648
672
 
649
673
  // src/mobile/emulatorDoctor.ts
674
+ var ABSOLUTE_ANDROID_AVD_NAME = "AbsoluteJS_API_36";
675
+ var captureCommand = (command) => {
676
+ try {
677
+ const result = Bun.spawnSync(command, {
678
+ stderr: "ignore",
679
+ stdout: "pipe"
680
+ });
681
+ return {
682
+ exitCode: result.exitCode,
683
+ stdout: result.stdout.toString()
684
+ };
685
+ } catch {
686
+ return { exitCode: 1, stdout: "" };
687
+ }
688
+ };
689
+ var hasAvailableIosRuntime = (output) => {
690
+ try {
691
+ const parsed = JSON.parse(output);
692
+ if (typeof parsed !== "object" || parsed === null)
693
+ return false;
694
+ const runtimes = Reflect.get(parsed, "runtimes");
695
+ if (!Array.isArray(runtimes))
696
+ return false;
697
+ 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"));
698
+ } catch {
699
+ return false;
700
+ }
701
+ };
650
702
  var windowsPathToWsl = (path) => {
651
703
  const match = /^([a-z]):[\\/](.*)$/i.exec(path.trim());
652
704
  if (!match)
@@ -678,6 +730,14 @@ var absoluteManagedAndroidSdkRoot = (host, env = process.env) => {
678
730
  }
679
731
  return join2(homedir(), ".absolutejs", "android-sdk");
680
732
  };
733
+ var pathExists = async (path) => {
734
+ try {
735
+ await access(path);
736
+ return true;
737
+ } catch {
738
+ return false;
739
+ }
740
+ };
681
741
  var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironment()) => {
682
742
  if (platform === "darwin")
683
743
  return "macos";
@@ -687,10 +747,151 @@ var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironm
687
747
  return "wsl";
688
748
  return "linux";
689
749
  };
750
+ var executableNames = (host, name) => {
751
+ if (host === "wsl")
752
+ return [`${name}.exe`, `${name}.bat`, name];
753
+ if (host === "windows")
754
+ return [name, `${name}.exe`, `${name}.bat`];
755
+ return [name];
756
+ };
757
+ var findExecutable = async (name, paths, options, host) => {
758
+ const existing = await Promise.all(paths.map(async (path) => await options.exists(path) ? path : undefined));
759
+ const configured = existing.find((path) => path !== undefined);
760
+ if (configured)
761
+ return configured;
762
+ for (const candidate of executableNames(host, name)) {
763
+ const path = options.which(candidate);
764
+ if (path)
765
+ return path;
766
+ }
767
+ return;
768
+ };
769
+ var toolCheck = (id, label, platform, path, remediation) => path ? {
770
+ id,
771
+ label,
772
+ path,
773
+ platform,
774
+ status: "pass"
775
+ } : {
776
+ id,
777
+ label,
778
+ platform,
779
+ remediation,
780
+ status: "fail"
781
+ };
782
+ var inspectAbsoluteMobileToolchain = async (input = {}) => {
783
+ const env = input.env ?? process.env;
784
+ const host = input.host ?? detectAbsoluteMobileHost();
785
+ const exists = input.exists ?? pathExists;
786
+ const which = input.which ?? ((command) => Bun.which(command));
787
+ const capture = input.capture ?? captureCommand;
788
+ const androidRoot = input.androidRoot === null ? undefined : input.androidRoot ?? env.ANDROID_HOME ?? env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host, env);
789
+ const windowsAndroidTools = host === "windows" || host === "wsl";
790
+ const android = (segments) => androidRoot ? join2(androidRoot, ...segments) : undefined;
791
+ const paths = (values) => values.filter((value) => Boolean(value));
792
+ const adb = await findExecutable("adb", paths([
793
+ android(["platform-tools", windowsAndroidTools ? "adb.exe" : "adb"])
794
+ ]), { exists, which }, host);
795
+ const emulator = await findExecutable("emulator", paths([
796
+ android([
797
+ "emulator",
798
+ windowsAndroidTools ? "emulator.exe" : "emulator"
799
+ ])
800
+ ]), { exists, which }, host);
801
+ const sdkmanager = await findExecutable("sdkmanager", paths([
802
+ android([
803
+ "cmdline-tools",
804
+ "latest",
805
+ "bin",
806
+ windowsAndroidTools ? "sdkmanager.bat" : "sdkmanager"
807
+ ])
808
+ ]), { exists, which }, host);
809
+ const avdmanager = await findExecutable("avdmanager", paths([
810
+ android([
811
+ "cmdline-tools",
812
+ "latest",
813
+ "bin",
814
+ windowsAndroidTools ? "avdmanager.bat" : "avdmanager"
815
+ ])
816
+ ]), { exists, which }, host);
817
+ const java = await findExecutable("java", [], { exists, which }, host);
818
+ const checks = [
819
+ {
820
+ id: "host",
821
+ label: `Development host: ${host}`,
822
+ platform: "host",
823
+ status: "pass"
824
+ },
825
+ toolCheck("android.adb", "Android Debug Bridge", "android", adb, "Install Android SDK Platform Tools or expose adb on PATH."),
826
+ toolCheck("android.emulator", "Android Emulator", "android", emulator, "Install the Android Emulator from Android Studio SDK Manager."),
827
+ toolCheck("android.sdkmanager", "Android SDK Manager", "android", sdkmanager, "Install Android SDK Command-line Tools (latest)."),
828
+ toolCheck("android.avdmanager", "Android Virtual Device Manager", "android", avdmanager, "Install Android SDK Command-line Tools (latest)."),
829
+ toolCheck("android.java", "Java runtime", "android", java, "Install the JDK required by the configured Android Gradle plugin.")
830
+ ];
831
+ if (emulator) {
832
+ const avds = capture([emulator, "-list-avds"]);
833
+ const hasManagedAvd = avds.exitCode === 0 && avds.stdout.split(/\r?\n/).includes(ABSOLUTE_ANDROID_AVD_NAME);
834
+ checks.push({
835
+ id: "android.avd",
836
+ label: `AbsoluteJS Android emulator (${ABSOLUTE_ANDROID_AVD_NAME})`,
837
+ platform: "android",
838
+ remediation: hasManagedAvd ? undefined : "Run absolute mobile doctor android --fix to provision the managed emulator.",
839
+ status: hasManagedAvd ? "pass" : "fail"
840
+ });
841
+ }
842
+ if (host === "wsl") {
843
+ checks.push({
844
+ id: "android.virtualization",
845
+ label: adb?.endsWith(".exe") ? "Windows-host Android bridge available to WSL" : "WSL requires a Windows-host emulator bridge or Linux KVM",
846
+ platform: "android",
847
+ remediation: adb?.endsWith(".exe") ? undefined : "Expose the Windows Android SDK adb.exe to WSL, or enable /dev/kvm for a Linux SDK.",
848
+ status: adb?.endsWith(".exe") ? "pass" : "warn"
849
+ });
850
+ } else if (host === "linux") {
851
+ const hasKvm = await exists("/dev/kvm");
852
+ checks.push({
853
+ id: "android.virtualization",
854
+ label: "Linux KVM acceleration",
855
+ platform: "android",
856
+ remediation: hasKvm ? undefined : "Enable KVM and grant the current user access to /dev/kvm.",
857
+ status: hasKvm ? "pass" : "warn"
858
+ });
859
+ }
860
+ if (host !== "macos") {
861
+ checks.push({
862
+ id: "ios.simulator",
863
+ label: "iOS Simulator requires macOS and Xcode",
864
+ platform: "ios",
865
+ status: "skip"
866
+ });
867
+ return checks;
868
+ }
869
+ const xcrun = await findExecutable("xcrun", [], { exists, which }, host);
870
+ const xcodebuild = await findExecutable("xcodebuild", [], { exists, which }, host);
871
+ 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."));
872
+ if (xcrun) {
873
+ const runtimes = capture([
874
+ xcrun,
875
+ "simctl",
876
+ "list",
877
+ "runtimes",
878
+ "--json"
879
+ ]);
880
+ const hasRuntime = runtimes.exitCode === 0 && hasAvailableIosRuntime(runtimes.stdout);
881
+ checks.push({
882
+ id: "ios.runtime",
883
+ label: "iOS Simulator runtime",
884
+ platform: "ios",
885
+ remediation: hasRuntime ? undefined : "Run absolute mobile doctor ios --fix to download an iOS Simulator runtime.",
886
+ status: hasRuntime ? "pass" : "fail"
887
+ });
888
+ }
889
+ return checks;
890
+ };
690
891
 
691
892
  // src/mobile/androidEmulatorController.ts
692
893
  import {
693
- access as access2,
894
+ access as access3,
694
895
  copyFile,
695
896
  lstat,
696
897
  mkdir as mkdir2,
@@ -702,6 +903,7 @@ import {
702
903
  rm as rm2,
703
904
  writeFile as writeFile3
704
905
  } from "fs/promises";
906
+ import { createHash as createHash3, randomUUID } from "crypto";
705
907
  import {
706
908
  dirname,
707
909
  isAbsolute,
@@ -713,7 +915,7 @@ import {
713
915
  } from "path";
714
916
 
715
917
  // src/mobile/capacitorProject.ts
716
- import { access, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
918
+ import { access as access2, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
717
919
  import { relative, resolve } from "path";
718
920
  var CONFIG_FILE = "capacitor.config.ts";
719
921
  var portableRelative = (root, path) => relative(root, path).replaceAll("\\", "/");
@@ -735,7 +937,7 @@ export default config;
735
937
  `;
736
938
  var exists = async (path) => {
737
939
  try {
738
- await access(path);
940
+ await access2(path);
739
941
  return true;
740
942
  } catch {
741
943
  return false;
@@ -762,10 +964,12 @@ var writeAbsoluteCapacitorConfig = async (config, options) => {
762
964
  // src/mobile/androidEmulatorController.ts
763
965
  init_getDurationString();
764
966
  var HASH_RADIX = 16;
967
+ var EXECUTABLE_MODE_MASK = 73;
968
+ var NATIVE_PUBLIC_PATH_SEGMENTS = 5;
765
969
  var CAPACITOR_PROJECT_DIRECTORY_PATTERN = /project\(['"](:[^'"]+)['"]\)\.projectDir\s*=\s*new File\(['"]([^'"]+)['"]\)/gu;
766
- var pathExists = async (path) => {
970
+ var pathExists2 = async (path) => {
767
971
  try {
768
- await access2(path);
972
+ await access3(path);
769
973
  return true;
770
974
  } catch {
771
975
  return false;
@@ -792,7 +996,7 @@ var runCommand = async (command, options = {}) => {
792
996
  ]);
793
997
  return exitCode;
794
998
  };
795
- var captureCommand = (command, options = {}) => {
999
+ var captureCommand2 = (command, options = {}) => {
796
1000
  try {
797
1001
  const result = Bun.spawnSync(command, {
798
1002
  cwd: options.cwd,
@@ -826,6 +1030,75 @@ var throwIfAborted = (signal) => {
826
1030
  return;
827
1031
  throw new DOMException("Android development startup was cancelled.", "AbortError");
828
1032
  };
1033
+ var nativeDependencySources = async (nativeDirectory) => {
1034
+ const settings = await readFile3(join3(nativeDirectory, "capacitor.settings.gradle"), "utf8");
1035
+ const pattern = new RegExp(CAPACITOR_PROJECT_DIRECTORY_PATTERN.source, CAPACITOR_PROJECT_DIRECTORY_PATTERN.flags);
1036
+ const dependencies = [...settings.matchAll(pattern)].map((match) => ({
1037
+ name: (match[1] ?? "").slice(1).replaceAll(/[^a-zA-Z0-9_.-]/gu, "_"),
1038
+ source: resolve2(nativeDirectory, match[2] ?? "")
1039
+ }));
1040
+ if (dependencies.length === 0) {
1041
+ throw new Error("Capacitor Android settings did not declare any native dependencies.");
1042
+ }
1043
+ return { dependencies, settings };
1044
+ };
1045
+ var shouldIgnoreNativePath = (relativePath, ignorePublicBundle) => {
1046
+ const parts = relativePath.split(sep);
1047
+ if (parts.includes(".gradle") || parts.includes("build") || parts.includes(".absolutejs-dependencies")) {
1048
+ return true;
1049
+ }
1050
+ return ignorePublicBundle && parts.slice(0, NATIVE_PUBLIC_PATH_SEGMENTS).join("/") === "app/src/main/assets/public";
1051
+ };
1052
+ var collectNativePath = async (root, label, path, isDirectory, isFile, isSymbolicLink, ignorePublicBundle) => {
1053
+ const relativePath = relative2(root, path);
1054
+ if (shouldIgnoreNativePath(relativePath, ignorePublicBundle))
1055
+ return [];
1056
+ const identity = `${label}:${relativePath.split(sep).join("/")}\x00`;
1057
+ if (isDirectory) {
1058
+ return collectNativeDirectory(root, label, path, ignorePublicBundle);
1059
+ }
1060
+ if (isSymbolicLink) {
1061
+ return [`link\x00${identity}${await readlink(path)}\x00`];
1062
+ }
1063
+ if (!isFile)
1064
+ return [];
1065
+ const [metadata, contents] = await Promise.all([
1066
+ lstat(path),
1067
+ readFile3(path)
1068
+ ]);
1069
+ const contentDigest = createHash3("sha256").update(contents).digest("hex");
1070
+ return [
1071
+ `file\x00${identity}${metadata.mode & EXECUTABLE_MODE_MASK}\x00${contentDigest}\x00`
1072
+ ];
1073
+ };
1074
+ var collectNativeDirectory = async (root, label, directory, ignorePublicBundle) => {
1075
+ const entries = await readdir2(directory, { withFileTypes: true });
1076
+ entries.sort((left, right) => left.name.localeCompare(right.name));
1077
+ const records = await Promise.all(entries.map((entry) => collectNativePath(root, label, join3(directory, entry.name), entry.isDirectory(), entry.isFile(), entry.isSymbolicLink(), ignorePublicBundle)));
1078
+ return records.flat();
1079
+ };
1080
+ var hashNativeTree = async (root, label, ignorePublicBundle) => {
1081
+ const resolvedRoot = await realpath(root);
1082
+ const records = await collectNativeDirectory(resolvedRoot, label, resolvedRoot, ignorePublicBundle);
1083
+ return createHash3("sha256").update(records.join("")).digest("hex");
1084
+ };
1085
+ var fingerprintAbsoluteAndroidNativeProject = async (project, options = {}) => {
1086
+ const { dependencies } = await nativeDependencySources(project.nativeDirectory);
1087
+ const roots = [
1088
+ {
1089
+ ignorePublicBundle: options.includePublicBundle !== true,
1090
+ label: "android",
1091
+ source: project.nativeDirectory
1092
+ },
1093
+ ...dependencies.sort((left, right) => left.name.localeCompare(right.name)).map((dependency) => ({
1094
+ ignorePublicBundle: false,
1095
+ label: `dependency:${dependency.name}`,
1096
+ source: dependency.source
1097
+ }))
1098
+ ];
1099
+ const treeDigests = await Promise.all(roots.map((root) => hashNativeTree(root.source, root.label, root.ignorePublicBundle)));
1100
+ return createHash3("sha256").update(treeDigests.join("\x00")).digest("hex");
1101
+ };
829
1102
  var windowsPathFromWsl = (path, capture) => {
830
1103
  const result = capture(["wslpath", "-w", path]);
831
1104
  if (result.exitCode !== 0 || !result.stdout.trim()) {
@@ -851,12 +1124,13 @@ var mirroredCapacitorDependencies = async (project, capture) => {
851
1124
  }
852
1125
  return { dependencies, rewrittenSettings };
853
1126
  };
854
- var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task) => {
1127
+ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task, gradleArguments) => {
855
1128
  const sourceDirectory = Buffer.from(windowsSource, "utf8").toString("base64");
856
1129
  const buildDirectory = Buffer.from(windowsDirectory, "utf8").toString("base64");
857
1130
  const androidRoot = Buffer.from(windowsAndroidRoot, "utf8").toString("base64");
858
1131
  const dependencyData = Buffer.from(JSON.stringify(dependencies), "utf8").toString("base64");
859
1132
  const settingsData = Buffer.from(rewrittenSettings, "utf8").toString("base64");
1133
+ const argumentsData = Buffer.from(JSON.stringify(gradleArguments), "utf8").toString("base64");
860
1134
  const source = [
861
1135
  "$ErrorActionPreference = 'Stop'",
862
1136
  `$source = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${sourceDirectory}'))`,
@@ -864,6 +1138,7 @@ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndro
864
1138
  `$androidHome = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${androidRoot}'))`,
865
1139
  `$dependencies = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${dependencyData}')) | ConvertFrom-Json`,
866
1140
  `$settings = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${settingsData}'))`,
1141
+ `$gradleArguments = @([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${argumentsData}')) | ConvertFrom-Json)`,
867
1142
  "$env:ANDROID_HOME = $androidHome",
868
1143
  "$env:ANDROID_SDK_ROOT = $androidHome",
869
1144
  "New-Item -ItemType Directory -Force -Path $directory | Out-Null",
@@ -873,7 +1148,7 @@ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndro
873
1148
  "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
1149
  "[IO.File]::WriteAllText((Join-Path $directory 'capacitor.settings.gradle'), $settings)",
875
1150
  "$wrapper = Join-Path $directory 'gradlew.bat'",
876
- `& $wrapper --no-daemon --console=plain -p $directory ${task}`,
1151
+ `& $wrapper --no-daemon --console=plain -p $directory @gradleArguments ${task}`,
877
1152
  "if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }",
878
1153
  "exit 0"
879
1154
  ].join("; ");
@@ -891,16 +1166,17 @@ var gradleArtifactPath = (nativeDirectory, task, windows = false) => {
891
1166
  };
892
1167
  var resolveGradleArtifactPath = async (nativeDirectory, task) => {
893
1168
  const primary = gradleArtifactPath(nativeDirectory, task);
894
- if (task !== "assembleRelease" || await pathExists(primary)) {
1169
+ if (task !== "assembleRelease" || await pathExists2(primary)) {
895
1170
  return primary;
896
1171
  }
897
1172
  return join3(nativeDirectory, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk");
898
1173
  };
899
1174
  var buildAbsoluteAndroidGradleArtifact = async (options) => {
900
1175
  const { project, task } = options;
901
- const capture = options.capture ?? captureCommand;
1176
+ const capture = options.capture ?? captureCommand2;
902
1177
  const run = options.run ?? runCommand;
903
1178
  const env = options.env ?? process.env;
1179
+ const gradleArguments = options.gradleArguments ?? [];
904
1180
  if (project.host === "wsl") {
905
1181
  const windowsSource = windowsPathFromWsl(project.nativeDirectory, capture);
906
1182
  const buildId = Bun.hash(project.projectRoot).toString(HASH_RADIX);
@@ -912,7 +1188,7 @@ var buildAbsoluteAndroidGradleArtifact = async (options) => {
912
1188
  "powershell.exe",
913
1189
  "-NoProfile",
914
1190
  "-EncodedCommand",
915
- encodedWindowsGradleCommand(windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task)
1191
+ encodedWindowsGradleCommand(windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task, gradleArguments)
916
1192
  ], "Android Gradle build", run, { env, signal: options.signal });
917
1193
  const artifactPath2 = await resolveGradleArtifactPath(managedBuildDirectory, task);
918
1194
  const unsigned = artifactPath2.endsWith("-unsigned.apk");
@@ -922,7 +1198,7 @@ var buildAbsoluteAndroidGradleArtifact = async (options) => {
922
1198
  };
923
1199
  }
924
1200
  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 });
1201
+ await requireSuccess([wrapper, "--no-daemon", "--console=plain", ...gradleArguments, task], "Android Gradle build", run, { cwd: project.nativeDirectory, env, signal: options.signal });
926
1202
  const artifactPath = await resolveGradleArtifactPath(project.nativeDirectory, task);
927
1203
  return { artifactPath, installPath: artifactPath };
928
1204
  };
@@ -940,9 +1216,9 @@ var requireManifest = (value) => {
940
1216
  runtime: value.runtime
941
1217
  };
942
1218
  };
943
- var pathExists2 = async (path) => {
1219
+ var pathExists3 = async (path) => {
944
1220
  try {
945
- await access3(path);
1221
+ await access4(path);
946
1222
  return true;
947
1223
  } catch {
948
1224
  return false;
@@ -983,7 +1259,7 @@ var verifyAabSignature = (artifactPath, capture, jarsigner) => {
983
1259
  ]);
984
1260
  return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
985
1261
  };
986
- var sha256File = async (path) => createHash3("sha256").update(await readFile4(path)).digest("hex");
1262
+ var sha256File = async (path) => createHash4("sha256").update(await readFile4(path)).digest("hex");
987
1263
  var safeOutputDirectory = (projectRoot, requested) => {
988
1264
  const root = resolve3(projectRoot);
989
1265
  const output = resolve3(root, requested ?? ".absolutejs/mobile/releases/android");
@@ -997,7 +1273,7 @@ var installRelease = async (artifactPath, metadata, outputRoot) => {
997
1273
  const releaseRoot = join4(outputRoot, metadata.releaseId);
998
1274
  const artifactName = "app-release.aab";
999
1275
  const destination = join4(releaseRoot, artifactName);
1000
- if (await pathExists2(releaseRoot)) {
1276
+ if (await pathExists3(releaseRoot)) {
1001
1277
  const existing = requireManifestIdentity(JSON.parse(await readFile4(join4(releaseRoot, "release.json"), "utf8")), metadata);
1002
1278
  const [installedBytes, installedSha256] = await Promise.all([
1003
1279
  stat(destination).then(({ size }) => size),
@@ -1037,12 +1313,32 @@ var requireManifestIdentity = (value, expected) => {
1037
1313
  return { ...expected, artifact };
1038
1314
  };
1039
1315
  var buildAbsoluteAndroidRelease = async (options) => {
1316
+ if (options.versionCode !== undefined && options.prepareVersionCode) {
1317
+ throw new TypeError("Android release versionCode and prepareVersionCode cannot be combined.");
1318
+ }
1319
+ if (options.versionCode !== undefined && (!Number.isSafeInteger(options.versionCode) || options.versionCode < 1 || options.versionCode > 2100000000)) {
1320
+ throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
1321
+ }
1040
1322
  const projectRoot = resolve3(options.projectRoot);
1041
1323
  const host = options.host ?? detectAbsoluteMobileHost();
1042
1324
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
1043
1325
  const nativeDirectory = join4(options.config.nativeProjectDirectory, "android");
1326
+ const manifest = requireManifest(JSON.parse(await readFile4(join4(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1327
+ if (manifest.appId !== options.config.appId) {
1328
+ throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1329
+ }
1330
+ let { versionCode } = options;
1331
+ if (options.prepareVersionCode) {
1332
+ const nativeFingerprint = await fingerprintAbsoluteAndroidNativeProject({ nativeDirectory });
1333
+ const buildIdentity = createHash4("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
1334
+ versionCode = await options.prepareVersionCode(buildIdentity);
1335
+ }
1336
+ if (versionCode !== undefined && (!Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000)) {
1337
+ throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
1338
+ }
1044
1339
  const { artifactPath } = await buildAbsoluteAndroidGradleArtifact({
1045
1340
  capture: options.capture,
1341
+ gradleArguments: versionCode === undefined ? [] : [`-Pandroid.injected.version.code=${versionCode}`],
1046
1342
  project: {
1047
1343
  androidRoot,
1048
1344
  config: options.config,
@@ -1053,7 +1349,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
1053
1349
  run: options.run,
1054
1350
  task: "bundleRelease"
1055
1351
  });
1056
- if (!await pathExists2(artifactPath)) {
1352
+ if (!await pathExists3(artifactPath)) {
1057
1353
  throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
1058
1354
  }
1059
1355
  const capture = options.capture ?? defaultCapture;
@@ -1064,52 +1360,1792 @@ var buildAbsoluteAndroidRelease = async (options) => {
1064
1360
  if (!signed && !options.allowUnsigned) {
1065
1361
  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
1362
  }
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.");
1363
+ const [bytes, sha256] = await Promise.all([
1364
+ stat(artifactPath).then(({ size }) => size),
1365
+ sha256File(artifactPath)
1366
+ ]);
1367
+ const releaseId = `amobile_android_${sha256}`;
1368
+ const metadata = {
1369
+ appBuild: manifest.appBuild,
1370
+ appId: manifest.appId,
1371
+ bytes,
1372
+ engine: "capacitor",
1373
+ format: ABSOLUTE_ANDROID_RELEASE_FORMAT,
1374
+ platform: "android",
1375
+ releaseId,
1376
+ runtime: manifest.runtime,
1377
+ sha256,
1378
+ signed: signed === true,
1379
+ type: "aab",
1380
+ ...versionCode === undefined ? {} : { versionCode }
1381
+ };
1382
+ return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
1383
+ };
1384
+ // src/mobile/iosRelease.ts
1385
+ import { createHash as createHash5 } from "crypto";
1386
+ import {
1387
+ access as access5,
1388
+ copyFile as copyFile3,
1389
+ mkdir as mkdir4,
1390
+ mkdtemp as mkdtemp3,
1391
+ readdir as readdir3,
1392
+ readFile as readFile5,
1393
+ rename as rename5,
1394
+ rm as rm4,
1395
+ stat as stat2,
1396
+ writeFile as writeFile5
1397
+ } from "fs/promises";
1398
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join5, relative as relative4, resolve as resolve4, sep as sep3 } from "path";
1399
+ var ABSOLUTE_IOS_RELEASE_FORMAT = 1;
1400
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1401
+ var requireManifest2 = (value) => {
1402
+ if (!isRecord2(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
1403
+ throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
1404
+ }
1405
+ return {
1406
+ appBuild: value.appBuild,
1407
+ appId: value.appId,
1408
+ runtime: value.runtime
1409
+ };
1410
+ };
1411
+ var pathExists4 = async (path) => {
1412
+ try {
1413
+ await access5(path);
1414
+ return true;
1415
+ } catch {
1416
+ return false;
1417
+ }
1418
+ };
1419
+ var defaultRun = async (command, options = {}) => {
1420
+ const process2 = Bun.spawn(command, {
1421
+ cwd: options.cwd,
1422
+ env: options.env,
1423
+ stderr: "inherit",
1424
+ stdin: "inherit",
1425
+ stdout: "inherit"
1426
+ });
1427
+ return process2.exited;
1428
+ };
1429
+ var defaultCapture2 = (command, options = {}) => {
1430
+ try {
1431
+ const result = Bun.spawnSync(command, {
1432
+ cwd: options.cwd,
1433
+ env: options.env,
1434
+ stderr: "pipe",
1435
+ stdin: "ignore",
1436
+ stdout: "pipe"
1437
+ });
1438
+ return {
1439
+ exitCode: result.exitCode,
1440
+ stderr: result.stderr.toString(),
1441
+ stdout: result.stdout.toString()
1442
+ };
1443
+ } catch (error) {
1444
+ return {
1445
+ exitCode: 1,
1446
+ stderr: error instanceof Error ? error.message : String(error),
1447
+ stdout: ""
1448
+ };
1449
+ }
1450
+ };
1451
+ var ignoredFingerprintDirectories = new Set([
1452
+ "Pods",
1453
+ "DerivedData",
1454
+ "build",
1455
+ "xcuserdata"
1456
+ ]);
1457
+ var fingerprintFiles = async (root, current = root) => {
1458
+ const entries = await readdir3(current, { withFileTypes: true });
1459
+ const nested = await Promise.all(entries.sort((left, right) => left.name.localeCompare(right.name)).map(async (entry) => {
1460
+ const path = join5(current, entry.name);
1461
+ const projectRelative = relative4(root, path).replaceAll("\\", "/");
1462
+ const ignored = entry.isDirectory() && (ignoredFingerprintDirectories.has(entry.name) || projectRelative === "App/App/public");
1463
+ if (ignored)
1464
+ return [];
1465
+ if (entry.isDirectory())
1466
+ return fingerprintFiles(root, path);
1467
+ return entry.isFile() ? [path] : [];
1468
+ }));
1469
+ return nested.flat();
1470
+ };
1471
+ var fingerprintAbsoluteIosNativeProject = async (nativeDirectory) => {
1472
+ const hasher = createHash5("sha256");
1473
+ const files = await fingerprintFiles(nativeDirectory);
1474
+ const contents = await Promise.all(files.map((file) => readFile5(file)));
1475
+ files.forEach((file, index) => {
1476
+ hasher.update(relative4(nativeDirectory, file).replaceAll("\\", "/"));
1477
+ hasher.update("\x00");
1478
+ hasher.update(contents[index] ?? new Uint8Array);
1479
+ hasher.update("\x00");
1480
+ });
1481
+ return hasher.digest("hex");
1482
+ };
1483
+ var safeOutputDirectory2 = (projectRoot, requested) => {
1484
+ const root = resolve4(projectRoot);
1485
+ const output = resolve4(root, requested ?? ".absolutejs/mobile/releases/ios");
1486
+ const projectRelative = relative4(root, output);
1487
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep3}`) || isAbsolute3(projectRelative)) {
1488
+ throw new TypeError("mobile build --outdir must remain inside the project.");
1489
+ }
1490
+ return output;
1491
+ };
1492
+ var sha256File2 = async (path) => createHash5("sha256").update(await readFile5(path)).digest("hex");
1493
+ var findByExtension = async (root, extension) => {
1494
+ if (!await pathExists4(root))
1495
+ return;
1496
+ const entries = await readdir3(root, { withFileTypes: true });
1497
+ const matches = await Promise.all(entries.map(async (entry) => {
1498
+ const path = join5(root, entry.name);
1499
+ if (entry.isDirectory() && entry.name.endsWith(extension))
1500
+ return path;
1501
+ if (entry.isFile() && entry.name.endsWith(extension))
1502
+ return path;
1503
+ return entry.isDirectory() ? findByExtension(path, extension) : undefined;
1504
+ }));
1505
+ return matches.find((match) => match !== undefined);
1506
+ };
1507
+ var exportOptions = () => `<?xml version="1.0" encoding="UTF-8"?>
1508
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1509
+ <plist version="1.0"><dict>
1510
+ <key>destination</key><string>export</string>
1511
+ <key>manageAppVersionAndBuildNumber</key><false/>
1512
+ <key>method</key><string>app-store-connect</string>
1513
+ <key>signingStyle</key><string>automatic</string>
1514
+ <key>stripSwiftSymbols</key><true/>
1515
+ <key>uploadSymbols</key><true/>
1516
+ </dict></plist>
1517
+ `;
1518
+ var requireBuildNumber = (value) => {
1519
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))
1520
+ throw new TypeError("iOS build number must be a positive integer.");
1521
+ return value;
1522
+ };
1523
+ var installRelease2 = async (artifactPath, metadata, outputRoot) => {
1524
+ const releaseRoot = join5(outputRoot, metadata.releaseId);
1525
+ const destination = join5(releaseRoot, "App.ipa");
1526
+ if (await pathExists4(releaseRoot)) {
1527
+ const value = JSON.parse(await readFile5(join5(releaseRoot, "release.json"), "utf8"));
1528
+ if (!isRecord2(value) || value.artifact !== "App.ipa" || Object.entries(metadata).some(([key, expected]) => Reflect.get(value, key) !== expected)) {
1529
+ throw new TypeError(`Immutable iOS release ${metadata.releaseId} does not match its content.`);
1530
+ }
1531
+ const [bytes, sha256] = await Promise.all([
1532
+ stat2(destination).then(({ size }) => size),
1533
+ sha256File2(destination)
1534
+ ]);
1535
+ if (bytes !== metadata.bytes || sha256 !== metadata.sha256)
1536
+ throw new TypeError(`Immutable iOS release ${metadata.releaseId} artifact is missing or modified.`);
1537
+ return {
1538
+ artifactPath: destination,
1539
+ metadata: {
1540
+ ...metadata,
1541
+ artifact: "App.ipa"
1542
+ },
1543
+ releaseRoot
1544
+ };
1545
+ }
1546
+ await mkdir4(dirname3(releaseRoot), { recursive: true });
1547
+ const staging = await mkdtemp3(join5(dirname3(releaseRoot), ".ios-stage-"));
1548
+ try {
1549
+ await copyFile3(artifactPath, join5(staging, "App.ipa"));
1550
+ const complete = {
1551
+ ...metadata,
1552
+ artifact: "App.ipa"
1553
+ };
1554
+ await writeFile5(join5(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
1555
+ `, { flag: "wx" });
1556
+ await rename5(staging, releaseRoot);
1557
+ return { artifactPath: destination, metadata: complete, releaseRoot };
1558
+ } finally {
1559
+ await rm4(staging, { force: true, recursive: true }).catch(() => {
1560
+ return;
1561
+ });
1562
+ }
1563
+ };
1564
+ var buildAbsoluteIosRelease = async (options) => {
1565
+ if (options.buildNumber !== undefined && options.prepareBuildNumber)
1566
+ throw new TypeError("iOS release buildNumber and prepareBuildNumber cannot be combined.");
1567
+ if ((options.host ?? detectAbsoluteMobileHost()) !== "macos")
1568
+ throw new TypeError("iOS release builds require macOS and Xcode.");
1569
+ const marketingVersion = options.config.iosVersion;
1570
+ if (!marketingVersion)
1571
+ throw new TypeError("iOS release builds require mobile.ios.version in absolutejs.config.ts.");
1572
+ const manifest = requireManifest2(JSON.parse(await readFile5(join5(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1573
+ if (manifest.appId !== options.config.appId)
1574
+ throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1575
+ const nativeDirectory = join5(options.config.nativeProjectDirectory, "ios");
1576
+ let buildNumber = requireBuildNumber(options.buildNumber);
1577
+ if (options.prepareBuildNumber) {
1578
+ const nativeFingerprint = await fingerprintAbsoluteIosNativeProject(nativeDirectory);
1579
+ const buildIdentity = createHash5("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}\x00${marketingVersion}`).digest("hex");
1580
+ buildNumber = requireBuildNumber(await options.prepareBuildNumber(buildIdentity));
1581
+ }
1582
+ const stagingParent = resolve4(options.projectRoot, ".absolutejs/mobile");
1583
+ await mkdir4(stagingParent, { recursive: true });
1584
+ const staging = await mkdtemp3(join5(stagingParent, ".ios-build-"));
1585
+ const archivePath = join5(staging, "App.xcarchive");
1586
+ const exportPath = join5(staging, "export");
1587
+ const exportPlist = join5(staging, "ExportOptions.plist");
1588
+ await mkdir4(exportPath, { recursive: true });
1589
+ await writeFile5(exportPlist, exportOptions());
1590
+ const run = options.run ?? defaultRun;
1591
+ try {
1592
+ const versionArguments = [
1593
+ `MARKETING_VERSION=${marketingVersion}`,
1594
+ ...buildNumber === undefined ? [] : [`CURRENT_PROJECT_VERSION=${buildNumber}`]
1595
+ ];
1596
+ const archiveExit = await run([
1597
+ "xcodebuild",
1598
+ "-workspace",
1599
+ join5(nativeDirectory, "App", "App.xcworkspace"),
1600
+ "-scheme",
1601
+ "App",
1602
+ "-configuration",
1603
+ "Release",
1604
+ "-destination",
1605
+ "generic/platform=iOS",
1606
+ "-archivePath",
1607
+ archivePath,
1608
+ ...versionArguments,
1609
+ "archive"
1610
+ ], { cwd: nativeDirectory });
1611
+ if (archiveExit !== 0)
1612
+ throw new TypeError("Xcode failed to archive the iOS app.");
1613
+ const archivedApp = await findByExtension(join5(archivePath, "Products", "Applications"), ".app");
1614
+ const capture = options.capture ?? defaultCapture2;
1615
+ const signed = archivedApp ? capture([
1616
+ "codesign",
1617
+ "--verify",
1618
+ "--deep",
1619
+ "--strict",
1620
+ archivedApp
1621
+ ]).exitCode === 0 : false;
1622
+ if (!signed && !options.allowUnsigned)
1623
+ 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.");
1624
+ const exportExit = await run([
1625
+ "xcodebuild",
1626
+ "-exportArchive",
1627
+ "-archivePath",
1628
+ archivePath,
1629
+ "-exportPath",
1630
+ exportPath,
1631
+ "-exportOptionsPlist",
1632
+ exportPlist
1633
+ ], { cwd: nativeDirectory });
1634
+ if (exportExit !== 0)
1635
+ throw new TypeError("Xcode failed to export the App Store IPA.");
1636
+ const artifactPath = await findByExtension(exportPath, ".ipa");
1637
+ if (!artifactPath)
1638
+ throw new TypeError("Xcode did not produce an exported IPA.");
1639
+ const [bytes, sha256] = await Promise.all([
1640
+ stat2(artifactPath).then(({ size }) => size),
1641
+ sha256File2(artifactPath)
1642
+ ]);
1643
+ const releaseId = `amobile_ios_${sha256}`;
1644
+ return await installRelease2(artifactPath, {
1645
+ appBuild: manifest.appBuild,
1646
+ appId: manifest.appId,
1647
+ ...buildNumber === undefined ? {} : { buildNumber },
1648
+ bytes,
1649
+ engine: "capacitor",
1650
+ format: 1,
1651
+ marketingVersion,
1652
+ platform: "ios",
1653
+ releaseId,
1654
+ runtime: manifest.runtime,
1655
+ sha256,
1656
+ signed,
1657
+ type: "ipa"
1658
+ }, safeOutputDirectory2(options.projectRoot, options.outputDirectory));
1659
+ } finally {
1660
+ await rm4(staging, { force: true, recursive: true }).catch(() => {
1661
+ return;
1662
+ });
1663
+ }
1664
+ };
1665
+ // src/mobile/iosConformance.ts
1666
+ import { readFile as readFile6, stat as stat3 } from "fs/promises";
1667
+ 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");
1668
+ var parseAbsoluteIosHmrLog = (line) => {
1669
+ const match = HMR_LINE.exec(line);
1670
+ if (!match)
1671
+ return null;
1672
+ const [, , action, durationValue, serverValue, clientValue] = match;
1673
+ let outcome = "reloaded";
1674
+ if (action === "applied in")
1675
+ outcome = "applied";
1676
+ if (action === "failed after")
1677
+ outcome = "failed";
1678
+ const serverMs = serverValue === undefined ? undefined : Number(serverValue);
1679
+ const clientMs = clientValue === undefined ? undefined : Number(clientValue);
1680
+ return {
1681
+ ...clientMs === undefined ? {} : { clientMs },
1682
+ duration: Number(durationValue),
1683
+ line: match[0],
1684
+ outcome,
1685
+ ...serverMs === undefined ? {} : { serverMs }
1686
+ };
1687
+ };
1688
+ var findHmrApply = (lines) => {
1689
+ const apply = lines.map((line) => parseAbsoluteIosHmrLog(line)).find((candidate) => candidate !== null);
1690
+ if (apply?.outcome === "failed")
1691
+ throw new Error(`iOS HMR client reported a failed apply: ${apply.line}`);
1692
+ return apply;
1693
+ };
1694
+ var waitForAbsoluteIosHmrLog = async (options) => {
1695
+ const sleep = options.sleep ?? Bun.sleep;
1696
+ const timeoutMs = options.timeoutMs ?? 30000;
1697
+ const deadline = Date.now() + timeoutMs;
1698
+ let offset = options.startOffset ?? await stat3(options.logPath).then(({ size }) => size).catch(() => 0);
1699
+ let buffered = "";
1700
+ const poll = async () => {
1701
+ if (Date.now() > deadline)
1702
+ throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
1703
+ options.signal?.throwIfAborted();
1704
+ const contents = await readFile6(options.logPath).catch(() => Buffer.alloc(0));
1705
+ if (contents.byteLength < offset) {
1706
+ offset = 0;
1707
+ buffered = "";
1708
+ }
1709
+ if (contents.byteLength > offset) {
1710
+ buffered += contents.subarray(offset).toString("utf8");
1711
+ offset = contents.byteLength;
1712
+ const lines = buffered.split(/\r?\n/u);
1713
+ buffered = lines.pop() ?? "";
1714
+ const apply = findHmrApply(lines);
1715
+ if (apply)
1716
+ return apply;
1717
+ }
1718
+ await sleep(100);
1719
+ return poll();
1720
+ };
1721
+ return poll();
1722
+ };
1723
+ // src/mobile/iosSimulatorController.ts
1724
+ import { createHash as createHash6, randomUUID as randomUUID2 } from "crypto";
1725
+ import {
1726
+ access as access6,
1727
+ copyFile as copyFile4,
1728
+ mkdir as mkdir5,
1729
+ readFile as readFile7,
1730
+ rename as rename6,
1731
+ rm as rm5,
1732
+ writeFile as writeFile6
1733
+ } from "fs/promises";
1734
+ import { dirname as dirname4, isAbsolute as isAbsolute4, join as join6, relative as relative5, resolve as resolve5, sep as sep4 } from "path";
1735
+ init_getDurationString();
1736
+ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone";
1737
+ var BOOT_TIMEOUT_MS = 180000;
1738
+ var BOOT_POLL_MS = 1000;
1739
+ var DEV_JOURNAL_FORMAT = 1;
1740
+ var NATIVE_CACHE_FORMAT = 1;
1741
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1742
+ var pathExists5 = async (path) => {
1743
+ try {
1744
+ await access6(path);
1745
+ return true;
1746
+ } catch {
1747
+ return false;
1748
+ }
1749
+ };
1750
+ var throwIfAborted2 = (signal) => signal?.throwIfAborted();
1751
+ var defaultCapture3 = (command, options = {}) => {
1752
+ try {
1753
+ const result = Bun.spawnSync(command, {
1754
+ cwd: options.cwd,
1755
+ env: options.env,
1756
+ stderr: "pipe",
1757
+ stdin: "ignore",
1758
+ stdout: "pipe"
1759
+ });
1760
+ return {
1761
+ exitCode: result.exitCode,
1762
+ stderr: result.stderr.toString(),
1763
+ stdout: result.stdout.toString()
1764
+ };
1765
+ } catch (error) {
1766
+ return {
1767
+ exitCode: 1,
1768
+ stderr: error instanceof Error ? error.message : String(error),
1769
+ stdout: ""
1770
+ };
1771
+ }
1772
+ };
1773
+ var defaultRun2 = async (command, options = {}) => {
1774
+ const process2 = Bun.spawn(command, {
1775
+ cwd: options.cwd,
1776
+ env: options.env,
1777
+ signal: options.signal,
1778
+ stderr: "inherit",
1779
+ stdin: "inherit",
1780
+ stdout: "inherit"
1781
+ });
1782
+ return process2.exited;
1783
+ };
1784
+ var defaultSpawn = (command, options = {}) => {
1785
+ Bun.spawn(command, {
1786
+ cwd: options.cwd,
1787
+ env: options.env,
1788
+ signal: options.signal,
1789
+ stderr: "ignore",
1790
+ stdin: "ignore",
1791
+ stdout: "ignore"
1792
+ });
1793
+ };
1794
+ var consumeLines = async (stream, onLine) => {
1795
+ const reader = stream.getReader();
1796
+ const decoder = new TextDecoder;
1797
+ let buffered = "";
1798
+ const pump = async () => {
1799
+ const { done, value } = await reader.read();
1800
+ if (done)
1801
+ return;
1802
+ buffered += decoder.decode(value, { stream: true });
1803
+ const lines = buffered.split(/\r?\n/u);
1804
+ buffered = lines.pop() ?? "";
1805
+ lines.forEach(onLine);
1806
+ await pump();
1807
+ };
1808
+ try {
1809
+ await pump();
1810
+ buffered += decoder.decode();
1811
+ if (buffered)
1812
+ onLine(buffered);
1813
+ } finally {
1814
+ reader.releaseLock();
1815
+ }
1816
+ };
1817
+ var defaultStartNativeLogs = (command, options, onLine) => {
1818
+ const process2 = Bun.spawn(command, {
1819
+ cwd: options.cwd,
1820
+ env: options.env,
1821
+ signal: options.signal,
1822
+ stderr: "pipe",
1823
+ stdin: "ignore",
1824
+ stdout: "pipe"
1825
+ });
1826
+ consumeLines(process2.stdout, onLine);
1827
+ consumeLines(process2.stderr, onLine);
1828
+ return {
1829
+ close: async () => {
1830
+ try {
1831
+ process2.kill();
1832
+ } catch {}
1833
+ await process2.exited.catch(() => {
1834
+ return;
1835
+ });
1836
+ }
1837
+ };
1838
+ };
1839
+ var requireSuccess2 = async (command, label, run, options) => {
1840
+ const exitCode = await run(command, options);
1841
+ if (exitCode !== 0)
1842
+ throw new Error(`${label} failed with status ${exitCode}.`);
1843
+ };
1844
+ var requireCapturedSuccess = (result, label) => {
1845
+ if (result.exitCode !== 0) {
1846
+ throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
1847
+ }
1848
+ return result.stdout.trim();
1849
+ };
1850
+ var parseJson = (source, label) => {
1851
+ try {
1852
+ const parsed = JSON.parse(source);
1853
+ if (isRecord3(parsed))
1854
+ return parsed;
1855
+ } catch {}
1856
+ throw new Error(`Invalid ${label} JSON from simctl.`);
1857
+ };
1858
+ var parseIosDeviceTypes = (source) => {
1859
+ const parsed = parseJson(source, "device type");
1860
+ const types = parsed.devicetypes;
1861
+ if (!Array.isArray(types))
1862
+ return [];
1863
+ return types.flatMap((type) => {
1864
+ if (!isRecord3(type))
1865
+ return [];
1866
+ const { identifier } = type;
1867
+ const { name } = type;
1868
+ return typeof identifier === "string" && typeof name === "string" ? [{ identifier, name }] : [];
1869
+ });
1870
+ };
1871
+ var parseIosRuntimes = (source) => {
1872
+ const parsed = parseJson(source, "runtime");
1873
+ const { runtimes } = parsed;
1874
+ if (!Array.isArray(runtimes))
1875
+ return [];
1876
+ return runtimes.flatMap((runtime) => {
1877
+ if (!isRecord3(runtime))
1878
+ return [];
1879
+ const { identifier } = runtime;
1880
+ const { name } = runtime;
1881
+ const { version } = runtime;
1882
+ if (typeof identifier !== "string" || typeof name !== "string" || typeof version !== "string")
1883
+ return [];
1884
+ return [
1885
+ {
1886
+ identifier,
1887
+ isAvailable: runtime.isAvailable !== false,
1888
+ name,
1889
+ version
1890
+ }
1891
+ ];
1892
+ });
1893
+ };
1894
+ var parseIosSimulators = (source) => {
1895
+ const parsed = parseJson(source, "device");
1896
+ const { devices } = parsed;
1897
+ if (!isRecord3(devices))
1898
+ return [];
1899
+ return Object.entries(devices).flatMap(([runtime, values]) => {
1900
+ if (!Array.isArray(values))
1901
+ return [];
1902
+ return values.flatMap((device) => {
1903
+ if (!isRecord3(device))
1904
+ return [];
1905
+ const { name } = device;
1906
+ const { state } = device;
1907
+ const { udid } = device;
1908
+ if (typeof name !== "string" || typeof state !== "string" || typeof udid !== "string")
1909
+ return [];
1910
+ return [
1911
+ {
1912
+ isAvailable: device.isAvailable !== false,
1913
+ name,
1914
+ runtime,
1915
+ state,
1916
+ udid
1917
+ }
1918
+ ];
1919
+ });
1920
+ });
1921
+ };
1922
+ var versionParts = (version) => version.split(".").map((part) => Number(part));
1923
+ var compareVersions = (left, right) => {
1924
+ const leftParts = versionParts(left);
1925
+ const rightParts = versionParts(right);
1926
+ const length = Math.max(leftParts.length, rightParts.length);
1927
+ for (let index = 0;index < length; index++) {
1928
+ const difference = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
1929
+ if (difference !== 0)
1930
+ return difference;
1931
+ }
1932
+ return 0;
1933
+ };
1934
+ var latestIosRuntime = (runtimes) => runtimes.filter((runtime) => runtime.isAvailable && runtime.identifier.includes("SimRuntime.iOS-")).sort((left, right) => compareVersions(right.version, left.version))[0];
1935
+ var iphoneGeneration = (name) => Number(/iPhone\s+(\d+)/u.exec(name)?.[1] ?? 0);
1936
+ var preferredIphoneType = (types) => types.filter((type) => type.name.startsWith("iPhone")).sort((left, right) => {
1937
+ const generation = iphoneGeneration(right.name) - iphoneGeneration(left.name);
1938
+ if (generation !== 0)
1939
+ return generation;
1940
+ const rightPro = right.name.includes("Pro") ? 1 : 0;
1941
+ const leftPro = left.name.includes("Pro") ? 1 : 0;
1942
+ return rightPro - leftPro;
1943
+ })[0];
1944
+ var journalPaths = (projectRoot) => {
1945
+ const root = join6(projectRoot, ".absolutejs", "mobile", "ios-dev-session");
1946
+ return {
1947
+ configBackup: join6(root, "capacitor-config.backup"),
1948
+ infoBackup: join6(root, "Info.plist.backup"),
1949
+ journal: join6(root, "journal.json"),
1950
+ root
1951
+ };
1952
+ };
1953
+ var nativeCachePath = (projectRoot) => join6(projectRoot, ".absolutejs", "mobile", "ios-native-cache.json");
1954
+ var isInside = (root, path) => {
1955
+ const value = relative5(resolve5(root), resolve5(path));
1956
+ return value === "" || !value.startsWith(`..${sep4}`) && value !== ".." && !isAbsolute4(value);
1957
+ };
1958
+ var parseJournal = (value) => {
1959
+ if (!isRecord3(value) || value.format !== DEV_JOURNAL_FORMAT)
1960
+ return null;
1961
+ const { configBackupPath } = value;
1962
+ const { infoBackupPath } = value;
1963
+ const { infoPath } = value;
1964
+ const { nativeConfigPath } = value;
1965
+ if (typeof configBackupPath !== "string" || typeof infoBackupPath !== "string" || typeof infoPath !== "string" || typeof nativeConfigPath !== "string")
1966
+ return null;
1967
+ return {
1968
+ configBackupPath,
1969
+ format: DEV_JOURNAL_FORMAT,
1970
+ infoBackupPath,
1971
+ infoPath,
1972
+ nativeConfigPath
1973
+ };
1974
+ };
1975
+ var repairAbsoluteIosDevSession = async (projectRoot) => {
1976
+ const paths = journalPaths(projectRoot);
1977
+ if (!await pathExists5(paths.journal)) {
1978
+ await rm5(paths.root, { force: true, recursive: true });
1979
+ return false;
1980
+ }
1981
+ const journal = await readFile7(paths.journal, "utf8").then((source) => parseJournal(JSON.parse(source))).catch(() => null);
1982
+ if (!journal || !isInside(projectRoot, journal.nativeConfigPath) || !isInside(projectRoot, journal.infoPath) || !isInside(paths.root, journal.configBackupPath) || !isInside(paths.root, journal.infoBackupPath)) {
1983
+ throw new Error(`Refusing unsafe or invalid iOS dev journal at ${paths.journal}.`);
1984
+ }
1985
+ if (await pathExists5(journal.configBackupPath))
1986
+ await copyFile4(journal.configBackupPath, journal.nativeConfigPath);
1987
+ if (await pathExists5(journal.infoBackupPath))
1988
+ await copyFile4(journal.infoBackupPath, journal.infoPath);
1989
+ await rm5(paths.root, { force: true, recursive: true });
1990
+ return true;
1991
+ };
1992
+ var iosDevelopmentInfoPlist = (source, cleartext) => {
1993
+ if (!cleartext)
1994
+ return source;
1995
+ const arbitraryLoads = /(<key>NSAllowsArbitraryLoads<\/key>\s*)<false\s*\/>/u;
1996
+ if (arbitraryLoads.test(source))
1997
+ return source.replace(arbitraryLoads, "$1<true/>");
1998
+ if (/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(source))
1999
+ return source;
2000
+ const transport = /(<key>NSAppTransportSecurity<\/key>\s*<dict>)/u;
2001
+ if (transport.test(source))
2002
+ return source.replace(transport, `$1
2003
+ <key>NSAllowsArbitraryLoads</key>
2004
+ <true/>`);
2005
+ return source.replace(/<dict>/u, `<dict>
2006
+ <key>NSAppTransportSecurity</key>
2007
+ <dict>
2008
+ <key>NSAllowsArbitraryLoads</key>
2009
+ <true/>
2010
+ </dict>`);
2011
+ };
2012
+ var writeDevProjection = async (project, port, https) => {
2013
+ const paths = journalPaths(project.projectRoot);
2014
+ await repairAbsoluteIosDevSession(project.projectRoot);
2015
+ const nativeConfigPath = join6(project.nativeDirectory, "App", "App", "capacitor.config.json");
2016
+ const infoPath = join6(project.nativeDirectory, "App", "App", "Info.plist");
2017
+ const [configSource, infoSource] = await Promise.all([
2018
+ readFile7(nativeConfigPath, "utf8"),
2019
+ readFile7(infoPath, "utf8")
2020
+ ]);
2021
+ const parsed = JSON.parse(configSource);
2022
+ if (!isRecord3(parsed))
2023
+ throw new Error(`Invalid Capacitor native config at ${nativeConfigPath}.`);
2024
+ await mkdir5(paths.root, { recursive: true });
2025
+ await Promise.all([
2026
+ writeFile6(paths.configBackup, configSource, { flag: "wx" }),
2027
+ writeFile6(paths.infoBackup, infoSource, { flag: "wx" })
2028
+ ]);
2029
+ const journal = {
2030
+ configBackupPath: paths.configBackup,
2031
+ format: DEV_JOURNAL_FORMAT,
2032
+ infoBackupPath: paths.infoBackup,
2033
+ infoPath,
2034
+ nativeConfigPath
2035
+ };
2036
+ await writeFile6(paths.journal, `${JSON.stringify(journal, null, "\t")}
2037
+ `, {
2038
+ flag: "wx"
2039
+ });
2040
+ const developmentUrl = new URL(`${https ? "https" : "http"}://localhost:${port}${project.config.entry}`);
2041
+ developmentUrl.searchParams.set("__absolute_target", "capacitor-ios");
2042
+ const existingServer = parsed.server;
2043
+ parsed.server = {
2044
+ ...isRecord3(existingServer) ? existingServer : {},
2045
+ cleartext: !https,
2046
+ url: developmentUrl.href
2047
+ };
2048
+ await Promise.all([
2049
+ writeFile6(nativeConfigPath, `${JSON.stringify(parsed, null, "\t")}
2050
+ `),
2051
+ writeFile6(infoPath, iosDevelopmentInfoPlist(infoSource, !https))
2052
+ ]);
2053
+ };
2054
+ var parseNativeCache = (value) => {
2055
+ if (!isRecord3(value))
2056
+ return null;
2057
+ const { appId, fingerprint, format, installations } = value;
2058
+ if (format !== NATIVE_CACHE_FORMAT || typeof appId !== "string" || typeof fingerprint !== "string" || !isRecord3(installations) || !Object.values(installations).every((identity) => typeof identity === "string"))
2059
+ return null;
2060
+ return {
2061
+ appId,
2062
+ fingerprint,
2063
+ format,
2064
+ installations: Object.fromEntries(Object.entries(installations).map(([udid, identity]) => [
2065
+ udid,
2066
+ String(identity)
2067
+ ]))
2068
+ };
2069
+ };
2070
+ var readNativeCache = (projectRoot) => readFile7(nativeCachePath(projectRoot), "utf8").then((source) => parseNativeCache(JSON.parse(source))).catch(() => null);
2071
+ var writeNativeCache = async (projectRoot, cache) => {
2072
+ const destination = nativeCachePath(projectRoot);
2073
+ const temporary = `${destination}.${process.pid}.${randomUUID2()}.tmp`;
2074
+ await mkdir5(dirname4(destination), { recursive: true });
2075
+ try {
2076
+ await writeFile6(temporary, `${JSON.stringify(cache, null, "\t")}
2077
+ `, {
2078
+ flag: "wx"
2079
+ });
2080
+ await rename6(temporary, destination);
2081
+ } finally {
2082
+ await rm5(temporary, { force: true }).catch(() => {
2083
+ return;
2084
+ });
2085
+ }
2086
+ };
2087
+ var fingerprintAbsoluteIosDevProject = async (project) => fingerprintAbsoluteIosNativeProject(project.nativeDirectory);
2088
+ var simulatorInventory = (xcrun, capture) => {
2089
+ const result = capture([
2090
+ xcrun,
2091
+ "simctl",
2092
+ "list",
2093
+ "devices",
2094
+ "available",
2095
+ "-j"
2096
+ ]);
2097
+ return parseIosSimulators(requireCapturedSuccess(result, "iOS simulator discovery"));
2098
+ };
2099
+ var ensureManagedSimulator = async (project, capture) => {
2100
+ const runtimes = parseIosRuntimes(requireCapturedSuccess(capture([project.xcrun, "simctl", "list", "runtimes", "-j"]), "iOS runtime discovery"));
2101
+ const runtime = latestIosRuntime(runtimes);
2102
+ if (!runtime)
2103
+ throw new Error("No available iOS Simulator runtime. Run absolute mobile doctor ios --fix.");
2104
+ 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"));
2105
+ if (existing)
2106
+ return { created: false, device: existing };
2107
+ const types = parseIosDeviceTypes(requireCapturedSuccess(capture([project.xcrun, "simctl", "list", "devicetypes", "-j"]), "iOS device-type discovery"));
2108
+ const type = preferredIphoneType(types);
2109
+ if (!type)
2110
+ throw new Error("Xcode did not report an iPhone simulator type.");
2111
+ const [udid] = requireCapturedSuccess(capture([
2112
+ project.xcrun,
2113
+ "simctl",
2114
+ "create",
2115
+ ABSOLUTE_IOS_SIMULATOR_NAME,
2116
+ type.identifier,
2117
+ runtime.identifier
2118
+ ]), "iOS simulator creation").split(/\s/u);
2119
+ if (!udid)
2120
+ throw new Error("simctl did not return the created simulator UDID.");
2121
+ return {
2122
+ created: true,
2123
+ device: {
2124
+ isAvailable: true,
2125
+ name: ABSOLUTE_IOS_SIMULATOR_NAME,
2126
+ runtime: runtime.identifier,
2127
+ state: "Shutdown",
2128
+ udid
2129
+ }
2130
+ };
2131
+ };
2132
+ var waitForBootedSimulator = async (project, udid, capture, sleep, signal) => {
2133
+ const deadline = Date.now() + BOOT_TIMEOUT_MS;
2134
+ const poll = async () => {
2135
+ throwIfAborted2(signal);
2136
+ const device = simulatorInventory(project.xcrun, capture).find((candidate) => candidate.udid === udid);
2137
+ if (device?.state === "Booted")
2138
+ return;
2139
+ if (Date.now() > deadline)
2140
+ throw new Error(`iOS simulator ${udid} did not finish booting within ${BOOT_TIMEOUT_MS / 1000}s.`);
2141
+ await sleep(BOOT_POLL_MS);
2142
+ await poll();
2143
+ };
2144
+ return poll();
2145
+ };
2146
+ var bootSimulator = (project, device, capture) => {
2147
+ if (device.state === "Booted")
2148
+ return;
2149
+ requireCapturedSuccess(capture([project.xcrun, "simctl", "boot", device.udid]), "iOS simulator boot");
2150
+ };
2151
+ var installedAppIdentity = (project, udid, capture) => {
2152
+ const result = capture([
2153
+ project.xcrun,
2154
+ "simctl",
2155
+ "get_app_container",
2156
+ udid,
2157
+ project.config.appId,
2158
+ "app"
2159
+ ]);
2160
+ return result.exitCode === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
2161
+ };
2162
+ var buildIosDebugApp = async (project, udid, fingerprint, run, signal) => {
2163
+ const derivedDataPath = join6(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash6("sha256").update(project.config.appId).digest("hex").slice(0, 16));
2164
+ await mkdir5(derivedDataPath, { recursive: true });
2165
+ await requireSuccess2([
2166
+ project.xcodebuild,
2167
+ "-workspace",
2168
+ join6(project.nativeDirectory, "App", "App.xcworkspace"),
2169
+ "-scheme",
2170
+ "App",
2171
+ "-configuration",
2172
+ "Debug",
2173
+ "-destination",
2174
+ `platform=iOS Simulator,id=${udid}`,
2175
+ "-derivedDataPath",
2176
+ derivedDataPath,
2177
+ "build"
2178
+ ], "iOS simulator build", run, { cwd: project.nativeDirectory, signal });
2179
+ const appPath = join6(derivedDataPath, "Build", "Products", "Debug-iphonesimulator", "App.app");
2180
+ if (!await pathExists5(appPath))
2181
+ throw new Error(`Xcode did not produce the simulator app at ${appPath}.`);
2182
+ return appPath;
2183
+ };
2184
+ var ensureIosDebugApp = async (options) => {
2185
+ const installed = installedAppIdentity(options.project, options.udid, options.capture);
2186
+ const cacheHit = options.cache?.appId === options.project.config.appId && options.cache.fingerprint === options.fingerprint && installed !== undefined && options.cache.installations[options.udid] === installed;
2187
+ if (cacheHit) {
2188
+ options.log(`iOS native app is unchanged on ${options.udid}; skipped Xcode build and install.`);
2189
+ return true;
2190
+ }
2191
+ options.log("iOS native inputs changed or the installed app is stale; rebuilding.");
2192
+ options.transition("building");
2193
+ const appPath = await buildIosDebugApp(options.project, options.udid, options.fingerprint, options.run, options.signal);
2194
+ throwIfAborted2(options.signal);
2195
+ options.transition("installing");
2196
+ await requireSuccess2([options.project.xcrun, "simctl", "install", options.udid, appPath], "iOS simulator app installation", options.run, { signal: options.signal });
2197
+ const updated = installedAppIdentity(options.project, options.udid, options.capture);
2198
+ if (updated) {
2199
+ await writeNativeCache(options.project.projectRoot, {
2200
+ appId: options.project.config.appId,
2201
+ fingerprint: options.fingerprint,
2202
+ format: NATIVE_CACHE_FORMAT,
2203
+ installations: { [options.udid]: updated }
2204
+ }).catch((error) => options.log(`iOS native cache could not be saved: ${error instanceof Error ? error.message : String(error)}`));
2205
+ }
2206
+ return false;
2207
+ };
2208
+ var SECRET_VALUE = /((?:authorization|cookie|password|secret|token|oauth[_-]?code)\s*[:=]\s*)([^\s,;]+)/giu;
2209
+ var BEARER_VALUE = new RegExp(String.raw`\bBearer\s+[A-Za-z0-9._~+/-]+=*`, "giu");
2210
+ var JWT_VALUE = /\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/gu;
2211
+ var IOS_LOG_PATTERN = new RegExp(String.raw`\s(Debug|Info|Notice|Error|Fault)\s+.*?\[[^\]]+\]\s+\[([^\]]+)\]\s*(.*)$`, "iu");
2212
+ var parseAbsoluteIosLogLine = (line) => {
2213
+ const sanitized = redactAbsoluteIosLog(line).trim();
2214
+ if (!sanitized)
2215
+ return null;
2216
+ const match = IOS_LOG_PATTERN.exec(sanitized);
2217
+ const candidate = match?.[1]?.toLowerCase();
2218
+ const level = candidate === "debug" || candidate === "error" || candidate === "fault" || candidate === "notice" ? candidate : "info";
2219
+ return {
2220
+ level,
2221
+ message: match?.[3]?.trim() || sanitized,
2222
+ tag: match?.[2]?.trim() || "App"
2223
+ };
2224
+ };
2225
+ var redactAbsoluteIosLog = (value) => value.replaceAll(BEARER_VALUE, "Bearer [REDACTED]").replaceAll(JWT_VALUE, "[REDACTED_JWT]").replaceAll(SECRET_VALUE, "$1[REDACTED]").replaceAll(/\p{C}/gu, "");
2226
+ var attachNativeLogs = (project, udid, options) => {
2227
+ if (!options.nativeLog)
2228
+ return null;
2229
+ const start = options.startNativeLogs ?? defaultStartNativeLogs;
2230
+ return start([
2231
+ project.xcrun,
2232
+ "simctl",
2233
+ "spawn",
2234
+ udid,
2235
+ "log",
2236
+ "stream",
2237
+ "--style",
2238
+ "compact",
2239
+ "--level",
2240
+ "debug",
2241
+ "--predicate",
2242
+ 'process == "App"'
2243
+ ], { signal: options.signal }, (line) => {
2244
+ const entry = parseAbsoluteIosLogLine(line);
2245
+ if (entry)
2246
+ options.nativeLog?.(entry);
2247
+ });
2248
+ };
2249
+ var IOS_TIMING_PHASES = [
2250
+ ["syncing", "Capacitor sync"],
2251
+ ["configuring", "dev config"],
2252
+ ["fingerprinting", "fingerprint"],
2253
+ ["booting", "simulator"],
2254
+ ["connecting", "device ready"],
2255
+ ["checking-native", "app check"],
2256
+ ["building", "Xcode"],
2257
+ ["installing", "install"],
2258
+ ["launching", "launch"],
2259
+ ["streaming-logs", "logs"]
2260
+ ];
2261
+ var timingSummary = (timings) => IOS_TIMING_PHASES.map(([phase, label]) => {
2262
+ const duration = timings[phase];
2263
+ return duration === undefined ? null : `${label} ${getDurationString(duration)}`;
2264
+ }).filter((value) => value !== null).join(", ");
2265
+ var prepareAbsoluteIosDevProject = async (config, options) => {
2266
+ if (detectAbsoluteMobileHost() !== "macos")
2267
+ throw new Error("iOS simulation requires macOS and Xcode.");
2268
+ const projectRoot = resolve5(options.projectRoot);
2269
+ const checks = await inspectAbsoluteMobileToolchain({ host: "macos" });
2270
+ const failed = checks.filter((check) => check.platform === "ios" && (check.status === "fail" || check.status === "warn"));
2271
+ if (failed.length > 0)
2272
+ throw new Error(`iOS simulation is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
2273
+ const xcrun = checks.find((check) => check.id === "ios.xcrun")?.path;
2274
+ const xcodebuild = checks.find((check) => check.id === "ios.xcodebuild")?.path;
2275
+ if (!xcrun || !xcodebuild)
2276
+ throw new Error("Xcode tools disappeared after readiness checks.");
2277
+ const cap = join6(projectRoot, "node_modules", ".bin", "cap");
2278
+ if (!await pathExists5(cap))
2279
+ throw new Error("Capacitor CLI is not installed. Run absolute mobile init first.");
2280
+ await writeAbsoluteCapacitorConfig(config, { projectRoot });
2281
+ await mkdir5(config.bundleDirectory, { recursive: true });
2282
+ const placeholder = join6(config.bundleDirectory, "index.html");
2283
+ if (!await pathExists5(placeholder))
2284
+ await writeFile6(placeholder, `<!doctype html><title>AbsoluteJS mobile development</title>
2285
+ `);
2286
+ const nativeDirectory = join6(config.nativeProjectDirectory, "ios");
2287
+ if (!await pathExists5(nativeDirectory)) {
2288
+ if (!options.createNativeProject)
2289
+ throw new Error("iOS native project has not been created.");
2290
+ const run = options.run ?? defaultRun2;
2291
+ if (await run([cap, "add", "ios"], { cwd: projectRoot }) !== 0)
2292
+ throw new Error("Capacitor iOS project creation failed.");
2293
+ }
2294
+ return {
2295
+ cap,
2296
+ config,
2297
+ nativeDirectory,
2298
+ projectRoot,
2299
+ xcodebuild,
2300
+ xcrun
2301
+ };
2302
+ };
2303
+ var startAbsoluteIosDevSession = async (options) => {
2304
+ const { project } = options;
2305
+ const capture = options.capture ?? defaultCapture3;
2306
+ const run = options.run ?? defaultRun2;
2307
+ const sleep = options.sleep ?? Bun.sleep;
2308
+ const spawn = options.spawn ?? defaultSpawn;
2309
+ const log = options.log ?? console.log;
2310
+ const startedAt = performance.now();
2311
+ let phaseStartedAt = performance.now();
2312
+ const timings = {};
2313
+ let state = "syncing";
2314
+ const transition = (next) => {
2315
+ if (next === state) {
2316
+ options.onStateChange?.(next);
2317
+ return;
2318
+ }
2319
+ const now = performance.now();
2320
+ const durationMs = now - phaseStartedAt;
2321
+ timings[state] = (timings[state] ?? 0) + durationMs;
2322
+ options.onPhaseTiming?.({
2323
+ durationMs,
2324
+ phase: state,
2325
+ totalMs: now - startedAt
2326
+ });
2327
+ state = next;
2328
+ phaseStartedAt = now;
2329
+ options.onStateChange?.(next);
2330
+ };
2331
+ let nativeLogs = null;
2332
+ const closeLogs = async () => {
2333
+ const stream = nativeLogs;
2334
+ nativeLogs = null;
2335
+ await stream?.close().catch(() => {
2336
+ return;
2337
+ });
2338
+ };
2339
+ try {
2340
+ await repairAbsoluteIosDevSession(project.projectRoot);
2341
+ throwIfAborted2(options.signal);
2342
+ transition("syncing");
2343
+ await requireSuccess2([project.cap, "sync", "ios"], "Capacitor iOS synchronization", run, { cwd: project.projectRoot, signal: options.signal });
2344
+ transition("configuring");
2345
+ await writeDevProjection(project, options.port, options.https === true);
2346
+ throwIfAborted2(options.signal);
2347
+ const fingerprintStartedAt = performance.now();
2348
+ const fingerprintPromise = fingerprintAbsoluteIosDevProject(project).then((fingerprint2) => {
2349
+ timings.fingerprinting = performance.now() - fingerprintStartedAt;
2350
+ return fingerprint2;
2351
+ });
2352
+ transition("booting");
2353
+ const { created, device } = await ensureManagedSimulator(project, capture);
2354
+ const startedSimulator = created || device.state !== "Booted";
2355
+ bootSimulator(project, device, capture);
2356
+ spawn([
2357
+ "open",
2358
+ "-a",
2359
+ "Simulator",
2360
+ "--args",
2361
+ "-CurrentDeviceUDID",
2362
+ device.udid
2363
+ ]);
2364
+ transition("connecting");
2365
+ await waitForBootedSimulator(project, device.udid, capture, sleep, options.signal);
2366
+ await requireSuccess2([project.xcrun, "simctl", "bootstatus", device.udid, "-b"], "iOS simulator boot readiness", run, { signal: options.signal });
2367
+ const fingerprint = await fingerprintPromise;
2368
+ transition("checking-native");
2369
+ const nativeCacheHit = await ensureIosDebugApp({
2370
+ cache: await readNativeCache(project.projectRoot),
2371
+ capture,
2372
+ fingerprint,
2373
+ log,
2374
+ project,
2375
+ run,
2376
+ signal: options.signal,
2377
+ transition,
2378
+ udid: device.udid
2379
+ });
2380
+ throwIfAborted2(options.signal);
2381
+ if (options.nativeLog)
2382
+ transition("streaming-logs");
2383
+ nativeLogs = attachNativeLogs(project, device.udid, options);
2384
+ transition("launching");
2385
+ await requireSuccess2([
2386
+ project.xcrun,
2387
+ "simctl",
2388
+ "launch",
2389
+ "--terminate-running-process",
2390
+ device.udid,
2391
+ project.config.appId
2392
+ ], "iOS app launch", run, { signal: options.signal });
2393
+ transition("ready");
2394
+ timings.total = performance.now() - startedAt;
2395
+ log(`iOS simulator connected (${device.udid}) with HMR on port ${options.port} in ${getDurationString(timings.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
2396
+ log(`iOS startup: ${timingSummary(timings)}.`);
2397
+ let closed = false;
2398
+ const close = async () => {
2399
+ if (closed)
2400
+ return;
2401
+ closed = true;
2402
+ transition("closing");
2403
+ await closeLogs();
2404
+ await repairAbsoluteIosDevSession(project.projectRoot);
2405
+ transition("closed");
2406
+ };
2407
+ return {
2408
+ close,
2409
+ nativeCacheHit,
2410
+ startedSimulator,
2411
+ timings: { ...timings },
2412
+ udid: device.udid,
2413
+ rebuild: async () => {
2414
+ if (closed)
2415
+ throw new Error("iOS development session is closed.");
2416
+ log("iOS native inputs changed; rebuilding without restarting the dev server.");
2417
+ await close();
2418
+ return startAbsoluteIosDevSession(options);
2419
+ },
2420
+ relaunch: async () => {
2421
+ if (closed)
2422
+ throw new Error("iOS development session is closed.");
2423
+ transition("launching");
2424
+ try {
2425
+ await requireSuccess2([
2426
+ project.xcrun,
2427
+ "simctl",
2428
+ "launch",
2429
+ "--terminate-running-process",
2430
+ device.udid,
2431
+ project.config.appId
2432
+ ], "iOS app relaunch", run, { signal: options.signal });
2433
+ transition("ready");
2434
+ log(`iOS app relaunched on ${device.udid}.`);
2435
+ } catch (error) {
2436
+ transition("failed");
2437
+ throw error;
2438
+ }
2439
+ },
2440
+ screenshot: async (destination) => {
2441
+ const resolved = resolve5(project.projectRoot, destination);
2442
+ if (!isInside(project.projectRoot, resolved))
2443
+ throw new Error("iOS screenshot destination must remain inside the project.");
2444
+ await mkdir5(dirname4(resolved), { recursive: true });
2445
+ await requireSuccess2([
2446
+ project.xcrun,
2447
+ "simctl",
2448
+ "io",
2449
+ device.udid,
2450
+ "screenshot",
2451
+ resolved
2452
+ ], "iOS simulator screenshot", run, { signal: options.signal });
2453
+ return resolved;
2454
+ },
2455
+ get state() {
2456
+ return state;
2457
+ }
2458
+ };
2459
+ } catch (error) {
2460
+ transition("failed");
2461
+ await closeLogs();
2462
+ await repairAbsoluteIosDevSession(project.projectRoot);
2463
+ throw error;
2464
+ }
2465
+ };
2466
+ // src/mobile/iosNativeWatcher.ts
2467
+ import { watch } from "fs";
2468
+ import { basename } from "path";
2469
+ var NATIVE_CHANGE_DEBOUNCE_MS = 500;
2470
+ var ROOT_NATIVE_INPUTS = new Set([
2471
+ "absolute.config.js",
2472
+ "absolute.config.mjs",
2473
+ "absolute.config.ts",
2474
+ "absolutejs.config.js",
2475
+ "absolutejs.config.mjs",
2476
+ "absolutejs.config.ts",
2477
+ "bun.lock",
2478
+ "bun.lockb",
2479
+ "capacitor.config.js",
2480
+ "capacitor.config.ts",
2481
+ "package.json"
2482
+ ]);
2483
+ var createAbsoluteIosNativeWatcher = async (options) => {
2484
+ let fingerprint = await fingerprintAbsoluteIosDevProject(options.project);
2485
+ let closed = false;
2486
+ let running = false;
2487
+ let timer;
2488
+ let rootInputChanged = false;
2489
+ const changedPaths = new Set;
2490
+ const watchers = [];
2491
+ const debounceMs = options.debounceMs ?? NATIVE_CHANGE_DEBOUNCE_MS;
2492
+ const close = () => {
2493
+ if (closed)
2494
+ return;
2495
+ closed = true;
2496
+ if (timer)
2497
+ clearTimeout(timer);
2498
+ watchers.forEach((watcher) => watcher.close());
2499
+ options.signal?.removeEventListener("abort", close);
2500
+ };
2501
+ const schedule = () => {
2502
+ if (closed || running)
2503
+ return;
2504
+ if (timer)
2505
+ clearTimeout(timer);
2506
+ timer = setTimeout(() => void flush(), debounceMs);
2507
+ };
2508
+ const flush = async () => {
2509
+ timer = undefined;
2510
+ if (closed || running || changedPaths.size === 0)
2511
+ return;
2512
+ running = true;
2513
+ const paths = [...changedPaths].sort();
2514
+ const forced = rootInputChanged;
2515
+ changedPaths.clear();
2516
+ rootInputChanged = false;
2517
+ try {
2518
+ const next = await fingerprintAbsoluteIosDevProject(options.project);
2519
+ if (!forced && next === fingerprint)
2520
+ return;
2521
+ await options.onChange({
2522
+ afterFingerprint: next,
2523
+ beforeFingerprint: fingerprint,
2524
+ paths,
2525
+ rootInputChanged: forced
2526
+ });
2527
+ fingerprint = await fingerprintAbsoluteIosDevProject(options.project);
2528
+ } catch (error) {
2529
+ options.onError?.(error);
2530
+ } finally {
2531
+ running = false;
2532
+ if (changedPaths.size > 0)
2533
+ schedule();
2534
+ }
2535
+ };
2536
+ const record = (path, force) => {
2537
+ if (closed)
2538
+ return;
2539
+ changedPaths.add(path);
2540
+ rootInputChanged ||= force;
2541
+ schedule();
2542
+ };
2543
+ watchers.push(watch(options.project.nativeDirectory, { recursive: true }, (_event, filename) => {
2544
+ if (filename)
2545
+ record(String(filename), false);
2546
+ }));
2547
+ watchers.push(watch(options.project.projectRoot, (_event, filename) => {
2548
+ if (!filename)
2549
+ return;
2550
+ const path = String(filename);
2551
+ if (isAbsoluteIosNativeRootInput(path))
2552
+ record(path, true);
2553
+ }));
2554
+ watchers.forEach((watcher) => watcher.on("error", (error) => options.onError?.(error)));
2555
+ options.signal?.addEventListener("abort", close, { once: true });
2556
+ return { close };
2557
+ };
2558
+ var isAbsoluteIosNativeRootInput = (path) => ROOT_NATIVE_INPUTS.has(basename(path));
2559
+ // src/mobile/remoteMacProtocol.ts
2560
+ import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
2561
+ import { chmod, mkdir as mkdir6, readFile as readFile8, rename as rename7, writeFile as writeFile7 } from "fs/promises";
2562
+ import { homedir as homedir2 } from "os";
2563
+ import {
2564
+ dirname as dirname5,
2565
+ isAbsolute as isAbsolute5,
2566
+ join as join7,
2567
+ posix,
2568
+ relative as relative6,
2569
+ resolve as resolvePath2,
2570
+ sep as sep5
2571
+ } from "path";
2572
+
2573
+ // src/mobile/remoteMacWire.ts
2574
+ var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t";
2575
+ var ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 1;
2576
+
2577
+ // src/mobile/remoteMacProtocol.ts
2578
+ var PROFILE_FORMAT = 1;
2579
+ var PROFILE_NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
2580
+ var SSH_DESTINATION = /^(?:[A-Za-z0-9._-]+@)?[A-Za-z0-9._:-]+$/u;
2581
+ var defaultProfilePath = () => join7(homedir2(), ".absolutejs", "mobile", "remote-macs.json");
2582
+ var emptyStore = () => ({
2583
+ format: PROFILE_FORMAT,
2584
+ profiles: {}
2585
+ });
2586
+ var loadStore = async (path = defaultProfilePath()) => {
2587
+ try {
2588
+ const parsed = JSON.parse(await readFile8(path, "utf8"));
2589
+ if (parsed.format !== PROFILE_FORMAT || typeof parsed.profiles !== "object" || parsed.profiles === null || Array.isArray(parsed.profiles))
2590
+ throw new Error("Unsupported remote Mac profile format.");
2591
+ for (const [key, profile] of Object.entries(parsed.profiles)) {
2592
+ if (typeof profile !== "object" || profile === null || validateAbsoluteRemoteMacProfileName(key) !== key || profile.name !== key || validateAbsoluteSshDestination(profile.destination) !== profile.destination || validatePort(profile.port) !== profile.port || typeof profile.createdAt !== "string" || !profile.createdAt || typeof profile.bunPath !== "string" || !profile.bunPath.startsWith("/") || /[\r\n\0]/u.test(profile.bunPath) || typeof profile.workspaceRoot !== "string" || !profile.workspaceRoot.startsWith("/") || profile.workspaceRoot === "/" || /[\r\n\0]/u.test(profile.workspaceRoot) || typeof profile.xcodeVersion !== "string" || !profile.xcodeVersion.startsWith("Xcode "))
2593
+ throw new Error(`Remote Mac profile ${JSON.stringify(key)} is invalid.`);
2594
+ }
2595
+ if (parsed.defaultProfile !== undefined && !parsed.profiles[parsed.defaultProfile])
2596
+ throw new Error("The default remote Mac profile does not exist.");
2597
+ return parsed;
2598
+ } catch (error) {
2599
+ if (error.code === "ENOENT")
2600
+ return emptyStore();
2601
+ throw error;
2602
+ }
2603
+ };
2604
+ var saveStore = async (store, path = defaultProfilePath()) => {
2605
+ await mkdir6(dirname5(path), { recursive: true });
2606
+ const temporary = `${path}.${randomUUID3()}.tmp`;
2607
+ await writeFile7(temporary, `${JSON.stringify(store, null, 2)}
2608
+ `, {
2609
+ mode: 384
2610
+ });
2611
+ await rename7(temporary, path);
2612
+ await chmod(path, 384);
2613
+ };
2614
+ var validateAbsoluteRemoteMacProfileName = (name) => {
2615
+ const normalized = name.trim().toLowerCase();
2616
+ if (!PROFILE_NAME.test(normalized))
2617
+ throw new TypeError("Remote Mac profile names must use 1-64 lowercase letters, digits, dots, dashes, or underscores.");
2618
+ return normalized;
2619
+ };
2620
+ var validateAbsoluteSshDestination = (destination) => {
2621
+ const normalized = destination.trim();
2622
+ if (!SSH_DESTINATION.test(normalized) || normalized.startsWith("-"))
2623
+ throw new TypeError("Remote Mac SSH destination must be a host, SSH alias, or user@host without command-line options.");
2624
+ return normalized;
2625
+ };
2626
+ var validatePort = (port) => {
2627
+ if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535))
2628
+ throw new TypeError("Remote Mac SSH port must be between 1 and 65535.");
2629
+ return port;
2630
+ };
2631
+ var shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
2632
+ var absoluteRemoteMacSshBase = (profile, options = {}) => [
2633
+ "ssh",
2634
+ "-o",
2635
+ "BatchMode=yes",
2636
+ "-o",
2637
+ "ConnectTimeout=10",
2638
+ "-o",
2639
+ "ServerAliveInterval=15",
2640
+ "-o",
2641
+ "ServerAliveCountMax=3",
2642
+ "-o",
2643
+ `StrictHostKeyChecking=${options.acceptNew ? "accept-new" : "yes"}`,
2644
+ ...profile.port ? ["-p", String(profile.port)] : [],
2645
+ profile.destination
2646
+ ];
2647
+ var localCapture = async (command) => {
2648
+ const process2 = Bun.spawn(command, {
2649
+ stderr: "pipe",
2650
+ stdin: "ignore",
2651
+ stdout: "pipe"
2652
+ });
2653
+ const [exitCode, stdout, stderr] = await Promise.all([
2654
+ process2.exited,
2655
+ new Response(process2.stdout).text(),
2656
+ new Response(process2.stderr).text()
2657
+ ]);
2658
+ return { exitCode, stderr, stdout };
2659
+ };
2660
+ var defaultTransport = {
2661
+ capture: localCapture,
2662
+ spawn: (command, options) => Bun.spawn(command, {
2663
+ signal: options.signal,
2664
+ stderr: "pipe",
2665
+ stdin: "pipe",
2666
+ stdout: "pipe"
2667
+ })
2668
+ };
2669
+ var requireRemoteSuccess = (result, label) => {
2670
+ if (result.exitCode !== 0)
2671
+ throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim() || `status ${result.exitCode}`}`);
2672
+ return result.stdout.trim();
2673
+ };
2674
+ var getAbsoluteRemoteMacProfile = async (name, profilePath) => {
2675
+ const store = await loadStore(profilePath);
2676
+ const selected = name ?? process.env.ABSOLUTE_IOS_REMOTE ?? store.defaultProfile;
2677
+ if (!selected)
2678
+ return;
2679
+ const profile = store.profiles[selected];
2680
+ if (!profile)
2681
+ throw new Error(`Remote Mac profile ${JSON.stringify(selected)} was not found.`);
2682
+ return profile;
2683
+ };
2684
+ var inspectAbsoluteRemoteMac = async (destination, options = {}) => {
2685
+ const profile = {
2686
+ destination: validateAbsoluteSshDestination(destination),
2687
+ port: validatePort(options.port)
2688
+ };
2689
+ const capture = options.transport?.capture ?? defaultTransport.capture;
2690
+ const command = [
2691
+ ...absoluteRemoteMacSshBase(profile, {
2692
+ acceptNew: options.acceptNew === true
2693
+ }),
2694
+ "/bin/sh -lc",
2695
+ shellQuote(`bun_path="$(command -v bun || true)"; if [ -z "$bun_path" ] && [ -x "$HOME/.bun/bin/bun" ]; then bun_path="$HOME/.bun/bin/bun"; fi; printf '%s\\n' "$(uname -s)" "$HOME" "$bun_path" "$(/usr/bin/xcodebuild -version 2>/dev/null | tr '\\n' ' ' || true)"`)
2696
+ ];
2697
+ const lines = requireRemoteSuccess(await capture(command), "Remote Mac handshake").split(/\r?\n/u);
2698
+ const [operatingSystem, home, bunPath, xcodeVersion] = lines;
2699
+ if (operatingSystem !== "Darwin")
2700
+ throw new Error("The SSH target is not a Mac.");
2701
+ if (!home?.startsWith("/") || !bunPath?.startsWith("/"))
2702
+ throw new Error("The remote Mac must have Bun installed and available to SSH.");
2703
+ if (!xcodeVersion?.startsWith("Xcode "))
2704
+ throw new Error("The remote Mac must have full Xcode installed and selected.");
2705
+ return { bunPath, home, os: operatingSystem, xcodeVersion };
2706
+ };
2707
+ var listAbsoluteRemoteMacProfiles = async (profilePath) => {
2708
+ const store = await loadStore(profilePath);
2709
+ return {
2710
+ defaultProfile: store.defaultProfile,
2711
+ profiles: Object.values(store.profiles).sort((left, right) => left.name.localeCompare(right.name))
2712
+ };
2713
+ };
2714
+ var pairAbsoluteRemoteMac = async (options) => {
2715
+ const name = validateAbsoluteRemoteMacProfileName(options.name);
2716
+ const destination = validateAbsoluteSshDestination(options.destination);
2717
+ const port = validatePort(options.port);
2718
+ const inspection = await inspectAbsoluteRemoteMac(destination, {
2719
+ acceptNew: true,
2720
+ port,
2721
+ transport: options.transport
2722
+ });
2723
+ const workspaceRoot = options.workspaceRoot ? options.workspaceRoot.trim() : posix.join(inspection.home, ".absolutejs", "remote-ios");
2724
+ if (!workspaceRoot.startsWith("/") || workspaceRoot === "/" || /[\r\n\0]/u.test(workspaceRoot))
2725
+ throw new TypeError("Remote Mac workspace must be an absolute macOS path.");
2726
+ const profile = {
2727
+ bunPath: inspection.bunPath,
2728
+ createdAt: new Date().toISOString(),
2729
+ destination,
2730
+ name,
2731
+ ...port ? { port } : {},
2732
+ workspaceRoot,
2733
+ xcodeVersion: inspection.xcodeVersion
2734
+ };
2735
+ const store = await loadStore(options.profilePath);
2736
+ store.profiles[name] = profile;
2737
+ store.defaultProfile = name;
2738
+ await saveStore(store, options.profilePath);
2739
+ return profile;
2740
+ };
2741
+ var removeAbsoluteRemoteMacProfile = async (name, profilePath) => {
2742
+ const normalized = validateAbsoluteRemoteMacProfileName(name);
2743
+ const store = await loadStore(profilePath);
2744
+ if (!store.profiles[normalized])
2745
+ return false;
2746
+ delete store.profiles[normalized];
2747
+ if (store.defaultProfile === normalized) {
2748
+ const [nextDefault] = Object.keys(store.profiles).sort();
2749
+ store.defaultProfile = nextDefault;
2750
+ }
2751
+ await saveStore(store, profilePath);
2752
+ return true;
2753
+ };
2754
+ var projectIdentity = (projectRoot, appId) => createHash7("sha256").update(`${resolvePath2(projectRoot)}\x00${appId}`).digest("hex").slice(0, 20);
2755
+ var createAbsoluteRemoteIosDevProject = (config, projectRoot, profile) => ({
2756
+ cap: join7(resolvePath2(projectRoot), "node_modules", ".bin", "cap"),
2757
+ config,
2758
+ nativeDirectory: join7(config.nativeProjectDirectory, "ios"),
2759
+ profile,
2760
+ projectRoot: resolvePath2(projectRoot),
2761
+ remote: true,
2762
+ remoteProjectRoot: posix.join(profile.workspaceRoot, "projects", projectIdentity(projectRoot, config.appId), "current"),
2763
+ xcodebuild: "remote:xcodebuild",
2764
+ xcrun: "remote:xcrun"
2765
+ });
2766
+ var installAbsoluteRemoteMacAgent = async (project) => {
2767
+ const artifact = await materializeAbsoluteRemoteMacAgent(project.projectRoot);
2768
+ const directory = posix.join(project.profile.workspaceRoot, "agents", `protocol-${ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION}`, artifact.sha256);
2769
+ const remotePath = posix.join(directory, "agent.js");
2770
+ const verifyScript = `test -f ${shellQuote(remotePath)} && ` + `test "$(shasum -a 256 ${shellQuote(remotePath)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`;
2771
+ const verified = await defaultTransport.capture([
2772
+ ...absoluteRemoteMacSshBase(project.profile),
2773
+ "/bin/sh -lc",
2774
+ shellQuote(verifyScript)
2775
+ ]);
2776
+ if (verified.exitCode === 0)
2777
+ return { ...artifact, remotePath, uploaded: false };
2778
+ const temporary = posix.join(directory, `.agent-${randomUUID3()}.tmp`);
2779
+ const installScript = [
2780
+ "set -eu",
2781
+ "umask 077",
2782
+ `mkdir -p ${shellQuote(directory)}`,
2783
+ `cat > ${shellQuote(temporary)}`,
2784
+ `test "$(shasum -a 256 ${shellQuote(temporary)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`,
2785
+ `chmod 600 ${shellQuote(temporary)}`,
2786
+ `mv ${shellQuote(temporary)} ${shellQuote(remotePath)}`
2787
+ ].join("; ");
2788
+ const upload = Bun.spawn([
2789
+ ...absoluteRemoteMacSshBase(project.profile),
2790
+ "/bin/sh -lc",
2791
+ shellQuote(installScript)
2792
+ ], {
2793
+ stderr: "pipe",
2794
+ stdin: Bun.file(artifact.path),
2795
+ stdout: "pipe"
2796
+ });
2797
+ const [exitCode, stderr] = await Promise.all([
2798
+ upload.exited,
2799
+ new Response(upload.stderr).text()
2800
+ ]);
2801
+ if (exitCode !== 0)
2802
+ throw new Error(`Remote Mac agent installation failed: ${stderr.trim() || `status ${exitCode}`}`);
2803
+ return { ...artifact, remotePath, uploaded: true };
2804
+ };
2805
+ var materializeAbsoluteRemoteMacAgent = async (projectRoot) => {
2806
+ const shippedCandidates = [
2807
+ join7(import.meta.dir, "remoteMacAgentEntry.js"),
2808
+ join7(import.meta.dir, "..", "mobile", "remoteMacAgentEntry.js")
2809
+ ];
2810
+ let path;
2811
+ for (const candidate of shippedCandidates) {
2812
+ if (await Bun.file(candidate).exists()) {
2813
+ path = candidate;
2814
+ break;
2815
+ }
1070
2816
  }
1071
- const [bytes, sha256] = await Promise.all([
1072
- stat(artifactPath).then(({ size }) => size),
1073
- sha256File(artifactPath)
2817
+ if (!path) {
2818
+ const sourceCandidates = [
2819
+ join7(import.meta.dir, "remoteMacAgentEntry.ts"),
2820
+ join7(import.meta.dir, "..", "..", "src", "mobile", "remoteMacAgentEntry.ts")
2821
+ ];
2822
+ const source = await sourceCandidates.reduce(async (found, candidate) => await found ?? (await Bun.file(candidate).exists() ? candidate : undefined), Promise.resolve(undefined));
2823
+ if (!source)
2824
+ throw new Error("The AbsoluteJS installation does not contain its remote Mac agent artifact.");
2825
+ const outdir = join7(resolvePath2(projectRoot), ".absolutejs", "mobile", "remote-agent");
2826
+ await mkdir6(outdir, { recursive: true });
2827
+ const result = await Bun.build({
2828
+ entrypoints: [source],
2829
+ minify: true,
2830
+ outdir,
2831
+ target: "bun"
2832
+ });
2833
+ if (!result.success)
2834
+ throw new AggregateError(result.logs, "Failed to build the AbsoluteJS remote Mac agent.");
2835
+ path = join7(outdir, "remoteMacAgentEntry.js");
2836
+ }
2837
+ const bytes = await Bun.file(path).arrayBuffer();
2838
+ const sha256 = createHash7("sha256").update(new Uint8Array(bytes)).digest("hex");
2839
+ return { bytes: bytes.byteLength, path, sha256 };
2840
+ };
2841
+ var portableRelativePath = (root, path) => relative6(root, path).split(sep5).join(posix.sep);
2842
+ var portableMobileConfig = (project) => ({
2843
+ appId: project.config.appId,
2844
+ appName: project.config.appName,
2845
+ bundleDirectory: portableRelativePath(project.projectRoot, project.config.bundleDirectory),
2846
+ ...project.config.deepLinkScheme || project.config.deepLinkHosts.length > 1 || project.config.appleAppIdPrefix ? {
2847
+ deepLinks: {
2848
+ ...project.config.deepLinkScheme ? { scheme: project.config.deepLinkScheme } : {},
2849
+ hosts: project.config.deepLinkHosts,
2850
+ ...project.config.appleAppIdPrefix ? {
2851
+ apple: {
2852
+ appIdPrefix: project.config.appleAppIdPrefix
2853
+ }
2854
+ } : {}
2855
+ }
2856
+ } : {},
2857
+ entry: project.config.entry,
2858
+ ...project.config.iosVersion ? { ios: { version: project.config.iosVersion } } : {},
2859
+ nativeProject: {
2860
+ directory: portableRelativePath(project.projectRoot, project.config.nativeProjectDirectory),
2861
+ mode: "source"
2862
+ },
2863
+ platforms: ["ios"],
2864
+ server: { productionOrigin: project.config.productionOrigin }
2865
+ });
2866
+ var absoluteRemoteProjectSyncCommands = (project) => {
2867
+ const current = project.remoteProjectRoot;
2868
+ const parent = posix.dirname(current);
2869
+ const staging = posix.join(parent, `.incoming-${randomUUID3()}`);
2870
+ const previous = posix.join(parent, ".previous");
2871
+ const script = [
2872
+ "set -eu",
2873
+ `mkdir -p ${shellQuote(staging)}`,
2874
+ `tar -xf - -C ${shellQuote(staging)}`,
2875
+ `if [ -d ${shellQuote(posix.join(current, "node_modules"))} ]; then mv ${shellQuote(posix.join(current, "node_modules"))} ${shellQuote(posix.join(staging, "node_modules"))}; fi`,
2876
+ `if [ -d ${shellQuote(posix.join(current, ".absolutejs"))} ]; then mv ${shellQuote(posix.join(current, ".absolutejs"))} ${shellQuote(posix.join(staging, ".absolutejs"))}; fi`,
2877
+ `rm -rf ${shellQuote(previous)}`,
2878
+ `if [ -d ${shellQuote(current)} ]; then mv ${shellQuote(current)} ${shellQuote(previous)}; fi`,
2879
+ `mv ${shellQuote(staging)} ${shellQuote(current)}`,
2880
+ `rm -rf ${shellQuote(previous)}`
2881
+ ].join("; ");
2882
+ return {
2883
+ remote: [
2884
+ ...absoluteRemoteMacSshBase(project.profile),
2885
+ "/bin/sh -lc",
2886
+ shellQuote(script)
2887
+ ],
2888
+ tar: [
2889
+ "tar",
2890
+ "--exclude=.git",
2891
+ "--exclude=node_modules",
2892
+ "--exclude=build",
2893
+ "--exclude=.absolutejs",
2894
+ "-cf",
2895
+ "-",
2896
+ "-C",
2897
+ project.projectRoot,
2898
+ "."
2899
+ ]
2900
+ };
2901
+ };
2902
+ var syncAbsoluteRemoteMacProject = async (project) => {
2903
+ const commands = absoluteRemoteProjectSyncCommands(project);
2904
+ const archive = Bun.spawn(commands.tar, {
2905
+ stderr: "pipe",
2906
+ stdout: "pipe"
2907
+ });
2908
+ const upload = Bun.spawn(commands.remote, {
2909
+ stderr: "pipe",
2910
+ stdin: archive.stdout,
2911
+ stdout: "pipe"
2912
+ });
2913
+ const [archiveExit, uploadExit, archiveError, uploadError] = await Promise.all([
2914
+ archive.exited,
2915
+ upload.exited,
2916
+ new Response(archive.stderr).text(),
2917
+ new Response(upload.stderr).text()
1074
2918
  ]);
1075
- const releaseId = `amobile_android_${sha256}`;
1076
- const metadata = {
1077
- appBuild: manifest.appBuild,
1078
- appId: manifest.appId,
1079
- bytes,
1080
- engine: "capacitor",
1081
- format: ABSOLUTE_ANDROID_RELEASE_FORMAT,
1082
- platform: "android",
1083
- releaseId,
1084
- runtime: manifest.runtime,
1085
- sha256,
1086
- signed: signed === true,
1087
- type: "aab"
2919
+ if (archiveExit !== 0 || uploadExit !== 0)
2920
+ throw new Error(`Remote Mac project synchronization failed: ${(archiveError || uploadError).trim()}`);
2921
+ const install = await defaultTransport.capture([
2922
+ ...absoluteRemoteMacSshBase(project.profile),
2923
+ "/bin/sh -lc",
2924
+ shellQuote(`cd ${shellQuote(project.remoteProjectRoot)} && ${shellQuote(project.profile.bunPath)} install --frozen-lockfile`)
2925
+ ]);
2926
+ requireRemoteSuccess(install, "Remote Mac dependency installation");
2927
+ };
2928
+ var consumeLines2 = async (stream, onLine) => {
2929
+ const reader = stream.getReader();
2930
+ const decoder = new TextDecoder;
2931
+ let buffered = "";
2932
+ try {
2933
+ while (true) {
2934
+ const { done, value } = await reader.read();
2935
+ if (done)
2936
+ break;
2937
+ buffered += decoder.decode(value, { stream: true });
2938
+ const lines = buffered.split(/\r?\n/u);
2939
+ buffered = lines.pop() ?? "";
2940
+ lines.forEach(onLine);
2941
+ }
2942
+ buffered += decoder.decode();
2943
+ if (buffered)
2944
+ onLine(buffered);
2945
+ } finally {
2946
+ reader.releaseLock();
2947
+ }
2948
+ };
2949
+ var startAbsoluteRemoteIosDevSession = async (options) => {
2950
+ const startedAt = performance.now();
2951
+ const transport = options.transport ?? defaultTransport;
2952
+ const installAgent = options.installAgent ?? installAbsoluteRemoteMacAgent;
2953
+ const syncProject = options.syncProject ?? syncAbsoluteRemoteMacProject;
2954
+ const agentStartedAt = performance.now();
2955
+ const agent = await installAgent(options.project);
2956
+ const agentDuration = performance.now() - agentStartedAt;
2957
+ const syncStartedAt = performance.now();
2958
+ await syncProject(options.project);
2959
+ const syncDuration = performance.now() - syncStartedAt;
2960
+ const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
2961
+ const remoteCommand = [
2962
+ `cd ${shellQuote(options.project.remoteProjectRoot)}`,
2963
+ "&&",
2964
+ "exec",
2965
+ shellQuote(options.project.profile.bunPath),
2966
+ shellQuote(agent.remotePath),
2967
+ "--port",
2968
+ String(options.port),
2969
+ "--mobile-config",
2970
+ shellQuote(encodedConfig),
2971
+ ...options.https ? ["--https"] : []
2972
+ ].join(" ");
2973
+ const command = [
2974
+ ...absoluteRemoteMacSshBase(options.project.profile),
2975
+ "-o",
2976
+ "ExitOnForwardFailure=yes",
2977
+ "-R",
2978
+ `${options.port}:127.0.0.1:${options.port}`,
2979
+ "/bin/sh -lc",
2980
+ shellQuote(remoteCommand)
2981
+ ];
2982
+ const connectStartedAt = performance.now();
2983
+ const process2 = transport.spawn(command, { signal: options.signal });
2984
+ let state = "syncing";
2985
+ let ready;
2986
+ let fatal;
2987
+ const pending = new Map;
2988
+ let resolveReady;
2989
+ let rejectReady;
2990
+ const readyPromise = new Promise((resolve6, reject) => {
2991
+ resolveReady = resolve6;
2992
+ rejectReady = reject;
2993
+ });
2994
+ const handleEvent = (event) => {
2995
+ if (event.v !== ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION) {
2996
+ rejectReady(new Error("Remote Mac protocol version mismatch."));
2997
+ return;
2998
+ }
2999
+ if (event.type === "log")
3000
+ options.log?.(event.message);
3001
+ if (event.type === "native-log")
3002
+ options.nativeLog?.(event.entry);
3003
+ if (event.type === "state") {
3004
+ ({ state } = event);
3005
+ options.onStateChange?.(state);
3006
+ }
3007
+ if (event.type === "timing")
3008
+ options.onPhaseTiming?.(event);
3009
+ if (event.type === "ready") {
3010
+ ready = event;
3011
+ resolveReady();
3012
+ }
3013
+ if (event.type === "fatal") {
3014
+ fatal = new Error(event.error);
3015
+ rejectReady(fatal);
3016
+ }
3017
+ if (event.type === "response") {
3018
+ const request2 = pending.get(event.id);
3019
+ if (!request2)
3020
+ return;
3021
+ pending.delete(event.id);
3022
+ if (event.ok)
3023
+ request2.resolve(event.result);
3024
+ else
3025
+ request2.reject(new Error(event.error ?? "Remote command failed."));
3026
+ }
1088
3027
  };
1089
- return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
3028
+ const stdoutDone = consumeLines2(process2.stdout, (line) => {
3029
+ if (!line.startsWith(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX))
3030
+ return;
3031
+ try {
3032
+ handleEvent(JSON.parse(line.slice(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX.length)));
3033
+ } catch {
3034
+ options.log?.(`Remote Mac emitted an invalid protocol event.`);
3035
+ }
3036
+ }).catch((error) => {
3037
+ fatal = error instanceof Error ? error : new Error("Failed to read the remote Mac protocol stream.");
3038
+ rejectReady(fatal);
3039
+ });
3040
+ const stderrDone = consumeLines2(process2.stderr, (line) => options.log?.(`[remote] ${line}`)).catch((error) => options.log?.(`[remote] ${error instanceof Error ? error.message : "Failed to read SSH stderr."}`));
3041
+ process2.exited.then(async (exitCode) => {
3042
+ await Promise.all([stdoutDone, stderrDone]);
3043
+ const error = fatal ?? new Error(`Remote Mac connection closed with status ${exitCode}.`);
3044
+ if (!ready)
3045
+ rejectReady(error);
3046
+ pending.forEach(({ reject }) => reject(error));
3047
+ pending.clear();
3048
+ return;
3049
+ });
3050
+ await readyPromise;
3051
+ if (!ready)
3052
+ throw fatal ?? new Error("Remote Mac did not become ready.");
3053
+ const totalDuration = performance.now() - startedAt;
3054
+ let currentReady = {
3055
+ ...ready,
3056
+ timings: {
3057
+ ...ready.timings,
3058
+ "remote-agent": agentDuration,
3059
+ "remote-connect": performance.now() - connectStartedAt,
3060
+ "remote-sync": syncDuration,
3061
+ total: totalDuration
3062
+ }
3063
+ };
3064
+ options.log?.(`Remote Mac connected (${options.project.profile.name}); agent ${agent.uploaded ? "uploaded" : "cache hit"}, project synced, and iOS ready in ${totalDuration.toFixed(2)}ms.`);
3065
+ const request = (commandName) => {
3066
+ const id = randomUUID3();
3067
+ const response = new Promise((resolve6, reject) => pending.set(id, { reject, resolve: resolve6 }));
3068
+ process2.stdin.write(`${JSON.stringify({ command: commandName, id, v: 1 })}
3069
+ `);
3070
+ process2.stdin.flush();
3071
+ return response;
3072
+ };
3073
+ let closed = false;
3074
+ const close = async () => {
3075
+ if (closed)
3076
+ return;
3077
+ closed = true;
3078
+ await request("close").catch(() => {
3079
+ return;
3080
+ });
3081
+ process2.stdin.end();
3082
+ await process2.exited.catch(() => {
3083
+ return;
3084
+ });
3085
+ };
3086
+ const makeSession = () => ({
3087
+ close,
3088
+ nativeCacheHit: currentReady.nativeCacheHit,
3089
+ startedSimulator: currentReady.startedSimulator,
3090
+ timings: currentReady.timings,
3091
+ udid: currentReady.udid,
3092
+ rebuild: async () => {
3093
+ const rebuildStartedAt = performance.now();
3094
+ const rebuildSyncStartedAt = performance.now();
3095
+ await syncProject(options.project);
3096
+ const rebuildSyncDuration = performance.now() - rebuildSyncStartedAt;
3097
+ const result = await request("rebuild");
3098
+ currentReady = {
3099
+ ...result,
3100
+ timings: {
3101
+ ...result.timings,
3102
+ "remote-sync": rebuildSyncDuration,
3103
+ total: performance.now() - rebuildStartedAt
3104
+ }
3105
+ };
3106
+ return makeSession();
3107
+ },
3108
+ relaunch: async () => {
3109
+ await request("relaunch");
3110
+ },
3111
+ screenshot: async (destination) => {
3112
+ const result = await request("screenshot");
3113
+ const target = resolvePath2(options.project.projectRoot, destination);
3114
+ const targetRelative = relative6(options.project.projectRoot, target);
3115
+ if (targetRelative.startsWith("..") || isAbsolute5(targetRelative))
3116
+ throw new Error("iOS screenshot must remain inside the project.");
3117
+ await mkdir6(dirname5(target), { recursive: true });
3118
+ await writeFile7(target, Buffer.from(result.data, "base64"));
3119
+ return target;
3120
+ },
3121
+ get state() {
3122
+ return state;
3123
+ }
3124
+ });
3125
+ return makeSession();
1090
3126
  };
1091
3127
  // src/mobile/associationFiles.ts
1092
3128
  import {
1093
- access as access4,
1094
- mkdir as mkdir4,
1095
- readFile as readFile5,
1096
- rename as rename5,
1097
- rm as rm4,
1098
- writeFile as writeFile5
3129
+ access as access7,
3130
+ mkdir as mkdir7,
3131
+ readFile as readFile9,
3132
+ rename as rename8,
3133
+ rm as rm6,
3134
+ writeFile as writeFile8
1099
3135
  } from "fs/promises";
1100
- import { resolve as resolve5 } from "path";
3136
+ import { resolve as resolve7 } from "path";
1101
3137
  import { Elysia } from "elysia";
1102
3138
 
1103
3139
  // src/mobile/config.ts
1104
- import { resolve as resolve4 } from "path";
3140
+ import { resolve as resolve6 } from "path";
1105
3141
  var APP_ID_PATTERN = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
1106
3142
  var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
1107
3143
  var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
1108
3144
  var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
1109
3145
  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
3146
  var resolveProjectPath = (projectRoot, value, field) => {
1111
- const root = resolve4(projectRoot);
1112
- const path = resolve4(root, value);
3147
+ const root = resolve6(projectRoot);
3148
+ const path = resolve6(root, value);
1113
3149
  if (path !== root && !path.startsWith(`${root}/`)) {
1114
3150
  throw new TypeError(`${field} must remain inside the project root.`);
1115
3151
  }
@@ -1130,8 +3166,9 @@ var normalizeEntry = (entry) => {
1130
3166
  };
1131
3167
  var normalizeProductionOrigin = (value) => {
1132
3168
  const parsed = new URL(requireText(value, "mobile.server.productionOrigin"));
1133
- if (parsed.protocol !== "https:") {
1134
- throw new TypeError("mobile.server.productionOrigin must use HTTPS in production.");
3169
+ const isLoopbackHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
3170
+ if (parsed.protocol !== "https:" && !isLoopbackHttp) {
3171
+ throw new TypeError("mobile.server.productionOrigin must use HTTPS, except for a loopback development origin.");
1135
3172
  }
1136
3173
  if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
1137
3174
  throw new TypeError("mobile.server.productionOrigin must be an origin without credentials, path, query, or hash.");
@@ -1155,9 +3192,8 @@ var normalizeHosts = (hosts, productionOrigin) => {
1155
3192
  }
1156
3193
  return value;
1157
3194
  };
1158
- const normalized = new Set([
1159
- normalizeHostname(new URL(productionOrigin).hostname)
1160
- ]);
3195
+ const productionHostname = new URL(productionOrigin).hostname;
3196
+ const normalized = new Set(productionHostname === "[::1]" ? [] : [normalizeHostname(productionHostname)]);
1161
3197
  for (const host of hosts ?? []) {
1162
3198
  normalized.add(normalizeHostname(host));
1163
3199
  }
@@ -1172,6 +3208,15 @@ var normalizeAppleAppIdPrefix = (value) => {
1172
3208
  }
1173
3209
  return normalized;
1174
3210
  };
3211
+ var normalizeIosVersion = (value) => {
3212
+ if (value === undefined)
3213
+ return;
3214
+ const normalized = requireText(value, "mobile.ios.version");
3215
+ if (!/^\d+(?:\.\d+){0,2}$/u.test(normalized)) {
3216
+ throw new TypeError("mobile.ios.version must contain one to three dot-separated integer components, for example 1.4.0.");
3217
+ }
3218
+ return normalized;
3219
+ };
1175
3220
  var normalizeCertificateFingerprints = (values) => [
1176
3221
  ...new Set((values ?? []).map((value) => requireText(value, "mobile.deepLinks.android.sha256CertificateFingerprints").replaceAll(":", "").toUpperCase()).map((value) => {
1177
3222
  if (!CERTIFICATE_FINGERPRINT_PATTERN.test(value)) {
@@ -1186,7 +3231,7 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
1186
3231
  throw new TypeError("mobile.appId must use reverse-domain notation, for example com.example.app.");
1187
3232
  }
1188
3233
  const productionOrigin = normalizeProductionOrigin(config.server.productionOrigin);
1189
- const deepLinkScheme = config.deepLinks?.scheme?.trim().toLowerCase();
3234
+ const deepLinkScheme = (config.deepLinks?.scheme ?? appId).trim().toLowerCase();
1190
3235
  if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
1191
3236
  throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
1192
3237
  }
@@ -1200,6 +3245,7 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
1200
3245
  deepLinkScheme,
1201
3246
  engine: "capacitor",
1202
3247
  entry: normalizeEntry(config.entry),
3248
+ iosVersion: normalizeIosVersion(config.ios?.version),
1203
3249
  nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? "mobile", "mobile.nativeProject.directory"),
1204
3250
  platforms: normalizePlatforms(config.platforms),
1205
3251
  productionOrigin
@@ -1287,7 +3333,7 @@ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {})
1287
3333
  var writeAtomic = async (path, source) => {
1288
3334
  let current;
1289
3335
  try {
1290
- current = await readFile5(path, "utf8");
3336
+ current = await readFile9(path, "utf8");
1291
3337
  } catch (error) {
1292
3338
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
1293
3339
  throw error;
@@ -1296,23 +3342,23 @@ var writeAtomic = async (path, source) => {
1296
3342
  if (current === source)
1297
3343
  return false;
1298
3344
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
1299
- await writeFile5(temporary, source, { flag: "wx" });
1300
- await rename5(temporary, path);
3345
+ await writeFile8(temporary, source, { flag: "wx" });
3346
+ await rename8(temporary, path);
1301
3347
  return true;
1302
3348
  };
1303
3349
  var exists2 = async (path) => {
1304
3350
  try {
1305
- await access4(path);
3351
+ await access7(path);
1306
3352
  return true;
1307
3353
  } catch {
1308
3354
  return false;
1309
3355
  }
1310
3356
  };
1311
3357
  var assertOwnedOutput = async (root) => {
1312
- const path = resolve5(root, OWNERSHIP_FILE);
3358
+ const path = resolve7(root, OWNERSHIP_FILE);
1313
3359
  let ownership;
1314
3360
  try {
1315
- ownership = JSON.parse(await readFile5(path, "utf8"));
3361
+ ownership = JSON.parse(await readFile9(path, "utf8"));
1316
3362
  } catch {
1317
3363
  throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
1318
3364
  }
@@ -1326,22 +3372,22 @@ var publishGeneratedDirectory = async (temporary, root) => {
1326
3372
  await assertOwnedOutput(root);
1327
3373
  const backup = `${root}.${crypto.randomUUID()}.previous`;
1328
3374
  if (hasCurrent)
1329
- await rename5(root, backup);
3375
+ await rename8(root, backup);
1330
3376
  try {
1331
- await rename5(temporary, root);
3377
+ await rename8(temporary, root);
1332
3378
  } catch (error) {
1333
3379
  if (hasCurrent)
1334
- await rename5(backup, root);
3380
+ await rename8(backup, root);
1335
3381
  throw error;
1336
3382
  }
1337
3383
  if (hasCurrent)
1338
- await rm4(backup, { force: true, recursive: true });
3384
+ await rm6(backup, { force: true, recursive: true });
1339
3385
  };
1340
3386
  var materializeHost = async (root, host, files) => {
1341
- const directory = resolve5(root, host, ".well-known");
1342
- await mkdir4(directory, { recursive: true });
3387
+ const directory = resolve7(root, host, ".well-known");
3388
+ await mkdir7(directory, { recursive: true });
1343
3389
  return Promise.all(files.map(async ([name, document]) => {
1344
- const path = resolve5(directory, name);
3390
+ const path = resolve7(directory, name);
1345
3391
  await writeAtomic(path, `${JSON.stringify(document, null, 2)}
1346
3392
  `);
1347
3393
  return path;
@@ -1366,7 +3412,7 @@ var associationEndpoints = (config, documents) => config.deepLinkHosts.flatMap((
1366
3412
  return endpoints;
1367
3413
  });
1368
3414
  var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory) => {
1369
- const root = resolve5(outputDirectory);
3415
+ const root = resolve7(outputDirectory);
1370
3416
  const temporary = `${root}.${crypto.randomUUID()}.tmp`;
1371
3417
  const documents = createAbsoluteMobileAssociationDocuments(config, {
1372
3418
  requireAll: true
@@ -1377,16 +3423,16 @@ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory)
1377
3423
  if (documents.apple) {
1378
3424
  files.push(["apple-app-site-association", documents.apple]);
1379
3425
  }
1380
- await mkdir4(temporary, { recursive: true });
3426
+ await mkdir7(temporary, { recursive: true });
1381
3427
  try {
1382
3428
  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)}
3429
+ await writeAtomic(resolve7(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
1384
3430
  `);
1385
3431
  await publishGeneratedDirectory(temporary, root);
1386
- const written = temporaryPaths.map((path) => resolve5(root, path.slice(temporary.length + 1)));
3432
+ const written = temporaryPaths.map((path) => resolve7(root, path.slice(temporary.length + 1)));
1387
3433
  return { root, written };
1388
3434
  } catch (error) {
1389
- await rm4(temporary, { force: true, recursive: true });
3435
+ await rm6(temporary, { force: true, recursive: true });
1390
3436
  throw error;
1391
3437
  }
1392
3438
  };
@@ -1431,10 +3477,10 @@ var frameworks2 = new Set([
1431
3477
  "svelte",
1432
3478
  "vue"
1433
3479
  ]);
1434
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3480
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1435
3481
  var isPageFramework2 = (value) => typeof value === "string" && frameworks2.has(value);
1436
3482
  var parseAbsoluteMobileBuildPageMetadata = (value) => {
1437
- if (!isRecord2(value))
3483
+ if (!isRecord4(value))
1438
3484
  return;
1439
3485
  if (typeof value.bundleKey !== "string" || typeof value.contract !== "string" || !isPageFramework2(value.framework) || typeof value.pageId !== "string" || typeof value.propsSchemaHash !== "string") {
1440
3486
  return;
@@ -1448,45 +3494,75 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
1448
3494
  };
1449
3495
  };
1450
3496
  // src/mobile/buildPipeline.ts
1451
- import { readFile as readFile9 } from "fs/promises";
1452
- import { join as join8, resolve as resolve8 } from "path";
3497
+ import { readFile as readFile13 } from "fs/promises";
3498
+ import { join as join12, resolve as resolve10 } from "path";
1453
3499
  import { pathToFileURL as pathToFileURL2 } from "url";
1454
3500
 
1455
3501
  // 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");
3502
+ import { createHash as createHash8 } from "crypto";
3503
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
3504
+ import { basename as basename2, dirname as dirname6, extname, join as join8, relative as relative7, resolve as resolve8 } from "path";
3505
+ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
3506
+ var STATIC_SCRIPT_PATTERN = /(<script\b[^>]*?\bsrc\s*=\s*["'])(\/[^"']+\.(?:js|ts))(["'][^>]*>)/giu;
3507
+ var rewriteStaticScriptPaths = (source, manifest) => source.replace(STATIC_SCRIPT_PATTERN, (match, prefix, path, suffix) => {
3508
+ if (path.endsWith("/htmx.min.js"))
3509
+ return match;
3510
+ const key = toPascal(basename2(path, extname(path)));
3511
+ const builtPath = manifest[key];
3512
+ return builtPath ? `${prefix}${builtPath}${suffix}` : match;
3513
+ });
1460
3514
  var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
1461
3515
  var resolveAssetPath = (buildDirectory, assetPath) => {
1462
- const resolvedBuildDirectory = resolve6(buildDirectory);
1463
- const resolvedAsset = resolve6(assetPath);
3516
+ const resolvedBuildDirectory = resolve8(buildDirectory);
3517
+ const resolvedAsset = resolve8(assetPath);
1464
3518
  if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
1465
3519
  return resolvedAsset;
1466
3520
  }
1467
- return join5(buildDirectory, assetPath.replace(/^\/+/, ""));
3521
+ return join8(buildDirectory, assetPath.replace(/^\/+/, ""));
1468
3522
  };
1469
3523
  var pageFor = async (metadata, manifest, buildDirectory) => {
1470
3524
  const assetPath = manifest[metadata.bundleKey];
1471
3525
  if (!assetPath) {
1472
3526
  throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
1473
3527
  }
1474
- const resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
1475
- const bytes = await readFile6(resolvedAssetPath);
1476
- const bundlePath = `/${relative4(resolve6(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
3528
+ let resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
3529
+ if (metadata.framework === "html" || metadata.framework === "htmx") {
3530
+ const source = await readFile10(resolvedAssetPath, "utf8");
3531
+ const rewritten = rewriteStaticScriptPaths(source, manifest);
3532
+ const documentHash = sha256(new TextEncoder().encode(rewritten));
3533
+ resolvedAssetPath = join8(buildDirectory, ".absolutejs", "mobile-pages", `${documentHash}.html`);
3534
+ await mkdir8(dirname6(resolvedAssetPath), { recursive: true });
3535
+ await writeFile9(resolvedAssetPath, rewritten);
3536
+ }
3537
+ const pageAssetKey = metadata.bundleKey.replace(/Index$/u, "");
3538
+ const styleAssetPath = [
3539
+ `${pageAssetKey}BundledCSS`,
3540
+ `${pageAssetKey}CompiledCSS`
3541
+ ].map((key) => manifest[key]).find((path) => typeof path === "string");
3542
+ const resolvedStylePath = styleAssetPath ? resolveAssetPath(buildDirectory, styleAssetPath) : undefined;
3543
+ const [bytes, styleBytes] = await Promise.all([
3544
+ readFile10(resolvedAssetPath),
3545
+ resolvedStylePath ? readFile10(resolvedStylePath) : undefined
3546
+ ]);
3547
+ const bundlePath = `/${relative7(resolve8(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
3548
+ const styleBundlePath = resolvedStylePath ? `/${relative7(resolve8(buildDirectory), resolvedStylePath).replaceAll("\\", "/")}` : undefined;
1477
3549
  return {
1478
3550
  bundleHash: sha256(bytes),
1479
3551
  bundlePath,
1480
3552
  contract: metadata.contract,
1481
3553
  framework: metadata.framework,
1482
3554
  pageId: metadata.pageId,
1483
- propsSchemaHash: metadata.propsSchemaHash
3555
+ propsSchemaHash: metadata.propsSchemaHash,
3556
+ ...styleBytes && styleBundlePath ? {
3557
+ styleBundleHash: sha256(styleBytes),
3558
+ styleBundlePath
3559
+ } : {}
1484
3560
  };
1485
3561
  };
1486
3562
  var buildAbsoluteMobileCompatibilityRelease = async (options) => {
1487
3563
  const [captured, producerBytes] = await Promise.all([
1488
3564
  captureAbsoluteMobileRouteGraph(options.app),
1489
- readFile6(options.producerPath)
3565
+ readFile10(options.producerPath)
1490
3566
  ]);
1491
3567
  if (captured.length === 0) {
1492
3568
  throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
@@ -1502,11 +3578,19 @@ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
1502
3578
  const pages = await Promise.all([...metadataByPage.values()].map((metadata) => pageFor(metadata, options.manifest, options.buildDirectory)));
1503
3579
  const producerHash = sha256(producerBytes);
1504
3580
  const appBuild = `ambuild_${sha256(new TextEncoder().encode(JSON.stringify({
1505
- pages: pages.map(({ bundleHash, bundlePath, contract, pageId }) => ({
3581
+ pages: pages.map(({
3582
+ bundleHash,
3583
+ bundlePath,
3584
+ contract,
3585
+ pageId,
3586
+ styleBundleHash,
3587
+ styleBundlePath
3588
+ }) => ({
1506
3589
  bundleHash,
1507
3590
  bundlePath,
1508
3591
  contract,
1509
- pageId
3592
+ pageId,
3593
+ ...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
1510
3594
  })),
1511
3595
  producerHash,
1512
3596
  runtime: options.runtime
@@ -1566,16 +3650,17 @@ var captureAbsoluteMobileRouteGraph = async (app) => {
1566
3650
 
1567
3651
  // src/mobile/capacitorBundle.ts
1568
3652
  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
3653
+ cp,
3654
+ copyFile as copyFile5,
3655
+ mkdir as mkdir9,
3656
+ mkdtemp as mkdtemp4,
3657
+ readFile as readFile11,
3658
+ rename as rename9,
3659
+ rm as rm7,
3660
+ writeFile as writeFile10
1576
3661
  } from "fs/promises";
1577
3662
  import { existsSync as existsSync2 } from "fs";
1578
- import { basename, dirname as dirname3, extname, join as join6, resolve as resolve7 } from "path";
3663
+ import { basename as basename3, dirname as dirname7, extname as extname2, join as join9, relative as relative8, resolve as resolve9 } from "path";
1579
3664
 
1580
3665
  // src/mobile/routeMatcher.ts
1581
3666
  var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
@@ -1731,14 +3816,14 @@ var envelopeResponse = (response, status) => new Response(JSON.stringify({
1731
3816
  protocol: ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
1732
3817
  response
1733
3818
  }), { headers: responseHeaders(), status });
1734
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3819
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1735
3820
  var normalizeJsonValue = (value) => {
1736
3821
  const serialized = JSON.stringify(value);
1737
3822
  if (serialized === undefined) {
1738
3823
  throw new TypeError("Mobile page props must be JSON-serializable.");
1739
3824
  }
1740
3825
  const parsed = JSON.parse(serialized);
1741
- if (!isRecord3(parsed)) {
3826
+ if (!isRecord5(parsed)) {
1742
3827
  throw new TypeError("Mobile page props must serialize to an object.");
1743
3828
  }
1744
3829
  return parsed;
@@ -1848,6 +3933,13 @@ class AbsoluteMobilePageProtocolError extends Error {
1848
3933
  this.code = code;
1849
3934
  }
1850
3935
  }
3936
+ var disposeAbsoluteMobilePage = async (target = window) => {
3937
+ const dispose = target.__ABSOLUTE_PAGE_DISPOSE__;
3938
+ target.__ABSOLUTE_PAGE_DISPOSE__ = undefined;
3939
+ target.__ABSOLUTE_PAGE_READY__ = undefined;
3940
+ if (dispose)
3941
+ await dispose();
3942
+ };
1851
3943
  var frameworks4 = new Set([
1852
3944
  "angular",
1853
3945
  "ember",
@@ -1863,14 +3955,14 @@ var upgradeReasons = new Set([
1863
3955
  "protocol",
1864
3956
  "runtime"
1865
3957
  ]);
1866
- var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3958
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1867
3959
  var isFramework = (value) => typeof value === "string" && frameworks4.has(value);
1868
3960
  var isUpgradeReason = (value) => typeof value === "string" && upgradeReasons.has(value);
1869
3961
  var parsePageResult = (value) => {
1870
3962
  if (typeof value.contract !== "string" || !isFramework(value.framework) || typeof value.pageId !== "string" || typeof value.status !== "number") {
1871
3963
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The mobile page response is missing required page metadata.");
1872
3964
  }
1873
- if (!isRecord4(value.props)) {
3965
+ if (!isRecord6(value.props)) {
1874
3966
  throw new AbsoluteMobilePageProtocolError("invalid-props", "The mobile page response must contain an object props value.");
1875
3967
  }
1876
3968
  return {
@@ -1903,12 +3995,17 @@ var activateAbsoluteMobilePage = async (value, options) => {
1903
3995
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "Expected a renderable mobile page response.");
1904
3996
  }
1905
3997
  const target = options.target ?? window;
3998
+ await disposeAbsoluteMobilePage(target);
1906
3999
  target.__INITIAL_PROPS__ = envelope.response.props;
4000
+ target.__ABS_ANGULAR_REQUEST_CONTEXT__ = envelope.response.props;
1907
4001
  target.__ABSOLUTE_PAGE_RENDER_MODE__ = "client";
1908
4002
  await options.loadPage({
1909
4003
  contract: envelope.response.contract,
1910
4004
  pageId: envelope.response.pageId
1911
4005
  });
4006
+ if (target.__ABSOLUTE_PAGE_READY__) {
4007
+ await target.__ABSOLUTE_PAGE_READY__;
4008
+ }
1912
4009
  return {
1913
4010
  contract: envelope.response.contract,
1914
4011
  kind: "rendered",
@@ -1916,7 +4013,7 @@ var activateAbsoluteMobilePage = async (value, options) => {
1916
4013
  };
1917
4014
  };
1918
4015
  var parseAbsoluteMobilePageEnvelope = (value) => {
1919
- if (!isRecord4(value) || !isRecord4(value.response)) {
4016
+ if (!isRecord6(value) || !isRecord6(value.response)) {
1920
4017
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The server did not return an AbsoluteJS mobile page envelope.");
1921
4018
  }
1922
4019
  if (value.protocol !== ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION) {
@@ -2001,19 +4098,51 @@ var resolveAbsoluteMobileDeepLink = (manifest, value) => {
2001
4098
  }
2002
4099
  return `${url.pathname || "/"}${url.search}${url.hash}`;
2003
4100
  };
4101
+ var resolveAbsoluteMobileNavigation = (manifest, value, localOrigin) => {
4102
+ const url = new URL(value, `${localOrigin}/`);
4103
+ const local = new URL(localOrigin);
4104
+ const production = new URL(manifest.productionOrigin);
4105
+ const matches = (candidate, allowed) => candidate.protocol === allowed.protocol && candidate.host === allowed.host;
4106
+ if (!matches(url, local) && !matches(url, production)) {
4107
+ return;
4108
+ }
4109
+ return `${url.pathname}${url.search}${url.hash}`;
4110
+ };
2004
4111
 
2005
4112
  // src/mobile/capacitorBundle.ts
2006
4113
  var MANIFEST_FILE = "absolute-mobile-manifest.json";
2007
4114
  var BOOTSTRAP_FILE = "absolute-mobile-bootstrap.js";
2008
4115
  var INDEX_FILE = "index.html";
2009
- var CLIENT_IMPORT_PATTERN = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s*)["'](\/[^"']+)["']/gu;
4116
+ var CLIENT_CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?|url\(\s*)["']?((?:\/|\.\.\/|\.\/)[^"')\s]+)["']?\s*\)?/gu;
4117
+ var CLIENT_MARKUP_DEPENDENCY_PATTERN = /<(?:script\b[^>]*\bsrc|link\b[^>]*\bhref|img\b[^>]*\bsrc|source\b[^>]*\bsrcset)\s*=\s*["']((?:\/|\.\.\/|\.\/)[^"',\s]+)/giu;
4118
+ var CAPACITOR_CLIENT_FRAMEWORKS = new Set([
4119
+ "angular",
4120
+ "html",
4121
+ "htmx",
4122
+ "react",
4123
+ "svelte",
4124
+ "vue"
4125
+ ]);
4126
+ var CLIENT_ASSET_DIRECTORIES = ["assets", "html", "htmx", "indexes"];
2010
4127
  var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2011
4128
  var shellBootstrapModule = () => {
2012
- const candidate = ["js", "ts"].map((extension) => join6(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
4129
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
2013
4130
  if (candidate)
2014
4131
  return candidate;
2015
4132
  throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
2016
4133
  };
4134
+ var shellAuthModule = () => {
4135
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellAuth.${extension}`)).find(existsSync2);
4136
+ if (candidate)
4137
+ return candidate;
4138
+ throw new TypeError("AbsoluteJS mobile auth shell module is missing.");
4139
+ };
4140
+ var shellSyncModule = () => {
4141
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellSync.${extension}`)).find(existsSync2);
4142
+ if (candidate)
4143
+ return candidate;
4144
+ throw new TypeError("AbsoluteJS mobile Sync shell module is missing.");
4145
+ };
2017
4146
  var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
2018
4147
  var indexHtml = (appName) => `<!doctype html>
2019
4148
  <html>
@@ -2030,18 +4159,23 @@ var indexHtml = (appName) => `<!doctype html>
2030
4159
  </html>
2031
4160
  `;
2032
4161
  var sourceAssetPath = (buildDirectory, bundlePath) => {
2033
- const root = resolve7(buildDirectory);
2034
- const asset = resolve7(root, bundlePath.replace(/^\/+/, ""));
4162
+ const root = resolve9(buildDirectory);
4163
+ const asset = resolve9(root, bundlePath.replace(/^\/+/, ""));
2035
4164
  if (!asset.startsWith(`${root}/`)) {
2036
4165
  throw new TypeError("Mobile page bundle escaped the build directory.");
2037
4166
  }
2038
4167
  return asset;
2039
4168
  };
2040
- var buildShellBootstrap = async (staging) => {
4169
+ var buildShellBootstrap = async (staging, auth, sync) => {
2041
4170
  const modulePath = shellBootstrapModule();
2042
- const entryPath = join6(staging, ".absolute-mobile-entry.ts");
2043
- await writeFile6(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
2044
- void startAbsoluteMobileShell();
4171
+ const authImport = auth ? `import { createAbsoluteMobileShellAuth } from ${JSON.stringify(shellAuthModule())};
4172
+ ` : "";
4173
+ const options = auth ? `{ createAuth: createAbsoluteMobileShellAuth${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : "";
4174
+ const syncImport = sync ? `import { installAbsoluteMobileShellSync } from ${JSON.stringify(shellSyncModule())};
4175
+ ` : "";
4176
+ const entryPath = join9(staging, ".absolute-mobile-entry.ts");
4177
+ await writeFile10(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
4178
+ ${authImport}${syncImport}void startAbsoluteMobileShell(${options});
2045
4179
  `);
2046
4180
  const build = await Bun.build({
2047
4181
  entrypoints: [entryPath],
@@ -2052,31 +4186,31 @@ void startAbsoluteMobileShell();
2052
4186
  if (!build.success || build.outputs.length !== 1) {
2053
4187
  throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
2054
4188
  }
2055
- await rename6(build.outputs[0]?.path ?? "", join6(staging, BOOTSTRAP_FILE));
2056
- await rm5(entryPath, { force: true });
4189
+ await rename9(build.outputs[0]?.path ?? "", join9(staging, BOOTSTRAP_FILE));
4190
+ await rm7(entryPath, { force: true });
2057
4191
  };
2058
4192
  var removePreviousBundle = async (backup, moved) => {
2059
4193
  if (!moved)
2060
4194
  return;
2061
- await rm5(backup, { force: true, recursive: true });
4195
+ await rm7(backup, { force: true, recursive: true });
2062
4196
  };
2063
4197
  var restorePreviousBundle = async (backup, destination, moved) => {
2064
4198
  if (!moved)
2065
4199
  return;
2066
- await rename6(backup, destination);
4200
+ await rename9(backup, destination);
2067
4201
  };
2068
4202
  var installBundle = async (staging, destination) => {
2069
4203
  const backup = `${destination}.previous-${crypto.randomUUID()}`;
2070
4204
  let movedPrevious = false;
2071
4205
  try {
2072
- await rename6(destination, backup);
4206
+ await rename9(destination, backup);
2073
4207
  movedPrevious = true;
2074
4208
  } catch (error) {
2075
4209
  if (!errorHasCode2(error, "ENOENT"))
2076
4210
  throw error;
2077
4211
  }
2078
4212
  try {
2079
- await rename6(staging, destination);
4213
+ await rename9(staging, destination);
2080
4214
  await removePreviousBundle(backup, movedPrevious);
2081
4215
  } catch (error) {
2082
4216
  await restorePreviousBundle(backup, destination, movedPrevious);
@@ -2084,21 +4218,62 @@ var installBundle = async (staging, destination) => {
2084
4218
  }
2085
4219
  };
2086
4220
  var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) => {
2087
- if (page.framework !== "react") {
2088
- throw new TypeError(`Capacitor spike currently supports React pages; ${page.pageId} is ${page.framework}.`);
4221
+ if (!CAPACITOR_CLIENT_FRAMEWORKS.has(page.framework)) {
4222
+ throw new TypeError(`Capacitor client rendering does not yet support ${page.framework} page ${page.pageId}.`);
2089
4223
  }
2090
- const extension = extname(page.bundlePath) || ".js";
4224
+ const extension = extname2(page.bundlePath) || ".js";
2091
4225
  const localBundlePath = `./pages/${page.bundleHash}${extension}`;
2092
4226
  const source = sourceAssetPath(buildDirectory, page.bundlePath);
2093
- await copyFile3(source, join6(staging, localBundlePath));
4227
+ await copyFile5(source, join9(staging, localBundlePath));
2094
4228
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
2095
- return { ...page, localBundlePath };
4229
+ let localStylePath;
4230
+ if (page.styleBundlePath && page.styleBundleHash) {
4231
+ const styleExtension = extname2(page.styleBundlePath) || ".css";
4232
+ localStylePath = `./styles/${page.styleBundleHash}${styleExtension}`;
4233
+ const styleSource = sourceAssetPath(buildDirectory, page.styleBundlePath);
4234
+ await mkdir9(dirname7(join9(staging, localStylePath)), {
4235
+ recursive: true
4236
+ });
4237
+ await copyFile5(styleSource, join9(staging, localStylePath));
4238
+ await copyAbsoluteClientDependencies(styleSource, buildDirectory, staging, copiedDependencies);
4239
+ }
4240
+ return {
4241
+ ...page,
4242
+ localBundlePath,
4243
+ ...localStylePath ? { localStylePath } : {}
4244
+ };
2096
4245
  };
2097
- var absoluteClientImports = async (sourcePath) => {
2098
- const source = await readFile7(sourcePath, "utf8");
2099
- return [...source.matchAll(CLIENT_IMPORT_PATTERN)].flatMap((match) => {
2100
- const [specifier] = match.slice(1);
2101
- return specifier ? [specifier.split(/[?#]/u, 1)[0] ?? specifier] : [];
4246
+ var absoluteClientImports = async (sourcePath, buildDirectory) => {
4247
+ const source = await readFile11(sourcePath, "utf8");
4248
+ const extension = extname2(sourcePath).toLowerCase();
4249
+ let scriptLoader;
4250
+ if (extension === ".tsx")
4251
+ scriptLoader = "tsx";
4252
+ else if (extension === ".ts")
4253
+ scriptLoader = "ts";
4254
+ else if (extension === ".jsx")
4255
+ scriptLoader = "jsx";
4256
+ else if ([".js", ".mjs", ".cjs"].includes(extension))
4257
+ scriptLoader = "js";
4258
+ const scriptImports = scriptLoader ? new Bun.Transpiler({ loader: scriptLoader }).scanImports(source).map(({ path }) => path) : [];
4259
+ const cssImports = extension === ".css" ? [...source.matchAll(CLIENT_CSS_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
4260
+ const markupImports = extension === ".html" ? [...source.matchAll(CLIENT_MARKUP_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
4261
+ return [...scriptImports, ...cssImports, ...markupImports].flatMap((specifier) => {
4262
+ if (!specifier)
4263
+ return [];
4264
+ if (!specifier.startsWith("/") && !specifier.startsWith("./") && !specifier.startsWith("../")) {
4265
+ return [];
4266
+ }
4267
+ const clean = specifier.split(/[?#]/u, 1)[0] ?? specifier;
4268
+ if (clean.startsWith("/"))
4269
+ return [clean];
4270
+ const resolved = resolve9(dirname7(sourcePath), clean);
4271
+ const root = resolve9(buildDirectory);
4272
+ const relativePath = relative8(root, resolved).replaceAll("\\", "/");
4273
+ if (relativePath === ".." || relativePath.startsWith("../")) {
4274
+ throw new TypeError(`Mobile client dependency escaped the build directory: ${specifier}`);
4275
+ }
4276
+ return [`/${relativePath}`];
2102
4277
  });
2103
4278
  };
2104
4279
  var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, copied) => {
@@ -2106,13 +4281,13 @@ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, co
2106
4281
  return;
2107
4282
  copied.add(specifier);
2108
4283
  const source = sourceAssetPath(buildDirectory, specifier);
2109
- const destination = join6(staging, specifier.replace(/^\/+/, ""));
2110
- await mkdir5(dirname3(destination), { recursive: true });
2111
- await copyFile3(source, destination);
4284
+ const destination = join9(staging, specifier.replace(/^\/+/, ""));
4285
+ await mkdir9(dirname7(destination), { recursive: true });
4286
+ await copyFile5(source, destination);
2112
4287
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
2113
4288
  };
2114
4289
  var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
2115
- const dependencies = await absoluteClientImports(sourcePath);
4290
+ const dependencies = await absoluteClientImports(sourcePath, buildDirectory);
2116
4291
  await Promise.all(dependencies.map((specifier) => copyAbsoluteClientDependency(specifier, buildDirectory, staging, copied)));
2117
4292
  };
2118
4293
  var materializeAbsoluteCapacitorWebBundle = async (options) => {
@@ -2120,15 +4295,20 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
2120
4295
  throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
2121
4296
  }
2122
4297
  const destination = options.config.bundleDirectory;
2123
- await mkdir5(dirname3(destination), { recursive: true });
2124
- const staging = await mkdtemp3(join6(dirname3(destination), `.${basename(destination)}.stage-`));
4298
+ await mkdir9(dirname7(destination), { recursive: true });
4299
+ const staging = await mkdtemp4(join9(dirname7(destination), `.${basename3(destination)}.stage-`));
2125
4300
  try {
2126
- const pageDirectory = join6(staging, "pages");
2127
- await mkdir5(pageDirectory, { recursive: true });
4301
+ const pageDirectory = join9(staging, "pages");
4302
+ await mkdir9(pageDirectory, { recursive: true });
4303
+ await Promise.all(CLIENT_ASSET_DIRECTORIES.map((directory) => ({
4304
+ destination: join9(staging, directory),
4305
+ source: join9(options.buildDirectory, directory)
4306
+ })).filter(({ source }) => existsSync2(source)).map(({ destination: assetDestination, source }) => cp(source, assetDestination, { recursive: true })));
2128
4307
  const copiedDependencies = new Set;
2129
4308
  const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
2130
4309
  const manifest = {
2131
4310
  appBuild: options.artifact.appBuild,
4311
+ ...options.auth ? { auth: options.auth } : {},
2132
4312
  appId: options.config.appId,
2133
4313
  appName: options.config.appName,
2134
4314
  deepLinkHosts: options.config.deepLinkHosts,
@@ -2138,51 +4318,60 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
2138
4318
  pages,
2139
4319
  productionOrigin: options.config.productionOrigin,
2140
4320
  routes: options.artifact.routes,
2141
- runtime: options.artifact.runtime
4321
+ runtime: options.artifact.runtime,
4322
+ ...options.sync ? {
4323
+ sync: {
4324
+ background: {
4325
+ endpoint: new URL("/__absolute/sync/background", options.config.productionOrigin).href,
4326
+ intervalMinutes: 15
4327
+ },
4328
+ socketTickets: true
4329
+ }
4330
+ } : {}
2142
4331
  };
2143
4332
  await Promise.all([
2144
- writeFile6(join6(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
4333
+ writeFile10(join9(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
2145
4334
  `),
2146
- writeFile6(join6(staging, INDEX_FILE), indexHtml(options.config.appName)),
2147
- buildShellBootstrap(staging)
4335
+ writeFile10(join9(staging, INDEX_FILE), indexHtml(options.config.appName)),
4336
+ buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true)
2148
4337
  ]);
2149
4338
  await installBundle(staging, destination);
2150
4339
  return manifest;
2151
4340
  } catch (error) {
2152
- await rm5(staging, { force: true, recursive: true });
4341
+ await rm7(staging, { force: true, recursive: true });
2153
4342
  throw error;
2154
4343
  }
2155
4344
  };
2156
4345
 
2157
4346
  // src/mobile/materializedBundle.ts
2158
- import { createHash as createHash5 } from "crypto";
4347
+ import { createHash as createHash9 } from "crypto";
2159
4348
  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
4349
+ access as access8,
4350
+ mkdir as mkdir10,
4351
+ mkdtemp as mkdtemp5,
4352
+ readFile as readFile12,
4353
+ rename as rename10,
4354
+ rm as rm8,
4355
+ writeFile as writeFile11
2167
4356
  } from "fs/promises";
2168
- import { dirname as dirname4, join as join7, resolve as resolvePath2 } from "path";
4357
+ import { dirname as dirname8, join as join10, resolve as resolvePath3 } from "path";
2169
4358
  import { pathToFileURL } from "url";
2170
4359
  var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
2171
4360
  var CURRENT_BUNDLE_FILE = "current.json";
2172
4361
  var BUNDLES_DIRECTORY = "bundles";
2173
4362
  var ARTIFACT_FILE2 = "artifact.json";
2174
4363
  var BUNDLE_ID_PATTERN = /^amb_[a-f0-9]{64}$/;
2175
- var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4364
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2176
4365
  var errorHasCode3 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2177
4366
  var bundleIdFor = (currentReleaseId, releases) => {
2178
4367
  const identity = JSON.stringify({
2179
4368
  currentReleaseId,
2180
4369
  releases: releases.map(({ releaseId }) => releaseId)
2181
4370
  });
2182
- return `amb_${createHash5("sha256").update(identity).digest("hex")}`;
4371
+ return `amb_${createHash9("sha256").update(identity).digest("hex")}`;
2183
4372
  };
2184
4373
  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)) {
4374
+ 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
4375
  throw new TypeError("Invalid materialized mobile compatibility bundle.");
2187
4376
  }
2188
4377
  const releases = value.releases.map(parseAbsoluteMobileCompatibilityArtifact);
@@ -2205,30 +4394,30 @@ var parseBundleIndex = (value) => {
2205
4394
  };
2206
4395
  };
2207
4396
  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 });
4397
+ const directory = join10(root, release.artifact.releaseId);
4398
+ const producerPath = join10(directory, release.artifact.producer.module);
4399
+ await mkdir10(dirname8(producerPath), { recursive: true });
2211
4400
  await Promise.all([
2212
- writeFile7(join7(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
4401
+ writeFile11(join10(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
2213
4402
  `),
2214
- writeFile7(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
4403
+ writeFile11(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
2215
4404
  ]);
2216
4405
  };
2217
4406
  var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
2218
- const destination = join7(bundlesRoot, bundleId);
4407
+ const destination = join10(bundlesRoot, bundleId);
2219
4408
  try {
2220
- await access5(destination);
4409
+ await access8(destination);
2221
4410
  return destination;
2222
4411
  } catch (error) {
2223
4412
  if (!errorHasCode3(error, "ENOENT"))
2224
4413
  throw error;
2225
4414
  }
2226
- const staging = await mkdtemp4(join7(bundlesRoot, ".stage-"));
4415
+ const staging = await mkdtemp5(join10(bundlesRoot, ".stage-"));
2227
4416
  try {
2228
4417
  await Promise.all(releases.map((release) => writeRelease(staging, release)));
2229
- await rename7(staging, destination);
4418
+ await rename10(staging, destination);
2230
4419
  } catch (error) {
2231
- await rm6(staging, { force: true, recursive: true });
4420
+ await rm8(staging, { force: true, recursive: true });
2232
4421
  if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
2233
4422
  return destination;
2234
4423
  }
@@ -2239,7 +4428,7 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
2239
4428
  var readCompatibilityModule = (modulePath) => import(pathToFileURL(modulePath).href);
2240
4429
  var resolveProducerHandler = (loaded, exportName) => {
2241
4430
  const value = loaded[exportName];
2242
- if (!isRecord5(value) || typeof value.handle !== "function") {
4431
+ if (!isRecord7(value) || typeof value.handle !== "function") {
2243
4432
  throw new TypeError(`Compatibility producer export ${exportName} must expose handle(request).`);
2244
4433
  }
2245
4434
  const { handle } = value;
@@ -2256,16 +4445,16 @@ var resolveProducerHandler = (loaded, exportName) => {
2256
4445
  };
2257
4446
  };
2258
4447
  var loadAbsoluteMobileMaterializedBundle = async (root) => {
2259
- const resolvedRoot = resolvePath2(root);
2260
- const serialized = await readFile8(join7(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
4448
+ const resolvedRoot = resolvePath3(root);
4449
+ const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2261
4450
  const parsed = JSON.parse(serialized);
2262
4451
  const index = parseBundleIndex(parsed);
2263
- const bundleRoot = join7(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
4452
+ const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2264
4453
  return {
2265
4454
  artifacts: index.releases,
2266
4455
  currentReleaseId: index.currentReleaseId,
2267
4456
  loadProducer: async (artifact) => {
2268
- const modulePath = join7(bundleRoot, artifact.releaseId, artifact.producer.module);
4457
+ const modulePath = join10(bundleRoot, artifact.releaseId, artifact.producer.module);
2269
4458
  await verifyAbsoluteMobileCompatibilityProducer({
2270
4459
  artifact,
2271
4460
  producer: Bun.file(modulePath)
@@ -2291,9 +4480,9 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
2291
4480
  }
2292
4481
  return release;
2293
4482
  });
2294
- const root = resolvePath2(input.root);
2295
- const bundlesRoot = join7(root, BUNDLES_DIRECTORY);
2296
- await mkdir6(bundlesRoot, { recursive: true });
4483
+ const root = resolvePath3(input.root);
4484
+ const bundlesRoot = join10(root, BUNDLES_DIRECTORY);
4485
+ await mkdir10(bundlesRoot, { recursive: true });
2297
4486
  const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
2298
4487
  await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
2299
4488
  const index = {
@@ -2302,22 +4491,22 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
2302
4491
  format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
2303
4492
  releases: artifacts
2304
4493
  };
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")}
4494
+ const pointerPath = join10(root, CURRENT_BUNDLE_FILE);
4495
+ const temporaryPointerPath = join10(root, `.current-${crypto.randomUUID()}.json`);
4496
+ await writeFile11(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
2308
4497
  `, { flag: "wx" });
2309
- await rename7(temporaryPointerPath, pointerPath);
4498
+ await rename10(temporaryPointerPath, pointerPath);
2310
4499
  return index;
2311
4500
  };
2312
4501
  var readAbsoluteMobileMaterializedReleases = async (root) => {
2313
- const resolvedRoot = resolvePath2(root);
4502
+ const resolvedRoot = resolvePath3(root);
2314
4503
  try {
2315
- const serialized = await readFile8(join7(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
4504
+ const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2316
4505
  const parsed = JSON.parse(serialized);
2317
4506
  const index = parseBundleIndex(parsed);
2318
- const bundleRoot = join7(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
4507
+ const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2319
4508
  return Promise.all(index.releases.map(async (artifact) => {
2320
- const producer = Bun.file(join7(bundleRoot, artifact.releaseId, artifact.producer.module));
4509
+ const producer = Bun.file(join10(bundleRoot, artifact.releaseId, artifact.producer.module));
2321
4510
  await verifyAbsoluteMobileCompatibilityProducer({
2322
4511
  artifact,
2323
4512
  producer
@@ -2331,6 +4520,58 @@ var readAbsoluteMobileMaterializedReleases = async (root) => {
2331
4520
  }
2332
4521
  };
2333
4522
 
4523
+ // src/mobile/nativeAuth.ts
4524
+ import { readFileSync as readFileSync2 } from "fs";
4525
+ import { join as join11 } from "path";
4526
+ var ABSOLUTE_AUTH_PACKAGE = "@absolutejs/auth";
4527
+ var ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV = "ABSOLUTE_AUTH_NATIVE_CLIENTS";
4528
+ var ABSOLUTE_NATIVE_AUTH_SCOPES = ["openid", "profile"];
4529
+ var ABSOLUTE_SYNC_PACKAGE = "@absolutejs/sync";
4530
+ var readPackageManifest = (projectRoot) => {
4531
+ try {
4532
+ return JSON.parse(readFileSync2(join11(projectRoot, "package.json"), "utf8"));
4533
+ } catch {
4534
+ return;
4535
+ }
4536
+ };
4537
+ var packageManifestHas = (manifest, packageName) => {
4538
+ if (typeof manifest !== "object" || manifest === null)
4539
+ return false;
4540
+ return [
4541
+ Reflect.get(manifest, "dependencies"),
4542
+ Reflect.get(manifest, "devDependencies"),
4543
+ Reflect.get(manifest, "optionalDependencies"),
4544
+ Reflect.get(manifest, "peerDependencies")
4545
+ ].some((dependencies) => typeof dependencies === "object" && dependencies !== null && Object.hasOwn(dependencies, packageName));
4546
+ };
4547
+ var createAbsoluteMobileAuthManifest = (config) => {
4548
+ const scheme = config.deepLinkScheme ?? config.appId.toLowerCase();
4549
+ return {
4550
+ clientId: `absolutejs-native:${config.appId}`,
4551
+ issuer: config.productionOrigin,
4552
+ redirectUri: `${scheme}://auth/callback`,
4553
+ scopes: [...ABSOLUTE_NATIVE_AUTH_SCOPES]
4554
+ };
4555
+ };
4556
+ var installAbsoluteMobileAuthEnvironment = (projectRoot, config) => {
4557
+ const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
4558
+ const serialized = serializeAbsoluteMobileAuthEnvironment(config, auth);
4559
+ if (serialized === undefined)
4560
+ delete process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV];
4561
+ else
4562
+ process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV] = serialized;
4563
+ return auth;
4564
+ };
4565
+ var projectUsesAbsoluteAuth = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_AUTH_PACKAGE);
4566
+ var projectUsesAbsoluteSync = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_SYNC_PACKAGE);
4567
+ var resolveAbsoluteMobileAuthManifest = (projectRoot, config) => projectUsesAbsoluteAuth(projectRoot) ? createAbsoluteMobileAuthManifest(config) : undefined;
4568
+ var serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefined ? undefined : JSON.stringify([
4569
+ {
4570
+ ...auth,
4571
+ name: `${config.appName} native app`
4572
+ }
4573
+ ]);
4574
+
2334
4575
  // src/mobile/buildPipeline.ts
2335
4576
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
2336
4577
  var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
@@ -2341,12 +4582,11 @@ var serverExportName = (loaded, app) => {
2341
4582
  return "app";
2342
4583
  return "default";
2343
4584
  };
2344
- var restoreBuildDirectory = (previous) => {
2345
- if (previous !== undefined) {
2346
- process.env.ABSOLUTE_BUILD_DIR = previous;
2347
- return;
2348
- }
2349
- delete process.env.ABSOLUTE_BUILD_DIR;
4585
+ var restoreEnvironmentVariable = (name, previous) => {
4586
+ if (previous !== undefined)
4587
+ process.env[name] = previous;
4588
+ else
4589
+ delete process.env[name];
2350
4590
  };
2351
4591
  var requireRelease = (releases, releaseId) => {
2352
4592
  const release = releases.get(releaseId);
@@ -2366,11 +4606,11 @@ var loadServerApp = async (producerPath) => {
2366
4606
  return { app, exportName };
2367
4607
  };
2368
4608
  var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2369
- const buildDirectory = resolve8(options.buildDirectory);
4609
+ const buildDirectory = resolve10(options.buildDirectory);
2370
4610
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
2371
- const root = join8(buildDirectory, ".absolutejs", "mobile-compatibility");
4611
+ const root = join12(buildDirectory, ".absolutejs", "mobile-compatibility");
2372
4612
  const [manifestSource, previous] = await Promise.all([
2373
- readFile9(join8(buildDirectory, "manifest.json"), "utf8"),
4613
+ readFile13(join12(buildDirectory, "manifest.json"), "utf8"),
2374
4614
  readAbsoluteMobileMaterializedReleases(root)
2375
4615
  ]);
2376
4616
  const manifest = JSON.parse(manifestSource);
@@ -2378,12 +4618,20 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2378
4618
  throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
2379
4619
  }
2380
4620
  const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
4621
+ const previousCompiledRuntime = process.env.ABSOLUTE_COMPILED_RUNTIME;
4622
+ const previousConfigPath = process.env.ABSOLUTE_CONFIG;
2381
4623
  process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
4624
+ process.env.ABSOLUTE_COMPILED_RUNTIME = "1";
4625
+ if (options.configPath) {
4626
+ process.env.ABSOLUTE_CONFIG = resolve10(options.projectRoot, options.configPath);
4627
+ }
2382
4628
  let loaded;
2383
4629
  try {
2384
- loaded = await loadServerApp(resolve8(options.producerPath));
4630
+ loaded = await loadServerApp(resolve10(options.producerPath));
2385
4631
  } finally {
2386
- restoreBuildDirectory(previousBuildDirectory);
4632
+ restoreEnvironmentVariable("ABSOLUTE_BUILD_DIR", previousBuildDirectory);
4633
+ restoreEnvironmentVariable("ABSOLUTE_COMPILED_RUNTIME", previousCompiledRuntime);
4634
+ restoreEnvironmentVariable("ABSOLUTE_CONFIG", previousConfigPath);
2387
4635
  }
2388
4636
  const current = await buildAbsoluteMobileCompatibilityRelease({
2389
4637
  app: loaded.app,
@@ -2392,9 +4640,14 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2392
4640
  manifest,
2393
4641
  previousArtifacts: previous.map(({ artifact }) => artifact),
2394
4642
  producerExport: loaded.exportName,
2395
- producerPath: resolve8(options.producerPath),
4643
+ producerPath: resolve10(options.producerPath),
2396
4644
  runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
2397
4645
  });
4646
+ const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
4647
+ const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
4648
+ if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
4649
+ throw new TypeError("@absolutejs/auth is installed, but its OIDC provider is not mounted. Native authentication requires the auth oidc configuration so AbsoluteJS can provision a public PKCE client.");
4650
+ }
2398
4651
  const releasesById = new Map([current, ...previous].map((release) => [
2399
4652
  release.artifact.releaseId,
2400
4653
  release
@@ -2407,8 +4660,10 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2407
4660
  });
2408
4661
  await materializeAbsoluteCapacitorWebBundle({
2409
4662
  artifact: current.artifact,
4663
+ ...auth ? { auth } : {},
2410
4664
  buildDirectory,
2411
- config: mobile
4665
+ config: mobile,
4666
+ ...sync ? { sync: true } : {}
2412
4667
  });
2413
4668
  return current.artifact;
2414
4669
  };
@@ -2433,6 +4688,64 @@ var ensureProducerStorage = () => {
2433
4688
  var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
2434
4689
 
2435
4690
  // src/mobile/compatibilityDispatcher.ts
4691
+ var MOBILE_WEBVIEW_ORIGINS = new Set([
4692
+ "capacitor://localhost",
4693
+ "http://localhost",
4694
+ "https://localhost"
4695
+ ]);
4696
+ var MOBILE_REQUEST_HEADER_NAMES = Object.values(MOBILE_PAGE_REQUEST_HEADERS);
4697
+ var MOBILE_CORS_ALLOW_HEADERS = [
4698
+ "accept",
4699
+ "content-type",
4700
+ "authorization",
4701
+ "hx-current-url",
4702
+ "hx-request",
4703
+ "hx-target",
4704
+ "hx-trigger",
4705
+ "hx-trigger-name",
4706
+ ...MOBILE_REQUEST_HEADER_NAMES
4707
+ ].join(", ");
4708
+ var MOBILE_CORS_METHODS = new Set([
4709
+ "DELETE",
4710
+ "GET",
4711
+ "HEAD",
4712
+ "OPTIONS",
4713
+ "PATCH",
4714
+ "POST",
4715
+ "PUT"
4716
+ ]);
4717
+ var mobileWebViewOrigin = (request) => {
4718
+ const origin = request.headers.get("origin");
4719
+ return origin && MOBILE_WEBVIEW_ORIGINS.has(origin) ? origin : undefined;
4720
+ };
4721
+ var applyMobileCorsHeaders = (response, origin) => {
4722
+ response.headers.set("access-control-allow-credentials", "true");
4723
+ response.headers.set("access-control-allow-origin", origin);
4724
+ response.headers.append("vary", "Origin");
4725
+ return response;
4726
+ };
4727
+ var mobilePreflightResponse = (request) => {
4728
+ if (request.method !== "OPTIONS")
4729
+ return;
4730
+ const origin = mobileWebViewOrigin(request);
4731
+ if (!origin)
4732
+ return;
4733
+ const requestedHeaders = request.headers.get("access-control-request-headers");
4734
+ const requestedMethod = request.headers.get("access-control-request-method")?.toUpperCase() ?? "";
4735
+ if (!MOBILE_CORS_METHODS.has(requestedMethod))
4736
+ return;
4737
+ return new Response(null, {
4738
+ headers: {
4739
+ "access-control-allow-credentials": "true",
4740
+ "access-control-allow-headers": requestedHeaders || MOBILE_CORS_ALLOW_HEADERS,
4741
+ "access-control-allow-methods": [...MOBILE_CORS_METHODS].join(", "),
4742
+ "access-control-allow-origin": origin,
4743
+ "access-control-max-age": "600",
4744
+ vary: "Origin, Access-Control-Request-Headers"
4745
+ },
4746
+ status: 204
4747
+ });
4748
+ };
2436
4749
  var artifactOwnsRequest = (artifact, pageId, request) => {
2437
4750
  const { pathname } = new URL(request.url);
2438
4751
  return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
@@ -2460,6 +4773,9 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2460
4773
  return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
2461
4774
  if (getCurrentAbsoluteMobileProducerContext())
2462
4775
  return;
4776
+ const preflight = mobilePreflightResponse(request);
4777
+ if (preflight)
4778
+ return preflight;
2463
4779
  const parsed = parseAbsoluteMobilePageRequest(request);
2464
4780
  if (parsed.kind !== "mobile")
2465
4781
  return;
@@ -2483,23 +4799,28 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2483
4799
  console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
2484
4800
  return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
2485
4801
  }
4802
+ }).afterHandle("global", ({ request, responseValue }) => {
4803
+ const origin = mobileWebViewOrigin(request);
4804
+ if (!origin || !(responseValue instanceof Response))
4805
+ return;
4806
+ applyMobileCorsHeaders(responseValue, origin);
2486
4807
  }).as("global");
2487
4808
  };
2488
4809
  // 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";
4810
+ import { readFile as readFile14, rename as rename11, writeFile as writeFile12 } from "fs/promises";
4811
+ import { join as join13 } from "path";
2491
4812
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
2492
4813
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
2493
4814
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
2494
4815
  var NOT_FOUND = -1;
2495
4816
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2496
4817
  var writeChangedFile = async (path, source) => {
2497
- const current = await readFile10(path, "utf8");
4818
+ const current = await readFile14(path, "utf8");
2498
4819
  if (current === source)
2499
4820
  return false;
2500
4821
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2501
- await writeFile8(temporary, source, { flag: "wx" });
2502
- await rename8(temporary, path);
4822
+ await writeFile12(temporary, source, { flag: "wx" });
4823
+ await rename11(temporary, path);
2503
4824
  return true;
2504
4825
  };
2505
4826
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -2544,8 +4865,8 @@ ${hosts}
2544
4865
  `;
2545
4866
  };
2546
4867
  var configureAndroid = async (config) => {
2547
- const path = join9(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
2548
- const source = await readFile10(path, "utf8");
4868
+ const path = join13(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
4869
+ const source = await readFile14(path, "utf8");
2549
4870
  const mainActivity = source.indexOf('android:name=".MainActivity"');
2550
4871
  if (mainActivity === NOT_FOUND) {
2551
4872
  throw new TypeError("Android MainActivity was not found.");
@@ -2570,8 +4891,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
2570
4891
  ${END_MARKER}
2571
4892
  `;
2572
4893
  var configureIosInfo = async (config) => {
2573
- const path = join9(config.nativeProjectDirectory, "ios/App/App/Info.plist");
2574
- const source = await readFile10(path, "utf8");
4894
+ const path = join13(config.nativeProjectDirectory, "ios/App/App/Info.plist");
4895
+ const source = await readFile14(path, "utf8");
2575
4896
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
2576
4897
  ${END_MARKER}
2577
4898
  `;
@@ -2594,10 +4915,10 @@ ${domains}
2594
4915
  `;
2595
4916
  };
2596
4917
  var configureIosEntitlements = async (config) => {
2597
- const path = join9(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
4918
+ const path = join13(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
2598
4919
  let current = "";
2599
4920
  try {
2600
- current = await readFile10(path, "utf8");
4921
+ current = await readFile14(path, "utf8");
2601
4922
  } catch (error) {
2602
4923
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
2603
4924
  throw error;
@@ -2607,13 +4928,13 @@ var configureIosEntitlements = async (config) => {
2607
4928
  if (current === source)
2608
4929
  return false;
2609
4930
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2610
- await writeFile8(temporary, source, { flag: "wx" });
2611
- await rename8(temporary, path);
4931
+ await writeFile12(temporary, source, { flag: "wx" });
4932
+ await rename11(temporary, path);
2612
4933
  return true;
2613
4934
  };
2614
4935
  var configureIosProject = async (config) => {
2615
- const path = join9(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
2616
- const source = await readFile10(path, "utf8");
4936
+ const path = join13(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
4937
+ const source = await readFile14(path, "utf8");
2617
4938
  const declarations = [
2618
4939
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
2619
4940
  ].map((match) => match[1]);
@@ -2650,15 +4971,158 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
2650
4971
  changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
2651
4972
  };
2652
4973
  };
4974
+ // src/mobile/releasePublisher.ts
4975
+ import { access as access9 } from "fs/promises";
4976
+ import { isAbsolute as isAbsolute6, relative as relative9, resolve as resolve11, sep as sep6 } from "path";
4977
+ import { pathToFileURL as pathToFileURL3 } from "url";
4978
+ var prepareAbsoluteIosRelease = async (publisher, options) => {
4979
+ if (typeof publisher.prepareIosRelease !== "function") {
4980
+ throw new TypeError("App Store Connect publishing requires a registry module created with @absolutejs/deploy/app-store-connect.");
4981
+ }
4982
+ const { buildNumber } = await publisher.prepareIosRelease(options);
4983
+ if (!Number.isSafeInteger(buildNumber) || buildNumber < 1) {
4984
+ throw new TypeError("App Store Connect publisher returned an invalid iOS build number.");
4985
+ }
4986
+ return buildNumber;
4987
+ };
4988
+ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
4989
+ if (typeof publisher.prepareAndroidRelease !== "function") {
4990
+ throw new TypeError("Google Play publishing requires a registry module created with @absolutejs/deploy/google-play.");
4991
+ }
4992
+ const prepared = await publisher.prepareAndroidRelease(options);
4993
+ const { versionCode } = prepared;
4994
+ if (typeof versionCode !== "number" || !Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000) {
4995
+ throw new TypeError("Google Play publisher returned an invalid Android versionCode.");
4996
+ }
4997
+ return versionCode;
4998
+ };
4999
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5000
+ var isPublisher = (value) => isRecord8(value) && typeof value.publish === "function";
5001
+ var publisherModulePath = (projectRoot, requested) => {
5002
+ const root = resolve11(projectRoot);
5003
+ const path = resolve11(root, requested);
5004
+ const projectRelative = relative9(root, path);
5005
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
5006
+ throw new TypeError("mobile publish --registry must remain inside the project.");
5007
+ }
5008
+ return path;
5009
+ };
5010
+ var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
5011
+ const modulePath = publisherModulePath(projectRoot, requestedModulePath);
5012
+ await access9(modulePath).catch(() => {
5013
+ throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
5014
+ });
5015
+ const loaded = await import(pathToFileURL3(modulePath).href);
5016
+ const publisher = isRecord8(loaded) ? loaded.default ?? loaded.registry : undefined;
5017
+ if (!isPublisher(publisher)) {
5018
+ throw new TypeError("Native release registry module must default-export a registry with publish(options).");
5019
+ }
5020
+ return publisher;
5021
+ };
5022
+ var publishAbsoluteAndroidRelease = async (options) => {
5023
+ const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
5024
+ const publication = await publisher.publish({
5025
+ allowUnsigned: options.allowUnsigned,
5026
+ channel: options.channel,
5027
+ googlePlay: options.googlePlay,
5028
+ releaseRoot: options.release.releaseRoot,
5029
+ signal: options.signal
5030
+ });
5031
+ const expected = options.release.metadata;
5032
+ const actual = publication.record?.metadata;
5033
+ 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") {
5034
+ throw new TypeError("Native release registry returned a different Android release identity.");
5035
+ }
5036
+ if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
5037
+ throw new TypeError("Native release registry did not promote the requested channel.");
5038
+ }
5039
+ const { googlePlay } = publication;
5040
+ if (options.googlePlay) {
5041
+ 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") {
5042
+ throw new TypeError("Native release publisher did not commit the requested Google Play release.");
5043
+ }
5044
+ }
5045
+ return publication;
5046
+ };
5047
+ var publishAbsoluteIosRelease = async (options) => {
5048
+ const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
5049
+ const publication = await publisher.publish({
5050
+ allowUnsigned: options.allowUnsigned,
5051
+ appStoreConnect: options.appStoreConnect,
5052
+ channel: options.channel,
5053
+ releaseRoot: options.release.releaseRoot,
5054
+ signal: options.signal
5055
+ });
5056
+ const expected = options.release.metadata;
5057
+ const actual = publication.record?.metadata;
5058
+ 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") {
5059
+ throw new TypeError("Native release registry returned a different iOS release identity.");
5060
+ }
5061
+ if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
5062
+ throw new TypeError("Native release registry did not promote the requested channel.");
5063
+ }
5064
+ if (options.appStoreConnect) {
5065
+ const distributed = publication.appStoreConnect;
5066
+ 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") {
5067
+ throw new TypeError("Native release publisher did not complete the requested App Store Connect release.");
5068
+ }
5069
+ }
5070
+ return publication;
5071
+ };
2653
5072
  // src/mobile/routeMetadataTransform.ts
2654
- 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";
5073
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
5074
+ import { dirname as dirname9, extname as extname3, relative as relative10, resolve as resolve12 } from "path";
2656
5075
  import ts from "typescript";
2657
5076
  var ROUTE_METHODS = new Set(["get", "head"]);
2658
5077
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
2659
- var PAGE_HANDLER = "handleReactPageRequest";
5078
+ var PAGE_HANDLERS = new Map([
5079
+ [
5080
+ "handleHTMLPageRequest",
5081
+ { framework: "html", inputKind: "static", propsProperty: "props" }
5082
+ ],
5083
+ [
5084
+ "handleHTMXPageRequest",
5085
+ { framework: "htmx", inputKind: "static", propsProperty: "props" }
5086
+ ],
5087
+ [
5088
+ "handleAngularPageRequest",
5089
+ {
5090
+ bundleProperty: "indexPath",
5091
+ framework: "angular",
5092
+ propsProperty: "requestContext",
5093
+ sourceProperty: "pagePath"
5094
+ }
5095
+ ],
5096
+ [
5097
+ "handleReactPageRequest",
5098
+ {
5099
+ bundleProperty: "index",
5100
+ framework: "react",
5101
+ pageProperty: "Page",
5102
+ propsProperty: "props"
5103
+ }
5104
+ ],
5105
+ [
5106
+ "handleSveltePageRequest",
5107
+ {
5108
+ bundleProperty: "indexPath",
5109
+ framework: "svelte",
5110
+ propsProperty: "props",
5111
+ sourceProperty: "pagePath"
5112
+ }
5113
+ ],
5114
+ [
5115
+ "handleVuePageRequest",
5116
+ {
5117
+ bundleProperty: "indexPath",
5118
+ framework: "vue",
5119
+ propsProperty: "props",
5120
+ sourceProperty: "pagePath"
5121
+ }
5122
+ ]
5123
+ ]);
2660
5124
  var posixPath = (value) => value.replace(/\\/g, "/");
2661
- var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname5(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
5125
+ var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname9(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
2662
5126
  var createProgram = (entry, projectRoot) => {
2663
5127
  const configPath = findTsconfig(entry, projectRoot);
2664
5128
  if (!configPath) {
@@ -2670,7 +5134,7 @@ var createProgram = (entry, projectRoot) => {
2670
5134
  target: ts.ScriptTarget.ESNext
2671
5135
  });
2672
5136
  }
2673
- const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname5(configPath));
5137
+ const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync3(path, "utf8")).config, ts.sys, dirname9(configPath));
2674
5138
  if (!parsed.fileNames.includes(entry))
2675
5139
  parsed.fileNames.push(entry);
2676
5140
  return ts.createProgram(parsed.fileNames, parsed.options);
@@ -2776,7 +5240,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
2776
5240
  const declaration = symbol?.declarations?.[0];
2777
5241
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
2778
5242
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
2779
- const source = posixPath(relative5(projectRoot, file));
5243
+ const source = posixPath(relative10(projectRoot, file));
2780
5244
  return `${source}#${exportedName}`;
2781
5245
  };
2782
5246
  var resolveAlias = (symbol, checker) => {
@@ -2808,13 +5272,108 @@ var assetKey = (expression, checker, seen = new Set) => {
2808
5272
  const [, key] = expression.arguments;
2809
5273
  return key && ts.isStringLiteralLike(key) ? key.text : undefined;
2810
5274
  };
5275
+ var staticString = (expression, bindings) => {
5276
+ if (ts.isStringLiteralLike(expression))
5277
+ return expression.text;
5278
+ if (ts.isIdentifier(expression))
5279
+ return bindings.get(expression.text);
5280
+ if (ts.isNoSubstitutionTemplateLiteral(expression))
5281
+ return expression.text;
5282
+ if (!ts.isTemplateExpression(expression))
5283
+ return;
5284
+ let value = expression.head.text;
5285
+ for (const span of expression.templateSpans) {
5286
+ const substitution = staticString(span.expression, bindings);
5287
+ if (substitution === undefined)
5288
+ return;
5289
+ value += substitution + span.literal.text;
5290
+ }
5291
+ return value;
5292
+ };
5293
+ var assetKeyWithBindings = (expression, checker, bindings = new Map) => {
5294
+ if (!expression)
5295
+ return;
5296
+ if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "asset") {
5297
+ const [, key] = expression.arguments;
5298
+ return key ? staticString(key, bindings) : undefined;
5299
+ }
5300
+ return assetKey(expression, checker);
5301
+ };
5302
+ var callableObject = (call, checker) => {
5303
+ const symbol = checker.getSymbolAtLocation(call.expression);
5304
+ const resolved = symbol ? resolveAlias(symbol, checker) : undefined;
5305
+ const declaration = resolved?.valueDeclaration ?? resolved?.declarations?.[0];
5306
+ let callable;
5307
+ if (declaration && ts.isFunctionDeclaration(declaration)) {
5308
+ callable = declaration;
5309
+ } else if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) {
5310
+ callable = declaration.initializer;
5311
+ }
5312
+ if (!callable)
5313
+ return;
5314
+ const bindings = new Map;
5315
+ callable.parameters.forEach((parameter, index) => {
5316
+ if (!ts.isIdentifier(parameter.name))
5317
+ return;
5318
+ const argument = call.arguments[index];
5319
+ if (!argument)
5320
+ return;
5321
+ const value = staticString(argument, new Map);
5322
+ if (value !== undefined)
5323
+ bindings.set(parameter.name.text, value);
5324
+ });
5325
+ const { body } = callable;
5326
+ if (!body)
5327
+ return;
5328
+ const expressionBody = ts.isParenthesizedExpression(body) ? body.expression : body;
5329
+ if (ts.isObjectLiteralExpression(expressionBody)) {
5330
+ return { bindings, object: expressionBody };
5331
+ }
5332
+ if (ts.isBlock(body)) {
5333
+ const returned = body.statements.find(ts.isReturnStatement)?.expression;
5334
+ if (returned && ts.isObjectLiteralExpression(returned)) {
5335
+ return { bindings, object: returned };
5336
+ }
5337
+ }
5338
+ return;
5339
+ };
5340
+ var spreadObject = (expression, checker, bindings) => {
5341
+ if (ts.isObjectLiteralExpression(expression)) {
5342
+ return { bindings, object: expression };
5343
+ }
5344
+ if (!ts.isCallExpression(expression))
5345
+ return;
5346
+ return callableObject(expression, checker);
5347
+ };
5348
+ var objectAssetKey = (object, name, checker, bindings = new Map) => {
5349
+ for (const property of [...object.properties].reverse()) {
5350
+ if (propertyName(property) === name && ts.isShorthandPropertyAssignment(property)) {
5351
+ return assetKeyWithBindings(property.name, checker, bindings);
5352
+ }
5353
+ if (propertyName(property) === name && ts.isPropertyAssignment(property)) {
5354
+ return assetKeyWithBindings(property.initializer, checker, bindings);
5355
+ }
5356
+ if (!ts.isSpreadAssignment(property))
5357
+ continue;
5358
+ const nestedObject = spreadObject(property.expression, checker, bindings);
5359
+ if (!nestedObject)
5360
+ continue;
5361
+ const nested = objectAssetKey(nestedObject.object, name, checker, nestedObject.bindings);
5362
+ if (nested)
5363
+ return nested;
5364
+ }
5365
+ return;
5366
+ };
2811
5367
  var findPageCall = (nodes) => {
2812
5368
  let found;
2813
5369
  const visit = (candidate) => {
2814
5370
  if (found)
2815
5371
  return;
2816
- if (ts.isCallExpression(candidate) && ts.isIdentifier(candidate.expression) && candidate.expression.text === PAGE_HANDLER) {
2817
- found = candidate;
5372
+ if (ts.isCallExpression(candidate) && ts.isIdentifier(candidate.expression) && PAGE_HANDLERS.has(candidate.expression.text)) {
5373
+ const definition = PAGE_HANDLERS.get(candidate.expression.text);
5374
+ if (!definition)
5375
+ return;
5376
+ found = { definition, node: candidate };
2818
5377
  return;
2819
5378
  }
2820
5379
  ts.forEachChild(candidate, visit);
@@ -2833,30 +5392,67 @@ var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
2833
5392
  const [routePath] = node.arguments;
2834
5393
  if (!routePath || !ts.isStringLiteralLike(routePath))
2835
5394
  return;
2836
- const pageCall = findPageCall(node.arguments.slice(1));
5395
+ const foundPageCall = findPageCall(node.arguments.slice(1));
5396
+ const pageCall = foundPageCall?.node;
5397
+ const definition = foundPageCall?.definition;
2837
5398
  const [input] = pageCall?.arguments ?? [];
2838
- if (!pageCall || !input || !ts.isObjectLiteralExpression(input)) {
5399
+ if (!pageCall || !input) {
2839
5400
  return;
2840
5401
  }
2841
- const page = objectPropertyExpression(input, "Page");
2842
- if (!page)
5402
+ if (!definition)
5403
+ return;
5404
+ if (definition.inputKind === "static") {
5405
+ const bundleKey2 = assetKey(input, checker);
5406
+ if (!bundleKey2)
5407
+ return;
5408
+ const pageId2 = `${definition.framework}:${bundleKey2}`;
5409
+ const propsSchemaHash2 = hashAbsoluteMobilePropsSchema({
5410
+ properties: {},
5411
+ type: "object"
5412
+ });
5413
+ return {
5414
+ inputKind: "static",
5415
+ metadata: {
5416
+ bundleKey: bundleKey2,
5417
+ contract: `${definition.framework}:${pageId2}:${propsSchemaHash2}`,
5418
+ framework: definition.framework,
5419
+ pageId: pageId2,
5420
+ propsSchemaHash: propsSchemaHash2
5421
+ },
5422
+ pageCallStart: pageCall.getStart(sourceFile),
5423
+ routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
5424
+ };
5425
+ }
5426
+ if (!ts.isObjectLiteralExpression(input) || !definition.bundleProperty) {
5427
+ return;
5428
+ }
5429
+ const page = definition.pageProperty ? objectPropertyExpression(input, definition.pageProperty) : undefined;
5430
+ const source = definition.sourceProperty ? objectAssetKey(input, definition.sourceProperty, checker) : undefined;
5431
+ if (definition.pageProperty && !page)
5432
+ return;
5433
+ if (definition.sourceProperty && !source)
2843
5434
  return;
2844
- const props = objectPropertyExpression(input, "props");
2845
- const index = objectPropertyExpression(input, "index");
2846
- const bundleKey = assetKey(index, checker);
5435
+ const props = objectPropertyExpression(input, definition.propsProperty);
5436
+ const bundleKey = objectAssetKey(input, definition.bundleProperty, checker);
2847
5437
  if (!bundleKey)
2848
5438
  return;
2849
- const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot);
2850
- const schema = serializeType(pagePropsType(page, props, checker), checker);
5439
+ const pageId = page ? resolvePageIdentity(page, sourceFile, checker, projectRoot) : `${definition.framework}:${source}`;
5440
+ let propsType;
5441
+ if (page)
5442
+ propsType = pagePropsType(page, props, checker);
5443
+ else if (props)
5444
+ propsType = checker.getTypeAtLocation(props);
5445
+ const schema = propsType ? serializeType(propsType, checker) : { properties: {}, type: "object" };
2851
5446
  const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
2852
5447
  const metadata = {
2853
5448
  bundleKey,
2854
- contract: `react:${pageId}:${propsSchemaHash}`,
2855
- framework: "react",
5449
+ contract: `${definition.framework}:${pageId}:${propsSchemaHash}`,
5450
+ framework: definition.framework,
2856
5451
  pageId,
2857
5452
  propsSchemaHash
2858
5453
  };
2859
5454
  const result = {
5455
+ inputKind: "object",
2860
5456
  metadata,
2861
5457
  pageCallStart: pageCall.getStart(sourceFile),
2862
5458
  routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
@@ -2883,7 +5479,7 @@ var analyzeProgram = (program, projectRoot) => {
2883
5479
  const checker = program.getTypeChecker();
2884
5480
  const analyzed = new Map;
2885
5481
  for (const sourceFile of program.getSourceFiles()) {
2886
- const resolvedFile = resolve9(sourceFile.fileName);
5482
+ const resolvedFile = resolve12(sourceFile.fileName);
2887
5483
  if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
2888
5484
  continue;
2889
5485
  const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
@@ -2913,6 +5509,16 @@ var routeOptions = (existing, metadata) => {
2913
5509
  var transformPageCall = (node, page) => {
2914
5510
  if (!page)
2915
5511
  return;
5512
+ if (page.inputKind === "static") {
5513
+ const [pagePath, existingOptions, ...rest] = node.arguments;
5514
+ if (!pagePath)
5515
+ return;
5516
+ const options = ts.factory.createObjectLiteralExpression([
5517
+ ...existingOptions ? [ts.factory.createSpreadAssignment(existingOptions)] : [],
5518
+ ts.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
5519
+ ]);
5520
+ return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [pagePath, options, ...rest]);
5521
+ }
2916
5522
  const [input] = node.arguments;
2917
5523
  if (!input || !ts.isObjectLiteralExpression(input))
2918
5524
  return;
@@ -2966,61 +5572,98 @@ var transformFile = (source, fileName, analysis) => {
2966
5572
  };
2967
5573
  var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
2968
5574
  var createAbsoluteMobileRouteMetadataPlugin = (options) => {
2969
- const projectRoot = resolve9(options.projectRoot ?? process.cwd());
2970
- const entry = resolve9(options.entry);
5575
+ const projectRoot = resolve12(options.projectRoot ?? process.cwd());
5576
+ const entry = resolve12(options.entry);
2971
5577
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
2972
5578
  return {
2973
5579
  name: "absolute-mobile-route-metadata",
2974
5580
  setup(build) {
2975
5581
  build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
2976
- const analysis = analyzed.get(resolve9(path));
5582
+ const analysis = analyzed.get(resolve12(path));
2977
5583
  if (!analysis)
2978
5584
  return;
2979
5585
  const source = await Bun.file(path).text();
2980
5586
  return {
2981
5587
  contents: transformFile(source, path, analysis),
2982
- loader: extname2(path).endsWith("x") ? "tsx" : "ts"
5588
+ loader: extname3(path).endsWith("x") ? "tsx" : "ts"
2983
5589
  };
2984
5590
  });
2985
5591
  }
2986
5592
  };
2987
5593
  };
2988
5594
  var inspectAbsoluteMobileRouteMetadata = (options) => {
2989
- const projectRoot = resolve9(options.projectRoot ?? process.cwd());
2990
- const entry = resolve9(options.entry);
5595
+ const projectRoot = resolve12(options.projectRoot ?? process.cwd());
5596
+ const entry = resolve12(options.entry);
2991
5597
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
2992
5598
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
2993
- file: posixPath(relative5(projectRoot, file)),
5599
+ file: posixPath(relative10(projectRoot, file)),
2994
5600
  metadata
2995
5601
  })));
2996
5602
  };
2997
5603
  export {
2998
5604
  writeAbsoluteCapacitorConfig,
5605
+ waitForAbsoluteIosHmrLog,
2999
5606
  verifyAbsoluteMobileCompatibilityProducer,
3000
5607
  verifyAbsoluteMobileAssociationFiles,
5608
+ validateAbsoluteSshDestination,
5609
+ validateAbsoluteRemoteMacProfileName,
5610
+ syncAbsoluteRemoteMacProject,
5611
+ startAbsoluteRemoteIosDevSession,
5612
+ startAbsoluteIosDevSession,
5613
+ serializeAbsoluteMobileAuthEnvironment,
3001
5614
  runWithAbsoluteMobileProducer,
3002
5615
  retainAbsoluteMobileCompatibilityArtifacts,
3003
5616
  resolveAbsoluteMobileRoute,
5617
+ resolveAbsoluteMobileNavigation,
3004
5618
  resolveAbsoluteMobileDeepLink,
3005
5619
  resolveAbsoluteMobileCompatibilityRelease,
5620
+ resolveAbsoluteMobileAuthManifest,
5621
+ repairAbsoluteIosDevSession,
5622
+ removeAbsoluteRemoteMacProfile,
5623
+ redactAbsoluteIosLog,
3006
5624
  readAbsoluteMobileMaterializedReleases,
5625
+ publishAbsoluteIosRelease,
5626
+ publishAbsoluteAndroidRelease,
5627
+ projectUsesAbsoluteSync,
5628
+ projectUsesAbsoluteAuth,
5629
+ prepareAbsoluteIosRelease,
5630
+ prepareAbsoluteIosDevProject,
5631
+ prepareAbsoluteAndroidRelease,
5632
+ parseIosSimulators,
5633
+ parseIosRuntimes,
5634
+ parseIosDeviceTypes,
3007
5635
  parseAbsoluteMobilePageRequest,
3008
5636
  parseAbsoluteMobilePageEnvelope,
3009
5637
  parseAbsoluteMobileCompatibilityArtifact,
3010
5638
  parseAbsoluteMobileBuildPageMetadata,
5639
+ parseAbsoluteIosLogLine,
5640
+ parseAbsoluteIosHmrLog,
5641
+ pairAbsoluteRemoteMac,
3011
5642
  normalizeAbsoluteMobileConfig,
3012
5643
  navigateAbsoluteMobilePage,
5644
+ materializeAbsoluteRemoteMacAgent,
3013
5645
  materializeAbsoluteMobileCompatibilityBundle,
3014
5646
  materializeAbsoluteMobileAssociationFiles,
3015
5647
  materializeAbsoluteCapacitorWebBundle,
3016
5648
  matchesAbsoluteMobileRoutePattern,
5649
+ loadAbsoluteNativeReleasePublisher,
3017
5650
  loadAbsoluteMobileMaterializedBundle,
5651
+ listAbsoluteRemoteMacProfiles,
5652
+ isAbsoluteIosNativeRootInput,
5653
+ installAbsoluteRemoteMacAgent,
5654
+ installAbsoluteMobileAuthEnvironment,
5655
+ inspectAbsoluteRemoteMac,
3018
5656
  inspectAbsoluteMobileRouteMetadata,
3019
5657
  hashAbsoluteMobilePropsSchema,
3020
5658
  getCurrentAbsoluteMobileProducerContext,
5659
+ getAbsoluteRemoteMacProfile,
5660
+ fingerprintAbsoluteIosNativeProject,
5661
+ fingerprintAbsoluteIosDevProject,
3021
5662
  finalizeAbsoluteMobilePage,
3022
5663
  finalizeAbsoluteMobileCompatibilityBuild,
3023
5664
  fetchAbsoluteMobilePage,
5665
+ disposeAbsoluteMobilePage,
5666
+ createAbsoluteRemoteIosDevProject,
3024
5667
  createAbsoluteMobileUpgradeResponse,
3025
5668
  createAbsoluteMobileRouteMetadataPlugin,
3026
5669
  createAbsoluteMobilePageRequest,
@@ -3030,19 +5673,29 @@ export {
3030
5673
  createAbsoluteMobileCompatibilityDispatcher,
3031
5674
  createAbsoluteMobileCompatibilityArtifact,
3032
5675
  createAbsoluteMobileBlobArtifactStore,
5676
+ createAbsoluteMobileAuthManifest,
3033
5677
  createAbsoluteMobileAssociationPlugin,
3034
5678
  createAbsoluteMobileAssociationDocuments,
5679
+ createAbsoluteIosNativeWatcher,
3035
5680
  carryForwardAbsoluteMobileCompatibilityReleases,
3036
5681
  captureAbsoluteMobileRouteGraph,
3037
5682
  buildAbsoluteMobileCompatibilityRelease,
5683
+ buildAbsoluteIosRelease,
3038
5684
  buildAbsoluteAndroidRelease,
3039
5685
  applyAbsoluteNativeDeepLinks,
3040
5686
  activateAbsoluteMobilePage,
3041
5687
  acceptsAbsoluteMobilePage,
5688
+ absoluteRemoteProjectSyncCommands,
5689
+ absoluteRemoteMacSshBase,
3042
5690
  MOBILE_PAGE_REQUEST_HEADERS,
3043
5691
  AbsoluteMobilePageProtocolError,
3044
5692
  APPLE_ASSOCIATION_PATH,
3045
5693
  ANDROID_ASSOCIATION_PATH,
5694
+ ABSOLUTE_SYNC_PACKAGE,
5695
+ ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION,
5696
+ ABSOLUTE_REMOTE_MAC_EVENT_PREFIX,
5697
+ ABSOLUTE_NATIVE_AUTH_SCOPES,
5698
+ ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
3046
5699
  ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
3047
5700
  ABSOLUTE_MOBILE_ROUTE_DETAIL,
3048
5701
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
@@ -3051,8 +5704,11 @@ export {
3051
5704
  ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
3052
5705
  ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
3053
5706
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
5707
+ ABSOLUTE_IOS_SIMULATOR_NAME,
5708
+ ABSOLUTE_IOS_RELEASE_FORMAT,
5709
+ ABSOLUTE_AUTH_PACKAGE,
3054
5710
  ABSOLUTE_ANDROID_RELEASE_FORMAT
3055
5711
  };
3056
5712
 
3057
- //# debugId=EC6A042BA6D550B864756E2164756E21
5713
+ //# debugId=712D22B2B8D89AC264756E2164756E21
3058
5714
  //# sourceMappingURL=index.js.map