@absolutejs/absolute 0.20.0-beta.0 → 0.20.0-beta.2

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 (91) hide show
  1. package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
  2. package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
  3. package/dist/angular/index.js +14 -1
  4. package/dist/angular/index.js.map +3 -3
  5. package/dist/angular/server.js +14 -1
  6. package/dist/angular/server.js.map +3 -3
  7. package/dist/build.js +206 -47
  8. package/dist/build.js.map +20 -19
  9. package/dist/cli/config/server.js +2 -2
  10. package/dist/cli/index.js +16506 -11326
  11. package/dist/dev/client/cssUtils.ts +98 -41
  12. package/dist/dev/client/handlers/html.ts +75 -15
  13. package/dist/dev/client/handlers/htmx.ts +43 -8
  14. package/dist/dev/client/handlers/react.ts +114 -32
  15. package/dist/dev/client/handlers/rebuild.ts +28 -8
  16. package/dist/dev/client/handlers/svelte.ts +33 -50
  17. package/dist/dev/client/handlers/vue.ts +36 -45
  18. package/dist/dev/client/hmrClient.ts +38 -6
  19. package/dist/dev/client/hmrTiming.ts +141 -0
  20. package/dist/dev/serverBootstrap.js +20 -11
  21. package/dist/index.js +953 -423
  22. package/dist/index.js.map +27 -23
  23. package/dist/mobile/browser.js.map +1 -1
  24. package/dist/mobile/index.js +2667 -260
  25. package/dist/mobile/index.js.map +27 -7
  26. package/dist/react/index.js +16 -3
  27. package/dist/react/index.js.map +4 -4
  28. package/dist/react/jsxDevRuntimeCompat.js +22 -2
  29. package/dist/react/jsxDevRuntimeCompat.js.map +5 -4
  30. package/dist/react/jsxRuntimeCompat.js +90 -0
  31. package/dist/react/jsxRuntimeCompat.js.map +10 -0
  32. package/dist/react/server.js +16 -3
  33. package/dist/react/server.js.map +4 -4
  34. package/dist/src/build/compileVue.d.ts +1 -1
  35. package/dist/src/build/resolveBuildMode.d.ts +7 -0
  36. package/dist/src/cli/scripts/dev.d.ts +4 -1
  37. package/dist/src/cli/scripts/mobile.d.ts +1 -0
  38. package/dist/src/cli/utils.d.ts +2 -2
  39. package/dist/src/core/build.d.ts +1 -1
  40. package/dist/src/dev/clientManager.d.ts +7 -0
  41. package/dist/src/dev/configResolver.d.ts +2 -0
  42. package/dist/src/dev/moduleMapper.d.ts +1 -1
  43. package/dist/src/dev/moduleServer.d.ts +2 -1
  44. package/dist/src/dev/pathUtils.d.ts +1 -1
  45. package/dist/src/dev/reactComponentClassifier.d.ts +1 -1
  46. package/dist/src/mobile/androidConformance.d.ts +42 -0
  47. package/dist/src/mobile/androidEmulatorController.d.ts +100 -0
  48. package/dist/src/mobile/androidNativeWatcher.d.ts +19 -0
  49. package/dist/src/mobile/androidRelease.d.ts +51 -0
  50. package/dist/src/mobile/androidWebView.d.ts +46 -0
  51. package/dist/src/mobile/associationFiles.d.ts +49 -0
  52. package/dist/src/mobile/buildMetadata.d.ts +10 -0
  53. package/dist/src/mobile/buildPipeline.d.ts +8 -0
  54. package/dist/src/mobile/buildRelease.d.ts +24 -0
  55. package/dist/src/mobile/capacitorBundle.d.ts +28 -0
  56. package/dist/src/mobile/capacitorProject.d.ts +9 -0
  57. package/dist/src/mobile/client.d.ts +1 -1
  58. package/dist/src/mobile/config.d.ts +17 -0
  59. package/dist/src/mobile/emulatorDoctor.d.ts +25 -0
  60. package/dist/src/mobile/emulatorInstaller.d.ts +41 -0
  61. package/dist/src/mobile/index.d.ts +14 -0
  62. package/dist/src/mobile/iosRelease.d.ts +61 -0
  63. package/dist/src/mobile/materializedBundle.d.ts +14 -0
  64. package/dist/src/mobile/nativeDeepLinks.d.ts +5 -0
  65. package/dist/src/mobile/releaseArtifact.d.ts +1 -0
  66. package/dist/src/mobile/releaseDoctor.d.ts +13 -0
  67. package/dist/src/mobile/releasePublisher.d.ts +130 -0
  68. package/dist/src/mobile/routeMatcher.d.ts +3 -0
  69. package/dist/src/mobile/routeMetadataTransform.d.ts +13 -0
  70. package/dist/src/mobile/shellBootstrap.d.ts +1 -0
  71. package/dist/src/mobile/transport.d.ts +31 -0
  72. package/dist/src/react/hooks/useMediaQuery.d.ts +1 -1
  73. package/dist/src/react/jsxDevRuntimeCompat.d.ts +1 -1
  74. package/dist/src/react/jsxRuntimeCompat.d.ts +7 -0
  75. package/dist/src/react/pageHandler.d.ts +3 -0
  76. package/dist/src/utils/loadConfig.d.ts +1 -0
  77. package/dist/src/utils/logger.d.ts +2 -0
  78. package/dist/src/utils/userAgentFunctions.d.ts +1 -1
  79. package/dist/svelte/index.js +14 -1
  80. package/dist/svelte/index.js.map +3 -3
  81. package/dist/svelte/server.js +14 -1
  82. package/dist/svelte/server.js.map +3 -3
  83. package/dist/types/build.d.ts +45 -0
  84. package/dist/types/cli.d.ts +2 -0
  85. package/dist/types/globals.d.ts +14 -0
  86. package/dist/types/messages.d.ts +12 -0
  87. package/dist/vue/index.js +14 -1
  88. package/dist/vue/index.js.map +3 -3
  89. package/dist/vue/server.js +14 -1
  90. package/dist/vue/server.js.map +3 -3
  91. package/package.json +35 -7
@@ -60,6 +60,105 @@ var __legacyMetadataTS = (k, v) => {
60
60
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
61
61
  var __require = import.meta.require;
62
62
 
63
+ // src/constants.ts
64
+ var ANGULAR_INIT_TIMEOUT_MS = 500, ANSI_ESCAPE_CODE = 27, ANSI_ESCAPE_LENGTH = 3, ASCII_SPACE = 32, BASE_36_RADIX = 36, BUN_BUILD_WARNING_SUPPRESSION = "wildcard sideEffects are not supported yet", TAILWIND_BUN_CSS_WARNING_PATTERN, BODY_SLICE_LENGTH = 2000, BYTES_PER_KILOBYTE = 1024, CLI_ARGS_OFFSET = 3, CSS_ERROR_RESOLVE_DELAY_MS = 50, CSS_MAX_CHECK_ATTEMPTS = 10, CSS_MAX_PARSE_TIMEOUT_MS = 500, CSS_SHEET_READY_TIMEOUT_MS = 100, DEFAULT_CHUNK_SIZE = 16384, DEFAULT_DEBOUNCE_MS = 15, DEFAULT_PORT = 3000, DEV_SERVER_RESTART_DEBOUNCE_MS = 100, DOM_UPDATE_DELAY_MS = 50, FILE_PROTOCOL_PREFIX_LENGTH = 7, FOCUS_ID_PREFIX_LENGTH = 3, FOCUS_IDX_PREFIX_LENGTH = 4, FOCUS_NAME_PREFIX_LENGTH = 5, ESLINT_STUDIO_DEFAULT_HOST = "eslint.absolute.localhost", ESLINT_STUDIO_DEFAULT_PORT = 4099, CONFIG_DEFAULT_HOST = "config.absolute.localhost", CONFIG_DEFAULT_PORT = 4099, HMR_UPDATE_TIMEOUT_MS = 2000, HOOK_SIGNATURE_LENGTH = 12, EXCLUDE_LAST_OFFSET = -1, HTTP_STATUS_OK = 200, HTTP_STATUS_BAD_REQUEST = 400, HTTP_STATUS_NOT_FOUND = 404, HOURS_IN_DAY = 24, HOURS_IN_HALF_DAY = 12, IMAGE_DEFAULT_DEVICE_SIZES, IMAGE_DEFAULT_IMAGE_SIZES, IMAGE_DEFAULT_QUALITY = 75, IMAGE_GLOB_SUFFIX_LENGTH = 2, INSTANCE_PROBE_TIMEOUT_MS = 250, MAX_ERROR_LENGTH = 200, MAX_RECONNECT_ATTEMPTS = 60, MILLISECONDS_IN_A_SECOND = 1000, MINUTES_IN_AN_HOUR = 60, SECONDS_IN_A_MINUTE = 60, MILLISECONDS_IN_A_MINUTE, MILLISECONDS_IN_A_DAY, OVERLAY_FADE_DURATION_MS = 150, PING_INTERVAL_MS = 30000, LIST_LOG_TAIL_MAX_BYTES = 65536, LIST_TUI_COLUMN_GAP = 2, LIST_TUI_DEFAULT_HEIGHT = 28, LIST_TUI_DEFAULT_WIDTH = 100, LIST_TUI_ESCAPE_SEQUENCE_TIMEOUT_MS = 30, LIST_TUI_FOOTER_LINE_COUNT = 2, LIST_TUI_MARKER_WIDTH = 2, LIST_TUI_MIN_LOG_HEIGHT = 3, LIST_TUI_MIN_URL_WIDTH = 16, LIST_TUI_RENDER_DEBOUNCE_MS = 16, LIST_TUI_STATUS_MESSAGE_TIMEOUT_MS = 4000, LIST_WATCH_REFRESH_MS = 1000, RAF_BATCH_COUNT = 3, RANDOM_ID_END_INDEX = 11, REBUILD_BATCH_DELAY_MS = 10, REBUILD_RELOAD_DELAY_MS = 200, RECONNECT_INITIAL_DELAY_MS = 500, RECONNECT_POLL_INTERVAL_MS = 300, REACT_STREAM_SLOT_FAST_DELAY_MS = 5, REACT_STREAM_SLOT_SLOW_DELAY_MS = 20, SIGINT_EXIT_CODE = 130, SIGTERM_EXIT_CODE = 143, SVELTE_CSS_LOAD_TIMEOUT_MS = 500, TIME_PRECISION = 2, TWO_THIRDS, UNFOUND_INDEX = -1, WEBSOCKET_NORMAL_CLOSURE = 1000, DEFAULT_WEBSOCKET_IDLE_TIMEOUT_SECONDS = 240, DEFAULT_HTTP_IDLE_TIMEOUT_SECONDS = 240, MAX_HTTP_IDLE_TIMEOUT_SECONDS = 255, WORKSPACE_COMMAND_ARGS_OFFSET = 3, WORKSPACE_FAILURE_LOG_PRINT_LIMIT = 30, WORKSPACE_FAILURE_RECENT_LOG_LIMIT = 60, WORKSPACE_READY_ATTEMPT_TIMEOUT_MS = 5000, WORKSPACE_READY_PROBE_INTERVAL_MS = 250, WORKSPACE_READY_TIMEOUT_MS = 30000, WORKSPACE_SHUTDOWN_TIMEOUT_MS = 1e4, WORKSPACE_TUI_DEFAULT_HEIGHT = 28, WORKSPACE_TUI_DEFAULT_WIDTH = 100, WORKSPACE_TUI_ESCAPE_SEQUENCE_TIMEOUT_MS = 30, WORKSPACE_TUI_FOOTER_LINE_COUNT = 3, WORKSPACE_TUI_MIN_LOG_HEIGHT = 3, WORKSPACE_TUI_MIN_SERVICE_NAME_WIDTH = 7, WORKSPACE_TUI_MIN_TARGET_WIDTH = 8, WORKSPACE_TUI_MIN_WRAP_WIDTH = 12, WORKSPACE_TUI_PROMPT_CURSOR_OFFSET = 3, WORKSPACE_TUI_RECENT_LOG_LIMIT = 40, WORKSPACE_TUI_RENDER_DEBOUNCE_MS = 16, WORKSPACE_TUI_STATUS_WIDTH = 10, WORKSPACE_TUI_TARGET_PADDING_WIDTH = 6, WORKSPACE_TUI_VISIBILITY_WIDTH = 8;
65
+ var init_constants = __esm(() => {
66
+ TAILWIND_BUN_CSS_WARNING_PATTERN = /invalid @ rule encountered: '@(theme|tailwind|source|utility|variant|custom-variant|apply|reference|plugin|config)'/;
67
+ IMAGE_DEFAULT_DEVICE_SIZES = [
68
+ 640,
69
+ 750,
70
+ 828,
71
+ 1080,
72
+ 1200,
73
+ 1920,
74
+ 2048,
75
+ 3840
76
+ ];
77
+ IMAGE_DEFAULT_IMAGE_SIZES = [16, 32, 48, 64, 96, 128, 256, 384];
78
+ MILLISECONDS_IN_A_MINUTE = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE;
79
+ MILLISECONDS_IN_A_DAY = MILLISECONDS_IN_A_SECOND * SECONDS_IN_A_MINUTE * MINUTES_IN_AN_HOUR * HOURS_IN_DAY;
80
+ TWO_THIRDS = 2 / 3;
81
+ });
82
+
83
+ // src/utils/getDurationString.ts
84
+ var getDurationString = (duration) => {
85
+ let durationString;
86
+ if (duration < MILLISECONDS_IN_A_SECOND) {
87
+ durationString = `${duration.toFixed(TIME_PRECISION)}ms`;
88
+ } else if (duration < MILLISECONDS_IN_A_MINUTE) {
89
+ durationString = `${(duration / MILLISECONDS_IN_A_SECOND).toFixed(TIME_PRECISION)}s`;
90
+ } else {
91
+ const totalSeconds = Math.round(duration / MILLISECONDS_IN_A_SECOND);
92
+ const minutes = Math.floor(totalSeconds / 60);
93
+ const seconds = totalSeconds % 60;
94
+ durationString = seconds === 0 ? `${minutes}m` : `${minutes}m ${seconds}s`;
95
+ }
96
+ return durationString;
97
+ };
98
+ var init_getDurationString = __esm(() => {
99
+ init_constants();
100
+ });
101
+
102
+ // src/utils/startupBanner.ts
103
+ var colors, MONTHS, formatTimestamp = () => {
104
+ const now = new Date;
105
+ const month = MONTHS[now.getMonth()];
106
+ const day = now.getDate().toString().padStart(2, "0");
107
+ let hours = now.getHours();
108
+ const minutes = now.getMinutes().toString().padStart(2, "0");
109
+ const seconds = now.getSeconds().toString().padStart(2, "0");
110
+ const ampm = hours >= HOURS_IN_HALF_DAY ? "PM" : "AM";
111
+ hours = hours % HOURS_IN_HALF_DAY || HOURS_IN_HALF_DAY;
112
+ return `${month} ${day} ${hours}:${minutes}:${seconds} ${ampm}`;
113
+ }, startupBanner = (options) => {
114
+ const {
115
+ version,
116
+ readyDuration,
117
+ buildDuration,
118
+ port,
119
+ host,
120
+ networkUrl,
121
+ protocol = "http"
122
+ } = options;
123
+ const name = `${colors.cyan}${colors.bold}ABSOLUTEJS${colors.reset}`;
124
+ const ver = `${colors.dim}v${version}${colors.reset}`;
125
+ const time = `${colors.dim}ready in${colors.reset} ${colors.bold}${getDurationString(readyDuration)}${colors.reset}`;
126
+ const build = typeof buildDuration === "number" && Number.isFinite(buildDuration) ? ` ${colors.dim}(build ${getDurationString(buildDuration)})${colors.reset}` : "";
127
+ console.log("");
128
+ console.log(` ${name} ${ver} ${time}${build}`);
129
+ console.log("");
130
+ console.log(` ${colors.green}\u279C${colors.reset} ${colors.bold}Local:${colors.reset} ${protocol}://${host === "0.0.0.0" ? "localhost" : host}:${port}/`);
131
+ if (networkUrl) {
132
+ console.log(` ${colors.green}\u279C${colors.reset} ${colors.bold}Network:${colors.reset} ${networkUrl}`);
133
+ }
134
+ console.log("");
135
+ };
136
+ var init_startupBanner = __esm(() => {
137
+ init_constants();
138
+ init_getDurationString();
139
+ colors = {
140
+ bold: "\x1B[1m",
141
+ cyan: "\x1B[36m",
142
+ dim: "\x1B[2m",
143
+ green: "\x1B[32m",
144
+ reset: "\x1B[0m"
145
+ };
146
+ MONTHS = [
147
+ "Jan",
148
+ "Feb",
149
+ "Mar",
150
+ "Apr",
151
+ "May",
152
+ "Jun",
153
+ "Jul",
154
+ "Aug",
155
+ "Sep",
156
+ "Oct",
157
+ "Nov",
158
+ "Dec"
159
+ ];
160
+ });
161
+
63
162
  // src/mobile/artifactStore.ts
64
163
  import { createHash as createHash2 } from "crypto";
65
164
  import {
@@ -152,6 +251,7 @@ var parseCompatibilityPage = (value) => {
152
251
  }
153
252
  return {
154
253
  bundleHash: readString(value.bundleHash, "page.bundleHash"),
254
+ bundlePath: readString(value.bundlePath, "page.bundlePath"),
155
255
  contract: readString(value.contract, "page.contract"),
156
256
  framework: value.framework,
157
257
  pageId: readString(value.pageId, "page.pageId"),
@@ -189,6 +289,7 @@ var validateProducerModule = (module) => {
189
289
  };
190
290
  var normalizePage = (page) => ({
191
291
  bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
292
+ bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
192
293
  contract: requireNonEmpty(page.contract, "page.contract"),
193
294
  framework: page.framework,
194
295
  pageId: requireNonEmpty(page.pageId, "page.pageId"),
@@ -513,183 +614,1546 @@ var verifyAbsoluteMobileCompatibilityProducer = async (release, maxProducerBytes
513
614
  }
514
615
  return release;
515
616
  };
516
- // src/mobile/producerContextState.ts
517
- var ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY = Symbol.for("absolutejs.mobileProducerAsyncLocalStorage");
518
- var frameworks2 = new Set([
519
- "angular",
520
- "ember",
521
- "html",
522
- "htmx",
523
- "react",
524
- "svelte",
525
- "vue"
526
- ]);
527
- var isCompatibilityPage = (value) => typeof value === "object" && value !== null && ("bundleHash" in value) && typeof value.bundleHash === "string" && ("contract" in value) && typeof value.contract === "string" && ("framework" in value) && typeof value.framework === "string" && frameworks2.has(value.framework) && ("pageId" in value) && typeof value.pageId === "string" && ("propsSchemaHash" in value) && typeof value.propsSchemaHash === "string";
528
- var getCurrentAbsoluteMobileProducerContext = () => {
529
- const value = Reflect.get(globalThis, ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY);
530
- if (typeof value !== "object" || value === null || !("getStore" in value) || typeof value.getStore !== "function") {
531
- return;
617
+ // src/mobile/androidRelease.ts
618
+ import { createHash as createHash4 } from "crypto";
619
+ import {
620
+ access as access3,
621
+ copyFile as copyFile2,
622
+ mkdir as mkdir3,
623
+ mkdtemp as mkdtemp2,
624
+ readFile as readFile4,
625
+ rename as rename4,
626
+ rm as rm3,
627
+ stat,
628
+ writeFile as writeFile4
629
+ } from "fs/promises";
630
+ import { dirname as dirname2, isAbsolute as isAbsolute2, join as join4, relative as relative3, resolve as resolve3, sep as sep2 } from "path";
631
+
632
+ // src/mobile/emulatorDoctor.ts
633
+ import { homedir } from "os";
634
+ import { join as join2 } from "path";
635
+
636
+ // src/cli/utils.ts
637
+ import { existsSync, readFileSync } from "fs";
638
+ init_constants();
639
+ init_startupBanner();
640
+ var isWSLEnvironment = () => {
641
+ try {
642
+ const release = readFileSync("/proc/version", "utf-8");
643
+ return /microsoft|wsl/i.test(release);
644
+ } catch {
645
+ return false;
532
646
  }
533
- const context = value.getStore();
534
- if (typeof context !== "object" || context === null || !("page" in context) || !isCompatibilityPage(context.page) || !("releaseId" in context) || typeof context.releaseId !== "string") {
647
+ };
648
+
649
+ // src/mobile/emulatorDoctor.ts
650
+ var windowsPathToWsl = (path) => {
651
+ const match = /^([a-z]):[\\/](.*)$/i.exec(path.trim());
652
+ if (!match)
653
+ return path.trim().replaceAll("\\", "/");
654
+ return `/mnt/${match[1]?.toLowerCase()}/${match[2]?.replaceAll("\\", "/")}`;
655
+ };
656
+ var windowsLocalAppDataFromWsl = () => {
657
+ try {
658
+ const result = Bun.spawnSync(["cmd.exe", "/d", "/c", "echo", "%LOCALAPPDATA%"], { stderr: "ignore", stdout: "pipe" });
659
+ if (result.exitCode !== 0)
660
+ return;
661
+ const output = result.stdout.toString().trim();
662
+ if (!output || output === "%LOCALAPPDATA%")
663
+ return;
664
+ return windowsPathToWsl(output);
665
+ } catch {
535
666
  return;
536
667
  }
537
- return { page: context.page, releaseId: context.releaseId };
668
+ };
669
+ var absoluteManagedAndroidSdkRoot = (host, env = process.env) => {
670
+ if (host === "windows") {
671
+ return join2(env.LOCALAPPDATA ?? join2(homedir(), "AppData", "Local"), "AbsoluteJS", "Android", "Sdk");
672
+ }
673
+ if (host === "wsl") {
674
+ const localAppData = windowsLocalAppDataFromWsl();
675
+ if (localAppData) {
676
+ return join2(localAppData, "AbsoluteJS", "Android", "Sdk");
677
+ }
678
+ }
679
+ return join2(homedir(), ".absolutejs", "android-sdk");
680
+ };
681
+ var detectAbsoluteMobileHost = (platform = process.platform, wsl = isWSLEnvironment()) => {
682
+ if (platform === "darwin")
683
+ return "macos";
684
+ if (platform === "win32")
685
+ return "windows";
686
+ if (platform === "linux" && wsl)
687
+ return "wsl";
688
+ return "linux";
538
689
  };
539
690
 
540
- // src/mobile/pageProtocol.ts
541
- var ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE = "application/vnd.absolute.page+json";
542
- var ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION = 1;
543
- var BAD_REQUEST_STATUS = 400;
544
- var OK_STATUS = 200;
545
- var SERVER_ERROR_STATUS = 500;
546
- var UPGRADE_REQUIRED_STATUS = 426;
547
- var MOBILE_PAGE_REQUEST_HEADERS = {
548
- appBuild: "x-absolute-mobile-app-build",
549
- pageBundle: "x-absolute-mobile-page-bundle",
550
- pageContracts: "x-absolute-mobile-page-contracts",
551
- pageId: "x-absolute-mobile-page-id",
552
- protocol: "x-absolute-mobile-protocol",
553
- runtime: "x-absolute-mobile-runtime"
691
+ // src/mobile/androidEmulatorController.ts
692
+ import {
693
+ access as access2,
694
+ copyFile,
695
+ lstat,
696
+ mkdir as mkdir2,
697
+ readFile as readFile3,
698
+ readdir as readdir2,
699
+ readlink,
700
+ realpath,
701
+ rename as rename3,
702
+ rm as rm2,
703
+ writeFile as writeFile3
704
+ } from "fs/promises";
705
+ import { createHash as createHash3, randomUUID } from "crypto";
706
+ import {
707
+ dirname,
708
+ isAbsolute,
709
+ join as join3,
710
+ relative as relative2,
711
+ resolve as resolve2,
712
+ sep,
713
+ win32
714
+ } from "path";
715
+
716
+ // src/mobile/capacitorProject.ts
717
+ import { access, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
718
+ import { relative, resolve } from "path";
719
+ var CONFIG_FILE = "capacitor.config.ts";
720
+ var portableRelative = (root, path) => relative(root, path).replaceAll("\\", "/");
721
+ var capacitorConfigSource = (config, projectRoot) => `import type { CapacitorConfig } from '@capacitor/cli';
722
+
723
+ const config: CapacitorConfig = {
724
+ appId: ${JSON.stringify(config.appId)},
725
+ appName: ${JSON.stringify(config.appName)},
726
+ webDir: ${JSON.stringify(portableRelative(projectRoot, config.bundleDirectory))},
727
+ android: {
728
+ path: ${JSON.stringify(portableRelative(projectRoot, `${config.nativeProjectDirectory}/android`))}
729
+ },
730
+ ios: {
731
+ path: ${JSON.stringify(portableRelative(projectRoot, `${config.nativeProjectDirectory}/ios`))}
732
+ }
554
733
  };
555
- var parseMediaType = (value) => value.split(";", 1)[0]?.trim().toLowerCase();
556
- var acceptsAbsoluteMobilePage = (request) => {
557
- if (!request)
734
+
735
+ export default config;
736
+ `;
737
+ var exists = async (path) => {
738
+ try {
739
+ await access(path);
740
+ return true;
741
+ } catch {
558
742
  return false;
559
- return (request.headers.get("accept") ?? "").split(",").some((value) => parseMediaType(value) === ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE);
743
+ }
560
744
  };
561
- var readRequiredHeader = (request, name) => {
562
- const value = request.headers.get(name)?.trim();
563
- return value ? value : undefined;
745
+ var writeAbsoluteCapacitorConfig = async (config, options) => {
746
+ const projectRoot = resolve(options.projectRoot);
747
+ const destination = resolve(projectRoot, CONFIG_FILE);
748
+ const source = capacitorConfigSource(config, projectRoot);
749
+ if (await exists(destination)) {
750
+ const current = await readFile2(destination, "utf8");
751
+ if (current === source)
752
+ return { changed: false, path: destination };
753
+ if (!options.force) {
754
+ throw new TypeError(`${CONFIG_FILE} already exists and differs; rerun with --force only after reviewing it.`);
755
+ }
756
+ }
757
+ const temporary = `${destination}.${crypto.randomUUID()}.tmp`;
758
+ await writeFile2(temporary, source, { flag: "wx" });
759
+ await rename2(temporary, destination);
760
+ return { changed: true, path: destination };
564
761
  };
565
- var parseAbsoluteMobilePageRequest = (request) => {
566
- if (!request || !acceptsAbsoluteMobilePage(request)) {
567
- return { kind: "not-mobile" };
762
+
763
+ // src/mobile/androidEmulatorController.ts
764
+ init_getDurationString();
765
+ var HASH_RADIX = 16;
766
+ var EXECUTABLE_MODE_MASK = 73;
767
+ var NATIVE_PUBLIC_PATH_SEGMENTS = 5;
768
+ var CAPACITOR_PROJECT_DIRECTORY_PATTERN = /project\(['"](:[^'"]+)['"]\)\.projectDir\s*=\s*new File\(['"]([^'"]+)['"]\)/gu;
769
+ var pathExists = async (path) => {
770
+ try {
771
+ await access2(path);
772
+ return true;
773
+ } catch {
774
+ return false;
568
775
  }
569
- const protocolValue = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.protocol);
570
- const runtime = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.runtime);
571
- const appBuild = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.appBuild);
572
- const pageBundle = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.pageBundle);
573
- const pageContractsValue = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.pageContracts);
574
- const pageId = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.pageId);
575
- if (!protocolValue || !/^\d+$/.test(protocolValue)) {
776
+ };
777
+ var forwardCommandOutput = (stream, destination) => stream.pipeTo(new WritableStream({
778
+ write: (chunk) => {
779
+ destination.write(chunk);
780
+ }
781
+ }));
782
+ var runCommand = async (command, options = {}) => {
783
+ const subprocess = Bun.spawn(command, {
784
+ cwd: options.cwd,
785
+ env: options.env,
786
+ signal: options.signal,
787
+ stderr: "pipe",
788
+ stdin: "ignore",
789
+ stdout: "pipe"
790
+ });
791
+ const [exitCode] = await Promise.all([
792
+ subprocess.exited,
793
+ forwardCommandOutput(subprocess.stdout, process.stdout),
794
+ forwardCommandOutput(subprocess.stderr, process.stderr)
795
+ ]);
796
+ return exitCode;
797
+ };
798
+ var captureCommand = (command, options = {}) => {
799
+ try {
800
+ const result = Bun.spawnSync(command, {
801
+ cwd: options.cwd,
802
+ env: options.env,
803
+ stderr: "pipe",
804
+ stdout: "pipe"
805
+ });
576
806
  return {
577
- kind: "invalid",
578
- message: `Missing or invalid ${MOBILE_PAGE_REQUEST_HEADERS.protocol} header.`
807
+ exitCode: result.exitCode,
808
+ stderr: result.stderr.toString(),
809
+ stdout: result.stdout.toString()
579
810
  };
580
- }
581
- if (!runtime) {
811
+ } catch (error) {
582
812
  return {
583
- kind: "invalid",
584
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.runtime} header.`
813
+ exitCode: 1,
814
+ stderr: error instanceof Error ? error.message : String(error),
815
+ stdout: ""
585
816
  };
586
817
  }
587
- if (!appBuild) {
588
- return {
589
- kind: "invalid",
590
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.appBuild} header.`
591
- };
818
+ };
819
+ var ASCII_ESCAPE = 27;
820
+ var ANSI_SEQUENCE_PATTERN = new RegExp(`${String.fromCharCode(ASCII_ESCAPE)}(?:\\[[0-?]*[ -/]*[@-~]|[@-_])`, "gu");
821
+ var requireSuccess = async (command, label, run, options) => {
822
+ const exitCode = await run(command, options);
823
+ throwIfAborted(options?.signal);
824
+ if (exitCode !== 0)
825
+ throw new Error(`${label} failed (exit ${exitCode}).`);
826
+ };
827
+ var throwIfAborted = (signal) => {
828
+ if (!signal?.aborted)
829
+ return;
830
+ throw new DOMException("Android development startup was cancelled.", "AbortError");
831
+ };
832
+ var nativeDependencySources = async (nativeDirectory) => {
833
+ const settings = await readFile3(join3(nativeDirectory, "capacitor.settings.gradle"), "utf8");
834
+ const pattern = new RegExp(CAPACITOR_PROJECT_DIRECTORY_PATTERN.source, CAPACITOR_PROJECT_DIRECTORY_PATTERN.flags);
835
+ const dependencies = [...settings.matchAll(pattern)].map((match) => ({
836
+ name: (match[1] ?? "").slice(1).replaceAll(/[^a-zA-Z0-9_.-]/gu, "_"),
837
+ source: resolve2(nativeDirectory, match[2] ?? "")
838
+ }));
839
+ if (dependencies.length === 0) {
840
+ throw new Error("Capacitor Android settings did not declare any native dependencies.");
592
841
  }
593
- if (!pageBundle) {
842
+ return { dependencies, settings };
843
+ };
844
+ var shouldIgnoreNativePath = (relativePath, ignorePublicBundle) => {
845
+ const parts = relativePath.split(sep);
846
+ if (parts.includes(".gradle") || parts.includes("build") || parts.includes(".absolutejs-dependencies")) {
847
+ return true;
848
+ }
849
+ return ignorePublicBundle && parts.slice(0, NATIVE_PUBLIC_PATH_SEGMENTS).join("/") === "app/src/main/assets/public";
850
+ };
851
+ var collectNativePath = async (root, label, path, isDirectory, isFile, isSymbolicLink, ignorePublicBundle) => {
852
+ const relativePath = relative2(root, path);
853
+ if (shouldIgnoreNativePath(relativePath, ignorePublicBundle))
854
+ return [];
855
+ const identity = `${label}:${relativePath.split(sep).join("/")}\x00`;
856
+ if (isDirectory) {
857
+ return collectNativeDirectory(root, label, path, ignorePublicBundle);
858
+ }
859
+ if (isSymbolicLink) {
860
+ return [`link\x00${identity}${await readlink(path)}\x00`];
861
+ }
862
+ if (!isFile)
863
+ return [];
864
+ const [metadata, contents] = await Promise.all([
865
+ lstat(path),
866
+ readFile3(path)
867
+ ]);
868
+ const contentDigest = createHash3("sha256").update(contents).digest("hex");
869
+ return [
870
+ `file\x00${identity}${metadata.mode & EXECUTABLE_MODE_MASK}\x00${contentDigest}\x00`
871
+ ];
872
+ };
873
+ var collectNativeDirectory = async (root, label, directory, ignorePublicBundle) => {
874
+ const entries = await readdir2(directory, { withFileTypes: true });
875
+ entries.sort((left, right) => left.name.localeCompare(right.name));
876
+ const records = await Promise.all(entries.map((entry) => collectNativePath(root, label, join3(directory, entry.name), entry.isDirectory(), entry.isFile(), entry.isSymbolicLink(), ignorePublicBundle)));
877
+ return records.flat();
878
+ };
879
+ var hashNativeTree = async (root, label, ignorePublicBundle) => {
880
+ const resolvedRoot = await realpath(root);
881
+ const records = await collectNativeDirectory(resolvedRoot, label, resolvedRoot, ignorePublicBundle);
882
+ return createHash3("sha256").update(records.join("")).digest("hex");
883
+ };
884
+ var fingerprintAbsoluteAndroidNativeProject = async (project) => {
885
+ const { dependencies } = await nativeDependencySources(project.nativeDirectory);
886
+ const roots = [
887
+ {
888
+ ignorePublicBundle: true,
889
+ label: "android",
890
+ source: project.nativeDirectory
891
+ },
892
+ ...dependencies.sort((left, right) => left.name.localeCompare(right.name)).map((dependency) => ({
893
+ ignorePublicBundle: false,
894
+ label: `dependency:${dependency.name}`,
895
+ source: dependency.source
896
+ }))
897
+ ];
898
+ const treeDigests = await Promise.all(roots.map((root) => hashNativeTree(root.source, root.label, root.ignorePublicBundle)));
899
+ return createHash3("sha256").update(treeDigests.join("\x00")).digest("hex");
900
+ };
901
+ var windowsPathFromWsl = (path, capture) => {
902
+ const result = capture(["wslpath", "-w", path]);
903
+ if (result.exitCode !== 0 || !result.stdout.trim()) {
904
+ throw new Error(`Could not translate WSL path for Windows: ${path}`);
905
+ }
906
+ return result.stdout.trim();
907
+ };
908
+ var mirroredCapacitorDependencies = async (project, capture) => {
909
+ const settingsPath = join3(project.nativeDirectory, "capacitor.settings.gradle");
910
+ const settings = await readFile3(settingsPath, "utf8");
911
+ const dependencies = [];
912
+ const rewrittenSettings = settings.replace(CAPACITOR_PROJECT_DIRECTORY_PATTERN, (_statement, projectName, sourcePath) => {
913
+ const name = projectName.slice(1).replaceAll(/[^a-zA-Z0-9_.-]/gu, "_");
914
+ const source = resolve2(project.nativeDirectory, sourcePath);
915
+ dependencies.push({
916
+ name,
917
+ windowsSource: windowsPathFromWsl(source, capture)
918
+ });
919
+ return `project('${projectName}').projectDir = new File('./.absolutejs-dependencies/${name}')`;
920
+ });
921
+ if (dependencies.length === 0) {
922
+ throw new Error("Capacitor Android settings did not declare any native dependencies.");
923
+ }
924
+ return { dependencies, rewrittenSettings };
925
+ };
926
+ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task, gradleArguments) => {
927
+ const sourceDirectory = Buffer.from(windowsSource, "utf8").toString("base64");
928
+ const buildDirectory = Buffer.from(windowsDirectory, "utf8").toString("base64");
929
+ const androidRoot = Buffer.from(windowsAndroidRoot, "utf8").toString("base64");
930
+ const dependencyData = Buffer.from(JSON.stringify(dependencies), "utf8").toString("base64");
931
+ const settingsData = Buffer.from(rewrittenSettings, "utf8").toString("base64");
932
+ const argumentsData = Buffer.from(JSON.stringify(gradleArguments), "utf8").toString("base64");
933
+ const source = [
934
+ "$ErrorActionPreference = 'Stop'",
935
+ `$source = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${sourceDirectory}'))`,
936
+ `$directory = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${buildDirectory}'))`,
937
+ `$androidHome = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${androidRoot}'))`,
938
+ `$dependencies = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${dependencyData}')) | ConvertFrom-Json`,
939
+ `$settings = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${settingsData}'))`,
940
+ `$gradleArguments = @([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${argumentsData}')) | ConvertFrom-Json)`,
941
+ "$env:ANDROID_HOME = $androidHome",
942
+ "$env:ANDROID_SDK_ROOT = $androidHome",
943
+ "New-Item -ItemType Directory -Force -Path $directory | Out-Null",
944
+ "& robocopy.exe $source $directory /MIR /XD .gradle build .absolutejs-dependencies /NFL /NDL /NJH /NJS /NP",
945
+ "$copyExit = $LASTEXITCODE",
946
+ "if ($copyExit -ge 8) { exit $copyExit }",
947
+ "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 } }",
948
+ "[IO.File]::WriteAllText((Join-Path $directory 'capacitor.settings.gradle'), $settings)",
949
+ "$wrapper = Join-Path $directory 'gradlew.bat'",
950
+ `& $wrapper --no-daemon --console=plain -p $directory @gradleArguments ${task}`,
951
+ "if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }",
952
+ "exit 0"
953
+ ].join("; ");
954
+ return Buffer.from(source, "utf16le").toString("base64");
955
+ };
956
+ var gradleArtifactPath = (nativeDirectory, task, windows = false) => {
957
+ const pathJoin = windows ? win32.join : join3;
958
+ if (task === "assembleDebug") {
959
+ return pathJoin(nativeDirectory, "app", "build", "outputs", "apk", "debug", "app-debug.apk");
960
+ }
961
+ if (task === "assembleRelease") {
962
+ return pathJoin(nativeDirectory, "app", "build", "outputs", "apk", "release", "app-release.apk");
963
+ }
964
+ return pathJoin(nativeDirectory, "app", "build", "outputs", "bundle", "release", "app-release.aab");
965
+ };
966
+ var resolveGradleArtifactPath = async (nativeDirectory, task) => {
967
+ const primary = gradleArtifactPath(nativeDirectory, task);
968
+ if (task !== "assembleRelease" || await pathExists(primary)) {
969
+ return primary;
970
+ }
971
+ return join3(nativeDirectory, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk");
972
+ };
973
+ var buildAbsoluteAndroidGradleArtifact = async (options) => {
974
+ const { project, task } = options;
975
+ const capture = options.capture ?? captureCommand;
976
+ const run = options.run ?? runCommand;
977
+ const env = options.env ?? process.env;
978
+ const gradleArguments = options.gradleArguments ?? [];
979
+ if (project.host === "wsl") {
980
+ const windowsSource = windowsPathFromWsl(project.nativeDirectory, capture);
981
+ const buildId = Bun.hash(project.projectRoot).toString(HASH_RADIX);
982
+ const managedBuildDirectory = resolve2(project.androidRoot, "..", "..", "Builds", `${project.config.appId}-${buildId}`);
983
+ const windowsDirectory = windowsPathFromWsl(managedBuildDirectory, capture);
984
+ const windowsAndroidRoot = windowsPathFromWsl(project.androidRoot, capture);
985
+ const { dependencies, rewrittenSettings } = await mirroredCapacitorDependencies(project, capture);
986
+ await requireSuccess([
987
+ "powershell.exe",
988
+ "-NoProfile",
989
+ "-EncodedCommand",
990
+ encodedWindowsGradleCommand(windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task, gradleArguments)
991
+ ], "Android Gradle build", run, { env, signal: options.signal });
992
+ const artifactPath2 = await resolveGradleArtifactPath(managedBuildDirectory, task);
993
+ const unsigned = artifactPath2.endsWith("-unsigned.apk");
594
994
  return {
595
- kind: "invalid",
596
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageBundle} header.`
995
+ artifactPath: artifactPath2,
996
+ installPath: unsigned ? win32.join(windowsDirectory, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk") : gradleArtifactPath(windowsDirectory, task, true)
597
997
  };
598
998
  }
599
- if (!pageContractsValue) {
999
+ const wrapper = project.host === "windows" ? "gradlew.bat" : "./gradlew";
1000
+ await requireSuccess([wrapper, "--no-daemon", "--console=plain", ...gradleArguments, task], "Android Gradle build", run, { cwd: project.nativeDirectory, env, signal: options.signal });
1001
+ const artifactPath = await resolveGradleArtifactPath(project.nativeDirectory, task);
1002
+ return { artifactPath, installPath: artifactPath };
1003
+ };
1004
+
1005
+ // src/mobile/androidRelease.ts
1006
+ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1;
1007
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1008
+ var requireManifest = (value) => {
1009
+ if (!isRecord(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
1010
+ throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
1011
+ }
1012
+ return {
1013
+ appBuild: value.appBuild,
1014
+ appId: value.appId,
1015
+ runtime: value.runtime
1016
+ };
1017
+ };
1018
+ var pathExists2 = async (path) => {
1019
+ try {
1020
+ await access3(path);
1021
+ return true;
1022
+ } catch {
1023
+ return false;
1024
+ }
1025
+ };
1026
+ var defaultCapture = (command, options = {}) => {
1027
+ try {
1028
+ const result = Bun.spawnSync(command, {
1029
+ cwd: options.cwd,
1030
+ env: options.env,
1031
+ stderr: "pipe",
1032
+ stdin: "ignore",
1033
+ stdout: "pipe"
1034
+ });
600
1035
  return {
601
- kind: "invalid",
602
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageContracts} header.`
1036
+ exitCode: result.exitCode,
1037
+ stderr: result.stderr.toString(),
1038
+ stdout: result.stdout.toString()
603
1039
  };
604
- }
605
- if (!pageId) {
1040
+ } catch (error) {
606
1041
  return {
607
- kind: "invalid",
608
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageId} header.`
1042
+ exitCode: 1,
1043
+ stderr: error instanceof Error ? error.message : String(error),
1044
+ stdout: ""
609
1045
  };
610
1046
  }
611
- const pageContracts = [
612
- ...new Set(pageContractsValue.split(",").map((value) => value.trim()).filter(Boolean))
613
- ];
614
- if (pageContracts.length === 0) {
615
- return {
616
- kind: "invalid",
617
- message: `${MOBILE_PAGE_REQUEST_HEADERS.pageContracts} must contain at least one contract.`
1047
+ };
1048
+ var verifyAabSignature = (artifactPath, capture, jarsigner) => {
1049
+ const executable = jarsigner === undefined ? Bun.which("jarsigner") : jarsigner;
1050
+ if (!executable)
1051
+ return null;
1052
+ const result = capture([
1053
+ executable,
1054
+ "-J-Duser.language=en",
1055
+ "-J-Duser.country=US",
1056
+ "-verify",
1057
+ artifactPath
1058
+ ]);
1059
+ return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
1060
+ };
1061
+ var sha256File = async (path) => createHash4("sha256").update(await readFile4(path)).digest("hex");
1062
+ var safeOutputDirectory = (projectRoot, requested) => {
1063
+ const root = resolve3(projectRoot);
1064
+ const output = resolve3(root, requested ?? ".absolutejs/mobile/releases/android");
1065
+ const projectRelative = relative3(root, output);
1066
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep2}`) || isAbsolute2(projectRelative)) {
1067
+ throw new TypeError("mobile build --outdir must remain inside the project.");
1068
+ }
1069
+ return output;
1070
+ };
1071
+ var installRelease = async (artifactPath, metadata, outputRoot) => {
1072
+ const releaseRoot = join4(outputRoot, metadata.releaseId);
1073
+ const artifactName = "app-release.aab";
1074
+ const destination = join4(releaseRoot, artifactName);
1075
+ if (await pathExists2(releaseRoot)) {
1076
+ const existing = requireManifestIdentity(JSON.parse(await readFile4(join4(releaseRoot, "release.json"), "utf8")), metadata);
1077
+ const [installedBytes, installedSha256] = await Promise.all([
1078
+ stat(destination).then(({ size }) => size),
1079
+ sha256File(destination)
1080
+ ]);
1081
+ if (installedBytes !== metadata.bytes || installedSha256 !== metadata.sha256) {
1082
+ throw new TypeError(`Immutable Android release ${metadata.releaseId} artifact is missing or modified.`);
1083
+ }
1084
+ return { artifactPath: destination, metadata: existing, releaseRoot };
1085
+ }
1086
+ await mkdir3(dirname2(releaseRoot), { recursive: true });
1087
+ const staging = await mkdtemp2(join4(dirname2(releaseRoot), ".android-stage-"));
1088
+ try {
1089
+ await copyFile2(artifactPath, join4(staging, artifactName));
1090
+ const complete = {
1091
+ ...metadata,
1092
+ artifact: artifactName
618
1093
  };
1094
+ await writeFile4(join4(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
1095
+ `, { flag: "wx" });
1096
+ await rename4(staging, releaseRoot);
1097
+ return { artifactPath: destination, metadata: complete, releaseRoot };
1098
+ } finally {
1099
+ await rm3(staging, { force: true, recursive: true }).catch(() => {
1100
+ return;
1101
+ });
619
1102
  }
620
- return {
621
- client: {
622
- appBuild,
623
- pageBundle,
624
- pageContracts,
625
- pageId,
626
- protocol: Number(protocolValue),
627
- runtime
628
- },
629
- kind: "mobile"
630
- };
631
1103
  };
632
- var responseHeaders = () => {
633
- const headers = new Headers({
634
- "cache-control": "no-store",
635
- "content-type": `${ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE}; version=${ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION}`
636
- });
637
- headerVaryValues.forEach((value) => headers.append("vary", value));
638
- return headers;
1104
+ var requireManifestIdentity = (value, expected) => {
1105
+ if (!isRecord(value)) {
1106
+ throw new TypeError("Existing Android release metadata is invalid.");
1107
+ }
1108
+ const { artifact } = value;
1109
+ if (artifact !== "app-release.aab" || Object.entries(expected).some(([key, expectedValue]) => Reflect.get(value, key) !== expectedValue)) {
1110
+ throw new TypeError(`Immutable Android release ${expected.releaseId} does not match its content.`);
1111
+ }
1112
+ return { ...expected, artifact };
639
1113
  };
640
- var headerVaryValues = [
641
- "Accept",
642
- MOBILE_PAGE_REQUEST_HEADERS.protocol,
643
- MOBILE_PAGE_REQUEST_HEADERS.runtime,
644
- MOBILE_PAGE_REQUEST_HEADERS.appBuild,
645
- MOBILE_PAGE_REQUEST_HEADERS.pageBundle,
646
- MOBILE_PAGE_REQUEST_HEADERS.pageContracts,
647
- MOBILE_PAGE_REQUEST_HEADERS.pageId
648
- ];
649
- var envelopeResponse = (response, status) => new Response(JSON.stringify({
650
- protocol: ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
651
- response
652
- }), { headers: responseHeaders(), status });
653
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
654
- var normalizeJsonValue = (value) => {
655
- const serialized = JSON.stringify(value);
656
- if (serialized === undefined) {
657
- throw new TypeError("Mobile page props must be JSON-serializable.");
1114
+ var buildAbsoluteAndroidRelease = async (options) => {
1115
+ if (options.versionCode !== undefined && options.prepareVersionCode) {
1116
+ throw new TypeError("Android release versionCode and prepareVersionCode cannot be combined.");
1117
+ }
1118
+ if (options.versionCode !== undefined && (!Number.isSafeInteger(options.versionCode) || options.versionCode < 1 || options.versionCode > 2100000000)) {
1119
+ throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
1120
+ }
1121
+ const projectRoot = resolve3(options.projectRoot);
1122
+ const host = options.host ?? detectAbsoluteMobileHost();
1123
+ const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
1124
+ const nativeDirectory = join4(options.config.nativeProjectDirectory, "android");
1125
+ const manifest = requireManifest(JSON.parse(await readFile4(join4(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1126
+ if (manifest.appId !== options.config.appId) {
1127
+ throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1128
+ }
1129
+ let { versionCode } = options;
1130
+ if (options.prepareVersionCode) {
1131
+ const nativeFingerprint = await fingerprintAbsoluteAndroidNativeProject({ nativeDirectory });
1132
+ const buildIdentity = createHash4("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
1133
+ versionCode = await options.prepareVersionCode(buildIdentity);
1134
+ }
1135
+ if (versionCode !== undefined && (!Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000)) {
1136
+ throw new TypeError("Android versionCode must be an integer from 1 through 2100000000.");
1137
+ }
1138
+ const { artifactPath } = await buildAbsoluteAndroidGradleArtifact({
1139
+ capture: options.capture,
1140
+ gradleArguments: versionCode === undefined ? [] : [`-Pandroid.injected.version.code=${versionCode}`],
1141
+ project: {
1142
+ androidRoot,
1143
+ config: options.config,
1144
+ host,
1145
+ nativeDirectory,
1146
+ projectRoot
1147
+ },
1148
+ run: options.run,
1149
+ task: "bundleRelease"
1150
+ });
1151
+ if (!await pathExists2(artifactPath)) {
1152
+ throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
658
1153
  }
659
- const parsed = JSON.parse(serialized);
660
- if (!isRecord(parsed)) {
661
- throw new TypeError("Mobile page props must serialize to an object.");
1154
+ const capture = options.capture ?? defaultCapture;
1155
+ const signed = verifyAabSignature(artifactPath, capture, options.jarsigner);
1156
+ if (signed === null && !options.allowUnsigned) {
1157
+ throw new TypeError("Could not verify the Android App Bundle signature because jarsigner is unavailable. Install a JDK, or use --unsigned only for a non-publishable build.");
662
1158
  }
663
- return parsed;
1159
+ if (!signed && !options.allowUnsigned) {
1160
+ 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.");
1161
+ }
1162
+ const [bytes, sha256] = await Promise.all([
1163
+ stat(artifactPath).then(({ size }) => size),
1164
+ sha256File(artifactPath)
1165
+ ]);
1166
+ const releaseId = `amobile_android_${sha256}`;
1167
+ const metadata = {
1168
+ appBuild: manifest.appBuild,
1169
+ appId: manifest.appId,
1170
+ bytes,
1171
+ engine: "capacitor",
1172
+ format: ABSOLUTE_ANDROID_RELEASE_FORMAT,
1173
+ platform: "android",
1174
+ releaseId,
1175
+ runtime: manifest.runtime,
1176
+ sha256,
1177
+ signed: signed === true,
1178
+ type: "aab",
1179
+ ...versionCode === undefined ? {} : { versionCode }
1180
+ };
1181
+ return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
664
1182
  };
665
- var finalizeArchivedMobilePage = (context, input) => {
666
- const { page } = context;
667
- if (page.pageId !== input.compatibility.pageId || page.framework !== input.compatibility.framework) {
668
- return envelopeResponse({
669
- kind: "invalid-request",
670
- message: "Archived producer page identity does not match its release artifact."
671
- }, BAD_REQUEST_STATUS);
1183
+ // src/mobile/iosRelease.ts
1184
+ import { createHash as createHash5 } from "crypto";
1185
+ import {
1186
+ access as access4,
1187
+ copyFile as copyFile3,
1188
+ mkdir as mkdir4,
1189
+ mkdtemp as mkdtemp3,
1190
+ readdir as readdir3,
1191
+ readFile as readFile5,
1192
+ rename as rename5,
1193
+ rm as rm4,
1194
+ stat as stat2,
1195
+ writeFile as writeFile5
1196
+ } from "fs/promises";
1197
+ import { dirname as dirname3, isAbsolute as isAbsolute3, join as join5, relative as relative4, resolve as resolve4, sep as sep3 } from "path";
1198
+ var ABSOLUTE_IOS_RELEASE_FORMAT = 1;
1199
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1200
+ var requireManifest2 = (value) => {
1201
+ if (!isRecord2(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
1202
+ throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
672
1203
  }
1204
+ return {
1205
+ appBuild: value.appBuild,
1206
+ appId: value.appId,
1207
+ runtime: value.runtime
1208
+ };
1209
+ };
1210
+ var pathExists3 = async (path) => {
673
1211
  try {
674
- const [currentRepresentation] = input.compatibility.representations;
675
- if (!currentRepresentation) {
676
- throw new TypeError("Mobile page has no current representation.");
677
- }
678
- return envelopeResponse({
679
- contract: page.contract,
680
- framework: page.framework,
681
- kind: "page",
682
- pageId: page.pageId,
683
- props: normalizeJsonValue(currentRepresentation.mapProps(input.props)),
684
- status: input.status ?? OK_STATUS
685
- }, input.status ?? OK_STATUS);
686
- } catch (error) {
687
- console.error(`[Mobile] Archived producer ${context.releaseId} failed for ${page.pageId}:`, error);
688
- return createAbsoluteMobilePageErrorResponse(page.pageId);
1212
+ await access4(path);
1213
+ return true;
1214
+ } catch {
1215
+ return false;
689
1216
  }
690
1217
  };
691
- var createAbsoluteMobileInvalidRequestResponse = (message) => envelopeResponse({ kind: "invalid-request", message }, BAD_REQUEST_STATUS);
692
- var createAbsoluteMobilePageErrorResponse = (pageId) => envelopeResponse({
1218
+ var defaultRun = async (command, options = {}) => {
1219
+ const process2 = Bun.spawn(command, {
1220
+ cwd: options.cwd,
1221
+ env: options.env,
1222
+ stderr: "inherit",
1223
+ stdin: "inherit",
1224
+ stdout: "inherit"
1225
+ });
1226
+ return process2.exited;
1227
+ };
1228
+ var defaultCapture2 = (command, options = {}) => {
1229
+ try {
1230
+ const result = Bun.spawnSync(command, {
1231
+ cwd: options.cwd,
1232
+ env: options.env,
1233
+ stderr: "pipe",
1234
+ stdin: "ignore",
1235
+ stdout: "pipe"
1236
+ });
1237
+ return {
1238
+ exitCode: result.exitCode,
1239
+ stderr: result.stderr.toString(),
1240
+ stdout: result.stdout.toString()
1241
+ };
1242
+ } catch (error) {
1243
+ return {
1244
+ exitCode: 1,
1245
+ stderr: error instanceof Error ? error.message : String(error),
1246
+ stdout: ""
1247
+ };
1248
+ }
1249
+ };
1250
+ var ignoredFingerprintDirectories = new Set([
1251
+ "Pods",
1252
+ "DerivedData",
1253
+ "build",
1254
+ "xcuserdata"
1255
+ ]);
1256
+ var fingerprintFiles = async (root, current = root) => {
1257
+ const entries = await readdir3(current, { withFileTypes: true });
1258
+ const nested = await Promise.all(entries.sort((left, right) => left.name.localeCompare(right.name)).map(async (entry) => {
1259
+ const path = join5(current, entry.name);
1260
+ const projectRelative = relative4(root, path).replaceAll("\\", "/");
1261
+ const ignored = entry.isDirectory() && (ignoredFingerprintDirectories.has(entry.name) || projectRelative === "App/App/public");
1262
+ if (ignored)
1263
+ return [];
1264
+ if (entry.isDirectory())
1265
+ return fingerprintFiles(root, path);
1266
+ return entry.isFile() ? [path] : [];
1267
+ }));
1268
+ return nested.flat();
1269
+ };
1270
+ var fingerprintAbsoluteIosNativeProject = async (nativeDirectory) => {
1271
+ const hasher = createHash5("sha256");
1272
+ const files = await fingerprintFiles(nativeDirectory);
1273
+ const contents = await Promise.all(files.map((file) => readFile5(file)));
1274
+ files.forEach((file, index) => {
1275
+ hasher.update(relative4(nativeDirectory, file).replaceAll("\\", "/"));
1276
+ hasher.update("\x00");
1277
+ hasher.update(contents[index] ?? new Uint8Array);
1278
+ hasher.update("\x00");
1279
+ });
1280
+ return hasher.digest("hex");
1281
+ };
1282
+ var safeOutputDirectory2 = (projectRoot, requested) => {
1283
+ const root = resolve4(projectRoot);
1284
+ const output = resolve4(root, requested ?? ".absolutejs/mobile/releases/ios");
1285
+ const projectRelative = relative4(root, output);
1286
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep3}`) || isAbsolute3(projectRelative)) {
1287
+ throw new TypeError("mobile build --outdir must remain inside the project.");
1288
+ }
1289
+ return output;
1290
+ };
1291
+ var sha256File2 = async (path) => createHash5("sha256").update(await readFile5(path)).digest("hex");
1292
+ var findByExtension = async (root, extension) => {
1293
+ if (!await pathExists3(root))
1294
+ return;
1295
+ const entries = await readdir3(root, { withFileTypes: true });
1296
+ const matches = await Promise.all(entries.map(async (entry) => {
1297
+ const path = join5(root, entry.name);
1298
+ if (entry.isDirectory() && entry.name.endsWith(extension))
1299
+ return path;
1300
+ if (entry.isFile() && entry.name.endsWith(extension))
1301
+ return path;
1302
+ return entry.isDirectory() ? findByExtension(path, extension) : undefined;
1303
+ }));
1304
+ return matches.find((match) => match !== undefined);
1305
+ };
1306
+ var exportOptions = () => `<?xml version="1.0" encoding="UTF-8"?>
1307
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1308
+ <plist version="1.0"><dict>
1309
+ <key>destination</key><string>export</string>
1310
+ <key>manageAppVersionAndBuildNumber</key><false/>
1311
+ <key>method</key><string>app-store-connect</string>
1312
+ <key>signingStyle</key><string>automatic</string>
1313
+ <key>stripSwiftSymbols</key><true/>
1314
+ <key>uploadSymbols</key><true/>
1315
+ </dict></plist>
1316
+ `;
1317
+ var requireBuildNumber = (value) => {
1318
+ if (value !== undefined && (!Number.isSafeInteger(value) || value < 1))
1319
+ throw new TypeError("iOS build number must be a positive integer.");
1320
+ return value;
1321
+ };
1322
+ var installRelease2 = async (artifactPath, metadata, outputRoot) => {
1323
+ const releaseRoot = join5(outputRoot, metadata.releaseId);
1324
+ const destination = join5(releaseRoot, "App.ipa");
1325
+ if (await pathExists3(releaseRoot)) {
1326
+ const value = JSON.parse(await readFile5(join5(releaseRoot, "release.json"), "utf8"));
1327
+ if (!isRecord2(value) || value.artifact !== "App.ipa" || Object.entries(metadata).some(([key, expected]) => Reflect.get(value, key) !== expected)) {
1328
+ throw new TypeError(`Immutable iOS release ${metadata.releaseId} does not match its content.`);
1329
+ }
1330
+ const [bytes, sha256] = await Promise.all([
1331
+ stat2(destination).then(({ size }) => size),
1332
+ sha256File2(destination)
1333
+ ]);
1334
+ if (bytes !== metadata.bytes || sha256 !== metadata.sha256)
1335
+ throw new TypeError(`Immutable iOS release ${metadata.releaseId} artifact is missing or modified.`);
1336
+ return {
1337
+ artifactPath: destination,
1338
+ metadata: {
1339
+ ...metadata,
1340
+ artifact: "App.ipa"
1341
+ },
1342
+ releaseRoot
1343
+ };
1344
+ }
1345
+ await mkdir4(dirname3(releaseRoot), { recursive: true });
1346
+ const staging = await mkdtemp3(join5(dirname3(releaseRoot), ".ios-stage-"));
1347
+ try {
1348
+ await copyFile3(artifactPath, join5(staging, "App.ipa"));
1349
+ const complete = {
1350
+ ...metadata,
1351
+ artifact: "App.ipa"
1352
+ };
1353
+ await writeFile5(join5(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
1354
+ `, { flag: "wx" });
1355
+ await rename5(staging, releaseRoot);
1356
+ return { artifactPath: destination, metadata: complete, releaseRoot };
1357
+ } finally {
1358
+ await rm4(staging, { force: true, recursive: true }).catch(() => {
1359
+ return;
1360
+ });
1361
+ }
1362
+ };
1363
+ var buildAbsoluteIosRelease = async (options) => {
1364
+ if (options.buildNumber !== undefined && options.prepareBuildNumber)
1365
+ throw new TypeError("iOS release buildNumber and prepareBuildNumber cannot be combined.");
1366
+ if ((options.host ?? detectAbsoluteMobileHost()) !== "macos")
1367
+ throw new TypeError("iOS release builds require macOS and Xcode.");
1368
+ const marketingVersion = options.config.iosVersion;
1369
+ if (!marketingVersion)
1370
+ throw new TypeError("iOS release builds require mobile.ios.version in absolutejs.config.ts.");
1371
+ const manifest = requireManifest2(JSON.parse(await readFile5(join5(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1372
+ if (manifest.appId !== options.config.appId)
1373
+ throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1374
+ const nativeDirectory = join5(options.config.nativeProjectDirectory, "ios");
1375
+ let buildNumber = requireBuildNumber(options.buildNumber);
1376
+ if (options.prepareBuildNumber) {
1377
+ const nativeFingerprint = await fingerprintAbsoluteIosNativeProject(nativeDirectory);
1378
+ const buildIdentity = createHash5("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}\x00${marketingVersion}`).digest("hex");
1379
+ buildNumber = requireBuildNumber(await options.prepareBuildNumber(buildIdentity));
1380
+ }
1381
+ const stagingParent = resolve4(options.projectRoot, ".absolutejs/mobile");
1382
+ await mkdir4(stagingParent, { recursive: true });
1383
+ const staging = await mkdtemp3(join5(stagingParent, ".ios-build-"));
1384
+ const archivePath = join5(staging, "App.xcarchive");
1385
+ const exportPath = join5(staging, "export");
1386
+ const exportPlist = join5(staging, "ExportOptions.plist");
1387
+ await mkdir4(exportPath, { recursive: true });
1388
+ await writeFile5(exportPlist, exportOptions());
1389
+ const run = options.run ?? defaultRun;
1390
+ try {
1391
+ const versionArguments = [
1392
+ `MARKETING_VERSION=${marketingVersion}`,
1393
+ ...buildNumber === undefined ? [] : [`CURRENT_PROJECT_VERSION=${buildNumber}`]
1394
+ ];
1395
+ const archiveExit = await run([
1396
+ "xcodebuild",
1397
+ "-workspace",
1398
+ join5(nativeDirectory, "App", "App.xcworkspace"),
1399
+ "-scheme",
1400
+ "App",
1401
+ "-configuration",
1402
+ "Release",
1403
+ "-destination",
1404
+ "generic/platform=iOS",
1405
+ "-archivePath",
1406
+ archivePath,
1407
+ ...versionArguments,
1408
+ "archive"
1409
+ ], { cwd: nativeDirectory });
1410
+ if (archiveExit !== 0)
1411
+ throw new TypeError("Xcode failed to archive the iOS app.");
1412
+ const archivedApp = await findByExtension(join5(archivePath, "Products", "Applications"), ".app");
1413
+ const capture = options.capture ?? defaultCapture2;
1414
+ const signed = archivedApp ? capture([
1415
+ "codesign",
1416
+ "--verify",
1417
+ "--deep",
1418
+ "--strict",
1419
+ archivedApp
1420
+ ]).exitCode === 0 : false;
1421
+ if (!signed && !options.allowUnsigned)
1422
+ 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.");
1423
+ const exportExit = await run([
1424
+ "xcodebuild",
1425
+ "-exportArchive",
1426
+ "-archivePath",
1427
+ archivePath,
1428
+ "-exportPath",
1429
+ exportPath,
1430
+ "-exportOptionsPlist",
1431
+ exportPlist
1432
+ ], { cwd: nativeDirectory });
1433
+ if (exportExit !== 0)
1434
+ throw new TypeError("Xcode failed to export the App Store IPA.");
1435
+ const artifactPath = await findByExtension(exportPath, ".ipa");
1436
+ if (!artifactPath)
1437
+ throw new TypeError("Xcode did not produce an exported IPA.");
1438
+ const [bytes, sha256] = await Promise.all([
1439
+ stat2(artifactPath).then(({ size }) => size),
1440
+ sha256File2(artifactPath)
1441
+ ]);
1442
+ const releaseId = `amobile_ios_${sha256}`;
1443
+ return await installRelease2(artifactPath, {
1444
+ appBuild: manifest.appBuild,
1445
+ appId: manifest.appId,
1446
+ ...buildNumber === undefined ? {} : { buildNumber },
1447
+ bytes,
1448
+ engine: "capacitor",
1449
+ format: 1,
1450
+ marketingVersion,
1451
+ platform: "ios",
1452
+ releaseId,
1453
+ runtime: manifest.runtime,
1454
+ sha256,
1455
+ signed,
1456
+ type: "ipa"
1457
+ }, safeOutputDirectory2(options.projectRoot, options.outputDirectory));
1458
+ } finally {
1459
+ await rm4(staging, { force: true, recursive: true }).catch(() => {
1460
+ return;
1461
+ });
1462
+ }
1463
+ };
1464
+ // src/mobile/associationFiles.ts
1465
+ import {
1466
+ access as access5,
1467
+ mkdir as mkdir5,
1468
+ readFile as readFile6,
1469
+ rename as rename6,
1470
+ rm as rm5,
1471
+ writeFile as writeFile6
1472
+ } from "fs/promises";
1473
+ import { resolve as resolve6 } from "path";
1474
+ import { Elysia } from "elysia";
1475
+
1476
+ // src/mobile/config.ts
1477
+ import { resolve as resolve5 } from "path";
1478
+ var APP_ID_PATTERN = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
1479
+ var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
1480
+ var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
1481
+ var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
1482
+ 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])?))*$/;
1483
+ var resolveProjectPath = (projectRoot, value, field) => {
1484
+ const root = resolve5(projectRoot);
1485
+ const path = resolve5(root, value);
1486
+ if (path !== root && !path.startsWith(`${root}/`)) {
1487
+ throw new TypeError(`${field} must remain inside the project root.`);
1488
+ }
1489
+ return path;
1490
+ };
1491
+ var requireText = (value, field) => {
1492
+ const normalized = value.trim();
1493
+ if (!normalized)
1494
+ throw new TypeError(`${field} must not be empty.`);
1495
+ return normalized;
1496
+ };
1497
+ var normalizeEntry = (entry) => {
1498
+ const normalized = requireText(entry ?? "/", "mobile.entry");
1499
+ if (!normalized.startsWith("/") || normalized.startsWith("//")) {
1500
+ throw new TypeError("mobile.entry must be an absolute application path.");
1501
+ }
1502
+ return normalized;
1503
+ };
1504
+ var normalizeProductionOrigin = (value) => {
1505
+ const parsed = new URL(requireText(value, "mobile.server.productionOrigin"));
1506
+ if (parsed.protocol !== "https:") {
1507
+ throw new TypeError("mobile.server.productionOrigin must use HTTPS in production.");
1508
+ }
1509
+ if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
1510
+ throw new TypeError("mobile.server.productionOrigin must be an origin without credentials, path, query, or hash.");
1511
+ }
1512
+ return parsed.origin;
1513
+ };
1514
+ var normalizePlatforms = (platforms) => {
1515
+ const normalized = [
1516
+ ...new Set(platforms ?? ["ios", "android"])
1517
+ ];
1518
+ if (normalized.length === 0) {
1519
+ throw new TypeError("mobile.platforms must contain at least one platform.");
1520
+ }
1521
+ return normalized;
1522
+ };
1523
+ var normalizeHosts = (hosts, productionOrigin) => {
1524
+ const normalizeHostname = (host) => {
1525
+ const value = requireText(host, "mobile.deepLinks.hosts").toLowerCase();
1526
+ if (!HOSTNAME_PATTERN.test(value)) {
1527
+ throw new TypeError("mobile.deepLinks.hosts entries must be valid hostnames without ports, wildcards, or paths.");
1528
+ }
1529
+ return value;
1530
+ };
1531
+ const normalized = new Set([
1532
+ normalizeHostname(new URL(productionOrigin).hostname)
1533
+ ]);
1534
+ for (const host of hosts ?? []) {
1535
+ normalized.add(normalizeHostname(host));
1536
+ }
1537
+ return [...normalized].sort();
1538
+ };
1539
+ var normalizeAppleAppIdPrefix = (value) => {
1540
+ if (value === undefined)
1541
+ return;
1542
+ const normalized = requireText(value, "mobile.deepLinks.apple.appIdPrefix").toUpperCase();
1543
+ if (!APPLE_APP_ID_PREFIX_PATTERN.test(normalized)) {
1544
+ throw new TypeError("mobile.deepLinks.apple.appIdPrefix must contain ten letters or digits.");
1545
+ }
1546
+ return normalized;
1547
+ };
1548
+ var normalizeIosVersion = (value) => {
1549
+ if (value === undefined)
1550
+ return;
1551
+ const normalized = requireText(value, "mobile.ios.version");
1552
+ if (!/^\d+(?:\.\d+){0,2}$/u.test(normalized)) {
1553
+ throw new TypeError("mobile.ios.version must contain one to three dot-separated integer components, for example 1.4.0.");
1554
+ }
1555
+ return normalized;
1556
+ };
1557
+ var normalizeCertificateFingerprints = (values) => [
1558
+ ...new Set((values ?? []).map((value) => requireText(value, "mobile.deepLinks.android.sha256CertificateFingerprints").replaceAll(":", "").toUpperCase()).map((value) => {
1559
+ if (!CERTIFICATE_FINGERPRINT_PATTERN.test(value)) {
1560
+ throw new TypeError("mobile.deepLinks.android.sha256CertificateFingerprints entries must be SHA-256 certificate fingerprints.");
1561
+ }
1562
+ return value.match(/.{2}/g)?.join(":") ?? value;
1563
+ }))
1564
+ ].sort();
1565
+ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
1566
+ const appId = requireText(config.appId, "mobile.appId");
1567
+ if (!APP_ID_PATTERN.test(appId)) {
1568
+ throw new TypeError("mobile.appId must use reverse-domain notation, for example com.example.app.");
1569
+ }
1570
+ const productionOrigin = normalizeProductionOrigin(config.server.productionOrigin);
1571
+ const deepLinkScheme = config.deepLinks?.scheme?.trim().toLowerCase();
1572
+ if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
1573
+ throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
1574
+ }
1575
+ return {
1576
+ androidCertificateFingerprints: normalizeCertificateFingerprints(config.deepLinks?.android?.sha256CertificateFingerprints),
1577
+ appId,
1578
+ appleAppIdPrefix: normalizeAppleAppIdPrefix(config.deepLinks?.apple?.appIdPrefix),
1579
+ appName: requireText(config.appName, "mobile.appName"),
1580
+ bundleDirectory: resolveProjectPath(projectRoot, config.bundleDirectory ?? ".absolutejs/mobile/web", "mobile.bundleDirectory"),
1581
+ deepLinkHosts: normalizeHosts(config.deepLinks?.hosts, productionOrigin),
1582
+ deepLinkScheme,
1583
+ engine: "capacitor",
1584
+ entry: normalizeEntry(config.entry),
1585
+ iosVersion: normalizeIosVersion(config.ios?.version),
1586
+ nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? "mobile", "mobile.nativeProject.directory"),
1587
+ platforms: normalizePlatforms(config.platforms),
1588
+ productionOrigin
1589
+ };
1590
+ };
1591
+
1592
+ // src/mobile/associationFiles.ts
1593
+ var JSON_HEADERS = {
1594
+ "cache-control": "public, max-age=300, must-revalidate",
1595
+ "content-type": "application/json; charset=utf-8"
1596
+ };
1597
+ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json";
1598
+ var HTTP_OK = 200;
1599
+ var VERIFY_TIMEOUT_MS = 1e4;
1600
+ var ANDROID_ASSOCIATION_PATH = "/.well-known/assetlinks.json";
1601
+ var APPLE_ASSOCIATION_PATH = "/.well-known/apple-app-site-association";
1602
+ var missingIdentity = (field, platform) => new TypeError(`${field} is required to publish ${platform} deep-link association files.`);
1603
+ var createAppleDocument = (config, requireAll) => {
1604
+ if (!config.platforms.includes("ios"))
1605
+ return;
1606
+ if (!config.appleAppIdPrefix && requireAll) {
1607
+ throw missingIdentity("mobile.deepLinks.apple.appIdPrefix", "iOS");
1608
+ }
1609
+ if (!config.appleAppIdPrefix)
1610
+ return;
1611
+ return {
1612
+ applinks: {
1613
+ details: [
1614
+ {
1615
+ appIDs: [
1616
+ `${config.appleAppIdPrefix}.${config.appId}`
1617
+ ],
1618
+ components: [{ "/": "/*" }]
1619
+ }
1620
+ ]
1621
+ }
1622
+ };
1623
+ };
1624
+ var createAndroidDocument = (config, requireAll) => {
1625
+ if (!config.platforms.includes("android"))
1626
+ return;
1627
+ if (config.androidCertificateFingerprints.length === 0 && requireAll) {
1628
+ throw missingIdentity("mobile.deepLinks.android.sha256CertificateFingerprints", "Android");
1629
+ }
1630
+ if (config.androidCertificateFingerprints.length === 0)
1631
+ return;
1632
+ return [
1633
+ {
1634
+ relation: ["delegate_permission/common.handle_all_urls"],
1635
+ target: {
1636
+ namespace: "android_app",
1637
+ package_name: config.appId,
1638
+ sha256_cert_fingerprints: config.androidCertificateFingerprints
1639
+ }
1640
+ }
1641
+ ];
1642
+ };
1643
+ var createAbsoluteMobileAssociationDocuments = (config, options = {}) => {
1644
+ const documents = {};
1645
+ const requireAll = options.requireAll === true;
1646
+ const android = createAndroidDocument(config, requireAll);
1647
+ const apple = createAppleDocument(config, requireAll);
1648
+ if (android)
1649
+ documents.android = android;
1650
+ if (apple)
1651
+ documents.apple = apple;
1652
+ return documents;
1653
+ };
1654
+ var jsonResponse = (value) => new Response(`${JSON.stringify(value)}
1655
+ `, { headers: JSON_HEADERS });
1656
+ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {}) => {
1657
+ const plugin = new Elysia({ name: "absolutejs-mobile-associations" });
1658
+ if (!mobile)
1659
+ return plugin;
1660
+ const config = normalizeAbsoluteMobileConfig(mobile, projectRoot);
1661
+ const documents = createAbsoluteMobileAssociationDocuments(config, options);
1662
+ if (documents.apple) {
1663
+ plugin.get(APPLE_ASSOCIATION_PATH, () => jsonResponse(documents.apple));
1664
+ }
1665
+ if (documents.android) {
1666
+ plugin.get(ANDROID_ASSOCIATION_PATH, () => jsonResponse(documents.android));
1667
+ }
1668
+ return plugin;
1669
+ };
1670
+ var writeAtomic = async (path, source) => {
1671
+ let current;
1672
+ try {
1673
+ current = await readFile6(path, "utf8");
1674
+ } catch (error) {
1675
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
1676
+ throw error;
1677
+ }
1678
+ }
1679
+ if (current === source)
1680
+ return false;
1681
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
1682
+ await writeFile6(temporary, source, { flag: "wx" });
1683
+ await rename6(temporary, path);
1684
+ return true;
1685
+ };
1686
+ var exists2 = async (path) => {
1687
+ try {
1688
+ await access5(path);
1689
+ return true;
1690
+ } catch {
1691
+ return false;
1692
+ }
1693
+ };
1694
+ var assertOwnedOutput = async (root) => {
1695
+ const path = resolve6(root, OWNERSHIP_FILE);
1696
+ let ownership;
1697
+ try {
1698
+ ownership = JSON.parse(await readFile6(path, "utf8"));
1699
+ } catch {
1700
+ throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
1701
+ }
1702
+ if (typeof ownership !== "object" || ownership === null || Reflect.get(ownership, "format") !== 1) {
1703
+ throw new TypeError(`Association output ${root} has an unsupported ownership manifest.`);
1704
+ }
1705
+ };
1706
+ var publishGeneratedDirectory = async (temporary, root) => {
1707
+ const hasCurrent = await exists2(root);
1708
+ if (hasCurrent)
1709
+ await assertOwnedOutput(root);
1710
+ const backup = `${root}.${crypto.randomUUID()}.previous`;
1711
+ if (hasCurrent)
1712
+ await rename6(root, backup);
1713
+ try {
1714
+ await rename6(temporary, root);
1715
+ } catch (error) {
1716
+ if (hasCurrent)
1717
+ await rename6(backup, root);
1718
+ throw error;
1719
+ }
1720
+ if (hasCurrent)
1721
+ await rm5(backup, { force: true, recursive: true });
1722
+ };
1723
+ var materializeHost = async (root, host, files) => {
1724
+ const directory = resolve6(root, host, ".well-known");
1725
+ await mkdir5(directory, { recursive: true });
1726
+ return Promise.all(files.map(async ([name, document]) => {
1727
+ const path = resolve6(directory, name);
1728
+ await writeAtomic(path, `${JSON.stringify(document, null, 2)}
1729
+ `);
1730
+ return path;
1731
+ }));
1732
+ };
1733
+ var associationEndpoints = (config, documents) => config.deepLinkHosts.flatMap((host) => {
1734
+ const endpoints = [];
1735
+ if (documents.android)
1736
+ endpoints.push({
1737
+ document: documents.android,
1738
+ host,
1739
+ path: ANDROID_ASSOCIATION_PATH,
1740
+ platform: "Android"
1741
+ });
1742
+ if (documents.apple)
1743
+ endpoints.push({
1744
+ document: documents.apple,
1745
+ host,
1746
+ path: APPLE_ASSOCIATION_PATH,
1747
+ platform: "Apple"
1748
+ });
1749
+ return endpoints;
1750
+ });
1751
+ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory) => {
1752
+ const root = resolve6(outputDirectory);
1753
+ const temporary = `${root}.${crypto.randomUUID()}.tmp`;
1754
+ const documents = createAbsoluteMobileAssociationDocuments(config, {
1755
+ requireAll: true
1756
+ });
1757
+ const files = [];
1758
+ if (documents.android)
1759
+ files.push(["assetlinks.json", documents.android]);
1760
+ if (documents.apple) {
1761
+ files.push(["apple-app-site-association", documents.apple]);
1762
+ }
1763
+ await mkdir5(temporary, { recursive: true });
1764
+ try {
1765
+ const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
1766
+ await writeAtomic(resolve6(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
1767
+ `);
1768
+ await publishGeneratedDirectory(temporary, root);
1769
+ const written = temporaryPaths.map((path) => resolve6(root, path.slice(temporary.length + 1)));
1770
+ return { root, written };
1771
+ } catch (error) {
1772
+ await rm5(temporary, { force: true, recursive: true });
1773
+ throw error;
1774
+ }
1775
+ };
1776
+ var verifyAbsoluteMobileAssociationFiles = async (config, request = globalThis.fetch) => {
1777
+ const documents = createAbsoluteMobileAssociationDocuments(config, {
1778
+ requireAll: true
1779
+ });
1780
+ const results = await Promise.all(associationEndpoints(config, documents).map(async (endpoint) => {
1781
+ const url = `https://${endpoint.host}${endpoint.path}`;
1782
+ const response = await request(url, {
1783
+ redirect: "manual",
1784
+ signal: AbortSignal.timeout(VERIFY_TIMEOUT_MS)
1785
+ });
1786
+ if (response.status !== HTTP_OK) {
1787
+ throw new TypeError(`${endpoint.platform} association verification failed for ${url}: HTTP ${response.status}; redirects are not allowed.`);
1788
+ }
1789
+ const contentType = response.headers.get("content-type") ?? "";
1790
+ if (!contentType.toLowerCase().startsWith("application/json")) {
1791
+ throw new TypeError(`${endpoint.platform} association verification failed for ${url}: expected application/json.`);
1792
+ }
1793
+ let document;
1794
+ try {
1795
+ document = await response.json();
1796
+ } catch {
1797
+ throw new TypeError(`${endpoint.platform} association verification failed for ${url}: invalid JSON.`);
1798
+ }
1799
+ if (JSON.stringify(document) !== JSON.stringify(endpoint.document)) {
1800
+ throw new TypeError(`${endpoint.platform} association verification failed for ${url}: the hosted identity does not match mobile config.`);
1801
+ }
1802
+ return { platform: endpoint.platform, url };
1803
+ }));
1804
+ return { results };
1805
+ };
1806
+ // src/mobile/buildMetadata.ts
1807
+ var ABSOLUTE_MOBILE_ROUTE_DETAIL = "x-absolute-mobile";
1808
+ var frameworks2 = new Set([
1809
+ "angular",
1810
+ "ember",
1811
+ "html",
1812
+ "htmx",
1813
+ "react",
1814
+ "svelte",
1815
+ "vue"
1816
+ ]);
1817
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1818
+ var isPageFramework2 = (value) => typeof value === "string" && frameworks2.has(value);
1819
+ var parseAbsoluteMobileBuildPageMetadata = (value) => {
1820
+ if (!isRecord3(value))
1821
+ return;
1822
+ if (typeof value.bundleKey !== "string" || typeof value.contract !== "string" || !isPageFramework2(value.framework) || typeof value.pageId !== "string" || typeof value.propsSchemaHash !== "string") {
1823
+ return;
1824
+ }
1825
+ return {
1826
+ bundleKey: value.bundleKey,
1827
+ contract: value.contract,
1828
+ framework: value.framework,
1829
+ pageId: value.pageId,
1830
+ propsSchemaHash: value.propsSchemaHash
1831
+ };
1832
+ };
1833
+ // src/mobile/buildPipeline.ts
1834
+ import { readFile as readFile10 } from "fs/promises";
1835
+ import { join as join9, resolve as resolve9 } from "path";
1836
+ import { pathToFileURL as pathToFileURL2 } from "url";
1837
+
1838
+ // src/mobile/buildRelease.ts
1839
+ import { createHash as createHash6 } from "crypto";
1840
+ import { readFile as readFile7 } from "fs/promises";
1841
+ import { join as join6, relative as relative5, resolve as resolve7 } from "path";
1842
+ var sha256 = (bytes) => createHash6("sha256").update(bytes).digest("hex");
1843
+ var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
1844
+ var resolveAssetPath = (buildDirectory, assetPath) => {
1845
+ const resolvedBuildDirectory = resolve7(buildDirectory);
1846
+ const resolvedAsset = resolve7(assetPath);
1847
+ if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
1848
+ return resolvedAsset;
1849
+ }
1850
+ return join6(buildDirectory, assetPath.replace(/^\/+/, ""));
1851
+ };
1852
+ var pageFor = async (metadata, manifest, buildDirectory) => {
1853
+ const assetPath = manifest[metadata.bundleKey];
1854
+ if (!assetPath) {
1855
+ throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
1856
+ }
1857
+ const resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
1858
+ const bytes = await readFile7(resolvedAssetPath);
1859
+ const bundlePath = `/${relative5(resolve7(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
1860
+ return {
1861
+ bundleHash: sha256(bytes),
1862
+ bundlePath,
1863
+ contract: metadata.contract,
1864
+ framework: metadata.framework,
1865
+ pageId: metadata.pageId,
1866
+ propsSchemaHash: metadata.propsSchemaHash
1867
+ };
1868
+ };
1869
+ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
1870
+ const [captured, producerBytes] = await Promise.all([
1871
+ captureAbsoluteMobileRouteGraph(options.app),
1872
+ readFile7(options.producerPath)
1873
+ ]);
1874
+ if (captured.length === 0) {
1875
+ throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
1876
+ }
1877
+ const metadataByPage = new Map;
1878
+ for (const { metadata } of captured) {
1879
+ const prior = metadataByPage.get(metadata.pageId);
1880
+ if (prior && JSON.stringify(prior) !== JSON.stringify(metadata)) {
1881
+ throw new TypeError(`Mobile page ${metadata.pageId} has inconsistent generated contracts.`);
1882
+ }
1883
+ metadataByPage.set(metadata.pageId, metadata);
1884
+ }
1885
+ const pages = await Promise.all([...metadataByPage.values()].map((metadata) => pageFor(metadata, options.manifest, options.buildDirectory)));
1886
+ const producerHash = sha256(producerBytes);
1887
+ const appBuild = `ambuild_${sha256(new TextEncoder().encode(JSON.stringify({
1888
+ pages: pages.map(({ bundleHash, bundlePath, contract, pageId }) => ({
1889
+ bundleHash,
1890
+ bundlePath,
1891
+ contract,
1892
+ pageId
1893
+ })),
1894
+ producerHash,
1895
+ runtime: options.runtime
1896
+ })))}`;
1897
+ const priorAppIds = new Set((options.previousArtifacts ?? []).map(({ appId }) => appId));
1898
+ if (priorAppIds.size > 1 || priorAppIds.size === 1 && !priorAppIds.has(options.appId)) {
1899
+ throw new TypeError("Previous mobile compatibility artifacts belong to another app.");
1900
+ }
1901
+ const unchanged = options.previousArtifacts?.find((artifact2) => artifact2.appBuild === appBuild && artifact2.runtime === options.runtime);
1902
+ const generation = unchanged?.generation ?? Math.max(0, ...(options.previousArtifacts ?? []).map((artifact2) => artifact2.generation)) + 1;
1903
+ const artifact = createAbsoluteMobileCompatibilityArtifact({
1904
+ appBuild,
1905
+ appId: options.appId,
1906
+ generation,
1907
+ pages,
1908
+ producer: {
1909
+ bundleHash: producerHash,
1910
+ bytes: producerBytes.byteLength,
1911
+ exportName: options.producerExport ?? "server",
1912
+ module: options.producerModule ?? "producer.js"
1913
+ },
1914
+ routes: captured.map(({ route }) => route),
1915
+ runtime: options.runtime
1916
+ });
1917
+ return { artifact, producer: new Blob([producerBytes]) };
1918
+ };
1919
+ var captureAbsoluteMobileRouteGraph = async (app) => {
1920
+ if (app.modules)
1921
+ await app.modules;
1922
+ app.compile();
1923
+ const captured = app.routes.flatMap((route) => {
1924
+ const metadata = readPageMetadata(route);
1925
+ if (!metadata || route.method !== "GET" && route.method !== "HEAD") {
1926
+ return [];
1927
+ }
1928
+ return [
1929
+ {
1930
+ metadata,
1931
+ route: {
1932
+ method: route.method === "GET" ? "GET" : "HEAD",
1933
+ pageId: metadata.pageId,
1934
+ pattern: route.path
1935
+ }
1936
+ }
1937
+ ];
1938
+ });
1939
+ const identities = new Map;
1940
+ for (const { metadata, route } of captured) {
1941
+ const prior = identities.get(`${route.method}:${route.pattern}`);
1942
+ if (prior && prior !== metadata.pageId) {
1943
+ throw new TypeError(`Mobile route ${route.method} ${route.pattern} resolves to multiple pages.`);
1944
+ }
1945
+ identities.set(`${route.method}:${route.pattern}`, metadata.pageId);
1946
+ }
1947
+ return captured;
1948
+ };
1949
+
1950
+ // src/mobile/capacitorBundle.ts
1951
+ import {
1952
+ copyFile as copyFile4,
1953
+ mkdir as mkdir6,
1954
+ mkdtemp as mkdtemp4,
1955
+ readFile as readFile8,
1956
+ rename as rename7,
1957
+ rm as rm6,
1958
+ writeFile as writeFile7
1959
+ } from "fs/promises";
1960
+ import { existsSync as existsSync2 } from "fs";
1961
+ import { basename, dirname as dirname4, extname, join as join7, resolve as resolve8 } from "path";
1962
+
1963
+ // src/mobile/routeMatcher.ts
1964
+ var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
1965
+ var routeSegmentPattern = (segment) => {
1966
+ if (segment === "*")
1967
+ return ".*";
1968
+ if (segment.startsWith(":") && segment.endsWith("?"))
1969
+ return "[^/]*";
1970
+ if (segment.startsWith(":"))
1971
+ return "[^/]+";
1972
+ return segment.replace(REGEXP_SPECIAL_CHARACTERS, "\\$&");
1973
+ };
1974
+ var matchesAbsoluteMobileRoutePattern = (pattern, pathname) => {
1975
+ const expression = pattern.split("/").map(routeSegmentPattern).join("/");
1976
+ return new RegExp(`^${expression}/?$`).test(pathname);
1977
+ };
1978
+ var resolveAbsoluteMobileRoute = (routes, pathname, method = "GET") => routes.find((route) => route.method === method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
1979
+
1980
+ // src/mobile/producerContextState.ts
1981
+ var ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY = Symbol.for("absolutejs.mobileProducerAsyncLocalStorage");
1982
+ var frameworks3 = new Set([
1983
+ "angular",
1984
+ "ember",
1985
+ "html",
1986
+ "htmx",
1987
+ "react",
1988
+ "svelte",
1989
+ "vue"
1990
+ ]);
1991
+ var isCompatibilityPage = (value) => typeof value === "object" && value !== null && ("bundleHash" in value) && typeof value.bundleHash === "string" && ("contract" in value) && typeof value.contract === "string" && ("framework" in value) && typeof value.framework === "string" && frameworks3.has(value.framework) && ("pageId" in value) && typeof value.pageId === "string" && ("propsSchemaHash" in value) && typeof value.propsSchemaHash === "string";
1992
+ var getCurrentAbsoluteMobileProducerContext = () => {
1993
+ const value = Reflect.get(globalThis, ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY);
1994
+ if (typeof value !== "object" || value === null || !("getStore" in value) || typeof value.getStore !== "function") {
1995
+ return;
1996
+ }
1997
+ const context = value.getStore();
1998
+ if (typeof context !== "object" || context === null || !("page" in context) || !isCompatibilityPage(context.page) || !("releaseId" in context) || typeof context.releaseId !== "string") {
1999
+ return;
2000
+ }
2001
+ return { page: context.page, releaseId: context.releaseId };
2002
+ };
2003
+
2004
+ // src/mobile/pageProtocol.ts
2005
+ var ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE = "application/vnd.absolute.page+json";
2006
+ var ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION = 1;
2007
+ var BAD_REQUEST_STATUS = 400;
2008
+ var OK_STATUS = 200;
2009
+ var SERVER_ERROR_STATUS = 500;
2010
+ var UPGRADE_REQUIRED_STATUS = 426;
2011
+ var MOBILE_PAGE_REQUEST_HEADERS = {
2012
+ appBuild: "x-absolute-mobile-app-build",
2013
+ pageBundle: "x-absolute-mobile-page-bundle",
2014
+ pageContracts: "x-absolute-mobile-page-contracts",
2015
+ pageId: "x-absolute-mobile-page-id",
2016
+ protocol: "x-absolute-mobile-protocol",
2017
+ runtime: "x-absolute-mobile-runtime"
2018
+ };
2019
+ var parseMediaType = (value) => value.split(";", 1)[0]?.trim().toLowerCase();
2020
+ var acceptsAbsoluteMobilePage = (request) => {
2021
+ if (!request)
2022
+ return false;
2023
+ return (request.headers.get("accept") ?? "").split(",").some((value) => parseMediaType(value) === ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE);
2024
+ };
2025
+ var readRequiredHeader = (request, name) => {
2026
+ const value = request.headers.get(name)?.trim();
2027
+ return value ? value : undefined;
2028
+ };
2029
+ var parseAbsoluteMobilePageRequest = (request) => {
2030
+ if (!request || !acceptsAbsoluteMobilePage(request)) {
2031
+ return { kind: "not-mobile" };
2032
+ }
2033
+ const protocolValue = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.protocol);
2034
+ const runtime = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.runtime);
2035
+ const appBuild = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.appBuild);
2036
+ const pageBundle = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.pageBundle);
2037
+ const pageContractsValue = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.pageContracts);
2038
+ const pageId = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.pageId);
2039
+ if (!protocolValue || !/^\d+$/.test(protocolValue)) {
2040
+ return {
2041
+ kind: "invalid",
2042
+ message: `Missing or invalid ${MOBILE_PAGE_REQUEST_HEADERS.protocol} header.`
2043
+ };
2044
+ }
2045
+ if (!runtime) {
2046
+ return {
2047
+ kind: "invalid",
2048
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.runtime} header.`
2049
+ };
2050
+ }
2051
+ if (!appBuild) {
2052
+ return {
2053
+ kind: "invalid",
2054
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.appBuild} header.`
2055
+ };
2056
+ }
2057
+ if (!pageBundle) {
2058
+ return {
2059
+ kind: "invalid",
2060
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageBundle} header.`
2061
+ };
2062
+ }
2063
+ if (!pageContractsValue) {
2064
+ return {
2065
+ kind: "invalid",
2066
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageContracts} header.`
2067
+ };
2068
+ }
2069
+ if (!pageId) {
2070
+ return {
2071
+ kind: "invalid",
2072
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageId} header.`
2073
+ };
2074
+ }
2075
+ const pageContracts = [
2076
+ ...new Set(pageContractsValue.split(",").map((value) => value.trim()).filter(Boolean))
2077
+ ];
2078
+ if (pageContracts.length === 0) {
2079
+ return {
2080
+ kind: "invalid",
2081
+ message: `${MOBILE_PAGE_REQUEST_HEADERS.pageContracts} must contain at least one contract.`
2082
+ };
2083
+ }
2084
+ return {
2085
+ client: {
2086
+ appBuild,
2087
+ pageBundle,
2088
+ pageContracts,
2089
+ pageId,
2090
+ protocol: Number(protocolValue),
2091
+ runtime
2092
+ },
2093
+ kind: "mobile"
2094
+ };
2095
+ };
2096
+ var responseHeaders = () => {
2097
+ const headers = new Headers({
2098
+ "cache-control": "no-store",
2099
+ "content-type": `${ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE}; version=${ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION}`
2100
+ });
2101
+ headerVaryValues.forEach((value) => headers.append("vary", value));
2102
+ return headers;
2103
+ };
2104
+ var headerVaryValues = [
2105
+ "Accept",
2106
+ MOBILE_PAGE_REQUEST_HEADERS.protocol,
2107
+ MOBILE_PAGE_REQUEST_HEADERS.runtime,
2108
+ MOBILE_PAGE_REQUEST_HEADERS.appBuild,
2109
+ MOBILE_PAGE_REQUEST_HEADERS.pageBundle,
2110
+ MOBILE_PAGE_REQUEST_HEADERS.pageContracts,
2111
+ MOBILE_PAGE_REQUEST_HEADERS.pageId
2112
+ ];
2113
+ var envelopeResponse = (response, status) => new Response(JSON.stringify({
2114
+ protocol: ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
2115
+ response
2116
+ }), { headers: responseHeaders(), status });
2117
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2118
+ var normalizeJsonValue = (value) => {
2119
+ const serialized = JSON.stringify(value);
2120
+ if (serialized === undefined) {
2121
+ throw new TypeError("Mobile page props must be JSON-serializable.");
2122
+ }
2123
+ const parsed = JSON.parse(serialized);
2124
+ if (!isRecord4(parsed)) {
2125
+ throw new TypeError("Mobile page props must serialize to an object.");
2126
+ }
2127
+ return parsed;
2128
+ };
2129
+ var finalizeArchivedMobilePage = (context, input) => {
2130
+ const { page } = context;
2131
+ if (page.pageId !== input.compatibility.pageId || page.framework !== input.compatibility.framework) {
2132
+ return envelopeResponse({
2133
+ kind: "invalid-request",
2134
+ message: "Archived producer page identity does not match its release artifact."
2135
+ }, BAD_REQUEST_STATUS);
2136
+ }
2137
+ try {
2138
+ const [currentRepresentation] = input.compatibility.representations;
2139
+ if (!currentRepresentation) {
2140
+ throw new TypeError("Mobile page has no current representation.");
2141
+ }
2142
+ return envelopeResponse({
2143
+ contract: page.contract,
2144
+ framework: page.framework,
2145
+ kind: "page",
2146
+ pageId: page.pageId,
2147
+ props: normalizeJsonValue(currentRepresentation.mapProps(input.props)),
2148
+ status: input.status ?? OK_STATUS
2149
+ }, input.status ?? OK_STATUS);
2150
+ } catch (error) {
2151
+ console.error(`[Mobile] Archived producer ${context.releaseId} failed for ${page.pageId}:`, error);
2152
+ return createAbsoluteMobilePageErrorResponse(page.pageId);
2153
+ }
2154
+ };
2155
+ var createAbsoluteMobileInvalidRequestResponse = (message) => envelopeResponse({ kind: "invalid-request", message }, BAD_REQUEST_STATUS);
2156
+ var createAbsoluteMobilePageErrorResponse = (pageId) => envelopeResponse({
693
2157
  code: "representation-failed",
694
2158
  kind: "error",
695
2159
  pageId,
@@ -767,7 +2231,7 @@ class AbsoluteMobilePageProtocolError extends Error {
767
2231
  this.code = code;
768
2232
  }
769
2233
  }
770
- var frameworks3 = new Set([
2234
+ var frameworks4 = new Set([
771
2235
  "angular",
772
2236
  "ember",
773
2237
  "html",
@@ -782,14 +2246,14 @@ var upgradeReasons = new Set([
782
2246
  "protocol",
783
2247
  "runtime"
784
2248
  ]);
785
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
786
- var isFramework = (value) => typeof value === "string" && frameworks3.has(value);
2249
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2250
+ var isFramework = (value) => typeof value === "string" && frameworks4.has(value);
787
2251
  var isUpgradeReason = (value) => typeof value === "string" && upgradeReasons.has(value);
788
2252
  var parsePageResult = (value) => {
789
2253
  if (typeof value.contract !== "string" || !isFramework(value.framework) || typeof value.pageId !== "string" || typeof value.status !== "number") {
790
2254
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The mobile page response is missing required page metadata.");
791
2255
  }
792
- if (!isRecord2(value.props)) {
2256
+ if (!isRecord5(value.props)) {
793
2257
  throw new AbsoluteMobilePageProtocolError("invalid-props", "The mobile page response must contain an object props value.");
794
2258
  }
795
2259
  return {
@@ -835,7 +2299,7 @@ var activateAbsoluteMobilePage = async (value, options) => {
835
2299
  };
836
2300
  };
837
2301
  var parseAbsoluteMobilePageEnvelope = (value) => {
838
- if (!isRecord2(value) || !isRecord2(value.response)) {
2302
+ if (!isRecord5(value) || !isRecord5(value.response)) {
839
2303
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The server did not return an AbsoluteJS mobile page envelope.");
840
2304
  }
841
2305
  if (value.protocol !== ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION) {
@@ -862,122 +2326,246 @@ var parseAbsoluteMobilePageEnvelope = (value) => {
862
2326
  }
863
2327
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", `Unknown mobile page response kind ${String(kind)}.`);
864
2328
  };
865
- // src/mobile/compatibilityDispatcher.ts
866
- import { Elysia } from "elysia";
867
2329
 
868
- // src/mobile/producerContext.ts
869
- import { AsyncLocalStorage } from "async_hooks";
870
- var isProducerStorage = (value) => typeof value === "object" && value !== null && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
871
- var getProducerStorage = () => {
872
- const value = Reflect.get(globalThis, ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY);
873
- return isProducerStorage(value) ? value : undefined;
2330
+ // src/mobile/transport.ts
2331
+ var ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT = 1;
2332
+ var pageForPathname = (manifest, pathname) => {
2333
+ const route = resolveAbsoluteMobileRoute(manifest.routes, pathname);
2334
+ if (!route) {
2335
+ throw new TypeError(`No embedded mobile page owns ${pathname}.`);
2336
+ }
2337
+ const page = manifest.pages.find((candidate) => candidate.pageId === route.pageId);
2338
+ if (!page) {
2339
+ throw new TypeError(`Mobile route ${route.pattern} references missing page ${route.pageId}.`);
2340
+ }
2341
+ return page;
874
2342
  };
875
- var ensureProducerStorage = () => {
876
- const existing = getProducerStorage();
877
- if (existing)
878
- return existing;
879
- const storage = new AsyncLocalStorage;
880
- Reflect.set(globalThis, ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY, storage);
881
- return storage;
2343
+ var canonicalBackendUrl = (manifest, path) => {
2344
+ const origin = new URL(manifest.productionOrigin);
2345
+ const url = new URL(path, `${origin.origin}/`);
2346
+ if (url.origin !== origin.origin) {
2347
+ throw new TypeError("Mobile transport cannot leave productionOrigin.");
2348
+ }
2349
+ url.hash = "";
2350
+ return url;
2351
+ };
2352
+ var createAbsoluteMobilePageRequest = (manifest, path, options = {}) => {
2353
+ const url = canonicalBackendUrl(manifest, path);
2354
+ const page = pageForPathname(manifest, url.pathname);
2355
+ const headers = new Headers(options.headers);
2356
+ headers.set("accept", ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE);
2357
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.appBuild, manifest.appBuild);
2358
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.pageBundle, page.bundleHash);
2359
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.pageContracts, page.contract);
2360
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.pageId, page.pageId);
2361
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.protocol, String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION));
2362
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.runtime, manifest.runtime);
2363
+ return new Request(url, { headers, method: "GET" });
2364
+ };
2365
+ var fetchAbsoluteMobilePage = async (manifest, path, options = {}) => {
2366
+ const request = createAbsoluteMobilePageRequest(manifest, path, options);
2367
+ const response = await (options.fetch ?? globalThis.fetch)(request);
2368
+ const value = await response.json();
2369
+ return value;
2370
+ };
2371
+ var navigateAbsoluteMobilePage = async (manifest, path, options) => {
2372
+ const envelope = await fetchAbsoluteMobilePage(manifest, path, options);
2373
+ return activateAbsoluteMobilePage(envelope, options);
2374
+ };
2375
+ var resolveAbsoluteMobileDeepLink = (manifest, value) => {
2376
+ const url = new URL(value);
2377
+ if (url.username || url.password) {
2378
+ throw new TypeError("Mobile deep links cannot contain credentials.");
2379
+ }
2380
+ const isHttpsHost = url.protocol === "https:" && manifest.deepLinkHosts.includes(url.hostname.toLowerCase());
2381
+ const isCustomScheme = manifest.deepLinkScheme !== undefined && url.protocol === `${manifest.deepLinkScheme}:`;
2382
+ if (!isHttpsHost && !isCustomScheme) {
2383
+ throw new TypeError("Mobile deep link is outside the configured app URLs.");
2384
+ }
2385
+ return `${url.pathname || "/"}${url.search}${url.hash}`;
882
2386
  };
883
- var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
884
2387
 
885
- // src/mobile/compatibilityDispatcher.ts
886
- var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
887
- var routeSegmentPattern = (segment) => {
888
- if (segment === "*")
889
- return ".*";
890
- if (segment.startsWith(":") && segment.endsWith("?"))
891
- return "[^/]*";
892
- if (segment.startsWith(":"))
893
- return "[^/]+";
894
- return segment.replace(REGEXP_SPECIAL_CHARACTERS, "\\$&");
2388
+ // src/mobile/capacitorBundle.ts
2389
+ var MANIFEST_FILE = "absolute-mobile-manifest.json";
2390
+ var BOOTSTRAP_FILE = "absolute-mobile-bootstrap.js";
2391
+ var INDEX_FILE = "index.html";
2392
+ var CLIENT_IMPORT_PATTERN = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s*)["'](\/[^"']+)["']/gu;
2393
+ var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2394
+ var shellBootstrapModule = () => {
2395
+ const candidate = ["js", "ts"].map((extension) => join7(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
2396
+ if (candidate)
2397
+ return candidate;
2398
+ throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
895
2399
  };
896
- var matchesRoutePattern = (pattern, pathname) => {
897
- const expression = pattern.split("/").map(routeSegmentPattern).join("/");
898
- return new RegExp(`^${expression}/?$`).test(pathname);
2400
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
2401
+ var indexHtml = (appName) => `<!doctype html>
2402
+ <html>
2403
+ <head>
2404
+ <meta charset="utf-8">
2405
+ <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
2406
+ <meta name="color-scheme" content="light dark">
2407
+ <title>${escapeHtml(appName)}</title>
2408
+ </head>
2409
+ <body>
2410
+ <main id="absolute-mobile-status" role="status">Starting\u2026</main>
2411
+ <script type="module" src="./${BOOTSTRAP_FILE}"></script>
2412
+ </body>
2413
+ </html>
2414
+ `;
2415
+ var sourceAssetPath = (buildDirectory, bundlePath) => {
2416
+ const root = resolve8(buildDirectory);
2417
+ const asset = resolve8(root, bundlePath.replace(/^\/+/, ""));
2418
+ if (!asset.startsWith(`${root}/`)) {
2419
+ throw new TypeError("Mobile page bundle escaped the build directory.");
2420
+ }
2421
+ return asset;
899
2422
  };
900
- var artifactOwnsRequest = (artifact, pageId, request) => {
901
- const { pathname } = new URL(request.url);
902
- return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesRoutePattern(route.pattern, pathname));
2423
+ var buildShellBootstrap = async (staging) => {
2424
+ const modulePath = shellBootstrapModule();
2425
+ const entryPath = join7(staging, ".absolute-mobile-entry.ts");
2426
+ await writeFile7(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
2427
+ void startAbsoluteMobileShell();
2428
+ `);
2429
+ const build = await Bun.build({
2430
+ entrypoints: [entryPath],
2431
+ minify: true,
2432
+ outdir: staging,
2433
+ target: "browser"
2434
+ });
2435
+ if (!build.success || build.outputs.length !== 1) {
2436
+ throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
2437
+ }
2438
+ await rename7(build.outputs[0]?.path ?? "", join7(staging, BOOTSTRAP_FILE));
2439
+ await rm6(entryPath, { force: true });
903
2440
  };
904
- var createProducerResolver = (loadProducer) => {
905
- const producers = new Map;
906
- return (artifact) => {
907
- const cached = producers.get(artifact.releaseId);
908
- if (cached)
909
- return cached;
910
- const loading = loadProducer(artifact).catch((error) => {
911
- producers.delete(artifact.releaseId);
2441
+ var removePreviousBundle = async (backup, moved) => {
2442
+ if (!moved)
2443
+ return;
2444
+ await rm6(backup, { force: true, recursive: true });
2445
+ };
2446
+ var restorePreviousBundle = async (backup, destination, moved) => {
2447
+ if (!moved)
2448
+ return;
2449
+ await rename7(backup, destination);
2450
+ };
2451
+ var installBundle = async (staging, destination) => {
2452
+ const backup = `${destination}.previous-${crypto.randomUUID()}`;
2453
+ let movedPrevious = false;
2454
+ try {
2455
+ await rename7(destination, backup);
2456
+ movedPrevious = true;
2457
+ } catch (error) {
2458
+ if (!errorHasCode2(error, "ENOENT"))
912
2459
  throw error;
913
- });
914
- producers.set(artifact.releaseId, loading);
915
- return loading;
916
- };
2460
+ }
2461
+ try {
2462
+ await rename7(staging, destination);
2463
+ await removePreviousBundle(backup, movedPrevious);
2464
+ } catch (error) {
2465
+ await restorePreviousBundle(backup, destination, movedPrevious);
2466
+ throw error;
2467
+ }
917
2468
  };
918
- var createAbsoluteMobileCompatibilityDispatcher = (options) => {
919
- const artifacts = retainAbsoluteMobileCompatibilityArtifacts(options.artifacts);
920
- if (!artifacts.some(({ releaseId }) => releaseId === options.currentReleaseId)) {
921
- throw new TypeError("currentReleaseId must identify a retained compatibility artifact.");
2469
+ var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) => {
2470
+ if (page.framework !== "react") {
2471
+ throw new TypeError(`Capacitor spike currently supports React pages; ${page.pageId} is ${page.framework}.`);
2472
+ }
2473
+ const extension = extname(page.bundlePath) || ".js";
2474
+ const localBundlePath = `./pages/${page.bundleHash}${extension}`;
2475
+ const source = sourceAssetPath(buildDirectory, page.bundlePath);
2476
+ await copyFile4(source, join7(staging, localBundlePath));
2477
+ await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
2478
+ return { ...page, localBundlePath };
2479
+ };
2480
+ var absoluteClientImports = async (sourcePath) => {
2481
+ const source = await readFile8(sourcePath, "utf8");
2482
+ return [...source.matchAll(CLIENT_IMPORT_PATTERN)].flatMap((match) => {
2483
+ const [specifier] = match.slice(1);
2484
+ return specifier ? [specifier.split(/[?#]/u, 1)[0] ?? specifier] : [];
2485
+ });
2486
+ };
2487
+ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, copied) => {
2488
+ if (copied.has(specifier))
2489
+ return;
2490
+ copied.add(specifier);
2491
+ const source = sourceAssetPath(buildDirectory, specifier);
2492
+ const destination = join7(staging, specifier.replace(/^\/+/, ""));
2493
+ await mkdir6(dirname4(destination), { recursive: true });
2494
+ await copyFile4(source, destination);
2495
+ await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
2496
+ };
2497
+ var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
2498
+ const dependencies = await absoluteClientImports(sourcePath);
2499
+ await Promise.all(dependencies.map((specifier) => copyAbsoluteClientDependency(specifier, buildDirectory, staging, copied)));
2500
+ };
2501
+ var materializeAbsoluteCapacitorWebBundle = async (options) => {
2502
+ if (!resolveAbsoluteMobileRoute(options.artifact.routes, new URL(options.config.entry, "https://absolute.invalid").pathname)) {
2503
+ throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
2504
+ }
2505
+ const destination = options.config.bundleDirectory;
2506
+ await mkdir6(dirname4(destination), { recursive: true });
2507
+ const staging = await mkdtemp4(join7(dirname4(destination), `.${basename(destination)}.stage-`));
2508
+ try {
2509
+ const pageDirectory = join7(staging, "pages");
2510
+ await mkdir6(pageDirectory, { recursive: true });
2511
+ const copiedDependencies = new Set;
2512
+ const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
2513
+ const manifest = {
2514
+ appBuild: options.artifact.appBuild,
2515
+ appId: options.config.appId,
2516
+ appName: options.config.appName,
2517
+ deepLinkHosts: options.config.deepLinkHosts,
2518
+ deepLinkScheme: options.config.deepLinkScheme,
2519
+ entry: options.config.entry,
2520
+ format: ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
2521
+ pages,
2522
+ productionOrigin: options.config.productionOrigin,
2523
+ routes: options.artifact.routes,
2524
+ runtime: options.artifact.runtime
2525
+ };
2526
+ await Promise.all([
2527
+ writeFile7(join7(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
2528
+ `),
2529
+ writeFile7(join7(staging, INDEX_FILE), indexHtml(options.config.appName)),
2530
+ buildShellBootstrap(staging)
2531
+ ]);
2532
+ await installBundle(staging, destination);
2533
+ return manifest;
2534
+ } catch (error) {
2535
+ await rm6(staging, { force: true, recursive: true });
2536
+ throw error;
922
2537
  }
923
- const resolveProducer = createProducerResolver(options.loadProducer);
924
- return new Elysia({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
925
- if (getCurrentAbsoluteMobileProducerContext())
926
- return;
927
- const parsed = parseAbsoluteMobilePageRequest(request);
928
- if (parsed.kind !== "mobile")
929
- return;
930
- const resolved = resolveAbsoluteMobileCompatibilityRelease(parsed.client, artifacts);
931
- if (resolved.kind === "upgrade-required") {
932
- return createAbsoluteMobileUpgradeResponse(resolved.result);
933
- }
934
- if (!artifactOwnsRequest(resolved.artifact, parsed.client.pageId, request)) {
935
- return createAbsoluteMobileInvalidRequestResponse("The requested URL is not assigned to this mobile page.");
936
- }
937
- if (resolved.artifact.releaseId === options.currentReleaseId) {
938
- return;
939
- }
940
- try {
941
- const producer = await resolveProducer(resolved.artifact);
942
- return runWithAbsoluteMobileProducer({
943
- page: resolved.page,
944
- releaseId: resolved.artifact.releaseId
945
- }, () => producer.handle(request));
946
- } catch (error) {
947
- console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
948
- return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
949
- }
950
- }).as("global");
951
2538
  };
2539
+
952
2540
  // src/mobile/materializedBundle.ts
953
- import { createHash as createHash3 } from "crypto";
2541
+ import { createHash as createHash7 } from "crypto";
954
2542
  import {
955
- access,
956
- mkdir as mkdir2,
957
- mkdtemp as mkdtemp2,
958
- readFile as readFile2,
959
- rename as rename2,
960
- rm as rm2,
961
- writeFile as writeFile2
2543
+ access as access6,
2544
+ mkdir as mkdir7,
2545
+ mkdtemp as mkdtemp5,
2546
+ readFile as readFile9,
2547
+ rename as rename8,
2548
+ rm as rm7,
2549
+ writeFile as writeFile8
962
2550
  } from "fs/promises";
963
- import { dirname, join as join2, resolve as resolvePath2 } from "path";
2551
+ import { dirname as dirname5, join as join8, resolve as resolvePath2 } from "path";
964
2552
  import { pathToFileURL } from "url";
965
2553
  var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
966
2554
  var CURRENT_BUNDLE_FILE = "current.json";
967
2555
  var BUNDLES_DIRECTORY = "bundles";
968
2556
  var ARTIFACT_FILE2 = "artifact.json";
969
2557
  var BUNDLE_ID_PATTERN = /^amb_[a-f0-9]{64}$/;
970
- var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
971
- var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2558
+ var isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2559
+ var errorHasCode3 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
972
2560
  var bundleIdFor = (currentReleaseId, releases) => {
973
2561
  const identity = JSON.stringify({
974
2562
  currentReleaseId,
975
2563
  releases: releases.map(({ releaseId }) => releaseId)
976
2564
  });
977
- return `amb_${createHash3("sha256").update(identity).digest("hex")}`;
2565
+ return `amb_${createHash7("sha256").update(identity).digest("hex")}`;
978
2566
  };
979
2567
  var parseBundleIndex = (value) => {
980
- if (!isRecord3(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)) {
2568
+ if (!isRecord6(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)) {
981
2569
  throw new TypeError("Invalid materialized mobile compatibility bundle.");
982
2570
  }
983
2571
  const releases = value.releases.map(parseAbsoluteMobileCompatibilityArtifact);
@@ -1000,31 +2588,31 @@ var parseBundleIndex = (value) => {
1000
2588
  };
1001
2589
  };
1002
2590
  var writeRelease = async (root, release) => {
1003
- const directory = join2(root, release.artifact.releaseId);
1004
- const producerPath = join2(directory, release.artifact.producer.module);
1005
- await mkdir2(dirname(producerPath), { recursive: true });
2591
+ const directory = join8(root, release.artifact.releaseId);
2592
+ const producerPath = join8(directory, release.artifact.producer.module);
2593
+ await mkdir7(dirname5(producerPath), { recursive: true });
1006
2594
  await Promise.all([
1007
- writeFile2(join2(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
2595
+ writeFile8(join8(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
1008
2596
  `),
1009
- writeFile2(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
2597
+ writeFile8(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
1010
2598
  ]);
1011
2599
  };
1012
2600
  var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
1013
- const destination = join2(bundlesRoot, bundleId);
2601
+ const destination = join8(bundlesRoot, bundleId);
1014
2602
  try {
1015
- await access(destination);
2603
+ await access6(destination);
1016
2604
  return destination;
1017
2605
  } catch (error) {
1018
- if (!errorHasCode2(error, "ENOENT"))
2606
+ if (!errorHasCode3(error, "ENOENT"))
1019
2607
  throw error;
1020
2608
  }
1021
- const staging = await mkdtemp2(join2(bundlesRoot, ".stage-"));
2609
+ const staging = await mkdtemp5(join8(bundlesRoot, ".stage-"));
1022
2610
  try {
1023
2611
  await Promise.all(releases.map((release) => writeRelease(staging, release)));
1024
- await rename2(staging, destination);
2612
+ await rename8(staging, destination);
1025
2613
  } catch (error) {
1026
- await rm2(staging, { force: true, recursive: true });
1027
- if (errorHasCode2(error, "EEXIST") || errorHasCode2(error, "ENOTEMPTY")) {
2614
+ await rm7(staging, { force: true, recursive: true });
2615
+ if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
1028
2616
  return destination;
1029
2617
  }
1030
2618
  throw error;
@@ -1034,7 +2622,7 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
1034
2622
  var readCompatibilityModule = (modulePath) => import(pathToFileURL(modulePath).href);
1035
2623
  var resolveProducerHandler = (loaded, exportName) => {
1036
2624
  const value = loaded[exportName];
1037
- if (!isRecord3(value) || typeof value.handle !== "function") {
2625
+ if (!isRecord6(value) || typeof value.handle !== "function") {
1038
2626
  throw new TypeError(`Compatibility producer export ${exportName} must expose handle(request).`);
1039
2627
  }
1040
2628
  const { handle } = value;
@@ -1052,15 +2640,15 @@ var resolveProducerHandler = (loaded, exportName) => {
1052
2640
  };
1053
2641
  var loadAbsoluteMobileMaterializedBundle = async (root) => {
1054
2642
  const resolvedRoot = resolvePath2(root);
1055
- const serialized = await readFile2(join2(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2643
+ const serialized = await readFile9(join8(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
1056
2644
  const parsed = JSON.parse(serialized);
1057
2645
  const index = parseBundleIndex(parsed);
1058
- const bundleRoot = join2(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2646
+ const bundleRoot = join8(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
1059
2647
  return {
1060
2648
  artifacts: index.releases,
1061
2649
  currentReleaseId: index.currentReleaseId,
1062
2650
  loadProducer: async (artifact) => {
1063
- const modulePath = join2(bundleRoot, artifact.releaseId, artifact.producer.module);
2651
+ const modulePath = join8(bundleRoot, artifact.releaseId, artifact.producer.module);
1064
2652
  await verifyAbsoluteMobileCompatibilityProducer({
1065
2653
  artifact,
1066
2654
  producer: Bun.file(modulePath)
@@ -1087,8 +2675,8 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
1087
2675
  return release;
1088
2676
  });
1089
2677
  const root = resolvePath2(input.root);
1090
- const bundlesRoot = join2(root, BUNDLES_DIRECTORY);
1091
- await mkdir2(bundlesRoot, { recursive: true });
2678
+ const bundlesRoot = join8(root, BUNDLES_DIRECTORY);
2679
+ await mkdir7(bundlesRoot, { recursive: true });
1092
2680
  const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
1093
2681
  await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
1094
2682
  const index = {
@@ -1097,44 +2685,863 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
1097
2685
  format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
1098
2686
  releases: artifacts
1099
2687
  };
1100
- const pointerPath = join2(root, CURRENT_BUNDLE_FILE);
1101
- const temporaryPointerPath = join2(root, `.current-${crypto.randomUUID()}.json`);
1102
- await writeFile2(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
2688
+ const pointerPath = join8(root, CURRENT_BUNDLE_FILE);
2689
+ const temporaryPointerPath = join8(root, `.current-${crypto.randomUUID()}.json`);
2690
+ await writeFile8(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
1103
2691
  `, { flag: "wx" });
1104
- await rename2(temporaryPointerPath, pointerPath);
2692
+ await rename8(temporaryPointerPath, pointerPath);
1105
2693
  return index;
1106
2694
  };
2695
+ var readAbsoluteMobileMaterializedReleases = async (root) => {
2696
+ const resolvedRoot = resolvePath2(root);
2697
+ try {
2698
+ const serialized = await readFile9(join8(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2699
+ const parsed = JSON.parse(serialized);
2700
+ const index = parseBundleIndex(parsed);
2701
+ const bundleRoot = join8(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2702
+ return Promise.all(index.releases.map(async (artifact) => {
2703
+ const producer = Bun.file(join8(bundleRoot, artifact.releaseId, artifact.producer.module));
2704
+ await verifyAbsoluteMobileCompatibilityProducer({
2705
+ artifact,
2706
+ producer
2707
+ });
2708
+ return { artifact, producer };
2709
+ }));
2710
+ } catch (error) {
2711
+ if (errorHasCode3(error, "ENOENT"))
2712
+ return [];
2713
+ throw error;
2714
+ }
2715
+ };
2716
+
2717
+ // src/mobile/buildPipeline.ts
2718
+ var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
2719
+ var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
2720
+ var serverExportName = (loaded, app) => {
2721
+ if (loaded.server === app)
2722
+ return "server";
2723
+ if (loaded.app === app)
2724
+ return "app";
2725
+ return "default";
2726
+ };
2727
+ var restoreBuildDirectory = (previous) => {
2728
+ if (previous !== undefined) {
2729
+ process.env.ABSOLUTE_BUILD_DIR = previous;
2730
+ return;
2731
+ }
2732
+ delete process.env.ABSOLUTE_BUILD_DIR;
2733
+ };
2734
+ var requireRelease = (releases, releaseId) => {
2735
+ const release = releases.get(releaseId);
2736
+ if (!release) {
2737
+ throw new TypeError(`Missing retained mobile release ${releaseId}.`);
2738
+ }
2739
+ return release;
2740
+ };
2741
+ var loadServerApp = async (producerPath) => {
2742
+ const loaded = await import(`${pathToFileURL2(producerPath).href}?absolute-mobile-capture=${crypto.randomUUID()}`);
2743
+ const candidates = [loaded.server, loaded.app, loaded.default];
2744
+ const app = candidates.find(isElysiaApp);
2745
+ if (!app) {
2746
+ throw new TypeError("Mobile builds require the server entry to export its Elysia app as server, app, or default.");
2747
+ }
2748
+ const exportName = serverExportName(loaded, app);
2749
+ return { app, exportName };
2750
+ };
2751
+ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2752
+ const buildDirectory = resolve9(options.buildDirectory);
2753
+ const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
2754
+ const root = join9(buildDirectory, ".absolutejs", "mobile-compatibility");
2755
+ const [manifestSource, previous] = await Promise.all([
2756
+ readFile10(join9(buildDirectory, "manifest.json"), "utf8"),
2757
+ readAbsoluteMobileMaterializedReleases(root)
2758
+ ]);
2759
+ const manifest = JSON.parse(manifestSource);
2760
+ if (!isStringRecord(manifest)) {
2761
+ throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
2762
+ }
2763
+ const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
2764
+ process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
2765
+ let loaded;
2766
+ try {
2767
+ loaded = await loadServerApp(resolve9(options.producerPath));
2768
+ } finally {
2769
+ restoreBuildDirectory(previousBuildDirectory);
2770
+ }
2771
+ const current = await buildAbsoluteMobileCompatibilityRelease({
2772
+ app: loaded.app,
2773
+ appId: mobile.appId,
2774
+ buildDirectory,
2775
+ manifest,
2776
+ previousArtifacts: previous.map(({ artifact }) => artifact),
2777
+ producerExport: loaded.exportName,
2778
+ producerPath: resolve9(options.producerPath),
2779
+ runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
2780
+ });
2781
+ const releasesById = new Map([current, ...previous].map((release) => [
2782
+ release.artifact.releaseId,
2783
+ release
2784
+ ]));
2785
+ const retained = retainAbsoluteMobileCompatibilityArtifacts([...releasesById.values()].map(({ artifact }) => artifact));
2786
+ await materializeAbsoluteMobileCompatibilityBundle({
2787
+ currentReleaseId: current.artifact.releaseId,
2788
+ releases: retained.map((artifact) => requireRelease(releasesById, artifact.releaseId)),
2789
+ root
2790
+ });
2791
+ await materializeAbsoluteCapacitorWebBundle({
2792
+ artifact: current.artifact,
2793
+ buildDirectory,
2794
+ config: mobile
2795
+ });
2796
+ return current.artifact;
2797
+ };
2798
+ // src/mobile/compatibilityDispatcher.ts
2799
+ import { Elysia as Elysia2 } from "elysia";
2800
+
2801
+ // src/mobile/producerContext.ts
2802
+ import { AsyncLocalStorage } from "async_hooks";
2803
+ var isProducerStorage = (value) => typeof value === "object" && value !== null && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
2804
+ var getProducerStorage = () => {
2805
+ const value = Reflect.get(globalThis, ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY);
2806
+ return isProducerStorage(value) ? value : undefined;
2807
+ };
2808
+ var ensureProducerStorage = () => {
2809
+ const existing = getProducerStorage();
2810
+ if (existing)
2811
+ return existing;
2812
+ const storage = new AsyncLocalStorage;
2813
+ Reflect.set(globalThis, ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY, storage);
2814
+ return storage;
2815
+ };
2816
+ var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
2817
+
2818
+ // src/mobile/compatibilityDispatcher.ts
2819
+ var artifactOwnsRequest = (artifact, pageId, request) => {
2820
+ const { pathname } = new URL(request.url);
2821
+ return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
2822
+ };
2823
+ var createProducerResolver = (loadProducer) => {
2824
+ const producers = new Map;
2825
+ return (artifact) => {
2826
+ const cached = producers.get(artifact.releaseId);
2827
+ if (cached)
2828
+ return cached;
2829
+ const loading = loadProducer(artifact).catch((error) => {
2830
+ producers.delete(artifact.releaseId);
2831
+ throw error;
2832
+ });
2833
+ producers.set(artifact.releaseId, loading);
2834
+ return loading;
2835
+ };
2836
+ };
2837
+ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2838
+ const artifacts = retainAbsoluteMobileCompatibilityArtifacts(options.artifacts);
2839
+ if (!artifacts.some(({ releaseId }) => releaseId === options.currentReleaseId)) {
2840
+ throw new TypeError("currentReleaseId must identify a retained compatibility artifact.");
2841
+ }
2842
+ const resolveProducer = createProducerResolver(options.loadProducer);
2843
+ return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
2844
+ if (getCurrentAbsoluteMobileProducerContext())
2845
+ return;
2846
+ const parsed = parseAbsoluteMobilePageRequest(request);
2847
+ if (parsed.kind !== "mobile")
2848
+ return;
2849
+ const resolved = resolveAbsoluteMobileCompatibilityRelease(parsed.client, artifacts);
2850
+ if (resolved.kind === "upgrade-required") {
2851
+ return createAbsoluteMobileUpgradeResponse(resolved.result);
2852
+ }
2853
+ if (!artifactOwnsRequest(resolved.artifact, parsed.client.pageId, request)) {
2854
+ return createAbsoluteMobileInvalidRequestResponse("The requested URL is not assigned to this mobile page.");
2855
+ }
2856
+ if (resolved.artifact.releaseId === options.currentReleaseId) {
2857
+ return;
2858
+ }
2859
+ try {
2860
+ const producer = await resolveProducer(resolved.artifact);
2861
+ return runWithAbsoluteMobileProducer({
2862
+ page: resolved.page,
2863
+ releaseId: resolved.artifact.releaseId
2864
+ }, () => producer.handle(request));
2865
+ } catch (error) {
2866
+ console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
2867
+ return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
2868
+ }
2869
+ }).as("global");
2870
+ };
2871
+ // src/mobile/nativeDeepLinks.ts
2872
+ import { readFile as readFile11, rename as rename9, writeFile as writeFile9 } from "fs/promises";
2873
+ import { join as join10 } from "path";
2874
+ var START_MARKER = "<!-- absolutejs:deep-links:start -->";
2875
+ var END_MARKER = "<!-- absolutejs:deep-links:end -->";
2876
+ var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
2877
+ var NOT_FOUND = -1;
2878
+ var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2879
+ var writeChangedFile = async (path, source) => {
2880
+ const current = await readFile11(path, "utf8");
2881
+ if (current === source)
2882
+ return false;
2883
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2884
+ await writeFile9(temporary, source, { flag: "wx" });
2885
+ await rename9(temporary, path);
2886
+ return true;
2887
+ };
2888
+ var replaceManagedRegion = (source, region, insertAt) => {
2889
+ const start = source.indexOf(START_MARKER);
2890
+ const end = source.indexOf(END_MARKER);
2891
+ if (start === NOT_FOUND !== (end === NOT_FOUND) || start !== NOT_FOUND && end < start) {
2892
+ throw new TypeError("AbsoluteJS deep-link ownership markers are malformed.");
2893
+ }
2894
+ if (start !== NOT_FOUND) {
2895
+ const lineStart = source.lastIndexOf(`
2896
+ `, start) + 1;
2897
+ const nextLine = source.indexOf(`
2898
+ `, end + END_MARKER.length);
2899
+ const lineEnd = nextLine === NOT_FOUND ? source.length : nextLine + 1;
2900
+ return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
2901
+ }
2902
+ const index = insertAt();
2903
+ if (index === NOT_FOUND) {
2904
+ throw new TypeError("Could not find a safe native project location for deep-link configuration.");
2905
+ }
2906
+ return `${source.slice(0, index)}${region}${source.slice(index)}`;
2907
+ };
2908
+ var androidRegion = (config) => {
2909
+ const hosts = config.deepLinkHosts.map((host) => ` <data android:scheme="https" android:host="${escapeXml(host)}" />`).join(`
2910
+ `);
2911
+ const customScheme = config.deepLinkScheme ? `
2912
+
2913
+ <intent-filter>
2914
+ <action android:name="android.intent.action.VIEW" />
2915
+ <category android:name="android.intent.category.DEFAULT" />
2916
+ <category android:name="android.intent.category.BROWSABLE" />
2917
+ <data android:scheme="${escapeXml(config.deepLinkScheme)}" />
2918
+ </intent-filter>` : "";
2919
+ return ` ${START_MARKER}
2920
+ <intent-filter android:autoVerify="true">
2921
+ <action android:name="android.intent.action.VIEW" />
2922
+ <category android:name="android.intent.category.DEFAULT" />
2923
+ <category android:name="android.intent.category.BROWSABLE" />
2924
+ ${hosts}
2925
+ </intent-filter>${customScheme}
2926
+ ${END_MARKER}
2927
+ `;
2928
+ };
2929
+ var configureAndroid = async (config) => {
2930
+ const path = join10(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
2931
+ const source = await readFile11(path, "utf8");
2932
+ const mainActivity = source.indexOf('android:name=".MainActivity"');
2933
+ if (mainActivity === NOT_FOUND) {
2934
+ throw new TypeError("Android MainActivity was not found.");
2935
+ }
2936
+ const activityEnd = source.indexOf("</activity>", mainActivity);
2937
+ const updated = replaceManagedRegion(source, androidRegion(config), () => activityEnd === NOT_FOUND ? NOT_FOUND : source.lastIndexOf(`
2938
+ `, activityEnd) + 1);
2939
+ return writeChangedFile(path, updated);
2940
+ };
2941
+ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
2942
+ <key>CFBundleURLTypes</key>
2943
+ <array>
2944
+ <dict>
2945
+ <key>CFBundleURLName</key>
2946
+ <string>absolutejs</string>
2947
+ <key>CFBundleURLSchemes</key>
2948
+ <array>
2949
+ <string>${escapeXml(scheme)}</string>
2950
+ </array>
2951
+ </dict>
2952
+ </array>
2953
+ ${END_MARKER}
2954
+ `;
2955
+ var configureIosInfo = async (config) => {
2956
+ const path = join10(config.nativeProjectDirectory, "ios/App/App/Info.plist");
2957
+ const source = await readFile11(path, "utf8");
2958
+ const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
2959
+ ${END_MARKER}
2960
+ `;
2961
+ const updated = replaceManagedRegion(source, region, () => source.lastIndexOf("</dict>"));
2962
+ return writeChangedFile(path, updated);
2963
+ };
2964
+ var iosEntitlementsSource = (config) => {
2965
+ const domains = config.deepLinkHosts.map((host) => ` <string>applinks:${escapeXml(host)}</string>`).join(`
2966
+ `);
2967
+ return `<?xml version="1.0" encoding="UTF-8"?>
2968
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2969
+ <plist version="1.0">
2970
+ <dict>
2971
+ <key>com.apple.developer.associated-domains</key>
2972
+ <array>
2973
+ ${domains}
2974
+ </array>
2975
+ </dict>
2976
+ </plist>
2977
+ `;
2978
+ };
2979
+ var configureIosEntitlements = async (config) => {
2980
+ const path = join10(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
2981
+ let current = "";
2982
+ try {
2983
+ current = await readFile11(path, "utf8");
2984
+ } catch (error) {
2985
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
2986
+ throw error;
2987
+ }
2988
+ }
2989
+ const source = iosEntitlementsSource(config);
2990
+ if (current === source)
2991
+ return false;
2992
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2993
+ await writeFile9(temporary, source, { flag: "wx" });
2994
+ await rename9(temporary, path);
2995
+ return true;
2996
+ };
2997
+ var configureIosProject = async (config) => {
2998
+ const path = join10(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
2999
+ const source = await readFile11(path, "utf8");
3000
+ const declarations = [
3001
+ ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
3002
+ ].map((match) => match[1]);
3003
+ if (declarations.some((value) => value !== IOS_ENTITLEMENTS)) {
3004
+ throw new TypeError("The iOS target already uses a different entitlements file; merge associated domains there before continuing.");
3005
+ }
3006
+ if (declarations.length > 0)
3007
+ return false;
3008
+ let count = 0;
3009
+ const updated = source.replaceAll("INFOPLIST_FILE = App/Info.plist;", () => {
3010
+ count += 1;
3011
+ return `CODE_SIGN_ENTITLEMENTS = ${IOS_ENTITLEMENTS};
3012
+ INFOPLIST_FILE = App/Info.plist;`;
3013
+ });
3014
+ if (count === 0) {
3015
+ throw new TypeError("The iOS application target build settings were not found.");
3016
+ }
3017
+ return writeChangedFile(path, updated);
3018
+ };
3019
+ var configureIos = async (config) => {
3020
+ const changed = await Promise.all([
3021
+ configureIosInfo(config),
3022
+ configureIosEntitlements(config),
3023
+ configureIosProject(config)
3024
+ ]);
3025
+ return changed.some(Boolean);
3026
+ };
3027
+ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms) => {
3028
+ const results = await Promise.all(platforms.map(async (platform) => {
3029
+ const didChange = platform === "android" ? await configureAndroid(config) : await configureIos(config);
3030
+ return { didChange, platform };
3031
+ }));
3032
+ return {
3033
+ changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
3034
+ };
3035
+ };
3036
+ // src/mobile/releasePublisher.ts
3037
+ import { access as access7 } from "fs/promises";
3038
+ import { isAbsolute as isAbsolute4, relative as relative6, resolve as resolve10, sep as sep4 } from "path";
3039
+ import { pathToFileURL as pathToFileURL3 } from "url";
3040
+ var prepareAbsoluteIosRelease = async (publisher, options) => {
3041
+ if (typeof publisher.prepareIosRelease !== "function") {
3042
+ throw new TypeError("App Store Connect publishing requires a registry module created with @absolutejs/deploy/app-store-connect.");
3043
+ }
3044
+ const { buildNumber } = await publisher.prepareIosRelease(options);
3045
+ if (!Number.isSafeInteger(buildNumber) || buildNumber < 1) {
3046
+ throw new TypeError("App Store Connect publisher returned an invalid iOS build number.");
3047
+ }
3048
+ return buildNumber;
3049
+ };
3050
+ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
3051
+ if (typeof publisher.prepareAndroidRelease !== "function") {
3052
+ throw new TypeError("Google Play publishing requires a registry module created with @absolutejs/deploy/google-play.");
3053
+ }
3054
+ const prepared = await publisher.prepareAndroidRelease(options);
3055
+ const { versionCode } = prepared;
3056
+ if (typeof versionCode !== "number" || !Number.isSafeInteger(versionCode) || versionCode < 1 || versionCode > 2100000000) {
3057
+ throw new TypeError("Google Play publisher returned an invalid Android versionCode.");
3058
+ }
3059
+ return versionCode;
3060
+ };
3061
+ var isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3062
+ var isPublisher = (value) => isRecord7(value) && typeof value.publish === "function";
3063
+ var publisherModulePath = (projectRoot, requested) => {
3064
+ const root = resolve10(projectRoot);
3065
+ const path = resolve10(root, requested);
3066
+ const projectRelative = relative6(root, path);
3067
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep4}`) || isAbsolute4(projectRelative)) {
3068
+ throw new TypeError("mobile publish --registry must remain inside the project.");
3069
+ }
3070
+ return path;
3071
+ };
3072
+ var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
3073
+ const modulePath = publisherModulePath(projectRoot, requestedModulePath);
3074
+ await access7(modulePath).catch(() => {
3075
+ throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
3076
+ });
3077
+ const loaded = await import(pathToFileURL3(modulePath).href);
3078
+ const publisher = isRecord7(loaded) ? loaded.default ?? loaded.registry : undefined;
3079
+ if (!isPublisher(publisher)) {
3080
+ throw new TypeError("Native release registry module must default-export a registry with publish(options).");
3081
+ }
3082
+ return publisher;
3083
+ };
3084
+ var publishAbsoluteAndroidRelease = async (options) => {
3085
+ const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
3086
+ const publication = await publisher.publish({
3087
+ allowUnsigned: options.allowUnsigned,
3088
+ channel: options.channel,
3089
+ googlePlay: options.googlePlay,
3090
+ releaseRoot: options.release.releaseRoot,
3091
+ signal: options.signal
3092
+ });
3093
+ const expected = options.release.metadata;
3094
+ const actual = publication.record?.metadata;
3095
+ 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") {
3096
+ throw new TypeError("Native release registry returned a different Android release identity.");
3097
+ }
3098
+ if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
3099
+ throw new TypeError("Native release registry did not promote the requested channel.");
3100
+ }
3101
+ const { googlePlay } = publication;
3102
+ if (options.googlePlay) {
3103
+ 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") {
3104
+ throw new TypeError("Native release publisher did not commit the requested Google Play release.");
3105
+ }
3106
+ }
3107
+ return publication;
3108
+ };
3109
+ var publishAbsoluteIosRelease = async (options) => {
3110
+ const publisher = await loadAbsoluteNativeReleasePublisher(options.projectRoot, options.modulePath);
3111
+ const publication = await publisher.publish({
3112
+ allowUnsigned: options.allowUnsigned,
3113
+ appStoreConnect: options.appStoreConnect,
3114
+ channel: options.channel,
3115
+ releaseRoot: options.release.releaseRoot,
3116
+ signal: options.signal
3117
+ });
3118
+ const expected = options.release.metadata;
3119
+ const actual = publication.record?.metadata;
3120
+ 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") {
3121
+ throw new TypeError("Native release registry returned a different iOS release identity.");
3122
+ }
3123
+ if (options.channel !== undefined && (publication.channel?.channel !== options.channel || publication.channel.releaseId !== expected.releaseId)) {
3124
+ throw new TypeError("Native release registry did not promote the requested channel.");
3125
+ }
3126
+ if (options.appStoreConnect) {
3127
+ const distributed = publication.appStoreConnect;
3128
+ 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") {
3129
+ throw new TypeError("Native release publisher did not complete the requested App Store Connect release.");
3130
+ }
3131
+ }
3132
+ return publication;
3133
+ };
3134
+ // src/mobile/routeMetadataTransform.ts
3135
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
3136
+ import { dirname as dirname6, extname as extname2, relative as relative7, resolve as resolve11 } from "path";
3137
+ import ts from "typescript";
3138
+ var ROUTE_METHODS = new Set(["get", "head"]);
3139
+ var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
3140
+ var PAGE_HANDLER = "handleReactPageRequest";
3141
+ var posixPath = (value) => value.replace(/\\/g, "/");
3142
+ var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname6(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
3143
+ var createProgram = (entry, projectRoot) => {
3144
+ const configPath = findTsconfig(entry, projectRoot);
3145
+ if (!configPath) {
3146
+ return ts.createProgram([entry], {
3147
+ allowJs: true,
3148
+ jsx: ts.JsxEmit.ReactJSX,
3149
+ module: ts.ModuleKind.ESNext,
3150
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
3151
+ target: ts.ScriptTarget.ESNext
3152
+ });
3153
+ }
3154
+ const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname6(configPath));
3155
+ if (!parsed.fileNames.includes(entry))
3156
+ parsed.fileNames.push(entry);
3157
+ return ts.createProgram(parsed.fileNames, parsed.options);
3158
+ };
3159
+ var propertyName = (property) => {
3160
+ if (!("name" in property) || !property.name)
3161
+ return;
3162
+ if (ts.isIdentifier(property.name))
3163
+ return property.name.text;
3164
+ if (ts.isStringLiteralLike(property.name))
3165
+ return property.name.text;
3166
+ return;
3167
+ };
3168
+ var objectPropertyExpression = (object, name) => {
3169
+ const property = object.properties.find((candidate) => propertyName(candidate) === name);
3170
+ if (property && ts.isPropertyAssignment(property)) {
3171
+ return property.initializer;
3172
+ }
3173
+ if (property && ts.isShorthandPropertyAssignment(property)) {
3174
+ return property.name;
3175
+ }
3176
+ return;
3177
+ };
3178
+ var serializeType = (type, checker, ancestors = new Set) => {
3179
+ if (type.flags & ts.TypeFlags.Any)
3180
+ return { type: "any" };
3181
+ if (type.flags & ts.TypeFlags.Unknown)
3182
+ return { type: "unknown" };
3183
+ if (type.flags & ts.TypeFlags.Never)
3184
+ return { type: "never" };
3185
+ if (type.flags & ts.TypeFlags.StringLike)
3186
+ return { type: "string" };
3187
+ if (type.flags & ts.TypeFlags.NumberLike)
3188
+ return { type: "number" };
3189
+ if (type.flags & ts.TypeFlags.BooleanLike)
3190
+ return { type: "boolean" };
3191
+ if (type.flags & ts.TypeFlags.BigIntLike)
3192
+ return { type: "bigint" };
3193
+ if (type.flags & ts.TypeFlags.Null)
3194
+ return { type: "null" };
3195
+ if (type.flags & ts.TypeFlags.Undefined)
3196
+ return { type: "undefined" };
3197
+ if (type.isUnion()) {
3198
+ return {
3199
+ anyOf: type.types.map((member) => serializeType(member, checker, ancestors)).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)))
3200
+ };
3201
+ }
3202
+ if (type.isIntersection()) {
3203
+ return {
3204
+ allOf: type.types.map((member) => serializeType(member, checker, ancestors)).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)))
3205
+ };
3206
+ }
3207
+ if (ancestors.has(type)) {
3208
+ return {
3209
+ ref: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
3210
+ };
3211
+ }
3212
+ ancestors.add(type);
3213
+ const arrayElement = checker.getIndexTypeOfType(type, ts.IndexKind.Number);
3214
+ const properties = checker.getPropertiesOfType(type);
3215
+ let schema;
3216
+ if (arrayElement && properties.some(({ name }) => name === "length")) {
3217
+ schema = {
3218
+ items: serializeType(arrayElement, checker, ancestors),
3219
+ type: "array"
3220
+ };
3221
+ } else if (properties.length > 0) {
3222
+ const entries = properties.filter(({ name }) => !name.startsWith("__")).map((property) => {
3223
+ const declaration = property.valueDeclaration ?? property.declarations?.[0];
3224
+ const propertyType = declaration ? checker.getTypeOfSymbolAtLocation(property, declaration) : checker.getDeclaredTypeOfSymbol(property);
3225
+ return [
3226
+ property.name,
3227
+ {
3228
+ optional: Boolean(property.flags & ts.SymbolFlags.Optional),
3229
+ schema: serializeType(propertyType, checker, ancestors)
3230
+ }
3231
+ ];
3232
+ }).sort(([left], [right]) => left.localeCompare(right));
3233
+ schema = { properties: Object.fromEntries(entries), type: "object" };
3234
+ } else {
3235
+ schema = {
3236
+ type: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
3237
+ };
3238
+ }
3239
+ ancestors.delete(type);
3240
+ return schema;
3241
+ };
3242
+ var pagePropsType = (pageExpression, propsExpression, checker) => {
3243
+ const pageType = checker.getTypeAtLocation(pageExpression);
3244
+ const [signature] = pageType.getCallSignatures();
3245
+ const [parameter] = signature?.parameters ?? [];
3246
+ const declaration = parameter?.valueDeclaration ?? parameter?.declarations?.[0];
3247
+ if (parameter && declaration) {
3248
+ return checker.getTypeOfSymbolAtLocation(parameter, declaration);
3249
+ }
3250
+ return propsExpression ? checker.getTypeAtLocation(propsExpression) : checker.getTypeAtLocation(pageExpression);
3251
+ };
3252
+ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
3253
+ let symbol = checker.getSymbolAtLocation(expression);
3254
+ if (symbol?.flags && symbol.flags & ts.SymbolFlags.Alias) {
3255
+ symbol = checker.getAliasedSymbol(symbol);
3256
+ }
3257
+ const declaration = symbol?.declarations?.[0];
3258
+ const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
3259
+ const exportedName = symbol?.name ?? expression.getText(sourceFile);
3260
+ const source = posixPath(relative7(projectRoot, file));
3261
+ return `${source}#${exportedName}`;
3262
+ };
3263
+ var resolveAlias = (symbol, checker) => {
3264
+ if (!(symbol.flags & ts.SymbolFlags.Alias))
3265
+ return symbol;
3266
+ return checker.getAliasedSymbol(symbol);
3267
+ };
3268
+ var assetKey = (expression, checker, seen = new Set) => {
3269
+ if (!expression)
3270
+ return;
3271
+ if (ts.isIdentifier(expression)) {
3272
+ const unresolved = ts.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
3273
+ if (!unresolved)
3274
+ return;
3275
+ const symbol = resolveAlias(unresolved, checker);
3276
+ if (seen.has(symbol))
3277
+ return;
3278
+ seen.add(symbol);
3279
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
3280
+ if (!declaration || !ts.isVariableDeclaration(declaration))
3281
+ return;
3282
+ return assetKey(declaration.initializer, checker, seen);
3283
+ }
3284
+ if (!ts.isCallExpression(expression))
3285
+ return;
3286
+ if (!ts.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
3287
+ return;
3288
+ }
3289
+ const [, key] = expression.arguments;
3290
+ return key && ts.isStringLiteralLike(key) ? key.text : undefined;
3291
+ };
3292
+ var findPageCall = (nodes) => {
3293
+ let found;
3294
+ const visit = (candidate) => {
3295
+ if (found)
3296
+ return;
3297
+ if (ts.isCallExpression(candidate) && ts.isIdentifier(candidate.expression) && candidate.expression.text === PAGE_HANDLER) {
3298
+ found = candidate;
3299
+ return;
3300
+ }
3301
+ ts.forEachChild(candidate, visit);
3302
+ };
3303
+ for (const node of nodes)
3304
+ visit(node);
3305
+ return found;
3306
+ };
3307
+ var isProjectSource = (sourceFile, resolvedFile, projectRoot) => !sourceFile.isDeclarationFile && !resolvedFile.includes("/node_modules/") && resolvedFile.startsWith(`${projectRoot}/`);
3308
+ var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
3309
+ const callee = node.expression;
3310
+ if (!ts.isPropertyAccessExpression(callee))
3311
+ return;
3312
+ if (!ROUTE_METHODS.has(callee.name.text))
3313
+ return;
3314
+ const [routePath] = node.arguments;
3315
+ if (!routePath || !ts.isStringLiteralLike(routePath))
3316
+ return;
3317
+ const pageCall = findPageCall(node.arguments.slice(1));
3318
+ const [input] = pageCall?.arguments ?? [];
3319
+ if (!pageCall || !input || !ts.isObjectLiteralExpression(input)) {
3320
+ return;
3321
+ }
3322
+ const page = objectPropertyExpression(input, "Page");
3323
+ if (!page)
3324
+ return;
3325
+ const props = objectPropertyExpression(input, "props");
3326
+ const index = objectPropertyExpression(input, "index");
3327
+ const bundleKey = assetKey(index, checker);
3328
+ if (!bundleKey)
3329
+ return;
3330
+ const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot);
3331
+ const schema = serializeType(pagePropsType(page, props, checker), checker);
3332
+ const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
3333
+ const metadata = {
3334
+ bundleKey,
3335
+ contract: `react:${pageId}:${propsSchemaHash}`,
3336
+ framework: "react",
3337
+ pageId,
3338
+ propsSchemaHash
3339
+ };
3340
+ const result = {
3341
+ metadata,
3342
+ pageCallStart: pageCall.getStart(sourceFile),
3343
+ routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
3344
+ };
3345
+ return result;
3346
+ };
3347
+ var analyzeSourceFile = (sourceFile, checker, projectRoot) => {
3348
+ const analysis = {
3349
+ byPageCall: new Map,
3350
+ byRouteCall: new Map
3351
+ };
3352
+ const visit = (node) => {
3353
+ const result = ts.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
3354
+ if (result) {
3355
+ analysis.byPageCall.set(result.pageCallStart, result);
3356
+ analysis.byRouteCall.set(result.routeCallSpan, result);
3357
+ }
3358
+ ts.forEachChild(node, visit);
3359
+ };
3360
+ ts.forEachChild(sourceFile, visit);
3361
+ return analysis;
3362
+ };
3363
+ var analyzeProgram = (program, projectRoot) => {
3364
+ const checker = program.getTypeChecker();
3365
+ const analyzed = new Map;
3366
+ for (const sourceFile of program.getSourceFiles()) {
3367
+ const resolvedFile = resolve11(sourceFile.fileName);
3368
+ if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
3369
+ continue;
3370
+ const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
3371
+ if (analysis.byPageCall.size > 0)
3372
+ analyzed.set(resolvedFile, analysis);
3373
+ }
3374
+ return analyzed;
3375
+ };
3376
+ var metadataExpression = (metadata) => ts.factory.createObjectLiteralExpression(Object.entries(metadata).map(([key, item]) => ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(key), ts.factory.createStringLiteral(item))), false);
3377
+ var routeOptions = (existing, metadata) => {
3378
+ const detail = ts.factory.createObjectLiteralExpression([
3379
+ ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
3380
+ ]);
3381
+ if (!existing) {
3382
+ return ts.factory.createObjectLiteralExpression([
3383
+ ts.factory.createPropertyAssignment("detail", detail)
3384
+ ]);
3385
+ }
3386
+ return ts.factory.createObjectLiteralExpression([
3387
+ ts.factory.createSpreadAssignment(existing),
3388
+ ts.factory.createPropertyAssignment("detail", ts.factory.createObjectLiteralExpression([
3389
+ ts.factory.createSpreadAssignment(ts.factory.createPropertyAccessExpression(existing, "detail")),
3390
+ ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
3391
+ ]))
3392
+ ]);
3393
+ };
3394
+ var transformPageCall = (node, page) => {
3395
+ if (!page)
3396
+ return;
3397
+ const [input] = node.arguments;
3398
+ if (!input || !ts.isObjectLiteralExpression(input))
3399
+ return;
3400
+ return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [
3401
+ ts.factory.updateObjectLiteralExpression(input, [
3402
+ ...input.properties,
3403
+ ts.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
3404
+ ]),
3405
+ ...node.arguments.slice(1)
3406
+ ]);
3407
+ };
3408
+ var transformRouteCall = (node, route) => {
3409
+ if (!route)
3410
+ return;
3411
+ const [path, maybeOptions, maybeHandler, ...rest] = node.arguments;
3412
+ if (!path || !maybeOptions)
3413
+ return;
3414
+ const options = maybeHandler ? maybeOptions : undefined;
3415
+ const handler = maybeHandler ?? maybeOptions;
3416
+ return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
3417
+ };
3418
+ var transformFile = (source, fileName, analysis) => {
3419
+ const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
3420
+ const transformer = (context) => {
3421
+ const visit = (node) => {
3422
+ if (!ts.isCallExpression(node)) {
3423
+ return ts.visitEachChild(node, visit, context);
3424
+ }
3425
+ const transformedChildren = ts.visitEachChild(node, visit, context);
3426
+ const page = analysis.byPageCall.get(node.getStart(sourceFile));
3427
+ const transformedPage = transformPageCall(transformedChildren, page);
3428
+ if (transformedPage)
3429
+ return transformedPage;
3430
+ const route = analysis.byRouteCall.get(`${node.getStart(sourceFile)}:${node.end}`);
3431
+ const transformedRoute = transformRouteCall(transformedChildren, route);
3432
+ if (transformedRoute)
3433
+ return transformedRoute;
3434
+ return transformedChildren;
3435
+ };
3436
+ return (node) => ts.visitNode(node, visit, ts.isSourceFile) ?? node;
3437
+ };
3438
+ const result = ts.transform(sourceFile, [transformer]);
3439
+ try {
3440
+ const [transformed] = result.transformed;
3441
+ if (!transformed)
3442
+ throw new TypeError("Mobile route transform failed.");
3443
+ return ts.createPrinter().printFile(transformed);
3444
+ } finally {
3445
+ result.dispose();
3446
+ }
3447
+ };
3448
+ var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
3449
+ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
3450
+ const projectRoot = resolve11(options.projectRoot ?? process.cwd());
3451
+ const entry = resolve11(options.entry);
3452
+ const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
3453
+ return {
3454
+ name: "absolute-mobile-route-metadata",
3455
+ setup(build) {
3456
+ build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
3457
+ const analysis = analyzed.get(resolve11(path));
3458
+ if (!analysis)
3459
+ return;
3460
+ const source = await Bun.file(path).text();
3461
+ return {
3462
+ contents: transformFile(source, path, analysis),
3463
+ loader: extname2(path).endsWith("x") ? "tsx" : "ts"
3464
+ };
3465
+ });
3466
+ }
3467
+ };
3468
+ };
3469
+ var inspectAbsoluteMobileRouteMetadata = (options) => {
3470
+ const projectRoot = resolve11(options.projectRoot ?? process.cwd());
3471
+ const entry = resolve11(options.entry);
3472
+ const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
3473
+ return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
3474
+ file: posixPath(relative7(projectRoot, file)),
3475
+ metadata
3476
+ })));
3477
+ };
1107
3478
  export {
3479
+ writeAbsoluteCapacitorConfig,
1108
3480
  verifyAbsoluteMobileCompatibilityProducer,
3481
+ verifyAbsoluteMobileAssociationFiles,
1109
3482
  runWithAbsoluteMobileProducer,
1110
3483
  retainAbsoluteMobileCompatibilityArtifacts,
3484
+ resolveAbsoluteMobileRoute,
3485
+ resolveAbsoluteMobileDeepLink,
1111
3486
  resolveAbsoluteMobileCompatibilityRelease,
3487
+ readAbsoluteMobileMaterializedReleases,
3488
+ publishAbsoluteIosRelease,
3489
+ publishAbsoluteAndroidRelease,
3490
+ prepareAbsoluteIosRelease,
3491
+ prepareAbsoluteAndroidRelease,
1112
3492
  parseAbsoluteMobilePageRequest,
1113
3493
  parseAbsoluteMobilePageEnvelope,
1114
3494
  parseAbsoluteMobileCompatibilityArtifact,
3495
+ parseAbsoluteMobileBuildPageMetadata,
3496
+ normalizeAbsoluteMobileConfig,
3497
+ navigateAbsoluteMobilePage,
1115
3498
  materializeAbsoluteMobileCompatibilityBundle,
3499
+ materializeAbsoluteMobileAssociationFiles,
3500
+ materializeAbsoluteCapacitorWebBundle,
3501
+ matchesAbsoluteMobileRoutePattern,
3502
+ loadAbsoluteNativeReleasePublisher,
1116
3503
  loadAbsoluteMobileMaterializedBundle,
3504
+ inspectAbsoluteMobileRouteMetadata,
1117
3505
  hashAbsoluteMobilePropsSchema,
1118
3506
  getCurrentAbsoluteMobileProducerContext,
3507
+ fingerprintAbsoluteIosNativeProject,
1119
3508
  finalizeAbsoluteMobilePage,
3509
+ finalizeAbsoluteMobileCompatibilityBuild,
3510
+ fetchAbsoluteMobilePage,
1120
3511
  createAbsoluteMobileUpgradeResponse,
3512
+ createAbsoluteMobileRouteMetadataPlugin,
3513
+ createAbsoluteMobilePageRequest,
1121
3514
  createAbsoluteMobilePageErrorResponse,
1122
3515
  createAbsoluteMobileInvalidRequestResponse,
1123
3516
  createAbsoluteMobileFileArtifactStore,
1124
3517
  createAbsoluteMobileCompatibilityDispatcher,
1125
3518
  createAbsoluteMobileCompatibilityArtifact,
1126
3519
  createAbsoluteMobileBlobArtifactStore,
3520
+ createAbsoluteMobileAssociationPlugin,
3521
+ createAbsoluteMobileAssociationDocuments,
1127
3522
  carryForwardAbsoluteMobileCompatibilityReleases,
3523
+ captureAbsoluteMobileRouteGraph,
3524
+ buildAbsoluteMobileCompatibilityRelease,
3525
+ buildAbsoluteIosRelease,
3526
+ buildAbsoluteAndroidRelease,
3527
+ applyAbsoluteNativeDeepLinks,
1128
3528
  activateAbsoluteMobilePage,
1129
3529
  acceptsAbsoluteMobilePage,
1130
3530
  MOBILE_PAGE_REQUEST_HEADERS,
1131
3531
  AbsoluteMobilePageProtocolError,
3532
+ APPLE_ASSOCIATION_PATH,
3533
+ ANDROID_ASSOCIATION_PATH,
3534
+ ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
3535
+ ABSOLUTE_MOBILE_ROUTE_DETAIL,
1132
3536
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
1133
3537
  ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
1134
3538
  ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE,
1135
3539
  ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
1136
- ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT
3540
+ ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
3541
+ ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
3542
+ ABSOLUTE_IOS_RELEASE_FORMAT,
3543
+ ABSOLUTE_ANDROID_RELEASE_FORMAT
1137
3544
  };
1138
3545
 
1139
- //# debugId=AF97492DC1F47D7364756E2164756E21
3546
+ //# debugId=8F6E00755BF98A2E64756E2164756E21
1140
3547
  //# sourceMappingURL=index.js.map