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

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 (89) 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 +12927 -8452
  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 +943 -423
  22. package/dist/index.js.map +27 -23
  23. package/dist/mobile/browser.js.map +1 -1
  24. package/dist/mobile/index.js +2179 -261
  25. package/dist/mobile/index.js.map +25 -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 +99 -0
  48. package/dist/src/mobile/androidNativeWatcher.d.ts +19 -0
  49. package/dist/src/mobile/androidRelease.d.ts +47 -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 +16 -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 +12 -0
  62. package/dist/src/mobile/materializedBundle.d.ts +14 -0
  63. package/dist/src/mobile/nativeDeepLinks.d.ts +5 -0
  64. package/dist/src/mobile/releaseArtifact.d.ts +1 -0
  65. package/dist/src/mobile/releaseDoctor.d.ts +13 -0
  66. package/dist/src/mobile/routeMatcher.d.ts +3 -0
  67. package/dist/src/mobile/routeMetadataTransform.d.ts +13 -0
  68. package/dist/src/mobile/shellBootstrap.d.ts +1 -0
  69. package/dist/src/mobile/transport.d.ts +31 -0
  70. package/dist/src/react/hooks/useMediaQuery.d.ts +1 -1
  71. package/dist/src/react/jsxDevRuntimeCompat.d.ts +1 -1
  72. package/dist/src/react/jsxRuntimeCompat.d.ts +7 -0
  73. package/dist/src/react/pageHandler.d.ts +3 -0
  74. package/dist/src/utils/loadConfig.d.ts +1 -0
  75. package/dist/src/utils/logger.d.ts +2 -0
  76. package/dist/src/utils/userAgentFunctions.d.ts +1 -1
  77. package/dist/svelte/index.js +14 -1
  78. package/dist/svelte/index.js.map +3 -3
  79. package/dist/svelte/server.js +14 -1
  80. package/dist/svelte/server.js.map +3 -3
  81. package/dist/types/build.d.ts +41 -0
  82. package/dist/types/cli.d.ts +2 -0
  83. package/dist/types/globals.d.ts +14 -0
  84. package/dist/types/messages.d.ts +12 -0
  85. package/dist/vue/index.js +14 -1
  86. package/dist/vue/index.js.map +3 -3
  87. package/dist/vue/server.js +14 -1
  88. package/dist/vue/server.js.map +3 -3
  89. package/package.json +29 -1
@@ -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,185 +614,1165 @@ 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 createHash3 } 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 {
706
+ dirname,
707
+ isAbsolute,
708
+ join as join3,
709
+ relative as relative2,
710
+ resolve as resolve2,
711
+ sep,
712
+ win32
713
+ } from "path";
714
+
715
+ // src/mobile/capacitorProject.ts
716
+ import { access, readFile as readFile2, rename as rename2, writeFile as writeFile2 } from "fs/promises";
717
+ import { relative, resolve } from "path";
718
+ var CONFIG_FILE = "capacitor.config.ts";
719
+ var portableRelative = (root, path) => relative(root, path).replaceAll("\\", "/");
720
+ var capacitorConfigSource = (config, projectRoot) => `import type { CapacitorConfig } from '@capacitor/cli';
721
+
722
+ const config: CapacitorConfig = {
723
+ appId: ${JSON.stringify(config.appId)},
724
+ appName: ${JSON.stringify(config.appName)},
725
+ webDir: ${JSON.stringify(portableRelative(projectRoot, config.bundleDirectory))},
726
+ android: {
727
+ path: ${JSON.stringify(portableRelative(projectRoot, `${config.nativeProjectDirectory}/android`))}
728
+ },
729
+ ios: {
730
+ path: ${JSON.stringify(portableRelative(projectRoot, `${config.nativeProjectDirectory}/ios`))}
731
+ }
554
732
  };
555
- var parseMediaType = (value) => value.split(";", 1)[0]?.trim().toLowerCase();
556
- var acceptsAbsoluteMobilePage = (request) => {
557
- if (!request)
733
+
734
+ export default config;
735
+ `;
736
+ var exists = async (path) => {
737
+ try {
738
+ await access(path);
739
+ return true;
740
+ } catch {
558
741
  return false;
559
- return (request.headers.get("accept") ?? "").split(",").some((value) => parseMediaType(value) === ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE);
742
+ }
560
743
  };
561
- var readRequiredHeader = (request, name) => {
562
- const value = request.headers.get(name)?.trim();
563
- return value ? value : undefined;
744
+ var writeAbsoluteCapacitorConfig = async (config, options) => {
745
+ const projectRoot = resolve(options.projectRoot);
746
+ const destination = resolve(projectRoot, CONFIG_FILE);
747
+ const source = capacitorConfigSource(config, projectRoot);
748
+ if (await exists(destination)) {
749
+ const current = await readFile2(destination, "utf8");
750
+ if (current === source)
751
+ return { changed: false, path: destination };
752
+ if (!options.force) {
753
+ throw new TypeError(`${CONFIG_FILE} already exists and differs; rerun with --force only after reviewing it.`);
754
+ }
755
+ }
756
+ const temporary = `${destination}.${crypto.randomUUID()}.tmp`;
757
+ await writeFile2(temporary, source, { flag: "wx" });
758
+ await rename2(temporary, destination);
759
+ return { changed: true, path: destination };
564
760
  };
565
- var parseAbsoluteMobilePageRequest = (request) => {
566
- if (!request || !acceptsAbsoluteMobilePage(request)) {
567
- return { kind: "not-mobile" };
761
+
762
+ // src/mobile/androidEmulatorController.ts
763
+ init_getDurationString();
764
+ var HASH_RADIX = 16;
765
+ var CAPACITOR_PROJECT_DIRECTORY_PATTERN = /project\(['"](:[^'"]+)['"]\)\.projectDir\s*=\s*new File\(['"]([^'"]+)['"]\)/gu;
766
+ var pathExists = async (path) => {
767
+ try {
768
+ await access2(path);
769
+ return true;
770
+ } catch {
771
+ return false;
568
772
  }
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)) {
773
+ };
774
+ var forwardCommandOutput = (stream, destination) => stream.pipeTo(new WritableStream({
775
+ write: (chunk) => {
776
+ destination.write(chunk);
777
+ }
778
+ }));
779
+ var runCommand = async (command, options = {}) => {
780
+ const subprocess = Bun.spawn(command, {
781
+ cwd: options.cwd,
782
+ env: options.env,
783
+ signal: options.signal,
784
+ stderr: "pipe",
785
+ stdin: "ignore",
786
+ stdout: "pipe"
787
+ });
788
+ const [exitCode] = await Promise.all([
789
+ subprocess.exited,
790
+ forwardCommandOutput(subprocess.stdout, process.stdout),
791
+ forwardCommandOutput(subprocess.stderr, process.stderr)
792
+ ]);
793
+ return exitCode;
794
+ };
795
+ var captureCommand = (command, options = {}) => {
796
+ try {
797
+ const result = Bun.spawnSync(command, {
798
+ cwd: options.cwd,
799
+ env: options.env,
800
+ stderr: "pipe",
801
+ stdout: "pipe"
802
+ });
576
803
  return {
577
- kind: "invalid",
578
- message: `Missing or invalid ${MOBILE_PAGE_REQUEST_HEADERS.protocol} header.`
804
+ exitCode: result.exitCode,
805
+ stderr: result.stderr.toString(),
806
+ stdout: result.stdout.toString()
579
807
  };
580
- }
581
- if (!runtime) {
808
+ } catch (error) {
582
809
  return {
583
- kind: "invalid",
584
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.runtime} header.`
810
+ exitCode: 1,
811
+ stderr: error instanceof Error ? error.message : String(error),
812
+ stdout: ""
585
813
  };
586
814
  }
587
- if (!appBuild) {
588
- return {
589
- kind: "invalid",
590
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.appBuild} header.`
591
- };
815
+ };
816
+ var ASCII_ESCAPE = 27;
817
+ var ANSI_SEQUENCE_PATTERN = new RegExp(`${String.fromCharCode(ASCII_ESCAPE)}(?:\\[[0-?]*[ -/]*[@-~]|[@-_])`, "gu");
818
+ var requireSuccess = async (command, label, run, options) => {
819
+ const exitCode = await run(command, options);
820
+ throwIfAborted(options?.signal);
821
+ if (exitCode !== 0)
822
+ throw new Error(`${label} failed (exit ${exitCode}).`);
823
+ };
824
+ var throwIfAborted = (signal) => {
825
+ if (!signal?.aborted)
826
+ return;
827
+ throw new DOMException("Android development startup was cancelled.", "AbortError");
828
+ };
829
+ var windowsPathFromWsl = (path, capture) => {
830
+ const result = capture(["wslpath", "-w", path]);
831
+ if (result.exitCode !== 0 || !result.stdout.trim()) {
832
+ throw new Error(`Could not translate WSL path for Windows: ${path}`);
592
833
  }
593
- if (!pageBundle) {
834
+ return result.stdout.trim();
835
+ };
836
+ var mirroredCapacitorDependencies = async (project, capture) => {
837
+ const settingsPath = join3(project.nativeDirectory, "capacitor.settings.gradle");
838
+ const settings = await readFile3(settingsPath, "utf8");
839
+ const dependencies = [];
840
+ const rewrittenSettings = settings.replace(CAPACITOR_PROJECT_DIRECTORY_PATTERN, (_statement, projectName, sourcePath) => {
841
+ const name = projectName.slice(1).replaceAll(/[^a-zA-Z0-9_.-]/gu, "_");
842
+ const source = resolve2(project.nativeDirectory, sourcePath);
843
+ dependencies.push({
844
+ name,
845
+ windowsSource: windowsPathFromWsl(source, capture)
846
+ });
847
+ return `project('${projectName}').projectDir = new File('./.absolutejs-dependencies/${name}')`;
848
+ });
849
+ if (dependencies.length === 0) {
850
+ throw new Error("Capacitor Android settings did not declare any native dependencies.");
851
+ }
852
+ return { dependencies, rewrittenSettings };
853
+ };
854
+ var encodedWindowsGradleCommand = (windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task) => {
855
+ const sourceDirectory = Buffer.from(windowsSource, "utf8").toString("base64");
856
+ const buildDirectory = Buffer.from(windowsDirectory, "utf8").toString("base64");
857
+ const androidRoot = Buffer.from(windowsAndroidRoot, "utf8").toString("base64");
858
+ const dependencyData = Buffer.from(JSON.stringify(dependencies), "utf8").toString("base64");
859
+ const settingsData = Buffer.from(rewrittenSettings, "utf8").toString("base64");
860
+ const source = [
861
+ "$ErrorActionPreference = 'Stop'",
862
+ `$source = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${sourceDirectory}'))`,
863
+ `$directory = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${buildDirectory}'))`,
864
+ `$androidHome = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${androidRoot}'))`,
865
+ `$dependencies = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${dependencyData}')) | ConvertFrom-Json`,
866
+ `$settings = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${settingsData}'))`,
867
+ "$env:ANDROID_HOME = $androidHome",
868
+ "$env:ANDROID_SDK_ROOT = $androidHome",
869
+ "New-Item -ItemType Directory -Force -Path $directory | Out-Null",
870
+ "& robocopy.exe $source $directory /MIR /XD .gradle build .absolutejs-dependencies /NFL /NDL /NJH /NJS /NP",
871
+ "$copyExit = $LASTEXITCODE",
872
+ "if ($copyExit -ge 8) { exit $copyExit }",
873
+ "foreach ($dependency in @($dependencies)) { $target = Join-Path $directory ('.absolutejs-dependencies\\' + $dependency.name); New-Item -ItemType Directory -Force -Path $target | Out-Null; & robocopy.exe $dependency.windowsSource $target /MIR /XD .gradle build /NFL /NDL /NJH /NJS /NP; if ($LASTEXITCODE -ge 8) { exit $LASTEXITCODE } }",
874
+ "[IO.File]::WriteAllText((Join-Path $directory 'capacitor.settings.gradle'), $settings)",
875
+ "$wrapper = Join-Path $directory 'gradlew.bat'",
876
+ `& $wrapper --no-daemon --console=plain -p $directory ${task}`,
877
+ "if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }",
878
+ "exit 0"
879
+ ].join("; ");
880
+ return Buffer.from(source, "utf16le").toString("base64");
881
+ };
882
+ var gradleArtifactPath = (nativeDirectory, task, windows = false) => {
883
+ const pathJoin = windows ? win32.join : join3;
884
+ if (task === "assembleDebug") {
885
+ return pathJoin(nativeDirectory, "app", "build", "outputs", "apk", "debug", "app-debug.apk");
886
+ }
887
+ if (task === "assembleRelease") {
888
+ return pathJoin(nativeDirectory, "app", "build", "outputs", "apk", "release", "app-release.apk");
889
+ }
890
+ return pathJoin(nativeDirectory, "app", "build", "outputs", "bundle", "release", "app-release.aab");
891
+ };
892
+ var resolveGradleArtifactPath = async (nativeDirectory, task) => {
893
+ const primary = gradleArtifactPath(nativeDirectory, task);
894
+ if (task !== "assembleRelease" || await pathExists(primary)) {
895
+ return primary;
896
+ }
897
+ return join3(nativeDirectory, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk");
898
+ };
899
+ var buildAbsoluteAndroidGradleArtifact = async (options) => {
900
+ const { project, task } = options;
901
+ const capture = options.capture ?? captureCommand;
902
+ const run = options.run ?? runCommand;
903
+ const env = options.env ?? process.env;
904
+ if (project.host === "wsl") {
905
+ const windowsSource = windowsPathFromWsl(project.nativeDirectory, capture);
906
+ const buildId = Bun.hash(project.projectRoot).toString(HASH_RADIX);
907
+ const managedBuildDirectory = resolve2(project.androidRoot, "..", "..", "Builds", `${project.config.appId}-${buildId}`);
908
+ const windowsDirectory = windowsPathFromWsl(managedBuildDirectory, capture);
909
+ const windowsAndroidRoot = windowsPathFromWsl(project.androidRoot, capture);
910
+ const { dependencies, rewrittenSettings } = await mirroredCapacitorDependencies(project, capture);
911
+ await requireSuccess([
912
+ "powershell.exe",
913
+ "-NoProfile",
914
+ "-EncodedCommand",
915
+ encodedWindowsGradleCommand(windowsSource, windowsDirectory, windowsAndroidRoot, dependencies, rewrittenSettings, task)
916
+ ], "Android Gradle build", run, { env, signal: options.signal });
917
+ const artifactPath2 = await resolveGradleArtifactPath(managedBuildDirectory, task);
918
+ const unsigned = artifactPath2.endsWith("-unsigned.apk");
594
919
  return {
595
- kind: "invalid",
596
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageBundle} header.`
920
+ artifactPath: artifactPath2,
921
+ installPath: unsigned ? win32.join(windowsDirectory, "app", "build", "outputs", "apk", "release", "app-release-unsigned.apk") : gradleArtifactPath(windowsDirectory, task, true)
597
922
  };
598
923
  }
599
- if (!pageContractsValue) {
924
+ const wrapper = project.host === "windows" ? "gradlew.bat" : "./gradlew";
925
+ await requireSuccess([wrapper, "--no-daemon", "--console=plain", task], "Android Gradle build", run, { cwd: project.nativeDirectory, env, signal: options.signal });
926
+ const artifactPath = await resolveGradleArtifactPath(project.nativeDirectory, task);
927
+ return { artifactPath, installPath: artifactPath };
928
+ };
929
+
930
+ // src/mobile/androidRelease.ts
931
+ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1;
932
+ var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
933
+ var requireManifest = (value) => {
934
+ if (!isRecord(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
935
+ throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
936
+ }
937
+ return {
938
+ appBuild: value.appBuild,
939
+ appId: value.appId,
940
+ runtime: value.runtime
941
+ };
942
+ };
943
+ var pathExists2 = async (path) => {
944
+ try {
945
+ await access3(path);
946
+ return true;
947
+ } catch {
948
+ return false;
949
+ }
950
+ };
951
+ var defaultCapture = (command, options = {}) => {
952
+ try {
953
+ const result = Bun.spawnSync(command, {
954
+ cwd: options.cwd,
955
+ env: options.env,
956
+ stderr: "pipe",
957
+ stdin: "ignore",
958
+ stdout: "pipe"
959
+ });
600
960
  return {
601
- kind: "invalid",
602
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageContracts} header.`
961
+ exitCode: result.exitCode,
962
+ stderr: result.stderr.toString(),
963
+ stdout: result.stdout.toString()
603
964
  };
604
- }
605
- if (!pageId) {
965
+ } catch (error) {
606
966
  return {
607
- kind: "invalid",
608
- message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageId} header.`
967
+ exitCode: 1,
968
+ stderr: error instanceof Error ? error.message : String(error),
969
+ stdout: ""
609
970
  };
610
971
  }
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.`
972
+ };
973
+ var verifyAabSignature = (artifactPath, capture, jarsigner) => {
974
+ const executable = jarsigner === undefined ? Bun.which("jarsigner") : jarsigner;
975
+ if (!executable)
976
+ return null;
977
+ const result = capture([
978
+ executable,
979
+ "-J-Duser.language=en",
980
+ "-J-Duser.country=US",
981
+ "-verify",
982
+ artifactPath
983
+ ]);
984
+ return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
985
+ };
986
+ var sha256File = async (path) => createHash3("sha256").update(await readFile4(path)).digest("hex");
987
+ var safeOutputDirectory = (projectRoot, requested) => {
988
+ const root = resolve3(projectRoot);
989
+ const output = resolve3(root, requested ?? ".absolutejs/mobile/releases/android");
990
+ const projectRelative = relative3(root, output);
991
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep2}`) || isAbsolute2(projectRelative)) {
992
+ throw new TypeError("mobile build --outdir must remain inside the project.");
993
+ }
994
+ return output;
995
+ };
996
+ var installRelease = async (artifactPath, metadata, outputRoot) => {
997
+ const releaseRoot = join4(outputRoot, metadata.releaseId);
998
+ const artifactName = "app-release.aab";
999
+ const destination = join4(releaseRoot, artifactName);
1000
+ if (await pathExists2(releaseRoot)) {
1001
+ const existing = requireManifestIdentity(JSON.parse(await readFile4(join4(releaseRoot, "release.json"), "utf8")), metadata);
1002
+ const [installedBytes, installedSha256] = await Promise.all([
1003
+ stat(destination).then(({ size }) => size),
1004
+ sha256File(destination)
1005
+ ]);
1006
+ if (installedBytes !== metadata.bytes || installedSha256 !== metadata.sha256) {
1007
+ throw new TypeError(`Immutable Android release ${metadata.releaseId} artifact is missing or modified.`);
1008
+ }
1009
+ return { artifactPath: destination, metadata: existing, releaseRoot };
1010
+ }
1011
+ await mkdir3(dirname2(releaseRoot), { recursive: true });
1012
+ const staging = await mkdtemp2(join4(dirname2(releaseRoot), ".android-stage-"));
1013
+ try {
1014
+ await copyFile2(artifactPath, join4(staging, artifactName));
1015
+ const complete = {
1016
+ ...metadata,
1017
+ artifact: artifactName
618
1018
  };
1019
+ await writeFile4(join4(staging, "release.json"), `${JSON.stringify(complete, null, "\t")}
1020
+ `, { flag: "wx" });
1021
+ await rename4(staging, releaseRoot);
1022
+ return { artifactPath: destination, metadata: complete, releaseRoot };
1023
+ } finally {
1024
+ await rm3(staging, { force: true, recursive: true }).catch(() => {
1025
+ return;
1026
+ });
619
1027
  }
620
- return {
621
- client: {
622
- appBuild,
623
- pageBundle,
624
- pageContracts,
625
- pageId,
626
- protocol: Number(protocolValue),
627
- runtime
1028
+ };
1029
+ var requireManifestIdentity = (value, expected) => {
1030
+ if (!isRecord(value)) {
1031
+ throw new TypeError("Existing Android release metadata is invalid.");
1032
+ }
1033
+ const { artifact } = value;
1034
+ if (artifact !== "app-release.aab" || Object.entries(expected).some(([key, expectedValue]) => Reflect.get(value, key) !== expectedValue)) {
1035
+ throw new TypeError(`Immutable Android release ${expected.releaseId} does not match its content.`);
1036
+ }
1037
+ return { ...expected, artifact };
1038
+ };
1039
+ var buildAbsoluteAndroidRelease = async (options) => {
1040
+ const projectRoot = resolve3(options.projectRoot);
1041
+ const host = options.host ?? detectAbsoluteMobileHost();
1042
+ const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
1043
+ const nativeDirectory = join4(options.config.nativeProjectDirectory, "android");
1044
+ const { artifactPath } = await buildAbsoluteAndroidGradleArtifact({
1045
+ capture: options.capture,
1046
+ project: {
1047
+ androidRoot,
1048
+ config: options.config,
1049
+ host,
1050
+ nativeDirectory,
1051
+ projectRoot
628
1052
  },
629
- kind: "mobile"
1053
+ run: options.run,
1054
+ task: "bundleRelease"
1055
+ });
1056
+ if (!await pathExists2(artifactPath)) {
1057
+ throw new TypeError(`Android Gradle did not produce the expected App Bundle: ${artifactPath}`);
1058
+ }
1059
+ const capture = options.capture ?? defaultCapture;
1060
+ const signed = verifyAabSignature(artifactPath, capture, options.jarsigner);
1061
+ if (signed === null && !options.allowUnsigned) {
1062
+ 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.");
1063
+ }
1064
+ if (!signed && !options.allowUnsigned) {
1065
+ throw new TypeError("Android Gradle produced an unsigned App Bundle. Configure the release signingConfig in the source-owned Android project (prefer external Gradle properties), or pass --unsigned only for a non-publishable build.");
1066
+ }
1067
+ const manifest = requireManifest(JSON.parse(await readFile4(join4(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
1068
+ if (manifest.appId !== options.config.appId) {
1069
+ throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
1070
+ }
1071
+ const [bytes, sha256] = await Promise.all([
1072
+ stat(artifactPath).then(({ size }) => size),
1073
+ sha256File(artifactPath)
1074
+ ]);
1075
+ const releaseId = `amobile_android_${sha256}`;
1076
+ const metadata = {
1077
+ appBuild: manifest.appBuild,
1078
+ appId: manifest.appId,
1079
+ bytes,
1080
+ engine: "capacitor",
1081
+ format: ABSOLUTE_ANDROID_RELEASE_FORMAT,
1082
+ platform: "android",
1083
+ releaseId,
1084
+ runtime: manifest.runtime,
1085
+ sha256,
1086
+ signed: signed === true,
1087
+ type: "aab"
630
1088
  };
1089
+ return installRelease(artifactPath, metadata, safeOutputDirectory(projectRoot, options.outputDirectory));
631
1090
  };
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;
1091
+ // src/mobile/associationFiles.ts
1092
+ import {
1093
+ access as access4,
1094
+ mkdir as mkdir4,
1095
+ readFile as readFile5,
1096
+ rename as rename5,
1097
+ rm as rm4,
1098
+ writeFile as writeFile5
1099
+ } from "fs/promises";
1100
+ import { resolve as resolve5 } from "path";
1101
+ import { Elysia } from "elysia";
1102
+
1103
+ // src/mobile/config.ts
1104
+ import { resolve as resolve4 } from "path";
1105
+ var APP_ID_PATTERN = /^[A-Za-z][\w]*(?:\.[A-Za-z][\w]*)+$/;
1106
+ var SCHEME_PATTERN = /^[a-z][a-z0-9+.-]*$/;
1107
+ var APPLE_APP_ID_PREFIX_PATTERN = /^[A-Z0-9]{10}$/;
1108
+ var CERTIFICATE_FINGERPRINT_PATTERN = /^[0-9A-F]{64}$/;
1109
+ var HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)(?:\.(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?))*$/;
1110
+ var resolveProjectPath = (projectRoot, value, field) => {
1111
+ const root = resolve4(projectRoot);
1112
+ const path = resolve4(root, value);
1113
+ if (path !== root && !path.startsWith(`${root}/`)) {
1114
+ throw new TypeError(`${field} must remain inside the project root.`);
1115
+ }
1116
+ return path;
639
1117
  };
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.");
1118
+ var requireText = (value, field) => {
1119
+ const normalized = value.trim();
1120
+ if (!normalized)
1121
+ throw new TypeError(`${field} must not be empty.`);
1122
+ return normalized;
1123
+ };
1124
+ var normalizeEntry = (entry) => {
1125
+ const normalized = requireText(entry ?? "/", "mobile.entry");
1126
+ if (!normalized.startsWith("/") || normalized.startsWith("//")) {
1127
+ throw new TypeError("mobile.entry must be an absolute application path.");
658
1128
  }
659
- const parsed = JSON.parse(serialized);
660
- if (!isRecord(parsed)) {
661
- throw new TypeError("Mobile page props must serialize to an object.");
1129
+ return normalized;
1130
+ };
1131
+ var normalizeProductionOrigin = (value) => {
1132
+ const parsed = new URL(requireText(value, "mobile.server.productionOrigin"));
1133
+ if (parsed.protocol !== "https:") {
1134
+ throw new TypeError("mobile.server.productionOrigin must use HTTPS in production.");
662
1135
  }
663
- return parsed;
1136
+ if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
1137
+ throw new TypeError("mobile.server.productionOrigin must be an origin without credentials, path, query, or hash.");
1138
+ }
1139
+ return parsed.origin;
664
1140
  };
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);
1141
+ var normalizePlatforms = (platforms) => {
1142
+ const normalized = [
1143
+ ...new Set(platforms ?? ["ios", "android"])
1144
+ ];
1145
+ if (normalized.length === 0) {
1146
+ throw new TypeError("mobile.platforms must contain at least one platform.");
672
1147
  }
673
- try {
674
- const [currentRepresentation] = input.compatibility.representations;
675
- if (!currentRepresentation) {
676
- throw new TypeError("Mobile page has no current representation.");
1148
+ return normalized;
1149
+ };
1150
+ var normalizeHosts = (hosts, productionOrigin) => {
1151
+ const normalizeHostname = (host) => {
1152
+ const value = requireText(host, "mobile.deepLinks.hosts").toLowerCase();
1153
+ if (!HOSTNAME_PATTERN.test(value)) {
1154
+ throw new TypeError("mobile.deepLinks.hosts entries must be valid hostnames without ports, wildcards, or paths.");
677
1155
  }
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);
1156
+ return value;
1157
+ };
1158
+ const normalized = new Set([
1159
+ normalizeHostname(new URL(productionOrigin).hostname)
1160
+ ]);
1161
+ for (const host of hosts ?? []) {
1162
+ normalized.add(normalizeHostname(host));
689
1163
  }
1164
+ return [...normalized].sort();
690
1165
  };
691
- var createAbsoluteMobileInvalidRequestResponse = (message) => envelopeResponse({ kind: "invalid-request", message }, BAD_REQUEST_STATUS);
692
- var createAbsoluteMobilePageErrorResponse = (pageId) => envelopeResponse({
693
- code: "representation-failed",
694
- kind: "error",
1166
+ var normalizeAppleAppIdPrefix = (value) => {
1167
+ if (value === undefined)
1168
+ return;
1169
+ const normalized = requireText(value, "mobile.deepLinks.apple.appIdPrefix").toUpperCase();
1170
+ if (!APPLE_APP_ID_PREFIX_PATTERN.test(normalized)) {
1171
+ throw new TypeError("mobile.deepLinks.apple.appIdPrefix must contain ten letters or digits.");
1172
+ }
1173
+ return normalized;
1174
+ };
1175
+ var normalizeCertificateFingerprints = (values) => [
1176
+ ...new Set((values ?? []).map((value) => requireText(value, "mobile.deepLinks.android.sha256CertificateFingerprints").replaceAll(":", "").toUpperCase()).map((value) => {
1177
+ if (!CERTIFICATE_FINGERPRINT_PATTERN.test(value)) {
1178
+ throw new TypeError("mobile.deepLinks.android.sha256CertificateFingerprints entries must be SHA-256 certificate fingerprints.");
1179
+ }
1180
+ return value.match(/.{2}/g)?.join(":") ?? value;
1181
+ }))
1182
+ ].sort();
1183
+ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
1184
+ const appId = requireText(config.appId, "mobile.appId");
1185
+ if (!APP_ID_PATTERN.test(appId)) {
1186
+ throw new TypeError("mobile.appId must use reverse-domain notation, for example com.example.app.");
1187
+ }
1188
+ const productionOrigin = normalizeProductionOrigin(config.server.productionOrigin);
1189
+ const deepLinkScheme = config.deepLinks?.scheme?.trim().toLowerCase();
1190
+ if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
1191
+ throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
1192
+ }
1193
+ return {
1194
+ androidCertificateFingerprints: normalizeCertificateFingerprints(config.deepLinks?.android?.sha256CertificateFingerprints),
1195
+ appId,
1196
+ appleAppIdPrefix: normalizeAppleAppIdPrefix(config.deepLinks?.apple?.appIdPrefix),
1197
+ appName: requireText(config.appName, "mobile.appName"),
1198
+ bundleDirectory: resolveProjectPath(projectRoot, config.bundleDirectory ?? ".absolutejs/mobile/web", "mobile.bundleDirectory"),
1199
+ deepLinkHosts: normalizeHosts(config.deepLinks?.hosts, productionOrigin),
1200
+ deepLinkScheme,
1201
+ engine: "capacitor",
1202
+ entry: normalizeEntry(config.entry),
1203
+ nativeProjectDirectory: resolveProjectPath(projectRoot, config.nativeProject?.directory ?? "mobile", "mobile.nativeProject.directory"),
1204
+ platforms: normalizePlatforms(config.platforms),
1205
+ productionOrigin
1206
+ };
1207
+ };
1208
+
1209
+ // src/mobile/associationFiles.ts
1210
+ var JSON_HEADERS = {
1211
+ "cache-control": "public, max-age=300, must-revalidate",
1212
+ "content-type": "application/json; charset=utf-8"
1213
+ };
1214
+ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json";
1215
+ var HTTP_OK = 200;
1216
+ var VERIFY_TIMEOUT_MS = 1e4;
1217
+ var ANDROID_ASSOCIATION_PATH = "/.well-known/assetlinks.json";
1218
+ var APPLE_ASSOCIATION_PATH = "/.well-known/apple-app-site-association";
1219
+ var missingIdentity = (field, platform) => new TypeError(`${field} is required to publish ${platform} deep-link association files.`);
1220
+ var createAppleDocument = (config, requireAll) => {
1221
+ if (!config.platforms.includes("ios"))
1222
+ return;
1223
+ if (!config.appleAppIdPrefix && requireAll) {
1224
+ throw missingIdentity("mobile.deepLinks.apple.appIdPrefix", "iOS");
1225
+ }
1226
+ if (!config.appleAppIdPrefix)
1227
+ return;
1228
+ return {
1229
+ applinks: {
1230
+ details: [
1231
+ {
1232
+ appIDs: [
1233
+ `${config.appleAppIdPrefix}.${config.appId}`
1234
+ ],
1235
+ components: [{ "/": "/*" }]
1236
+ }
1237
+ ]
1238
+ }
1239
+ };
1240
+ };
1241
+ var createAndroidDocument = (config, requireAll) => {
1242
+ if (!config.platforms.includes("android"))
1243
+ return;
1244
+ if (config.androidCertificateFingerprints.length === 0 && requireAll) {
1245
+ throw missingIdentity("mobile.deepLinks.android.sha256CertificateFingerprints", "Android");
1246
+ }
1247
+ if (config.androidCertificateFingerprints.length === 0)
1248
+ return;
1249
+ return [
1250
+ {
1251
+ relation: ["delegate_permission/common.handle_all_urls"],
1252
+ target: {
1253
+ namespace: "android_app",
1254
+ package_name: config.appId,
1255
+ sha256_cert_fingerprints: config.androidCertificateFingerprints
1256
+ }
1257
+ }
1258
+ ];
1259
+ };
1260
+ var createAbsoluteMobileAssociationDocuments = (config, options = {}) => {
1261
+ const documents = {};
1262
+ const requireAll = options.requireAll === true;
1263
+ const android = createAndroidDocument(config, requireAll);
1264
+ const apple = createAppleDocument(config, requireAll);
1265
+ if (android)
1266
+ documents.android = android;
1267
+ if (apple)
1268
+ documents.apple = apple;
1269
+ return documents;
1270
+ };
1271
+ var jsonResponse = (value) => new Response(`${JSON.stringify(value)}
1272
+ `, { headers: JSON_HEADERS });
1273
+ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {}) => {
1274
+ const plugin = new Elysia({ name: "absolutejs-mobile-associations" });
1275
+ if (!mobile)
1276
+ return plugin;
1277
+ const config = normalizeAbsoluteMobileConfig(mobile, projectRoot);
1278
+ const documents = createAbsoluteMobileAssociationDocuments(config, options);
1279
+ if (documents.apple) {
1280
+ plugin.get(APPLE_ASSOCIATION_PATH, () => jsonResponse(documents.apple));
1281
+ }
1282
+ if (documents.android) {
1283
+ plugin.get(ANDROID_ASSOCIATION_PATH, () => jsonResponse(documents.android));
1284
+ }
1285
+ return plugin;
1286
+ };
1287
+ var writeAtomic = async (path, source) => {
1288
+ let current;
1289
+ try {
1290
+ current = await readFile5(path, "utf8");
1291
+ } catch (error) {
1292
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
1293
+ throw error;
1294
+ }
1295
+ }
1296
+ if (current === source)
1297
+ return false;
1298
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
1299
+ await writeFile5(temporary, source, { flag: "wx" });
1300
+ await rename5(temporary, path);
1301
+ return true;
1302
+ };
1303
+ var exists2 = async (path) => {
1304
+ try {
1305
+ await access4(path);
1306
+ return true;
1307
+ } catch {
1308
+ return false;
1309
+ }
1310
+ };
1311
+ var assertOwnedOutput = async (root) => {
1312
+ const path = resolve5(root, OWNERSHIP_FILE);
1313
+ let ownership;
1314
+ try {
1315
+ ownership = JSON.parse(await readFile5(path, "utf8"));
1316
+ } catch {
1317
+ throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
1318
+ }
1319
+ if (typeof ownership !== "object" || ownership === null || Reflect.get(ownership, "format") !== 1) {
1320
+ throw new TypeError(`Association output ${root} has an unsupported ownership manifest.`);
1321
+ }
1322
+ };
1323
+ var publishGeneratedDirectory = async (temporary, root) => {
1324
+ const hasCurrent = await exists2(root);
1325
+ if (hasCurrent)
1326
+ await assertOwnedOutput(root);
1327
+ const backup = `${root}.${crypto.randomUUID()}.previous`;
1328
+ if (hasCurrent)
1329
+ await rename5(root, backup);
1330
+ try {
1331
+ await rename5(temporary, root);
1332
+ } catch (error) {
1333
+ if (hasCurrent)
1334
+ await rename5(backup, root);
1335
+ throw error;
1336
+ }
1337
+ if (hasCurrent)
1338
+ await rm4(backup, { force: true, recursive: true });
1339
+ };
1340
+ var materializeHost = async (root, host, files) => {
1341
+ const directory = resolve5(root, host, ".well-known");
1342
+ await mkdir4(directory, { recursive: true });
1343
+ return Promise.all(files.map(async ([name, document]) => {
1344
+ const path = resolve5(directory, name);
1345
+ await writeAtomic(path, `${JSON.stringify(document, null, 2)}
1346
+ `);
1347
+ return path;
1348
+ }));
1349
+ };
1350
+ var associationEndpoints = (config, documents) => config.deepLinkHosts.flatMap((host) => {
1351
+ const endpoints = [];
1352
+ if (documents.android)
1353
+ endpoints.push({
1354
+ document: documents.android,
1355
+ host,
1356
+ path: ANDROID_ASSOCIATION_PATH,
1357
+ platform: "Android"
1358
+ });
1359
+ if (documents.apple)
1360
+ endpoints.push({
1361
+ document: documents.apple,
1362
+ host,
1363
+ path: APPLE_ASSOCIATION_PATH,
1364
+ platform: "Apple"
1365
+ });
1366
+ return endpoints;
1367
+ });
1368
+ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory) => {
1369
+ const root = resolve5(outputDirectory);
1370
+ const temporary = `${root}.${crypto.randomUUID()}.tmp`;
1371
+ const documents = createAbsoluteMobileAssociationDocuments(config, {
1372
+ requireAll: true
1373
+ });
1374
+ const files = [];
1375
+ if (documents.android)
1376
+ files.push(["assetlinks.json", documents.android]);
1377
+ if (documents.apple) {
1378
+ files.push(["apple-app-site-association", documents.apple]);
1379
+ }
1380
+ await mkdir4(temporary, { recursive: true });
1381
+ try {
1382
+ const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
1383
+ await writeAtomic(resolve5(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
1384
+ `);
1385
+ await publishGeneratedDirectory(temporary, root);
1386
+ const written = temporaryPaths.map((path) => resolve5(root, path.slice(temporary.length + 1)));
1387
+ return { root, written };
1388
+ } catch (error) {
1389
+ await rm4(temporary, { force: true, recursive: true });
1390
+ throw error;
1391
+ }
1392
+ };
1393
+ var verifyAbsoluteMobileAssociationFiles = async (config, request = globalThis.fetch) => {
1394
+ const documents = createAbsoluteMobileAssociationDocuments(config, {
1395
+ requireAll: true
1396
+ });
1397
+ const results = await Promise.all(associationEndpoints(config, documents).map(async (endpoint) => {
1398
+ const url = `https://${endpoint.host}${endpoint.path}`;
1399
+ const response = await request(url, {
1400
+ redirect: "manual",
1401
+ signal: AbortSignal.timeout(VERIFY_TIMEOUT_MS)
1402
+ });
1403
+ if (response.status !== HTTP_OK) {
1404
+ throw new TypeError(`${endpoint.platform} association verification failed for ${url}: HTTP ${response.status}; redirects are not allowed.`);
1405
+ }
1406
+ const contentType = response.headers.get("content-type") ?? "";
1407
+ if (!contentType.toLowerCase().startsWith("application/json")) {
1408
+ throw new TypeError(`${endpoint.platform} association verification failed for ${url}: expected application/json.`);
1409
+ }
1410
+ let document;
1411
+ try {
1412
+ document = await response.json();
1413
+ } catch {
1414
+ throw new TypeError(`${endpoint.platform} association verification failed for ${url}: invalid JSON.`);
1415
+ }
1416
+ if (JSON.stringify(document) !== JSON.stringify(endpoint.document)) {
1417
+ throw new TypeError(`${endpoint.platform} association verification failed for ${url}: the hosted identity does not match mobile config.`);
1418
+ }
1419
+ return { platform: endpoint.platform, url };
1420
+ }));
1421
+ return { results };
1422
+ };
1423
+ // src/mobile/buildMetadata.ts
1424
+ var ABSOLUTE_MOBILE_ROUTE_DETAIL = "x-absolute-mobile";
1425
+ var frameworks2 = new Set([
1426
+ "angular",
1427
+ "ember",
1428
+ "html",
1429
+ "htmx",
1430
+ "react",
1431
+ "svelte",
1432
+ "vue"
1433
+ ]);
1434
+ var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1435
+ var isPageFramework2 = (value) => typeof value === "string" && frameworks2.has(value);
1436
+ var parseAbsoluteMobileBuildPageMetadata = (value) => {
1437
+ if (!isRecord2(value))
1438
+ return;
1439
+ if (typeof value.bundleKey !== "string" || typeof value.contract !== "string" || !isPageFramework2(value.framework) || typeof value.pageId !== "string" || typeof value.propsSchemaHash !== "string") {
1440
+ return;
1441
+ }
1442
+ return {
1443
+ bundleKey: value.bundleKey,
1444
+ contract: value.contract,
1445
+ framework: value.framework,
1446
+ pageId: value.pageId,
1447
+ propsSchemaHash: value.propsSchemaHash
1448
+ };
1449
+ };
1450
+ // src/mobile/buildPipeline.ts
1451
+ import { readFile as readFile9 } from "fs/promises";
1452
+ import { join as join8, resolve as resolve8 } from "path";
1453
+ import { pathToFileURL as pathToFileURL2 } from "url";
1454
+
1455
+ // src/mobile/buildRelease.ts
1456
+ import { createHash as createHash4 } from "crypto";
1457
+ import { readFile as readFile6 } from "fs/promises";
1458
+ import { join as join5, relative as relative4, resolve as resolve6 } from "path";
1459
+ var sha256 = (bytes) => createHash4("sha256").update(bytes).digest("hex");
1460
+ var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
1461
+ var resolveAssetPath = (buildDirectory, assetPath) => {
1462
+ const resolvedBuildDirectory = resolve6(buildDirectory);
1463
+ const resolvedAsset = resolve6(assetPath);
1464
+ if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
1465
+ return resolvedAsset;
1466
+ }
1467
+ return join5(buildDirectory, assetPath.replace(/^\/+/, ""));
1468
+ };
1469
+ var pageFor = async (metadata, manifest, buildDirectory) => {
1470
+ const assetPath = manifest[metadata.bundleKey];
1471
+ if (!assetPath) {
1472
+ throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
1473
+ }
1474
+ const resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
1475
+ const bytes = await readFile6(resolvedAssetPath);
1476
+ const bundlePath = `/${relative4(resolve6(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
1477
+ return {
1478
+ bundleHash: sha256(bytes),
1479
+ bundlePath,
1480
+ contract: metadata.contract,
1481
+ framework: metadata.framework,
1482
+ pageId: metadata.pageId,
1483
+ propsSchemaHash: metadata.propsSchemaHash
1484
+ };
1485
+ };
1486
+ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
1487
+ const [captured, producerBytes] = await Promise.all([
1488
+ captureAbsoluteMobileRouteGraph(options.app),
1489
+ readFile6(options.producerPath)
1490
+ ]);
1491
+ if (captured.length === 0) {
1492
+ throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
1493
+ }
1494
+ const metadataByPage = new Map;
1495
+ for (const { metadata } of captured) {
1496
+ const prior = metadataByPage.get(metadata.pageId);
1497
+ if (prior && JSON.stringify(prior) !== JSON.stringify(metadata)) {
1498
+ throw new TypeError(`Mobile page ${metadata.pageId} has inconsistent generated contracts.`);
1499
+ }
1500
+ metadataByPage.set(metadata.pageId, metadata);
1501
+ }
1502
+ const pages = await Promise.all([...metadataByPage.values()].map((metadata) => pageFor(metadata, options.manifest, options.buildDirectory)));
1503
+ const producerHash = sha256(producerBytes);
1504
+ const appBuild = `ambuild_${sha256(new TextEncoder().encode(JSON.stringify({
1505
+ pages: pages.map(({ bundleHash, bundlePath, contract, pageId }) => ({
1506
+ bundleHash,
1507
+ bundlePath,
1508
+ contract,
1509
+ pageId
1510
+ })),
1511
+ producerHash,
1512
+ runtime: options.runtime
1513
+ })))}`;
1514
+ const priorAppIds = new Set((options.previousArtifacts ?? []).map(({ appId }) => appId));
1515
+ if (priorAppIds.size > 1 || priorAppIds.size === 1 && !priorAppIds.has(options.appId)) {
1516
+ throw new TypeError("Previous mobile compatibility artifacts belong to another app.");
1517
+ }
1518
+ const unchanged = options.previousArtifacts?.find((artifact2) => artifact2.appBuild === appBuild && artifact2.runtime === options.runtime);
1519
+ const generation = unchanged?.generation ?? Math.max(0, ...(options.previousArtifacts ?? []).map((artifact2) => artifact2.generation)) + 1;
1520
+ const artifact = createAbsoluteMobileCompatibilityArtifact({
1521
+ appBuild,
1522
+ appId: options.appId,
1523
+ generation,
1524
+ pages,
1525
+ producer: {
1526
+ bundleHash: producerHash,
1527
+ bytes: producerBytes.byteLength,
1528
+ exportName: options.producerExport ?? "server",
1529
+ module: options.producerModule ?? "producer.js"
1530
+ },
1531
+ routes: captured.map(({ route }) => route),
1532
+ runtime: options.runtime
1533
+ });
1534
+ return { artifact, producer: new Blob([producerBytes]) };
1535
+ };
1536
+ var captureAbsoluteMobileRouteGraph = async (app) => {
1537
+ if (app.modules)
1538
+ await app.modules;
1539
+ app.compile();
1540
+ const captured = app.routes.flatMap((route) => {
1541
+ const metadata = readPageMetadata(route);
1542
+ if (!metadata || route.method !== "GET" && route.method !== "HEAD") {
1543
+ return [];
1544
+ }
1545
+ return [
1546
+ {
1547
+ metadata,
1548
+ route: {
1549
+ method: route.method === "GET" ? "GET" : "HEAD",
1550
+ pageId: metadata.pageId,
1551
+ pattern: route.path
1552
+ }
1553
+ }
1554
+ ];
1555
+ });
1556
+ const identities = new Map;
1557
+ for (const { metadata, route } of captured) {
1558
+ const prior = identities.get(`${route.method}:${route.pattern}`);
1559
+ if (prior && prior !== metadata.pageId) {
1560
+ throw new TypeError(`Mobile route ${route.method} ${route.pattern} resolves to multiple pages.`);
1561
+ }
1562
+ identities.set(`${route.method}:${route.pattern}`, metadata.pageId);
1563
+ }
1564
+ return captured;
1565
+ };
1566
+
1567
+ // src/mobile/capacitorBundle.ts
1568
+ import {
1569
+ copyFile as copyFile3,
1570
+ mkdir as mkdir5,
1571
+ mkdtemp as mkdtemp3,
1572
+ readFile as readFile7,
1573
+ rename as rename6,
1574
+ rm as rm5,
1575
+ writeFile as writeFile6
1576
+ } from "fs/promises";
1577
+ import { existsSync as existsSync2 } from "fs";
1578
+ import { basename, dirname as dirname3, extname, join as join6, resolve as resolve7 } from "path";
1579
+
1580
+ // src/mobile/routeMatcher.ts
1581
+ var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
1582
+ var routeSegmentPattern = (segment) => {
1583
+ if (segment === "*")
1584
+ return ".*";
1585
+ if (segment.startsWith(":") && segment.endsWith("?"))
1586
+ return "[^/]*";
1587
+ if (segment.startsWith(":"))
1588
+ return "[^/]+";
1589
+ return segment.replace(REGEXP_SPECIAL_CHARACTERS, "\\$&");
1590
+ };
1591
+ var matchesAbsoluteMobileRoutePattern = (pattern, pathname) => {
1592
+ const expression = pattern.split("/").map(routeSegmentPattern).join("/");
1593
+ return new RegExp(`^${expression}/?$`).test(pathname);
1594
+ };
1595
+ var resolveAbsoluteMobileRoute = (routes, pathname, method = "GET") => routes.find((route) => route.method === method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
1596
+
1597
+ // src/mobile/producerContextState.ts
1598
+ var ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY = Symbol.for("absolutejs.mobileProducerAsyncLocalStorage");
1599
+ var frameworks3 = new Set([
1600
+ "angular",
1601
+ "ember",
1602
+ "html",
1603
+ "htmx",
1604
+ "react",
1605
+ "svelte",
1606
+ "vue"
1607
+ ]);
1608
+ 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";
1609
+ var getCurrentAbsoluteMobileProducerContext = () => {
1610
+ const value = Reflect.get(globalThis, ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY);
1611
+ if (typeof value !== "object" || value === null || !("getStore" in value) || typeof value.getStore !== "function") {
1612
+ return;
1613
+ }
1614
+ const context = value.getStore();
1615
+ if (typeof context !== "object" || context === null || !("page" in context) || !isCompatibilityPage(context.page) || !("releaseId" in context) || typeof context.releaseId !== "string") {
1616
+ return;
1617
+ }
1618
+ return { page: context.page, releaseId: context.releaseId };
1619
+ };
1620
+
1621
+ // src/mobile/pageProtocol.ts
1622
+ var ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE = "application/vnd.absolute.page+json";
1623
+ var ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION = 1;
1624
+ var BAD_REQUEST_STATUS = 400;
1625
+ var OK_STATUS = 200;
1626
+ var SERVER_ERROR_STATUS = 500;
1627
+ var UPGRADE_REQUIRED_STATUS = 426;
1628
+ var MOBILE_PAGE_REQUEST_HEADERS = {
1629
+ appBuild: "x-absolute-mobile-app-build",
1630
+ pageBundle: "x-absolute-mobile-page-bundle",
1631
+ pageContracts: "x-absolute-mobile-page-contracts",
1632
+ pageId: "x-absolute-mobile-page-id",
1633
+ protocol: "x-absolute-mobile-protocol",
1634
+ runtime: "x-absolute-mobile-runtime"
1635
+ };
1636
+ var parseMediaType = (value) => value.split(";", 1)[0]?.trim().toLowerCase();
1637
+ var acceptsAbsoluteMobilePage = (request) => {
1638
+ if (!request)
1639
+ return false;
1640
+ return (request.headers.get("accept") ?? "").split(",").some((value) => parseMediaType(value) === ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE);
1641
+ };
1642
+ var readRequiredHeader = (request, name) => {
1643
+ const value = request.headers.get(name)?.trim();
1644
+ return value ? value : undefined;
1645
+ };
1646
+ var parseAbsoluteMobilePageRequest = (request) => {
1647
+ if (!request || !acceptsAbsoluteMobilePage(request)) {
1648
+ return { kind: "not-mobile" };
1649
+ }
1650
+ const protocolValue = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.protocol);
1651
+ const runtime = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.runtime);
1652
+ const appBuild = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.appBuild);
1653
+ const pageBundle = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.pageBundle);
1654
+ const pageContractsValue = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.pageContracts);
1655
+ const pageId = readRequiredHeader(request, MOBILE_PAGE_REQUEST_HEADERS.pageId);
1656
+ if (!protocolValue || !/^\d+$/.test(protocolValue)) {
1657
+ return {
1658
+ kind: "invalid",
1659
+ message: `Missing or invalid ${MOBILE_PAGE_REQUEST_HEADERS.protocol} header.`
1660
+ };
1661
+ }
1662
+ if (!runtime) {
1663
+ return {
1664
+ kind: "invalid",
1665
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.runtime} header.`
1666
+ };
1667
+ }
1668
+ if (!appBuild) {
1669
+ return {
1670
+ kind: "invalid",
1671
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.appBuild} header.`
1672
+ };
1673
+ }
1674
+ if (!pageBundle) {
1675
+ return {
1676
+ kind: "invalid",
1677
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageBundle} header.`
1678
+ };
1679
+ }
1680
+ if (!pageContractsValue) {
1681
+ return {
1682
+ kind: "invalid",
1683
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageContracts} header.`
1684
+ };
1685
+ }
1686
+ if (!pageId) {
1687
+ return {
1688
+ kind: "invalid",
1689
+ message: `Missing ${MOBILE_PAGE_REQUEST_HEADERS.pageId} header.`
1690
+ };
1691
+ }
1692
+ const pageContracts = [
1693
+ ...new Set(pageContractsValue.split(",").map((value) => value.trim()).filter(Boolean))
1694
+ ];
1695
+ if (pageContracts.length === 0) {
1696
+ return {
1697
+ kind: "invalid",
1698
+ message: `${MOBILE_PAGE_REQUEST_HEADERS.pageContracts} must contain at least one contract.`
1699
+ };
1700
+ }
1701
+ return {
1702
+ client: {
1703
+ appBuild,
1704
+ pageBundle,
1705
+ pageContracts,
1706
+ pageId,
1707
+ protocol: Number(protocolValue),
1708
+ runtime
1709
+ },
1710
+ kind: "mobile"
1711
+ };
1712
+ };
1713
+ var responseHeaders = () => {
1714
+ const headers = new Headers({
1715
+ "cache-control": "no-store",
1716
+ "content-type": `${ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE}; version=${ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION}`
1717
+ });
1718
+ headerVaryValues.forEach((value) => headers.append("vary", value));
1719
+ return headers;
1720
+ };
1721
+ var headerVaryValues = [
1722
+ "Accept",
1723
+ MOBILE_PAGE_REQUEST_HEADERS.protocol,
1724
+ MOBILE_PAGE_REQUEST_HEADERS.runtime,
1725
+ MOBILE_PAGE_REQUEST_HEADERS.appBuild,
1726
+ MOBILE_PAGE_REQUEST_HEADERS.pageBundle,
1727
+ MOBILE_PAGE_REQUEST_HEADERS.pageContracts,
1728
+ MOBILE_PAGE_REQUEST_HEADERS.pageId
1729
+ ];
1730
+ var envelopeResponse = (response, status) => new Response(JSON.stringify({
1731
+ protocol: ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
1732
+ response
1733
+ }), { headers: responseHeaders(), status });
1734
+ var isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1735
+ var normalizeJsonValue = (value) => {
1736
+ const serialized = JSON.stringify(value);
1737
+ if (serialized === undefined) {
1738
+ throw new TypeError("Mobile page props must be JSON-serializable.");
1739
+ }
1740
+ const parsed = JSON.parse(serialized);
1741
+ if (!isRecord3(parsed)) {
1742
+ throw new TypeError("Mobile page props must serialize to an object.");
1743
+ }
1744
+ return parsed;
1745
+ };
1746
+ var finalizeArchivedMobilePage = (context, input) => {
1747
+ const { page } = context;
1748
+ if (page.pageId !== input.compatibility.pageId || page.framework !== input.compatibility.framework) {
1749
+ return envelopeResponse({
1750
+ kind: "invalid-request",
1751
+ message: "Archived producer page identity does not match its release artifact."
1752
+ }, BAD_REQUEST_STATUS);
1753
+ }
1754
+ try {
1755
+ const [currentRepresentation] = input.compatibility.representations;
1756
+ if (!currentRepresentation) {
1757
+ throw new TypeError("Mobile page has no current representation.");
1758
+ }
1759
+ return envelopeResponse({
1760
+ contract: page.contract,
1761
+ framework: page.framework,
1762
+ kind: "page",
1763
+ pageId: page.pageId,
1764
+ props: normalizeJsonValue(currentRepresentation.mapProps(input.props)),
1765
+ status: input.status ?? OK_STATUS
1766
+ }, input.status ?? OK_STATUS);
1767
+ } catch (error) {
1768
+ console.error(`[Mobile] Archived producer ${context.releaseId} failed for ${page.pageId}:`, error);
1769
+ return createAbsoluteMobilePageErrorResponse(page.pageId);
1770
+ }
1771
+ };
1772
+ var createAbsoluteMobileInvalidRequestResponse = (message) => envelopeResponse({ kind: "invalid-request", message }, BAD_REQUEST_STATUS);
1773
+ var createAbsoluteMobilePageErrorResponse = (pageId) => envelopeResponse({
1774
+ code: "representation-failed",
1775
+ kind: "error",
695
1776
  pageId,
696
1777
  status: SERVER_ERROR_STATUS
697
1778
  }, SERVER_ERROR_STATUS);
@@ -767,7 +1848,7 @@ class AbsoluteMobilePageProtocolError extends Error {
767
1848
  this.code = code;
768
1849
  }
769
1850
  }
770
- var frameworks3 = new Set([
1851
+ var frameworks4 = new Set([
771
1852
  "angular",
772
1853
  "ember",
773
1854
  "html",
@@ -782,14 +1863,14 @@ var upgradeReasons = new Set([
782
1863
  "protocol",
783
1864
  "runtime"
784
1865
  ]);
785
- var isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
786
- var isFramework = (value) => typeof value === "string" && frameworks3.has(value);
1866
+ var isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
1867
+ var isFramework = (value) => typeof value === "string" && frameworks4.has(value);
787
1868
  var isUpgradeReason = (value) => typeof value === "string" && upgradeReasons.has(value);
788
1869
  var parsePageResult = (value) => {
789
1870
  if (typeof value.contract !== "string" || !isFramework(value.framework) || typeof value.pageId !== "string" || typeof value.status !== "number") {
790
1871
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The mobile page response is missing required page metadata.");
791
1872
  }
792
- if (!isRecord2(value.props)) {
1873
+ if (!isRecord4(value.props)) {
793
1874
  throw new AbsoluteMobilePageProtocolError("invalid-props", "The mobile page response must contain an object props value.");
794
1875
  }
795
1876
  return {
@@ -835,7 +1916,7 @@ var activateAbsoluteMobilePage = async (value, options) => {
835
1916
  };
836
1917
  };
837
1918
  var parseAbsoluteMobilePageEnvelope = (value) => {
838
- if (!isRecord2(value) || !isRecord2(value.response)) {
1919
+ if (!isRecord4(value) || !isRecord4(value.response)) {
839
1920
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "The server did not return an AbsoluteJS mobile page envelope.");
840
1921
  }
841
1922
  if (value.protocol !== ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION) {
@@ -862,122 +1943,246 @@ var parseAbsoluteMobilePageEnvelope = (value) => {
862
1943
  }
863
1944
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", `Unknown mobile page response kind ${String(kind)}.`);
864
1945
  };
865
- // src/mobile/compatibilityDispatcher.ts
866
- import { Elysia } from "elysia";
867
1946
 
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;
1947
+ // src/mobile/transport.ts
1948
+ var ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT = 1;
1949
+ var pageForPathname = (manifest, pathname) => {
1950
+ const route = resolveAbsoluteMobileRoute(manifest.routes, pathname);
1951
+ if (!route) {
1952
+ throw new TypeError(`No embedded mobile page owns ${pathname}.`);
1953
+ }
1954
+ const page = manifest.pages.find((candidate) => candidate.pageId === route.pageId);
1955
+ if (!page) {
1956
+ throw new TypeError(`Mobile route ${route.pattern} references missing page ${route.pageId}.`);
1957
+ }
1958
+ return page;
874
1959
  };
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;
1960
+ var canonicalBackendUrl = (manifest, path) => {
1961
+ const origin = new URL(manifest.productionOrigin);
1962
+ const url = new URL(path, `${origin.origin}/`);
1963
+ if (url.origin !== origin.origin) {
1964
+ throw new TypeError("Mobile transport cannot leave productionOrigin.");
1965
+ }
1966
+ url.hash = "";
1967
+ return url;
1968
+ };
1969
+ var createAbsoluteMobilePageRequest = (manifest, path, options = {}) => {
1970
+ const url = canonicalBackendUrl(manifest, path);
1971
+ const page = pageForPathname(manifest, url.pathname);
1972
+ const headers = new Headers(options.headers);
1973
+ headers.set("accept", ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE);
1974
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.appBuild, manifest.appBuild);
1975
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.pageBundle, page.bundleHash);
1976
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.pageContracts, page.contract);
1977
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.pageId, page.pageId);
1978
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.protocol, String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION));
1979
+ headers.set(MOBILE_PAGE_REQUEST_HEADERS.runtime, manifest.runtime);
1980
+ return new Request(url, { headers, method: "GET" });
1981
+ };
1982
+ var fetchAbsoluteMobilePage = async (manifest, path, options = {}) => {
1983
+ const request = createAbsoluteMobilePageRequest(manifest, path, options);
1984
+ const response = await (options.fetch ?? globalThis.fetch)(request);
1985
+ const value = await response.json();
1986
+ return value;
1987
+ };
1988
+ var navigateAbsoluteMobilePage = async (manifest, path, options) => {
1989
+ const envelope = await fetchAbsoluteMobilePage(manifest, path, options);
1990
+ return activateAbsoluteMobilePage(envelope, options);
1991
+ };
1992
+ var resolveAbsoluteMobileDeepLink = (manifest, value) => {
1993
+ const url = new URL(value);
1994
+ if (url.username || url.password) {
1995
+ throw new TypeError("Mobile deep links cannot contain credentials.");
1996
+ }
1997
+ const isHttpsHost = url.protocol === "https:" && manifest.deepLinkHosts.includes(url.hostname.toLowerCase());
1998
+ const isCustomScheme = manifest.deepLinkScheme !== undefined && url.protocol === `${manifest.deepLinkScheme}:`;
1999
+ if (!isHttpsHost && !isCustomScheme) {
2000
+ throw new TypeError("Mobile deep link is outside the configured app URLs.");
2001
+ }
2002
+ return `${url.pathname || "/"}${url.search}${url.hash}`;
882
2003
  };
883
- var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
884
2004
 
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, "\\$&");
2005
+ // src/mobile/capacitorBundle.ts
2006
+ var MANIFEST_FILE = "absolute-mobile-manifest.json";
2007
+ var BOOTSTRAP_FILE = "absolute-mobile-bootstrap.js";
2008
+ var INDEX_FILE = "index.html";
2009
+ var CLIENT_IMPORT_PATTERN = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s*)["'](\/[^"']+)["']/gu;
2010
+ var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
2011
+ var shellBootstrapModule = () => {
2012
+ const candidate = ["js", "ts"].map((extension) => join6(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
2013
+ if (candidate)
2014
+ return candidate;
2015
+ throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
895
2016
  };
896
- var matchesRoutePattern = (pattern, pathname) => {
897
- const expression = pattern.split("/").map(routeSegmentPattern).join("/");
898
- return new RegExp(`^${expression}/?$`).test(pathname);
2017
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
2018
+ var indexHtml = (appName) => `<!doctype html>
2019
+ <html>
2020
+ <head>
2021
+ <meta charset="utf-8">
2022
+ <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
2023
+ <meta name="color-scheme" content="light dark">
2024
+ <title>${escapeHtml(appName)}</title>
2025
+ </head>
2026
+ <body>
2027
+ <main id="absolute-mobile-status" role="status">Starting\u2026</main>
2028
+ <script type="module" src="./${BOOTSTRAP_FILE}"></script>
2029
+ </body>
2030
+ </html>
2031
+ `;
2032
+ var sourceAssetPath = (buildDirectory, bundlePath) => {
2033
+ const root = resolve7(buildDirectory);
2034
+ const asset = resolve7(root, bundlePath.replace(/^\/+/, ""));
2035
+ if (!asset.startsWith(`${root}/`)) {
2036
+ throw new TypeError("Mobile page bundle escaped the build directory.");
2037
+ }
2038
+ return asset;
899
2039
  };
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));
2040
+ var buildShellBootstrap = async (staging) => {
2041
+ const modulePath = shellBootstrapModule();
2042
+ const entryPath = join6(staging, ".absolute-mobile-entry.ts");
2043
+ await writeFile6(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
2044
+ void startAbsoluteMobileShell();
2045
+ `);
2046
+ const build = await Bun.build({
2047
+ entrypoints: [entryPath],
2048
+ minify: true,
2049
+ outdir: staging,
2050
+ target: "browser"
2051
+ });
2052
+ if (!build.success || build.outputs.length !== 1) {
2053
+ throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
2054
+ }
2055
+ await rename6(build.outputs[0]?.path ?? "", join6(staging, BOOTSTRAP_FILE));
2056
+ await rm5(entryPath, { force: true });
2057
+ };
2058
+ var removePreviousBundle = async (backup, moved) => {
2059
+ if (!moved)
2060
+ return;
2061
+ await rm5(backup, { force: true, recursive: true });
2062
+ };
2063
+ var restorePreviousBundle = async (backup, destination, moved) => {
2064
+ if (!moved)
2065
+ return;
2066
+ await rename6(backup, destination);
2067
+ };
2068
+ var installBundle = async (staging, destination) => {
2069
+ const backup = `${destination}.previous-${crypto.randomUUID()}`;
2070
+ let movedPrevious = false;
2071
+ try {
2072
+ await rename6(destination, backup);
2073
+ movedPrevious = true;
2074
+ } catch (error) {
2075
+ if (!errorHasCode2(error, "ENOENT"))
2076
+ throw error;
2077
+ }
2078
+ try {
2079
+ await rename6(staging, destination);
2080
+ await removePreviousBundle(backup, movedPrevious);
2081
+ } catch (error) {
2082
+ await restorePreviousBundle(backup, destination, movedPrevious);
2083
+ throw error;
2084
+ }
2085
+ };
2086
+ var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) => {
2087
+ if (page.framework !== "react") {
2088
+ throw new TypeError(`Capacitor spike currently supports React pages; ${page.pageId} is ${page.framework}.`);
2089
+ }
2090
+ const extension = extname(page.bundlePath) || ".js";
2091
+ const localBundlePath = `./pages/${page.bundleHash}${extension}`;
2092
+ const source = sourceAssetPath(buildDirectory, page.bundlePath);
2093
+ await copyFile3(source, join6(staging, localBundlePath));
2094
+ await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
2095
+ return { ...page, localBundlePath };
2096
+ };
2097
+ var absoluteClientImports = async (sourcePath) => {
2098
+ const source = await readFile7(sourcePath, "utf8");
2099
+ return [...source.matchAll(CLIENT_IMPORT_PATTERN)].flatMap((match) => {
2100
+ const [specifier] = match.slice(1);
2101
+ return specifier ? [specifier.split(/[?#]/u, 1)[0] ?? specifier] : [];
2102
+ });
2103
+ };
2104
+ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, copied) => {
2105
+ if (copied.has(specifier))
2106
+ return;
2107
+ copied.add(specifier);
2108
+ const source = sourceAssetPath(buildDirectory, specifier);
2109
+ const destination = join6(staging, specifier.replace(/^\/+/, ""));
2110
+ await mkdir5(dirname3(destination), { recursive: true });
2111
+ await copyFile3(source, destination);
2112
+ await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
903
2113
  };
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);
912
- throw error;
913
- });
914
- producers.set(artifact.releaseId, loading);
915
- return loading;
916
- };
2114
+ var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
2115
+ const dependencies = await absoluteClientImports(sourcePath);
2116
+ await Promise.all(dependencies.map((specifier) => copyAbsoluteClientDependency(specifier, buildDirectory, staging, copied)));
917
2117
  };
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.");
2118
+ var materializeAbsoluteCapacitorWebBundle = async (options) => {
2119
+ if (!resolveAbsoluteMobileRoute(options.artifact.routes, new URL(options.config.entry, "https://absolute.invalid").pathname)) {
2120
+ throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
2121
+ }
2122
+ const destination = options.config.bundleDirectory;
2123
+ await mkdir5(dirname3(destination), { recursive: true });
2124
+ const staging = await mkdtemp3(join6(dirname3(destination), `.${basename(destination)}.stage-`));
2125
+ try {
2126
+ const pageDirectory = join6(staging, "pages");
2127
+ await mkdir5(pageDirectory, { recursive: true });
2128
+ const copiedDependencies = new Set;
2129
+ const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
2130
+ const manifest = {
2131
+ appBuild: options.artifact.appBuild,
2132
+ appId: options.config.appId,
2133
+ appName: options.config.appName,
2134
+ deepLinkHosts: options.config.deepLinkHosts,
2135
+ deepLinkScheme: options.config.deepLinkScheme,
2136
+ entry: options.config.entry,
2137
+ format: ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
2138
+ pages,
2139
+ productionOrigin: options.config.productionOrigin,
2140
+ routes: options.artifact.routes,
2141
+ runtime: options.artifact.runtime
2142
+ };
2143
+ await Promise.all([
2144
+ writeFile6(join6(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
2145
+ `),
2146
+ writeFile6(join6(staging, INDEX_FILE), indexHtml(options.config.appName)),
2147
+ buildShellBootstrap(staging)
2148
+ ]);
2149
+ await installBundle(staging, destination);
2150
+ return manifest;
2151
+ } catch (error) {
2152
+ await rm5(staging, { force: true, recursive: true });
2153
+ throw error;
922
2154
  }
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
2155
  };
2156
+
952
2157
  // src/mobile/materializedBundle.ts
953
- import { createHash as createHash3 } from "crypto";
2158
+ import { createHash as createHash5 } from "crypto";
954
2159
  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
2160
+ access as access5,
2161
+ mkdir as mkdir6,
2162
+ mkdtemp as mkdtemp4,
2163
+ readFile as readFile8,
2164
+ rename as rename7,
2165
+ rm as rm6,
2166
+ writeFile as writeFile7
962
2167
  } from "fs/promises";
963
- import { dirname, join as join2, resolve as resolvePath2 } from "path";
2168
+ import { dirname as dirname4, join as join7, resolve as resolvePath2 } from "path";
964
2169
  import { pathToFileURL } from "url";
965
2170
  var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
966
2171
  var CURRENT_BUNDLE_FILE = "current.json";
967
2172
  var BUNDLES_DIRECTORY = "bundles";
968
2173
  var ARTIFACT_FILE2 = "artifact.json";
969
2174
  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;
2175
+ var isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2176
+ var errorHasCode3 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
972
2177
  var bundleIdFor = (currentReleaseId, releases) => {
973
2178
  const identity = JSON.stringify({
974
2179
  currentReleaseId,
975
2180
  releases: releases.map(({ releaseId }) => releaseId)
976
2181
  });
977
- return `amb_${createHash3("sha256").update(identity).digest("hex")}`;
2182
+ return `amb_${createHash5("sha256").update(identity).digest("hex")}`;
978
2183
  };
979
2184
  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)) {
2185
+ if (!isRecord5(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
981
2186
  throw new TypeError("Invalid materialized mobile compatibility bundle.");
982
2187
  }
983
2188
  const releases = value.releases.map(parseAbsoluteMobileCompatibilityArtifact);
@@ -1000,31 +2205,31 @@ var parseBundleIndex = (value) => {
1000
2205
  };
1001
2206
  };
1002
2207
  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 });
2208
+ const directory = join7(root, release.artifact.releaseId);
2209
+ const producerPath = join7(directory, release.artifact.producer.module);
2210
+ await mkdir6(dirname4(producerPath), { recursive: true });
1006
2211
  await Promise.all([
1007
- writeFile2(join2(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
2212
+ writeFile7(join7(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
1008
2213
  `),
1009
- writeFile2(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
2214
+ writeFile7(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
1010
2215
  ]);
1011
2216
  };
1012
2217
  var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
1013
- const destination = join2(bundlesRoot, bundleId);
2218
+ const destination = join7(bundlesRoot, bundleId);
1014
2219
  try {
1015
- await access(destination);
2220
+ await access5(destination);
1016
2221
  return destination;
1017
2222
  } catch (error) {
1018
- if (!errorHasCode2(error, "ENOENT"))
2223
+ if (!errorHasCode3(error, "ENOENT"))
1019
2224
  throw error;
1020
2225
  }
1021
- const staging = await mkdtemp2(join2(bundlesRoot, ".stage-"));
2226
+ const staging = await mkdtemp4(join7(bundlesRoot, ".stage-"));
1022
2227
  try {
1023
2228
  await Promise.all(releases.map((release) => writeRelease(staging, release)));
1024
- await rename2(staging, destination);
2229
+ await rename7(staging, destination);
1025
2230
  } catch (error) {
1026
- await rm2(staging, { force: true, recursive: true });
1027
- if (errorHasCode2(error, "EEXIST") || errorHasCode2(error, "ENOTEMPTY")) {
2231
+ await rm6(staging, { force: true, recursive: true });
2232
+ if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
1028
2233
  return destination;
1029
2234
  }
1030
2235
  throw error;
@@ -1034,7 +2239,7 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
1034
2239
  var readCompatibilityModule = (modulePath) => import(pathToFileURL(modulePath).href);
1035
2240
  var resolveProducerHandler = (loaded, exportName) => {
1036
2241
  const value = loaded[exportName];
1037
- if (!isRecord3(value) || typeof value.handle !== "function") {
2242
+ if (!isRecord5(value) || typeof value.handle !== "function") {
1038
2243
  throw new TypeError(`Compatibility producer export ${exportName} must expose handle(request).`);
1039
2244
  }
1040
2245
  const { handle } = value;
@@ -1052,15 +2257,15 @@ var resolveProducerHandler = (loaded, exportName) => {
1052
2257
  };
1053
2258
  var loadAbsoluteMobileMaterializedBundle = async (root) => {
1054
2259
  const resolvedRoot = resolvePath2(root);
1055
- const serialized = await readFile2(join2(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2260
+ const serialized = await readFile8(join7(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
1056
2261
  const parsed = JSON.parse(serialized);
1057
2262
  const index = parseBundleIndex(parsed);
1058
- const bundleRoot = join2(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2263
+ const bundleRoot = join7(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
1059
2264
  return {
1060
2265
  artifacts: index.releases,
1061
2266
  currentReleaseId: index.currentReleaseId,
1062
2267
  loadProducer: async (artifact) => {
1063
- const modulePath = join2(bundleRoot, artifact.releaseId, artifact.producer.module);
2268
+ const modulePath = join7(bundleRoot, artifact.releaseId, artifact.producer.module);
1064
2269
  await verifyAbsoluteMobileCompatibilityProducer({
1065
2270
  artifact,
1066
2271
  producer: Bun.file(modulePath)
@@ -1087,8 +2292,8 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
1087
2292
  return release;
1088
2293
  });
1089
2294
  const root = resolvePath2(input.root);
1090
- const bundlesRoot = join2(root, BUNDLES_DIRECTORY);
1091
- await mkdir2(bundlesRoot, { recursive: true });
2295
+ const bundlesRoot = join7(root, BUNDLES_DIRECTORY);
2296
+ await mkdir6(bundlesRoot, { recursive: true });
1092
2297
  const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
1093
2298
  await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
1094
2299
  const index = {
@@ -1097,44 +2302,757 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
1097
2302
  format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
1098
2303
  releases: artifacts
1099
2304
  };
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")}
2305
+ const pointerPath = join7(root, CURRENT_BUNDLE_FILE);
2306
+ const temporaryPointerPath = join7(root, `.current-${crypto.randomUUID()}.json`);
2307
+ await writeFile7(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
1103
2308
  `, { flag: "wx" });
1104
- await rename2(temporaryPointerPath, pointerPath);
2309
+ await rename7(temporaryPointerPath, pointerPath);
1105
2310
  return index;
1106
2311
  };
2312
+ var readAbsoluteMobileMaterializedReleases = async (root) => {
2313
+ const resolvedRoot = resolvePath2(root);
2314
+ try {
2315
+ const serialized = await readFile8(join7(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
2316
+ const parsed = JSON.parse(serialized);
2317
+ const index = parseBundleIndex(parsed);
2318
+ const bundleRoot = join7(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
2319
+ return Promise.all(index.releases.map(async (artifact) => {
2320
+ const producer = Bun.file(join7(bundleRoot, artifact.releaseId, artifact.producer.module));
2321
+ await verifyAbsoluteMobileCompatibilityProducer({
2322
+ artifact,
2323
+ producer
2324
+ });
2325
+ return { artifact, producer };
2326
+ }));
2327
+ } catch (error) {
2328
+ if (errorHasCode3(error, "ENOENT"))
2329
+ return [];
2330
+ throw error;
2331
+ }
2332
+ };
2333
+
2334
+ // src/mobile/buildPipeline.ts
2335
+ var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
2336
+ var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
2337
+ var serverExportName = (loaded, app) => {
2338
+ if (loaded.server === app)
2339
+ return "server";
2340
+ if (loaded.app === app)
2341
+ return "app";
2342
+ return "default";
2343
+ };
2344
+ var restoreBuildDirectory = (previous) => {
2345
+ if (previous !== undefined) {
2346
+ process.env.ABSOLUTE_BUILD_DIR = previous;
2347
+ return;
2348
+ }
2349
+ delete process.env.ABSOLUTE_BUILD_DIR;
2350
+ };
2351
+ var requireRelease = (releases, releaseId) => {
2352
+ const release = releases.get(releaseId);
2353
+ if (!release) {
2354
+ throw new TypeError(`Missing retained mobile release ${releaseId}.`);
2355
+ }
2356
+ return release;
2357
+ };
2358
+ var loadServerApp = async (producerPath) => {
2359
+ const loaded = await import(`${pathToFileURL2(producerPath).href}?absolute-mobile-capture=${crypto.randomUUID()}`);
2360
+ const candidates = [loaded.server, loaded.app, loaded.default];
2361
+ const app = candidates.find(isElysiaApp);
2362
+ if (!app) {
2363
+ throw new TypeError("Mobile builds require the server entry to export its Elysia app as server, app, or default.");
2364
+ }
2365
+ const exportName = serverExportName(loaded, app);
2366
+ return { app, exportName };
2367
+ };
2368
+ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
2369
+ const buildDirectory = resolve8(options.buildDirectory);
2370
+ const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
2371
+ const root = join8(buildDirectory, ".absolutejs", "mobile-compatibility");
2372
+ const [manifestSource, previous] = await Promise.all([
2373
+ readFile9(join8(buildDirectory, "manifest.json"), "utf8"),
2374
+ readAbsoluteMobileMaterializedReleases(root)
2375
+ ]);
2376
+ const manifest = JSON.parse(manifestSource);
2377
+ if (!isStringRecord(manifest)) {
2378
+ throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
2379
+ }
2380
+ const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
2381
+ process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
2382
+ let loaded;
2383
+ try {
2384
+ loaded = await loadServerApp(resolve8(options.producerPath));
2385
+ } finally {
2386
+ restoreBuildDirectory(previousBuildDirectory);
2387
+ }
2388
+ const current = await buildAbsoluteMobileCompatibilityRelease({
2389
+ app: loaded.app,
2390
+ appId: mobile.appId,
2391
+ buildDirectory,
2392
+ manifest,
2393
+ previousArtifacts: previous.map(({ artifact }) => artifact),
2394
+ producerExport: loaded.exportName,
2395
+ producerPath: resolve8(options.producerPath),
2396
+ runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
2397
+ });
2398
+ const releasesById = new Map([current, ...previous].map((release) => [
2399
+ release.artifact.releaseId,
2400
+ release
2401
+ ]));
2402
+ const retained = retainAbsoluteMobileCompatibilityArtifacts([...releasesById.values()].map(({ artifact }) => artifact));
2403
+ await materializeAbsoluteMobileCompatibilityBundle({
2404
+ currentReleaseId: current.artifact.releaseId,
2405
+ releases: retained.map((artifact) => requireRelease(releasesById, artifact.releaseId)),
2406
+ root
2407
+ });
2408
+ await materializeAbsoluteCapacitorWebBundle({
2409
+ artifact: current.artifact,
2410
+ buildDirectory,
2411
+ config: mobile
2412
+ });
2413
+ return current.artifact;
2414
+ };
2415
+ // src/mobile/compatibilityDispatcher.ts
2416
+ import { Elysia as Elysia2 } from "elysia";
2417
+
2418
+ // src/mobile/producerContext.ts
2419
+ import { AsyncLocalStorage } from "async_hooks";
2420
+ var isProducerStorage = (value) => typeof value === "object" && value !== null && ("getStore" in value) && typeof value.getStore === "function" && ("run" in value) && typeof value.run === "function";
2421
+ var getProducerStorage = () => {
2422
+ const value = Reflect.get(globalThis, ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY);
2423
+ return isProducerStorage(value) ? value : undefined;
2424
+ };
2425
+ var ensureProducerStorage = () => {
2426
+ const existing = getProducerStorage();
2427
+ if (existing)
2428
+ return existing;
2429
+ const storage = new AsyncLocalStorage;
2430
+ Reflect.set(globalThis, ABSOLUTE_MOBILE_PRODUCER_STORAGE_KEY, storage);
2431
+ return storage;
2432
+ };
2433
+ var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
2434
+
2435
+ // src/mobile/compatibilityDispatcher.ts
2436
+ var artifactOwnsRequest = (artifact, pageId, request) => {
2437
+ const { pathname } = new URL(request.url);
2438
+ return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
2439
+ };
2440
+ var createProducerResolver = (loadProducer) => {
2441
+ const producers = new Map;
2442
+ return (artifact) => {
2443
+ const cached = producers.get(artifact.releaseId);
2444
+ if (cached)
2445
+ return cached;
2446
+ const loading = loadProducer(artifact).catch((error) => {
2447
+ producers.delete(artifact.releaseId);
2448
+ throw error;
2449
+ });
2450
+ producers.set(artifact.releaseId, loading);
2451
+ return loading;
2452
+ };
2453
+ };
2454
+ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
2455
+ const artifacts = retainAbsoluteMobileCompatibilityArtifacts(options.artifacts);
2456
+ if (!artifacts.some(({ releaseId }) => releaseId === options.currentReleaseId)) {
2457
+ throw new TypeError("currentReleaseId must identify a retained compatibility artifact.");
2458
+ }
2459
+ const resolveProducer = createProducerResolver(options.loadProducer);
2460
+ return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
2461
+ if (getCurrentAbsoluteMobileProducerContext())
2462
+ return;
2463
+ const parsed = parseAbsoluteMobilePageRequest(request);
2464
+ if (parsed.kind !== "mobile")
2465
+ return;
2466
+ const resolved = resolveAbsoluteMobileCompatibilityRelease(parsed.client, artifacts);
2467
+ if (resolved.kind === "upgrade-required") {
2468
+ return createAbsoluteMobileUpgradeResponse(resolved.result);
2469
+ }
2470
+ if (!artifactOwnsRequest(resolved.artifact, parsed.client.pageId, request)) {
2471
+ return createAbsoluteMobileInvalidRequestResponse("The requested URL is not assigned to this mobile page.");
2472
+ }
2473
+ if (resolved.artifact.releaseId === options.currentReleaseId) {
2474
+ return;
2475
+ }
2476
+ try {
2477
+ const producer = await resolveProducer(resolved.artifact);
2478
+ return runWithAbsoluteMobileProducer({
2479
+ page: resolved.page,
2480
+ releaseId: resolved.artifact.releaseId
2481
+ }, () => producer.handle(request));
2482
+ } catch (error) {
2483
+ console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
2484
+ return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
2485
+ }
2486
+ }).as("global");
2487
+ };
2488
+ // src/mobile/nativeDeepLinks.ts
2489
+ import { readFile as readFile10, rename as rename8, writeFile as writeFile8 } from "fs/promises";
2490
+ import { join as join9 } from "path";
2491
+ var START_MARKER = "<!-- absolutejs:deep-links:start -->";
2492
+ var END_MARKER = "<!-- absolutejs:deep-links:end -->";
2493
+ var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
2494
+ var NOT_FOUND = -1;
2495
+ var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2496
+ var writeChangedFile = async (path, source) => {
2497
+ const current = await readFile10(path, "utf8");
2498
+ if (current === source)
2499
+ return false;
2500
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2501
+ await writeFile8(temporary, source, { flag: "wx" });
2502
+ await rename8(temporary, path);
2503
+ return true;
2504
+ };
2505
+ var replaceManagedRegion = (source, region, insertAt) => {
2506
+ const start = source.indexOf(START_MARKER);
2507
+ const end = source.indexOf(END_MARKER);
2508
+ if (start === NOT_FOUND !== (end === NOT_FOUND) || start !== NOT_FOUND && end < start) {
2509
+ throw new TypeError("AbsoluteJS deep-link ownership markers are malformed.");
2510
+ }
2511
+ if (start !== NOT_FOUND) {
2512
+ const lineStart = source.lastIndexOf(`
2513
+ `, start) + 1;
2514
+ const nextLine = source.indexOf(`
2515
+ `, end + END_MARKER.length);
2516
+ const lineEnd = nextLine === NOT_FOUND ? source.length : nextLine + 1;
2517
+ return `${source.slice(0, lineStart)}${region}${source.slice(lineEnd)}`;
2518
+ }
2519
+ const index = insertAt();
2520
+ if (index === NOT_FOUND) {
2521
+ throw new TypeError("Could not find a safe native project location for deep-link configuration.");
2522
+ }
2523
+ return `${source.slice(0, index)}${region}${source.slice(index)}`;
2524
+ };
2525
+ var androidRegion = (config) => {
2526
+ const hosts = config.deepLinkHosts.map((host) => ` <data android:scheme="https" android:host="${escapeXml(host)}" />`).join(`
2527
+ `);
2528
+ const customScheme = config.deepLinkScheme ? `
2529
+
2530
+ <intent-filter>
2531
+ <action android:name="android.intent.action.VIEW" />
2532
+ <category android:name="android.intent.category.DEFAULT" />
2533
+ <category android:name="android.intent.category.BROWSABLE" />
2534
+ <data android:scheme="${escapeXml(config.deepLinkScheme)}" />
2535
+ </intent-filter>` : "";
2536
+ return ` ${START_MARKER}
2537
+ <intent-filter android:autoVerify="true">
2538
+ <action android:name="android.intent.action.VIEW" />
2539
+ <category android:name="android.intent.category.DEFAULT" />
2540
+ <category android:name="android.intent.category.BROWSABLE" />
2541
+ ${hosts}
2542
+ </intent-filter>${customScheme}
2543
+ ${END_MARKER}
2544
+ `;
2545
+ };
2546
+ var configureAndroid = async (config) => {
2547
+ const path = join9(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
2548
+ const source = await readFile10(path, "utf8");
2549
+ const mainActivity = source.indexOf('android:name=".MainActivity"');
2550
+ if (mainActivity === NOT_FOUND) {
2551
+ throw new TypeError("Android MainActivity was not found.");
2552
+ }
2553
+ const activityEnd = source.indexOf("</activity>", mainActivity);
2554
+ const updated = replaceManagedRegion(source, androidRegion(config), () => activityEnd === NOT_FOUND ? NOT_FOUND : source.lastIndexOf(`
2555
+ `, activityEnd) + 1);
2556
+ return writeChangedFile(path, updated);
2557
+ };
2558
+ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
2559
+ <key>CFBundleURLTypes</key>
2560
+ <array>
2561
+ <dict>
2562
+ <key>CFBundleURLName</key>
2563
+ <string>absolutejs</string>
2564
+ <key>CFBundleURLSchemes</key>
2565
+ <array>
2566
+ <string>${escapeXml(scheme)}</string>
2567
+ </array>
2568
+ </dict>
2569
+ </array>
2570
+ ${END_MARKER}
2571
+ `;
2572
+ var configureIosInfo = async (config) => {
2573
+ const path = join9(config.nativeProjectDirectory, "ios/App/App/Info.plist");
2574
+ const source = await readFile10(path, "utf8");
2575
+ const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
2576
+ ${END_MARKER}
2577
+ `;
2578
+ const updated = replaceManagedRegion(source, region, () => source.lastIndexOf("</dict>"));
2579
+ return writeChangedFile(path, updated);
2580
+ };
2581
+ var iosEntitlementsSource = (config) => {
2582
+ const domains = config.deepLinkHosts.map((host) => ` <string>applinks:${escapeXml(host)}</string>`).join(`
2583
+ `);
2584
+ return `<?xml version="1.0" encoding="UTF-8"?>
2585
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2586
+ <plist version="1.0">
2587
+ <dict>
2588
+ <key>com.apple.developer.associated-domains</key>
2589
+ <array>
2590
+ ${domains}
2591
+ </array>
2592
+ </dict>
2593
+ </plist>
2594
+ `;
2595
+ };
2596
+ var configureIosEntitlements = async (config) => {
2597
+ const path = join9(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
2598
+ let current = "";
2599
+ try {
2600
+ current = await readFile10(path, "utf8");
2601
+ } catch (error) {
2602
+ if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
2603
+ throw error;
2604
+ }
2605
+ }
2606
+ const source = iosEntitlementsSource(config);
2607
+ if (current === source)
2608
+ return false;
2609
+ const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2610
+ await writeFile8(temporary, source, { flag: "wx" });
2611
+ await rename8(temporary, path);
2612
+ return true;
2613
+ };
2614
+ var configureIosProject = async (config) => {
2615
+ const path = join9(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
2616
+ const source = await readFile10(path, "utf8");
2617
+ const declarations = [
2618
+ ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
2619
+ ].map((match) => match[1]);
2620
+ if (declarations.some((value) => value !== IOS_ENTITLEMENTS)) {
2621
+ throw new TypeError("The iOS target already uses a different entitlements file; merge associated domains there before continuing.");
2622
+ }
2623
+ if (declarations.length > 0)
2624
+ return false;
2625
+ let count = 0;
2626
+ const updated = source.replaceAll("INFOPLIST_FILE = App/Info.plist;", () => {
2627
+ count += 1;
2628
+ return `CODE_SIGN_ENTITLEMENTS = ${IOS_ENTITLEMENTS};
2629
+ INFOPLIST_FILE = App/Info.plist;`;
2630
+ });
2631
+ if (count === 0) {
2632
+ throw new TypeError("The iOS application target build settings were not found.");
2633
+ }
2634
+ return writeChangedFile(path, updated);
2635
+ };
2636
+ var configureIos = async (config) => {
2637
+ const changed = await Promise.all([
2638
+ configureIosInfo(config),
2639
+ configureIosEntitlements(config),
2640
+ configureIosProject(config)
2641
+ ]);
2642
+ return changed.some(Boolean);
2643
+ };
2644
+ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms) => {
2645
+ const results = await Promise.all(platforms.map(async (platform) => {
2646
+ const didChange = platform === "android" ? await configureAndroid(config) : await configureIos(config);
2647
+ return { didChange, platform };
2648
+ }));
2649
+ return {
2650
+ changed: results.filter(({ didChange }) => didChange).map(({ platform }) => platform)
2651
+ };
2652
+ };
2653
+ // src/mobile/routeMetadataTransform.ts
2654
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2655
+ import { dirname as dirname5, extname as extname2, relative as relative5, resolve as resolve9 } from "path";
2656
+ import ts from "typescript";
2657
+ var ROUTE_METHODS = new Set(["get", "head"]);
2658
+ var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
2659
+ var PAGE_HANDLER = "handleReactPageRequest";
2660
+ var posixPath = (value) => value.replace(/\\/g, "/");
2661
+ var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname5(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
2662
+ var createProgram = (entry, projectRoot) => {
2663
+ const configPath = findTsconfig(entry, projectRoot);
2664
+ if (!configPath) {
2665
+ return ts.createProgram([entry], {
2666
+ allowJs: true,
2667
+ jsx: ts.JsxEmit.ReactJSX,
2668
+ module: ts.ModuleKind.ESNext,
2669
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
2670
+ target: ts.ScriptTarget.ESNext
2671
+ });
2672
+ }
2673
+ const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname5(configPath));
2674
+ if (!parsed.fileNames.includes(entry))
2675
+ parsed.fileNames.push(entry);
2676
+ return ts.createProgram(parsed.fileNames, parsed.options);
2677
+ };
2678
+ var propertyName = (property) => {
2679
+ if (!("name" in property) || !property.name)
2680
+ return;
2681
+ if (ts.isIdentifier(property.name))
2682
+ return property.name.text;
2683
+ if (ts.isStringLiteralLike(property.name))
2684
+ return property.name.text;
2685
+ return;
2686
+ };
2687
+ var objectPropertyExpression = (object, name) => {
2688
+ const property = object.properties.find((candidate) => propertyName(candidate) === name);
2689
+ if (property && ts.isPropertyAssignment(property)) {
2690
+ return property.initializer;
2691
+ }
2692
+ if (property && ts.isShorthandPropertyAssignment(property)) {
2693
+ return property.name;
2694
+ }
2695
+ return;
2696
+ };
2697
+ var serializeType = (type, checker, ancestors = new Set) => {
2698
+ if (type.flags & ts.TypeFlags.Any)
2699
+ return { type: "any" };
2700
+ if (type.flags & ts.TypeFlags.Unknown)
2701
+ return { type: "unknown" };
2702
+ if (type.flags & ts.TypeFlags.Never)
2703
+ return { type: "never" };
2704
+ if (type.flags & ts.TypeFlags.StringLike)
2705
+ return { type: "string" };
2706
+ if (type.flags & ts.TypeFlags.NumberLike)
2707
+ return { type: "number" };
2708
+ if (type.flags & ts.TypeFlags.BooleanLike)
2709
+ return { type: "boolean" };
2710
+ if (type.flags & ts.TypeFlags.BigIntLike)
2711
+ return { type: "bigint" };
2712
+ if (type.flags & ts.TypeFlags.Null)
2713
+ return { type: "null" };
2714
+ if (type.flags & ts.TypeFlags.Undefined)
2715
+ return { type: "undefined" };
2716
+ if (type.isUnion()) {
2717
+ return {
2718
+ anyOf: type.types.map((member) => serializeType(member, checker, ancestors)).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)))
2719
+ };
2720
+ }
2721
+ if (type.isIntersection()) {
2722
+ return {
2723
+ allOf: type.types.map((member) => serializeType(member, checker, ancestors)).sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)))
2724
+ };
2725
+ }
2726
+ if (ancestors.has(type)) {
2727
+ return {
2728
+ ref: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
2729
+ };
2730
+ }
2731
+ ancestors.add(type);
2732
+ const arrayElement = checker.getIndexTypeOfType(type, ts.IndexKind.Number);
2733
+ const properties = checker.getPropertiesOfType(type);
2734
+ let schema;
2735
+ if (arrayElement && properties.some(({ name }) => name === "length")) {
2736
+ schema = {
2737
+ items: serializeType(arrayElement, checker, ancestors),
2738
+ type: "array"
2739
+ };
2740
+ } else if (properties.length > 0) {
2741
+ const entries = properties.filter(({ name }) => !name.startsWith("__")).map((property) => {
2742
+ const declaration = property.valueDeclaration ?? property.declarations?.[0];
2743
+ const propertyType = declaration ? checker.getTypeOfSymbolAtLocation(property, declaration) : checker.getDeclaredTypeOfSymbol(property);
2744
+ return [
2745
+ property.name,
2746
+ {
2747
+ optional: Boolean(property.flags & ts.SymbolFlags.Optional),
2748
+ schema: serializeType(propertyType, checker, ancestors)
2749
+ }
2750
+ ];
2751
+ }).sort(([left], [right]) => left.localeCompare(right));
2752
+ schema = { properties: Object.fromEntries(entries), type: "object" };
2753
+ } else {
2754
+ schema = {
2755
+ type: checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
2756
+ };
2757
+ }
2758
+ ancestors.delete(type);
2759
+ return schema;
2760
+ };
2761
+ var pagePropsType = (pageExpression, propsExpression, checker) => {
2762
+ const pageType = checker.getTypeAtLocation(pageExpression);
2763
+ const [signature] = pageType.getCallSignatures();
2764
+ const [parameter] = signature?.parameters ?? [];
2765
+ const declaration = parameter?.valueDeclaration ?? parameter?.declarations?.[0];
2766
+ if (parameter && declaration) {
2767
+ return checker.getTypeOfSymbolAtLocation(parameter, declaration);
2768
+ }
2769
+ return propsExpression ? checker.getTypeAtLocation(propsExpression) : checker.getTypeAtLocation(pageExpression);
2770
+ };
2771
+ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
2772
+ let symbol = checker.getSymbolAtLocation(expression);
2773
+ if (symbol?.flags && symbol.flags & ts.SymbolFlags.Alias) {
2774
+ symbol = checker.getAliasedSymbol(symbol);
2775
+ }
2776
+ const declaration = symbol?.declarations?.[0];
2777
+ const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
2778
+ const exportedName = symbol?.name ?? expression.getText(sourceFile);
2779
+ const source = posixPath(relative5(projectRoot, file));
2780
+ return `${source}#${exportedName}`;
2781
+ };
2782
+ var resolveAlias = (symbol, checker) => {
2783
+ if (!(symbol.flags & ts.SymbolFlags.Alias))
2784
+ return symbol;
2785
+ return checker.getAliasedSymbol(symbol);
2786
+ };
2787
+ var assetKey = (expression, checker, seen = new Set) => {
2788
+ if (!expression)
2789
+ return;
2790
+ if (ts.isIdentifier(expression)) {
2791
+ const unresolved = ts.isShorthandPropertyAssignment(expression.parent) ? checker.getShorthandAssignmentValueSymbol(expression.parent) : checker.getSymbolAtLocation(expression);
2792
+ if (!unresolved)
2793
+ return;
2794
+ const symbol = resolveAlias(unresolved, checker);
2795
+ if (seen.has(symbol))
2796
+ return;
2797
+ seen.add(symbol);
2798
+ const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
2799
+ if (!declaration || !ts.isVariableDeclaration(declaration))
2800
+ return;
2801
+ return assetKey(declaration.initializer, checker, seen);
2802
+ }
2803
+ if (!ts.isCallExpression(expression))
2804
+ return;
2805
+ if (!ts.isIdentifier(expression.expression) || expression.expression.text !== "asset") {
2806
+ return;
2807
+ }
2808
+ const [, key] = expression.arguments;
2809
+ return key && ts.isStringLiteralLike(key) ? key.text : undefined;
2810
+ };
2811
+ var findPageCall = (nodes) => {
2812
+ let found;
2813
+ const visit = (candidate) => {
2814
+ if (found)
2815
+ return;
2816
+ if (ts.isCallExpression(candidate) && ts.isIdentifier(candidate.expression) && candidate.expression.text === PAGE_HANDLER) {
2817
+ found = candidate;
2818
+ return;
2819
+ }
2820
+ ts.forEachChild(candidate, visit);
2821
+ };
2822
+ for (const node of nodes)
2823
+ visit(node);
2824
+ return found;
2825
+ };
2826
+ var isProjectSource = (sourceFile, resolvedFile, projectRoot) => !sourceFile.isDeclarationFile && !resolvedFile.includes("/node_modules/") && resolvedFile.startsWith(`${projectRoot}/`);
2827
+ var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
2828
+ const callee = node.expression;
2829
+ if (!ts.isPropertyAccessExpression(callee))
2830
+ return;
2831
+ if (!ROUTE_METHODS.has(callee.name.text))
2832
+ return;
2833
+ const [routePath] = node.arguments;
2834
+ if (!routePath || !ts.isStringLiteralLike(routePath))
2835
+ return;
2836
+ const pageCall = findPageCall(node.arguments.slice(1));
2837
+ const [input] = pageCall?.arguments ?? [];
2838
+ if (!pageCall || !input || !ts.isObjectLiteralExpression(input)) {
2839
+ return;
2840
+ }
2841
+ const page = objectPropertyExpression(input, "Page");
2842
+ if (!page)
2843
+ return;
2844
+ const props = objectPropertyExpression(input, "props");
2845
+ const index = objectPropertyExpression(input, "index");
2846
+ const bundleKey = assetKey(index, checker);
2847
+ if (!bundleKey)
2848
+ return;
2849
+ const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot);
2850
+ const schema = serializeType(pagePropsType(page, props, checker), checker);
2851
+ const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
2852
+ const metadata = {
2853
+ bundleKey,
2854
+ contract: `react:${pageId}:${propsSchemaHash}`,
2855
+ framework: "react",
2856
+ pageId,
2857
+ propsSchemaHash
2858
+ };
2859
+ const result = {
2860
+ metadata,
2861
+ pageCallStart: pageCall.getStart(sourceFile),
2862
+ routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
2863
+ };
2864
+ return result;
2865
+ };
2866
+ var analyzeSourceFile = (sourceFile, checker, projectRoot) => {
2867
+ const analysis = {
2868
+ byPageCall: new Map,
2869
+ byRouteCall: new Map
2870
+ };
2871
+ const visit = (node) => {
2872
+ const result = ts.isCallExpression(node) ? analyzeRouteCall(node, sourceFile, checker, projectRoot) : undefined;
2873
+ if (result) {
2874
+ analysis.byPageCall.set(result.pageCallStart, result);
2875
+ analysis.byRouteCall.set(result.routeCallSpan, result);
2876
+ }
2877
+ ts.forEachChild(node, visit);
2878
+ };
2879
+ ts.forEachChild(sourceFile, visit);
2880
+ return analysis;
2881
+ };
2882
+ var analyzeProgram = (program, projectRoot) => {
2883
+ const checker = program.getTypeChecker();
2884
+ const analyzed = new Map;
2885
+ for (const sourceFile of program.getSourceFiles()) {
2886
+ const resolvedFile = resolve9(sourceFile.fileName);
2887
+ if (!isProjectSource(sourceFile, resolvedFile, projectRoot))
2888
+ continue;
2889
+ const analysis = analyzeSourceFile(sourceFile, checker, projectRoot);
2890
+ if (analysis.byPageCall.size > 0)
2891
+ analyzed.set(resolvedFile, analysis);
2892
+ }
2893
+ return analyzed;
2894
+ };
2895
+ var metadataExpression = (metadata) => ts.factory.createObjectLiteralExpression(Object.entries(metadata).map(([key, item]) => ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(key), ts.factory.createStringLiteral(item))), false);
2896
+ var routeOptions = (existing, metadata) => {
2897
+ const detail = ts.factory.createObjectLiteralExpression([
2898
+ ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
2899
+ ]);
2900
+ if (!existing) {
2901
+ return ts.factory.createObjectLiteralExpression([
2902
+ ts.factory.createPropertyAssignment("detail", detail)
2903
+ ]);
2904
+ }
2905
+ return ts.factory.createObjectLiteralExpression([
2906
+ ts.factory.createSpreadAssignment(existing),
2907
+ ts.factory.createPropertyAssignment("detail", ts.factory.createObjectLiteralExpression([
2908
+ ts.factory.createSpreadAssignment(ts.factory.createPropertyAccessExpression(existing, "detail")),
2909
+ ts.factory.createPropertyAssignment(ts.factory.createStringLiteral(ABSOLUTE_MOBILE_ROUTE_DETAIL), metadataExpression(metadata))
2910
+ ]))
2911
+ ]);
2912
+ };
2913
+ var transformPageCall = (node, page) => {
2914
+ if (!page)
2915
+ return;
2916
+ const [input] = node.arguments;
2917
+ if (!input || !ts.isObjectLiteralExpression(input))
2918
+ return;
2919
+ return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [
2920
+ ts.factory.updateObjectLiteralExpression(input, [
2921
+ ...input.properties,
2922
+ ts.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
2923
+ ]),
2924
+ ...node.arguments.slice(1)
2925
+ ]);
2926
+ };
2927
+ var transformRouteCall = (node, route) => {
2928
+ if (!route)
2929
+ return;
2930
+ const [path, maybeOptions, maybeHandler, ...rest] = node.arguments;
2931
+ if (!path || !maybeOptions)
2932
+ return;
2933
+ const options = maybeHandler ? maybeOptions : undefined;
2934
+ const handler = maybeHandler ?? maybeOptions;
2935
+ return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [path, routeOptions(options, route.metadata), handler, ...rest]);
2936
+ };
2937
+ var transformFile = (source, fileName, analysis) => {
2938
+ const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, fileName.endsWith("x") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
2939
+ const transformer = (context) => {
2940
+ const visit = (node) => {
2941
+ if (!ts.isCallExpression(node)) {
2942
+ return ts.visitEachChild(node, visit, context);
2943
+ }
2944
+ const transformedChildren = ts.visitEachChild(node, visit, context);
2945
+ const page = analysis.byPageCall.get(node.getStart(sourceFile));
2946
+ const transformedPage = transformPageCall(transformedChildren, page);
2947
+ if (transformedPage)
2948
+ return transformedPage;
2949
+ const route = analysis.byRouteCall.get(`${node.getStart(sourceFile)}:${node.end}`);
2950
+ const transformedRoute = transformRouteCall(transformedChildren, route);
2951
+ if (transformedRoute)
2952
+ return transformedRoute;
2953
+ return transformedChildren;
2954
+ };
2955
+ return (node) => ts.visitNode(node, visit, ts.isSourceFile) ?? node;
2956
+ };
2957
+ const result = ts.transform(sourceFile, [transformer]);
2958
+ try {
2959
+ const [transformed] = result.transformed;
2960
+ if (!transformed)
2961
+ throw new TypeError("Mobile route transform failed.");
2962
+ return ts.createPrinter().printFile(transformed);
2963
+ } finally {
2964
+ result.dispose();
2965
+ }
2966
+ };
2967
+ var ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL = ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION;
2968
+ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
2969
+ const projectRoot = resolve9(options.projectRoot ?? process.cwd());
2970
+ const entry = resolve9(options.entry);
2971
+ const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
2972
+ return {
2973
+ name: "absolute-mobile-route-metadata",
2974
+ setup(build) {
2975
+ build.onLoad({ filter: SOURCE_FILTER }, async ({ path }) => {
2976
+ const analysis = analyzed.get(resolve9(path));
2977
+ if (!analysis)
2978
+ return;
2979
+ const source = await Bun.file(path).text();
2980
+ return {
2981
+ contents: transformFile(source, path, analysis),
2982
+ loader: extname2(path).endsWith("x") ? "tsx" : "ts"
2983
+ };
2984
+ });
2985
+ }
2986
+ };
2987
+ };
2988
+ var inspectAbsoluteMobileRouteMetadata = (options) => {
2989
+ const projectRoot = resolve9(options.projectRoot ?? process.cwd());
2990
+ const entry = resolve9(options.entry);
2991
+ const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
2992
+ return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
2993
+ file: posixPath(relative5(projectRoot, file)),
2994
+ metadata
2995
+ })));
2996
+ };
1107
2997
  export {
2998
+ writeAbsoluteCapacitorConfig,
1108
2999
  verifyAbsoluteMobileCompatibilityProducer,
3000
+ verifyAbsoluteMobileAssociationFiles,
1109
3001
  runWithAbsoluteMobileProducer,
1110
3002
  retainAbsoluteMobileCompatibilityArtifacts,
3003
+ resolveAbsoluteMobileRoute,
3004
+ resolveAbsoluteMobileDeepLink,
1111
3005
  resolveAbsoluteMobileCompatibilityRelease,
3006
+ readAbsoluteMobileMaterializedReleases,
1112
3007
  parseAbsoluteMobilePageRequest,
1113
3008
  parseAbsoluteMobilePageEnvelope,
1114
3009
  parseAbsoluteMobileCompatibilityArtifact,
3010
+ parseAbsoluteMobileBuildPageMetadata,
3011
+ normalizeAbsoluteMobileConfig,
3012
+ navigateAbsoluteMobilePage,
1115
3013
  materializeAbsoluteMobileCompatibilityBundle,
3014
+ materializeAbsoluteMobileAssociationFiles,
3015
+ materializeAbsoluteCapacitorWebBundle,
3016
+ matchesAbsoluteMobileRoutePattern,
1116
3017
  loadAbsoluteMobileMaterializedBundle,
3018
+ inspectAbsoluteMobileRouteMetadata,
1117
3019
  hashAbsoluteMobilePropsSchema,
1118
3020
  getCurrentAbsoluteMobileProducerContext,
1119
3021
  finalizeAbsoluteMobilePage,
3022
+ finalizeAbsoluteMobileCompatibilityBuild,
3023
+ fetchAbsoluteMobilePage,
1120
3024
  createAbsoluteMobileUpgradeResponse,
3025
+ createAbsoluteMobileRouteMetadataPlugin,
3026
+ createAbsoluteMobilePageRequest,
1121
3027
  createAbsoluteMobilePageErrorResponse,
1122
3028
  createAbsoluteMobileInvalidRequestResponse,
1123
3029
  createAbsoluteMobileFileArtifactStore,
1124
3030
  createAbsoluteMobileCompatibilityDispatcher,
1125
3031
  createAbsoluteMobileCompatibilityArtifact,
1126
3032
  createAbsoluteMobileBlobArtifactStore,
3033
+ createAbsoluteMobileAssociationPlugin,
3034
+ createAbsoluteMobileAssociationDocuments,
1127
3035
  carryForwardAbsoluteMobileCompatibilityReleases,
3036
+ captureAbsoluteMobileRouteGraph,
3037
+ buildAbsoluteMobileCompatibilityRelease,
3038
+ buildAbsoluteAndroidRelease,
3039
+ applyAbsoluteNativeDeepLinks,
1128
3040
  activateAbsoluteMobilePage,
1129
3041
  acceptsAbsoluteMobilePage,
1130
3042
  MOBILE_PAGE_REQUEST_HEADERS,
1131
3043
  AbsoluteMobilePageProtocolError,
3044
+ APPLE_ASSOCIATION_PATH,
3045
+ ANDROID_ASSOCIATION_PATH,
3046
+ ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
3047
+ ABSOLUTE_MOBILE_ROUTE_DETAIL,
1132
3048
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
1133
3049
  ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION,
1134
3050
  ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE,
1135
3051
  ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
1136
- ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT
3052
+ ABSOLUTE_MOBILE_COMPATIBILITY_FORMAT,
3053
+ ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
3054
+ ABSOLUTE_ANDROID_RELEASE_FORMAT
1137
3055
  };
1138
3056
 
1139
- //# debugId=AF97492DC1F47D7364756E2164756E21
3057
+ //# debugId=EC6A042BA6D550B864756E2164756E21
1140
3058
  //# sourceMappingURL=index.js.map