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

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 (50) 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 +341 -22
  4. package/dist/angular/index.js.map +8 -5
  5. package/dist/angular/server.js +341 -22
  6. package/dist/angular/server.js.map +8 -5
  7. package/dist/build.js +94 -22
  8. package/dist/build.js.map +10 -10
  9. package/dist/cli/index.js +1750 -655
  10. package/dist/dev/client/cssUtils.ts +16 -2
  11. package/dist/dev/client/handlers/rebuild.ts +11 -1
  12. package/dist/dev/client/hmrTiming.ts +14 -7
  13. package/dist/index.js +364 -191
  14. package/dist/index.js.map +18 -18
  15. package/dist/mobile/browser.js +14 -1
  16. package/dist/mobile/browser.js.map +3 -3
  17. package/dist/mobile/index.js +1233 -160
  18. package/dist/mobile/index.js.map +17 -13
  19. package/dist/mobile/remoteMacAgentEntry.js +29 -0
  20. package/dist/src/angular/pageHandler.d.ts +3 -0
  21. package/dist/src/cli/config/server.d.ts +1 -1
  22. package/dist/src/core/pageHandlers.d.ts +11 -2
  23. package/dist/src/mobile/androidEmulatorController.d.ts +6 -1
  24. package/dist/src/mobile/buildPipeline.d.ts +1 -0
  25. package/dist/src/mobile/capacitorBundle.d.ts +11 -1
  26. package/dist/src/mobile/client.d.ts +4 -0
  27. package/dist/src/mobile/index.d.ts +2 -0
  28. package/dist/src/mobile/nativeAuth.d.ts +17 -0
  29. package/dist/src/mobile/releaseArtifact.d.ts +2 -0
  30. package/dist/src/mobile/remoteMacAgent.d.ts +2 -0
  31. package/dist/src/mobile/remoteMacAgentEntry.d.ts +1 -0
  32. package/dist/src/mobile/remoteMacProtocol.d.ts +114 -0
  33. package/dist/src/mobile/remoteMacWire.d.ts +2 -0
  34. package/dist/src/mobile/shellAuth.d.ts +8 -0
  35. package/dist/src/mobile/shellBootstrap.d.ts +11 -1
  36. package/dist/src/mobile/shellSync.d.ts +2 -0
  37. package/dist/src/mobile/staticDocument.d.ts +5 -0
  38. package/dist/src/mobile/transport.d.ts +9 -1
  39. package/dist/src/plugins/imageOptimizer.d.ts +1 -1
  40. package/dist/src/svelte/pageHandler.d.ts +3 -0
  41. package/dist/src/vue/pageHandler.d.ts +3 -0
  42. package/dist/svelte/index.js +312 -23
  43. package/dist/svelte/index.js.map +7 -4
  44. package/dist/svelte/server.js +307 -18
  45. package/dist/svelte/server.js.map +7 -4
  46. package/dist/vue/index.js +312 -23
  47. package/dist/vue/index.js.map +7 -4
  48. package/dist/vue/server.js +307 -18
  49. package/dist/vue/server.js.map +7 -4
  50. package/package.json +20 -8
@@ -159,6 +159,14 @@ var init_startupBanner = __esm(() => {
159
159
  ];
160
160
  });
161
161
 
162
+ // src/utils/stringModifiers.ts
163
+ var normalizeSlug = (str) => str.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-9\-_]+/g, "").replace(/[-_]{2,}/g, "-"), toKebab = (str) => normalizeSlug(str).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase(), toPascal = (str) => {
164
+ if (!str.includes("-") && !str.includes("_")) {
165
+ return str.charAt(0).toUpperCase() + str.slice(1);
166
+ }
167
+ return normalizeSlug(str).split(/[-_]/).filter(Boolean).map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1).toLowerCase()).join("");
168
+ };
169
+
162
170
  // src/mobile/artifactStore.ts
163
171
  import { createHash as createHash2 } from "crypto";
164
172
  import {
@@ -249,13 +257,19 @@ var parseCompatibilityPage = (value) => {
249
257
  if (!isCanonicalRecord(value) || !isPageFramework(value.framework)) {
250
258
  throw new TypeError("Compatibility artifact contains an invalid page.");
251
259
  }
260
+ const styleBundleHash = typeof value.styleBundleHash === "string" ? value.styleBundleHash : undefined;
261
+ const styleBundlePath = typeof value.styleBundlePath === "string" ? value.styleBundlePath : undefined;
262
+ if (Boolean(styleBundleHash) !== Boolean(styleBundlePath)) {
263
+ throw new TypeError("Compatibility page style hash and path must be provided together.");
264
+ }
252
265
  return {
253
266
  bundleHash: readString(value.bundleHash, "page.bundleHash"),
254
267
  bundlePath: readString(value.bundlePath, "page.bundlePath"),
255
268
  contract: readString(value.contract, "page.contract"),
256
269
  framework: value.framework,
257
270
  pageId: readString(value.pageId, "page.pageId"),
258
- propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash")
271
+ propsSchemaHash: readString(value.propsSchemaHash, "page.propsSchemaHash"),
272
+ ...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
259
273
  };
260
274
  };
261
275
  var parseCompatibilityRoute = (value) => {
@@ -287,14 +301,23 @@ var validateProducerModule = (module) => {
287
301
  }
288
302
  return module;
289
303
  };
290
- var normalizePage = (page) => ({
291
- bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
292
- bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
293
- contract: requireNonEmpty(page.contract, "page.contract"),
294
- framework: page.framework,
295
- pageId: requireNonEmpty(page.pageId, "page.pageId"),
296
- propsSchemaHash: requireNonEmpty(page.propsSchemaHash, "page.propsSchemaHash")
297
- });
304
+ var normalizePage = (page) => {
305
+ if (Boolean(page.styleBundleHash) !== Boolean(page.styleBundlePath)) {
306
+ throw new TypeError("Compatibility page style hash and path must be provided together.");
307
+ }
308
+ return {
309
+ bundleHash: requireNonEmpty(page.bundleHash, "page.bundleHash"),
310
+ bundlePath: requireNonEmpty(page.bundlePath, "page.bundlePath"),
311
+ contract: requireNonEmpty(page.contract, "page.contract"),
312
+ framework: page.framework,
313
+ pageId: requireNonEmpty(page.pageId, "page.pageId"),
314
+ propsSchemaHash: requireNonEmpty(page.propsSchemaHash, "page.propsSchemaHash"),
315
+ ...page.styleBundleHash && page.styleBundlePath ? {
316
+ styleBundleHash: requireNonEmpty(page.styleBundleHash, "page.styleBundleHash"),
317
+ styleBundlePath: requireNonEmpty(page.styleBundlePath, "page.styleBundlePath")
318
+ } : {}
319
+ };
320
+ };
298
321
  var normalizeRoute = (route) => {
299
322
  if (!route.pattern.startsWith("/")) {
300
323
  throw new TypeError("route.pattern must start with /.");
@@ -1059,11 +1082,11 @@ var hashNativeTree = async (root, label, ignorePublicBundle) => {
1059
1082
  const records = await collectNativeDirectory(resolvedRoot, label, resolvedRoot, ignorePublicBundle);
1060
1083
  return createHash3("sha256").update(records.join("")).digest("hex");
1061
1084
  };
1062
- var fingerprintAbsoluteAndroidNativeProject = async (project) => {
1085
+ var fingerprintAbsoluteAndroidNativeProject = async (project, options = {}) => {
1063
1086
  const { dependencies } = await nativeDependencySources(project.nativeDirectory);
1064
1087
  const roots = [
1065
1088
  {
1066
- ignorePublicBundle: true,
1089
+ ignorePublicBundle: options.includePublicBundle !== true,
1067
1090
  label: "android",
1068
1091
  source: project.nativeDirectory
1069
1092
  },
@@ -2533,14 +2556,582 @@ var createAbsoluteIosNativeWatcher = async (options) => {
2533
2556
  return { close };
2534
2557
  };
2535
2558
  var isAbsoluteIosNativeRootInput = (path) => ROOT_NATIVE_INPUTS.has(basename(path));
2559
+ // src/mobile/remoteMacProtocol.ts
2560
+ import { createHash as createHash7, randomUUID as randomUUID3 } from "crypto";
2561
+ import { chmod, mkdir as mkdir6, readFile as readFile8, rename as rename7, writeFile as writeFile7 } from "fs/promises";
2562
+ import { homedir as homedir2 } from "os";
2563
+ import {
2564
+ dirname as dirname5,
2565
+ isAbsolute as isAbsolute5,
2566
+ join as join7,
2567
+ posix,
2568
+ relative as relative6,
2569
+ resolve as resolvePath2,
2570
+ sep as sep5
2571
+ } from "path";
2572
+
2573
+ // src/mobile/remoteMacWire.ts
2574
+ var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t";
2575
+ var ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 1;
2576
+
2577
+ // src/mobile/remoteMacProtocol.ts
2578
+ var PROFILE_FORMAT = 1;
2579
+ var PROFILE_NAME = /^[a-z0-9](?:[a-z0-9._-]{0,62}[a-z0-9])?$/u;
2580
+ var SSH_DESTINATION = /^(?:[A-Za-z0-9._-]+@)?[A-Za-z0-9._:-]+$/u;
2581
+ var defaultProfilePath = () => join7(homedir2(), ".absolutejs", "mobile", "remote-macs.json");
2582
+ var emptyStore = () => ({
2583
+ format: PROFILE_FORMAT,
2584
+ profiles: {}
2585
+ });
2586
+ var loadStore = async (path = defaultProfilePath()) => {
2587
+ try {
2588
+ const parsed = JSON.parse(await readFile8(path, "utf8"));
2589
+ if (parsed.format !== PROFILE_FORMAT || typeof parsed.profiles !== "object" || parsed.profiles === null || Array.isArray(parsed.profiles))
2590
+ throw new Error("Unsupported remote Mac profile format.");
2591
+ for (const [key, profile] of Object.entries(parsed.profiles)) {
2592
+ if (typeof profile !== "object" || profile === null || validateAbsoluteRemoteMacProfileName(key) !== key || profile.name !== key || validateAbsoluteSshDestination(profile.destination) !== profile.destination || validatePort(profile.port) !== profile.port || typeof profile.createdAt !== "string" || !profile.createdAt || typeof profile.bunPath !== "string" || !profile.bunPath.startsWith("/") || /[\r\n\0]/u.test(profile.bunPath) || typeof profile.workspaceRoot !== "string" || !profile.workspaceRoot.startsWith("/") || profile.workspaceRoot === "/" || /[\r\n\0]/u.test(profile.workspaceRoot) || typeof profile.xcodeVersion !== "string" || !profile.xcodeVersion.startsWith("Xcode "))
2593
+ throw new Error(`Remote Mac profile ${JSON.stringify(key)} is invalid.`);
2594
+ }
2595
+ if (parsed.defaultProfile !== undefined && !parsed.profiles[parsed.defaultProfile])
2596
+ throw new Error("The default remote Mac profile does not exist.");
2597
+ return parsed;
2598
+ } catch (error) {
2599
+ if (error.code === "ENOENT")
2600
+ return emptyStore();
2601
+ throw error;
2602
+ }
2603
+ };
2604
+ var saveStore = async (store, path = defaultProfilePath()) => {
2605
+ await mkdir6(dirname5(path), { recursive: true });
2606
+ const temporary = `${path}.${randomUUID3()}.tmp`;
2607
+ await writeFile7(temporary, `${JSON.stringify(store, null, 2)}
2608
+ `, {
2609
+ mode: 384
2610
+ });
2611
+ await rename7(temporary, path);
2612
+ await chmod(path, 384);
2613
+ };
2614
+ var validateAbsoluteRemoteMacProfileName = (name) => {
2615
+ const normalized = name.trim().toLowerCase();
2616
+ if (!PROFILE_NAME.test(normalized))
2617
+ throw new TypeError("Remote Mac profile names must use 1-64 lowercase letters, digits, dots, dashes, or underscores.");
2618
+ return normalized;
2619
+ };
2620
+ var validateAbsoluteSshDestination = (destination) => {
2621
+ const normalized = destination.trim();
2622
+ if (!SSH_DESTINATION.test(normalized) || normalized.startsWith("-"))
2623
+ throw new TypeError("Remote Mac SSH destination must be a host, SSH alias, or user@host without command-line options.");
2624
+ return normalized;
2625
+ };
2626
+ var validatePort = (port) => {
2627
+ if (port !== undefined && (!Number.isInteger(port) || port < 1 || port > 65535))
2628
+ throw new TypeError("Remote Mac SSH port must be between 1 and 65535.");
2629
+ return port;
2630
+ };
2631
+ var shellQuote = (value) => `'${value.replaceAll("'", "'\\''")}'`;
2632
+ var absoluteRemoteMacSshBase = (profile, options = {}) => [
2633
+ "ssh",
2634
+ "-o",
2635
+ "BatchMode=yes",
2636
+ "-o",
2637
+ "ConnectTimeout=10",
2638
+ "-o",
2639
+ "ServerAliveInterval=15",
2640
+ "-o",
2641
+ "ServerAliveCountMax=3",
2642
+ "-o",
2643
+ `StrictHostKeyChecking=${options.acceptNew ? "accept-new" : "yes"}`,
2644
+ ...profile.port ? ["-p", String(profile.port)] : [],
2645
+ profile.destination
2646
+ ];
2647
+ var localCapture = async (command) => {
2648
+ const process2 = Bun.spawn(command, {
2649
+ stderr: "pipe",
2650
+ stdin: "ignore",
2651
+ stdout: "pipe"
2652
+ });
2653
+ const [exitCode, stdout, stderr] = await Promise.all([
2654
+ process2.exited,
2655
+ new Response(process2.stdout).text(),
2656
+ new Response(process2.stderr).text()
2657
+ ]);
2658
+ return { exitCode, stderr, stdout };
2659
+ };
2660
+ var defaultTransport = {
2661
+ capture: localCapture,
2662
+ spawn: (command, options) => Bun.spawn(command, {
2663
+ signal: options.signal,
2664
+ stderr: "pipe",
2665
+ stdin: "pipe",
2666
+ stdout: "pipe"
2667
+ })
2668
+ };
2669
+ var requireRemoteSuccess = (result, label) => {
2670
+ if (result.exitCode !== 0)
2671
+ throw new Error(`${label} failed: ${(result.stderr || result.stdout).trim() || `status ${result.exitCode}`}`);
2672
+ return result.stdout.trim();
2673
+ };
2674
+ var getAbsoluteRemoteMacProfile = async (name, profilePath) => {
2675
+ const store = await loadStore(profilePath);
2676
+ const selected = name ?? process.env.ABSOLUTE_IOS_REMOTE ?? store.defaultProfile;
2677
+ if (!selected)
2678
+ return;
2679
+ const profile = store.profiles[selected];
2680
+ if (!profile)
2681
+ throw new Error(`Remote Mac profile ${JSON.stringify(selected)} was not found.`);
2682
+ return profile;
2683
+ };
2684
+ var inspectAbsoluteRemoteMac = async (destination, options = {}) => {
2685
+ const profile = {
2686
+ destination: validateAbsoluteSshDestination(destination),
2687
+ port: validatePort(options.port)
2688
+ };
2689
+ const capture = options.transport?.capture ?? defaultTransport.capture;
2690
+ const command = [
2691
+ ...absoluteRemoteMacSshBase(profile, {
2692
+ acceptNew: options.acceptNew === true
2693
+ }),
2694
+ "/bin/sh -lc",
2695
+ shellQuote(`bun_path="$(command -v bun || true)"; if [ -z "$bun_path" ] && [ -x "$HOME/.bun/bin/bun" ]; then bun_path="$HOME/.bun/bin/bun"; fi; printf '%s\\n' "$(uname -s)" "$HOME" "$bun_path" "$(/usr/bin/xcodebuild -version 2>/dev/null | tr '\\n' ' ' || true)"`)
2696
+ ];
2697
+ const lines = requireRemoteSuccess(await capture(command), "Remote Mac handshake").split(/\r?\n/u);
2698
+ const [operatingSystem, home, bunPath, xcodeVersion] = lines;
2699
+ if (operatingSystem !== "Darwin")
2700
+ throw new Error("The SSH target is not a Mac.");
2701
+ if (!home?.startsWith("/") || !bunPath?.startsWith("/"))
2702
+ throw new Error("The remote Mac must have Bun installed and available to SSH.");
2703
+ if (!xcodeVersion?.startsWith("Xcode "))
2704
+ throw new Error("The remote Mac must have full Xcode installed and selected.");
2705
+ return { bunPath, home, os: operatingSystem, xcodeVersion };
2706
+ };
2707
+ var listAbsoluteRemoteMacProfiles = async (profilePath) => {
2708
+ const store = await loadStore(profilePath);
2709
+ return {
2710
+ defaultProfile: store.defaultProfile,
2711
+ profiles: Object.values(store.profiles).sort((left, right) => left.name.localeCompare(right.name))
2712
+ };
2713
+ };
2714
+ var pairAbsoluteRemoteMac = async (options) => {
2715
+ const name = validateAbsoluteRemoteMacProfileName(options.name);
2716
+ const destination = validateAbsoluteSshDestination(options.destination);
2717
+ const port = validatePort(options.port);
2718
+ const inspection = await inspectAbsoluteRemoteMac(destination, {
2719
+ acceptNew: true,
2720
+ port,
2721
+ transport: options.transport
2722
+ });
2723
+ const workspaceRoot = options.workspaceRoot ? options.workspaceRoot.trim() : posix.join(inspection.home, ".absolutejs", "remote-ios");
2724
+ if (!workspaceRoot.startsWith("/") || workspaceRoot === "/" || /[\r\n\0]/u.test(workspaceRoot))
2725
+ throw new TypeError("Remote Mac workspace must be an absolute macOS path.");
2726
+ const profile = {
2727
+ bunPath: inspection.bunPath,
2728
+ createdAt: new Date().toISOString(),
2729
+ destination,
2730
+ name,
2731
+ ...port ? { port } : {},
2732
+ workspaceRoot,
2733
+ xcodeVersion: inspection.xcodeVersion
2734
+ };
2735
+ const store = await loadStore(options.profilePath);
2736
+ store.profiles[name] = profile;
2737
+ store.defaultProfile = name;
2738
+ await saveStore(store, options.profilePath);
2739
+ return profile;
2740
+ };
2741
+ var removeAbsoluteRemoteMacProfile = async (name, profilePath) => {
2742
+ const normalized = validateAbsoluteRemoteMacProfileName(name);
2743
+ const store = await loadStore(profilePath);
2744
+ if (!store.profiles[normalized])
2745
+ return false;
2746
+ delete store.profiles[normalized];
2747
+ if (store.defaultProfile === normalized) {
2748
+ const [nextDefault] = Object.keys(store.profiles).sort();
2749
+ store.defaultProfile = nextDefault;
2750
+ }
2751
+ await saveStore(store, profilePath);
2752
+ return true;
2753
+ };
2754
+ var projectIdentity = (projectRoot, appId) => createHash7("sha256").update(`${resolvePath2(projectRoot)}\x00${appId}`).digest("hex").slice(0, 20);
2755
+ var createAbsoluteRemoteIosDevProject = (config, projectRoot, profile) => ({
2756
+ cap: join7(resolvePath2(projectRoot), "node_modules", ".bin", "cap"),
2757
+ config,
2758
+ nativeDirectory: join7(config.nativeProjectDirectory, "ios"),
2759
+ profile,
2760
+ projectRoot: resolvePath2(projectRoot),
2761
+ remote: true,
2762
+ remoteProjectRoot: posix.join(profile.workspaceRoot, "projects", projectIdentity(projectRoot, config.appId), "current"),
2763
+ xcodebuild: "remote:xcodebuild",
2764
+ xcrun: "remote:xcrun"
2765
+ });
2766
+ var installAbsoluteRemoteMacAgent = async (project) => {
2767
+ const artifact = await materializeAbsoluteRemoteMacAgent(project.projectRoot);
2768
+ const directory = posix.join(project.profile.workspaceRoot, "agents", `protocol-${ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION}`, artifact.sha256);
2769
+ const remotePath = posix.join(directory, "agent.js");
2770
+ const verifyScript = `test -f ${shellQuote(remotePath)} && ` + `test "$(shasum -a 256 ${shellQuote(remotePath)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`;
2771
+ const verified = await defaultTransport.capture([
2772
+ ...absoluteRemoteMacSshBase(project.profile),
2773
+ "/bin/sh -lc",
2774
+ shellQuote(verifyScript)
2775
+ ]);
2776
+ if (verified.exitCode === 0)
2777
+ return { ...artifact, remotePath, uploaded: false };
2778
+ const temporary = posix.join(directory, `.agent-${randomUUID3()}.tmp`);
2779
+ const installScript = [
2780
+ "set -eu",
2781
+ "umask 077",
2782
+ `mkdir -p ${shellQuote(directory)}`,
2783
+ `cat > ${shellQuote(temporary)}`,
2784
+ `test "$(shasum -a 256 ${shellQuote(temporary)} | awk '{print $1}')" = ${shellQuote(artifact.sha256)}`,
2785
+ `chmod 600 ${shellQuote(temporary)}`,
2786
+ `mv ${shellQuote(temporary)} ${shellQuote(remotePath)}`
2787
+ ].join("; ");
2788
+ const upload = Bun.spawn([
2789
+ ...absoluteRemoteMacSshBase(project.profile),
2790
+ "/bin/sh -lc",
2791
+ shellQuote(installScript)
2792
+ ], {
2793
+ stderr: "pipe",
2794
+ stdin: Bun.file(artifact.path),
2795
+ stdout: "pipe"
2796
+ });
2797
+ const [exitCode, stderr] = await Promise.all([
2798
+ upload.exited,
2799
+ new Response(upload.stderr).text()
2800
+ ]);
2801
+ if (exitCode !== 0)
2802
+ throw new Error(`Remote Mac agent installation failed: ${stderr.trim() || `status ${exitCode}`}`);
2803
+ return { ...artifact, remotePath, uploaded: true };
2804
+ };
2805
+ var materializeAbsoluteRemoteMacAgent = async (projectRoot) => {
2806
+ const shippedCandidates = [
2807
+ join7(import.meta.dir, "remoteMacAgentEntry.js"),
2808
+ join7(import.meta.dir, "..", "mobile", "remoteMacAgentEntry.js")
2809
+ ];
2810
+ let path;
2811
+ for (const candidate of shippedCandidates) {
2812
+ if (await Bun.file(candidate).exists()) {
2813
+ path = candidate;
2814
+ break;
2815
+ }
2816
+ }
2817
+ if (!path) {
2818
+ const sourceCandidates = [
2819
+ join7(import.meta.dir, "remoteMacAgentEntry.ts"),
2820
+ join7(import.meta.dir, "..", "..", "src", "mobile", "remoteMacAgentEntry.ts")
2821
+ ];
2822
+ const source = await sourceCandidates.reduce(async (found, candidate) => await found ?? (await Bun.file(candidate).exists() ? candidate : undefined), Promise.resolve(undefined));
2823
+ if (!source)
2824
+ throw new Error("The AbsoluteJS installation does not contain its remote Mac agent artifact.");
2825
+ const outdir = join7(resolvePath2(projectRoot), ".absolutejs", "mobile", "remote-agent");
2826
+ await mkdir6(outdir, { recursive: true });
2827
+ const result = await Bun.build({
2828
+ entrypoints: [source],
2829
+ minify: true,
2830
+ outdir,
2831
+ target: "bun"
2832
+ });
2833
+ if (!result.success)
2834
+ throw new AggregateError(result.logs, "Failed to build the AbsoluteJS remote Mac agent.");
2835
+ path = join7(outdir, "remoteMacAgentEntry.js");
2836
+ }
2837
+ const bytes = await Bun.file(path).arrayBuffer();
2838
+ const sha256 = createHash7("sha256").update(new Uint8Array(bytes)).digest("hex");
2839
+ return { bytes: bytes.byteLength, path, sha256 };
2840
+ };
2841
+ var portableRelativePath = (root, path) => relative6(root, path).split(sep5).join(posix.sep);
2842
+ var portableMobileConfig = (project) => ({
2843
+ appId: project.config.appId,
2844
+ appName: project.config.appName,
2845
+ bundleDirectory: portableRelativePath(project.projectRoot, project.config.bundleDirectory),
2846
+ ...project.config.deepLinkScheme || project.config.deepLinkHosts.length > 1 || project.config.appleAppIdPrefix ? {
2847
+ deepLinks: {
2848
+ ...project.config.deepLinkScheme ? { scheme: project.config.deepLinkScheme } : {},
2849
+ hosts: project.config.deepLinkHosts,
2850
+ ...project.config.appleAppIdPrefix ? {
2851
+ apple: {
2852
+ appIdPrefix: project.config.appleAppIdPrefix
2853
+ }
2854
+ } : {}
2855
+ }
2856
+ } : {},
2857
+ entry: project.config.entry,
2858
+ ...project.config.iosVersion ? { ios: { version: project.config.iosVersion } } : {},
2859
+ nativeProject: {
2860
+ directory: portableRelativePath(project.projectRoot, project.config.nativeProjectDirectory),
2861
+ mode: "source"
2862
+ },
2863
+ platforms: ["ios"],
2864
+ server: { productionOrigin: project.config.productionOrigin }
2865
+ });
2866
+ var absoluteRemoteProjectSyncCommands = (project) => {
2867
+ const current = project.remoteProjectRoot;
2868
+ const parent = posix.dirname(current);
2869
+ const staging = posix.join(parent, `.incoming-${randomUUID3()}`);
2870
+ const previous = posix.join(parent, ".previous");
2871
+ const script = [
2872
+ "set -eu",
2873
+ `mkdir -p ${shellQuote(staging)}`,
2874
+ `tar -xf - -C ${shellQuote(staging)}`,
2875
+ `if [ -d ${shellQuote(posix.join(current, "node_modules"))} ]; then mv ${shellQuote(posix.join(current, "node_modules"))} ${shellQuote(posix.join(staging, "node_modules"))}; fi`,
2876
+ `if [ -d ${shellQuote(posix.join(current, ".absolutejs"))} ]; then mv ${shellQuote(posix.join(current, ".absolutejs"))} ${shellQuote(posix.join(staging, ".absolutejs"))}; fi`,
2877
+ `rm -rf ${shellQuote(previous)}`,
2878
+ `if [ -d ${shellQuote(current)} ]; then mv ${shellQuote(current)} ${shellQuote(previous)}; fi`,
2879
+ `mv ${shellQuote(staging)} ${shellQuote(current)}`,
2880
+ `rm -rf ${shellQuote(previous)}`
2881
+ ].join("; ");
2882
+ return {
2883
+ remote: [
2884
+ ...absoluteRemoteMacSshBase(project.profile),
2885
+ "/bin/sh -lc",
2886
+ shellQuote(script)
2887
+ ],
2888
+ tar: [
2889
+ "tar",
2890
+ "--exclude=.git",
2891
+ "--exclude=node_modules",
2892
+ "--exclude=build",
2893
+ "--exclude=.absolutejs",
2894
+ "-cf",
2895
+ "-",
2896
+ "-C",
2897
+ project.projectRoot,
2898
+ "."
2899
+ ]
2900
+ };
2901
+ };
2902
+ var syncAbsoluteRemoteMacProject = async (project) => {
2903
+ const commands = absoluteRemoteProjectSyncCommands(project);
2904
+ const archive = Bun.spawn(commands.tar, {
2905
+ stderr: "pipe",
2906
+ stdout: "pipe"
2907
+ });
2908
+ const upload = Bun.spawn(commands.remote, {
2909
+ stderr: "pipe",
2910
+ stdin: archive.stdout,
2911
+ stdout: "pipe"
2912
+ });
2913
+ const [archiveExit, uploadExit, archiveError, uploadError] = await Promise.all([
2914
+ archive.exited,
2915
+ upload.exited,
2916
+ new Response(archive.stderr).text(),
2917
+ new Response(upload.stderr).text()
2918
+ ]);
2919
+ if (archiveExit !== 0 || uploadExit !== 0)
2920
+ throw new Error(`Remote Mac project synchronization failed: ${(archiveError || uploadError).trim()}`);
2921
+ const install = await defaultTransport.capture([
2922
+ ...absoluteRemoteMacSshBase(project.profile),
2923
+ "/bin/sh -lc",
2924
+ shellQuote(`cd ${shellQuote(project.remoteProjectRoot)} && ${shellQuote(project.profile.bunPath)} install --frozen-lockfile`)
2925
+ ]);
2926
+ requireRemoteSuccess(install, "Remote Mac dependency installation");
2927
+ };
2928
+ var consumeLines2 = async (stream, onLine) => {
2929
+ const reader = stream.getReader();
2930
+ const decoder = new TextDecoder;
2931
+ let buffered = "";
2932
+ try {
2933
+ while (true) {
2934
+ const { done, value } = await reader.read();
2935
+ if (done)
2936
+ break;
2937
+ buffered += decoder.decode(value, { stream: true });
2938
+ const lines = buffered.split(/\r?\n/u);
2939
+ buffered = lines.pop() ?? "";
2940
+ lines.forEach(onLine);
2941
+ }
2942
+ buffered += decoder.decode();
2943
+ if (buffered)
2944
+ onLine(buffered);
2945
+ } finally {
2946
+ reader.releaseLock();
2947
+ }
2948
+ };
2949
+ var startAbsoluteRemoteIosDevSession = async (options) => {
2950
+ const startedAt = performance.now();
2951
+ const transport = options.transport ?? defaultTransport;
2952
+ const installAgent = options.installAgent ?? installAbsoluteRemoteMacAgent;
2953
+ const syncProject = options.syncProject ?? syncAbsoluteRemoteMacProject;
2954
+ const agentStartedAt = performance.now();
2955
+ const agent = await installAgent(options.project);
2956
+ const agentDuration = performance.now() - agentStartedAt;
2957
+ const syncStartedAt = performance.now();
2958
+ await syncProject(options.project);
2959
+ const syncDuration = performance.now() - syncStartedAt;
2960
+ const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
2961
+ const remoteCommand = [
2962
+ `cd ${shellQuote(options.project.remoteProjectRoot)}`,
2963
+ "&&",
2964
+ "exec",
2965
+ shellQuote(options.project.profile.bunPath),
2966
+ shellQuote(agent.remotePath),
2967
+ "--port",
2968
+ String(options.port),
2969
+ "--mobile-config",
2970
+ shellQuote(encodedConfig),
2971
+ ...options.https ? ["--https"] : []
2972
+ ].join(" ");
2973
+ const command = [
2974
+ ...absoluteRemoteMacSshBase(options.project.profile),
2975
+ "-o",
2976
+ "ExitOnForwardFailure=yes",
2977
+ "-R",
2978
+ `${options.port}:127.0.0.1:${options.port}`,
2979
+ "/bin/sh -lc",
2980
+ shellQuote(remoteCommand)
2981
+ ];
2982
+ const connectStartedAt = performance.now();
2983
+ const process2 = transport.spawn(command, { signal: options.signal });
2984
+ let state = "syncing";
2985
+ let ready;
2986
+ let fatal;
2987
+ const pending = new Map;
2988
+ let resolveReady;
2989
+ let rejectReady;
2990
+ const readyPromise = new Promise((resolve6, reject) => {
2991
+ resolveReady = resolve6;
2992
+ rejectReady = reject;
2993
+ });
2994
+ const handleEvent = (event) => {
2995
+ if (event.v !== ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION) {
2996
+ rejectReady(new Error("Remote Mac protocol version mismatch."));
2997
+ return;
2998
+ }
2999
+ if (event.type === "log")
3000
+ options.log?.(event.message);
3001
+ if (event.type === "native-log")
3002
+ options.nativeLog?.(event.entry);
3003
+ if (event.type === "state") {
3004
+ ({ state } = event);
3005
+ options.onStateChange?.(state);
3006
+ }
3007
+ if (event.type === "timing")
3008
+ options.onPhaseTiming?.(event);
3009
+ if (event.type === "ready") {
3010
+ ready = event;
3011
+ resolveReady();
3012
+ }
3013
+ if (event.type === "fatal") {
3014
+ fatal = new Error(event.error);
3015
+ rejectReady(fatal);
3016
+ }
3017
+ if (event.type === "response") {
3018
+ const request2 = pending.get(event.id);
3019
+ if (!request2)
3020
+ return;
3021
+ pending.delete(event.id);
3022
+ if (event.ok)
3023
+ request2.resolve(event.result);
3024
+ else
3025
+ request2.reject(new Error(event.error ?? "Remote command failed."));
3026
+ }
3027
+ };
3028
+ const stdoutDone = consumeLines2(process2.stdout, (line) => {
3029
+ if (!line.startsWith(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX))
3030
+ return;
3031
+ try {
3032
+ handleEvent(JSON.parse(line.slice(ABSOLUTE_REMOTE_MAC_EVENT_PREFIX.length)));
3033
+ } catch {
3034
+ options.log?.(`Remote Mac emitted an invalid protocol event.`);
3035
+ }
3036
+ }).catch((error) => {
3037
+ fatal = error instanceof Error ? error : new Error("Failed to read the remote Mac protocol stream.");
3038
+ rejectReady(fatal);
3039
+ });
3040
+ const stderrDone = consumeLines2(process2.stderr, (line) => options.log?.(`[remote] ${line}`)).catch((error) => options.log?.(`[remote] ${error instanceof Error ? error.message : "Failed to read SSH stderr."}`));
3041
+ process2.exited.then(async (exitCode) => {
3042
+ await Promise.all([stdoutDone, stderrDone]);
3043
+ const error = fatal ?? new Error(`Remote Mac connection closed with status ${exitCode}.`);
3044
+ if (!ready)
3045
+ rejectReady(error);
3046
+ pending.forEach(({ reject }) => reject(error));
3047
+ pending.clear();
3048
+ return;
3049
+ });
3050
+ await readyPromise;
3051
+ if (!ready)
3052
+ throw fatal ?? new Error("Remote Mac did not become ready.");
3053
+ const totalDuration = performance.now() - startedAt;
3054
+ let currentReady = {
3055
+ ...ready,
3056
+ timings: {
3057
+ ...ready.timings,
3058
+ "remote-agent": agentDuration,
3059
+ "remote-connect": performance.now() - connectStartedAt,
3060
+ "remote-sync": syncDuration,
3061
+ total: totalDuration
3062
+ }
3063
+ };
3064
+ options.log?.(`Remote Mac connected (${options.project.profile.name}); agent ${agent.uploaded ? "uploaded" : "cache hit"}, project synced, and iOS ready in ${totalDuration.toFixed(2)}ms.`);
3065
+ const request = (commandName) => {
3066
+ const id = randomUUID3();
3067
+ const response = new Promise((resolve6, reject) => pending.set(id, { reject, resolve: resolve6 }));
3068
+ process2.stdin.write(`${JSON.stringify({ command: commandName, id, v: 1 })}
3069
+ `);
3070
+ process2.stdin.flush();
3071
+ return response;
3072
+ };
3073
+ let closed = false;
3074
+ const close = async () => {
3075
+ if (closed)
3076
+ return;
3077
+ closed = true;
3078
+ await request("close").catch(() => {
3079
+ return;
3080
+ });
3081
+ process2.stdin.end();
3082
+ await process2.exited.catch(() => {
3083
+ return;
3084
+ });
3085
+ };
3086
+ const makeSession = () => ({
3087
+ close,
3088
+ nativeCacheHit: currentReady.nativeCacheHit,
3089
+ startedSimulator: currentReady.startedSimulator,
3090
+ timings: currentReady.timings,
3091
+ udid: currentReady.udid,
3092
+ rebuild: async () => {
3093
+ const rebuildStartedAt = performance.now();
3094
+ const rebuildSyncStartedAt = performance.now();
3095
+ await syncProject(options.project);
3096
+ const rebuildSyncDuration = performance.now() - rebuildSyncStartedAt;
3097
+ const result = await request("rebuild");
3098
+ currentReady = {
3099
+ ...result,
3100
+ timings: {
3101
+ ...result.timings,
3102
+ "remote-sync": rebuildSyncDuration,
3103
+ total: performance.now() - rebuildStartedAt
3104
+ }
3105
+ };
3106
+ return makeSession();
3107
+ },
3108
+ relaunch: async () => {
3109
+ await request("relaunch");
3110
+ },
3111
+ screenshot: async (destination) => {
3112
+ const result = await request("screenshot");
3113
+ const target = resolvePath2(options.project.projectRoot, destination);
3114
+ const targetRelative = relative6(options.project.projectRoot, target);
3115
+ if (targetRelative.startsWith("..") || isAbsolute5(targetRelative))
3116
+ throw new Error("iOS screenshot must remain inside the project.");
3117
+ await mkdir6(dirname5(target), { recursive: true });
3118
+ await writeFile7(target, Buffer.from(result.data, "base64"));
3119
+ return target;
3120
+ },
3121
+ get state() {
3122
+ return state;
3123
+ }
3124
+ });
3125
+ return makeSession();
3126
+ };
2536
3127
  // src/mobile/associationFiles.ts
2537
3128
  import {
2538
3129
  access as access7,
2539
- mkdir as mkdir6,
2540
- readFile as readFile8,
2541
- rename as rename7,
3130
+ mkdir as mkdir7,
3131
+ readFile as readFile9,
3132
+ rename as rename8,
2542
3133
  rm as rm6,
2543
- writeFile as writeFile7
3134
+ writeFile as writeFile8
2544
3135
  } from "fs/promises";
2545
3136
  import { resolve as resolve7 } from "path";
2546
3137
  import { Elysia } from "elysia";
@@ -2575,8 +3166,9 @@ var normalizeEntry = (entry) => {
2575
3166
  };
2576
3167
  var normalizeProductionOrigin = (value) => {
2577
3168
  const parsed = new URL(requireText(value, "mobile.server.productionOrigin"));
2578
- if (parsed.protocol !== "https:") {
2579
- throw new TypeError("mobile.server.productionOrigin must use HTTPS in production.");
3169
+ const isLoopbackHttp = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
3170
+ if (parsed.protocol !== "https:" && !isLoopbackHttp) {
3171
+ throw new TypeError("mobile.server.productionOrigin must use HTTPS, except for a loopback development origin.");
2580
3172
  }
2581
3173
  if (parsed.username || parsed.password || parsed.pathname !== "/" || parsed.search || parsed.hash) {
2582
3174
  throw new TypeError("mobile.server.productionOrigin must be an origin without credentials, path, query, or hash.");
@@ -2600,9 +3192,8 @@ var normalizeHosts = (hosts, productionOrigin) => {
2600
3192
  }
2601
3193
  return value;
2602
3194
  };
2603
- const normalized = new Set([
2604
- normalizeHostname(new URL(productionOrigin).hostname)
2605
- ]);
3195
+ const productionHostname = new URL(productionOrigin).hostname;
3196
+ const normalized = new Set(productionHostname === "[::1]" ? [] : [normalizeHostname(productionHostname)]);
2606
3197
  for (const host of hosts ?? []) {
2607
3198
  normalized.add(normalizeHostname(host));
2608
3199
  }
@@ -2640,7 +3231,7 @@ var normalizeAbsoluteMobileConfig = (config, projectRoot) => {
2640
3231
  throw new TypeError("mobile.appId must use reverse-domain notation, for example com.example.app.");
2641
3232
  }
2642
3233
  const productionOrigin = normalizeProductionOrigin(config.server.productionOrigin);
2643
- const deepLinkScheme = config.deepLinks?.scheme?.trim().toLowerCase();
3234
+ const deepLinkScheme = (config.deepLinks?.scheme ?? appId).trim().toLowerCase();
2644
3235
  if (deepLinkScheme && !SCHEME_PATTERN.test(deepLinkScheme)) {
2645
3236
  throw new TypeError("mobile.deepLinks.scheme is not a valid URL scheme.");
2646
3237
  }
@@ -2742,7 +3333,7 @@ var createAbsoluteMobileAssociationPlugin = (mobile, projectRoot, options = {})
2742
3333
  var writeAtomic = async (path, source) => {
2743
3334
  let current;
2744
3335
  try {
2745
- current = await readFile8(path, "utf8");
3336
+ current = await readFile9(path, "utf8");
2746
3337
  } catch (error) {
2747
3338
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
2748
3339
  throw error;
@@ -2751,8 +3342,8 @@ var writeAtomic = async (path, source) => {
2751
3342
  if (current === source)
2752
3343
  return false;
2753
3344
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
2754
- await writeFile7(temporary, source, { flag: "wx" });
2755
- await rename7(temporary, path);
3345
+ await writeFile8(temporary, source, { flag: "wx" });
3346
+ await rename8(temporary, path);
2756
3347
  return true;
2757
3348
  };
2758
3349
  var exists2 = async (path) => {
@@ -2767,7 +3358,7 @@ var assertOwnedOutput = async (root) => {
2767
3358
  const path = resolve7(root, OWNERSHIP_FILE);
2768
3359
  let ownership;
2769
3360
  try {
2770
- ownership = JSON.parse(await readFile8(path, "utf8"));
3361
+ ownership = JSON.parse(await readFile9(path, "utf8"));
2771
3362
  } catch {
2772
3363
  throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
2773
3364
  }
@@ -2781,12 +3372,12 @@ var publishGeneratedDirectory = async (temporary, root) => {
2781
3372
  await assertOwnedOutput(root);
2782
3373
  const backup = `${root}.${crypto.randomUUID()}.previous`;
2783
3374
  if (hasCurrent)
2784
- await rename7(root, backup);
3375
+ await rename8(root, backup);
2785
3376
  try {
2786
- await rename7(temporary, root);
3377
+ await rename8(temporary, root);
2787
3378
  } catch (error) {
2788
3379
  if (hasCurrent)
2789
- await rename7(backup, root);
3380
+ await rename8(backup, root);
2790
3381
  throw error;
2791
3382
  }
2792
3383
  if (hasCurrent)
@@ -2794,7 +3385,7 @@ var publishGeneratedDirectory = async (temporary, root) => {
2794
3385
  };
2795
3386
  var materializeHost = async (root, host, files) => {
2796
3387
  const directory = resolve7(root, host, ".well-known");
2797
- await mkdir6(directory, { recursive: true });
3388
+ await mkdir7(directory, { recursive: true });
2798
3389
  return Promise.all(files.map(async ([name, document]) => {
2799
3390
  const path = resolve7(directory, name);
2800
3391
  await writeAtomic(path, `${JSON.stringify(document, null, 2)}
@@ -2832,7 +3423,7 @@ var materializeAbsoluteMobileAssociationFiles = async (config, outputDirectory)
2832
3423
  if (documents.apple) {
2833
3424
  files.push(["apple-app-site-association", documents.apple]);
2834
3425
  }
2835
- await mkdir6(temporary, { recursive: true });
3426
+ await mkdir7(temporary, { recursive: true });
2836
3427
  try {
2837
3428
  const temporaryPaths = (await Promise.all(config.deepLinkHosts.map((host) => materializeHost(temporary, host, files)))).flat();
2838
3429
  await writeAtomic(resolve7(temporary, OWNERSHIP_FILE), `${JSON.stringify({ format: 1, hosts: config.deepLinkHosts }, null, 2)}
@@ -2903,15 +3494,23 @@ var parseAbsoluteMobileBuildPageMetadata = (value) => {
2903
3494
  };
2904
3495
  };
2905
3496
  // src/mobile/buildPipeline.ts
2906
- import { readFile as readFile12 } from "fs/promises";
2907
- import { join as join10, resolve as resolve10 } from "path";
3497
+ import { readFile as readFile13 } from "fs/promises";
3498
+ import { join as join12, resolve as resolve10 } from "path";
2908
3499
  import { pathToFileURL as pathToFileURL2 } from "url";
2909
3500
 
2910
3501
  // src/mobile/buildRelease.ts
2911
- import { createHash as createHash7 } from "crypto";
2912
- import { readFile as readFile9 } from "fs/promises";
2913
- import { join as join7, relative as relative6, resolve as resolve8 } from "path";
2914
- var sha256 = (bytes) => createHash7("sha256").update(bytes).digest("hex");
3502
+ import { createHash as createHash8 } from "crypto";
3503
+ import { mkdir as mkdir8, readFile as readFile10, writeFile as writeFile9 } from "fs/promises";
3504
+ import { basename as basename2, dirname as dirname6, extname, join as join8, relative as relative7, resolve as resolve8 } from "path";
3505
+ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex");
3506
+ var STATIC_SCRIPT_PATTERN = /(<script\b[^>]*?\bsrc\s*=\s*["'])(\/[^"']+\.(?:js|ts))(["'][^>]*>)/giu;
3507
+ var rewriteStaticScriptPaths = (source, manifest) => source.replace(STATIC_SCRIPT_PATTERN, (match, prefix, path, suffix) => {
3508
+ if (path.endsWith("/htmx.min.js"))
3509
+ return match;
3510
+ const key = toPascal(basename2(path, extname(path)));
3511
+ const builtPath = manifest[key];
3512
+ return builtPath ? `${prefix}${builtPath}${suffix}` : match;
3513
+ });
2915
3514
  var readPageMetadata = (route) => parseAbsoluteMobileBuildPageMetadata(route.hooks?.detail?.[ABSOLUTE_MOBILE_ROUTE_DETAIL]);
2916
3515
  var resolveAssetPath = (buildDirectory, assetPath) => {
2917
3516
  const resolvedBuildDirectory = resolve8(buildDirectory);
@@ -2919,29 +3518,51 @@ var resolveAssetPath = (buildDirectory, assetPath) => {
2919
3518
  if (resolvedAsset.startsWith(`${resolvedBuildDirectory}/`)) {
2920
3519
  return resolvedAsset;
2921
3520
  }
2922
- return join7(buildDirectory, assetPath.replace(/^\/+/, ""));
3521
+ return join8(buildDirectory, assetPath.replace(/^\/+/, ""));
2923
3522
  };
2924
3523
  var pageFor = async (metadata, manifest, buildDirectory) => {
2925
3524
  const assetPath = manifest[metadata.bundleKey];
2926
3525
  if (!assetPath) {
2927
3526
  throw new TypeError(`Mobile page ${metadata.pageId} references missing manifest asset ${metadata.bundleKey}.`);
2928
3527
  }
2929
- const resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
2930
- const bytes = await readFile9(resolvedAssetPath);
2931
- const bundlePath = `/${relative6(resolve8(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
3528
+ let resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
3529
+ if (metadata.framework === "html" || metadata.framework === "htmx") {
3530
+ const source = await readFile10(resolvedAssetPath, "utf8");
3531
+ const rewritten = rewriteStaticScriptPaths(source, manifest);
3532
+ const documentHash = sha256(new TextEncoder().encode(rewritten));
3533
+ resolvedAssetPath = join8(buildDirectory, ".absolutejs", "mobile-pages", `${documentHash}.html`);
3534
+ await mkdir8(dirname6(resolvedAssetPath), { recursive: true });
3535
+ await writeFile9(resolvedAssetPath, rewritten);
3536
+ }
3537
+ const pageAssetKey = metadata.bundleKey.replace(/Index$/u, "");
3538
+ const styleAssetPath = [
3539
+ `${pageAssetKey}BundledCSS`,
3540
+ `${pageAssetKey}CompiledCSS`
3541
+ ].map((key) => manifest[key]).find((path) => typeof path === "string");
3542
+ const resolvedStylePath = styleAssetPath ? resolveAssetPath(buildDirectory, styleAssetPath) : undefined;
3543
+ const [bytes, styleBytes] = await Promise.all([
3544
+ readFile10(resolvedAssetPath),
3545
+ resolvedStylePath ? readFile10(resolvedStylePath) : undefined
3546
+ ]);
3547
+ const bundlePath = `/${relative7(resolve8(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
3548
+ const styleBundlePath = resolvedStylePath ? `/${relative7(resolve8(buildDirectory), resolvedStylePath).replaceAll("\\", "/")}` : undefined;
2932
3549
  return {
2933
3550
  bundleHash: sha256(bytes),
2934
3551
  bundlePath,
2935
3552
  contract: metadata.contract,
2936
3553
  framework: metadata.framework,
2937
3554
  pageId: metadata.pageId,
2938
- propsSchemaHash: metadata.propsSchemaHash
3555
+ propsSchemaHash: metadata.propsSchemaHash,
3556
+ ...styleBytes && styleBundlePath ? {
3557
+ styleBundleHash: sha256(styleBytes),
3558
+ styleBundlePath
3559
+ } : {}
2939
3560
  };
2940
3561
  };
2941
3562
  var buildAbsoluteMobileCompatibilityRelease = async (options) => {
2942
3563
  const [captured, producerBytes] = await Promise.all([
2943
3564
  captureAbsoluteMobileRouteGraph(options.app),
2944
- readFile9(options.producerPath)
3565
+ readFile10(options.producerPath)
2945
3566
  ]);
2946
3567
  if (captured.length === 0) {
2947
3568
  throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
@@ -2957,11 +3578,19 @@ var buildAbsoluteMobileCompatibilityRelease = async (options) => {
2957
3578
  const pages = await Promise.all([...metadataByPage.values()].map((metadata) => pageFor(metadata, options.manifest, options.buildDirectory)));
2958
3579
  const producerHash = sha256(producerBytes);
2959
3580
  const appBuild = `ambuild_${sha256(new TextEncoder().encode(JSON.stringify({
2960
- pages: pages.map(({ bundleHash, bundlePath, contract, pageId }) => ({
3581
+ pages: pages.map(({
3582
+ bundleHash,
3583
+ bundlePath,
3584
+ contract,
3585
+ pageId,
3586
+ styleBundleHash,
3587
+ styleBundlePath
3588
+ }) => ({
2961
3589
  bundleHash,
2962
3590
  bundlePath,
2963
3591
  contract,
2964
- pageId
3592
+ pageId,
3593
+ ...styleBundleHash && styleBundlePath ? { styleBundleHash, styleBundlePath } : {}
2965
3594
  })),
2966
3595
  producerHash,
2967
3596
  runtime: options.runtime
@@ -3021,16 +3650,17 @@ var captureAbsoluteMobileRouteGraph = async (app) => {
3021
3650
 
3022
3651
  // src/mobile/capacitorBundle.ts
3023
3652
  import {
3653
+ cp,
3024
3654
  copyFile as copyFile5,
3025
- mkdir as mkdir7,
3655
+ mkdir as mkdir9,
3026
3656
  mkdtemp as mkdtemp4,
3027
- readFile as readFile10,
3028
- rename as rename8,
3657
+ readFile as readFile11,
3658
+ rename as rename9,
3029
3659
  rm as rm7,
3030
- writeFile as writeFile8
3660
+ writeFile as writeFile10
3031
3661
  } from "fs/promises";
3032
3662
  import { existsSync as existsSync2 } from "fs";
3033
- import { basename as basename2, dirname as dirname5, extname, join as join8, resolve as resolve9 } from "path";
3663
+ import { basename as basename3, dirname as dirname7, extname as extname2, join as join9, relative as relative8, resolve as resolve9 } from "path";
3034
3664
 
3035
3665
  // src/mobile/routeMatcher.ts
3036
3666
  var REGEXP_SPECIAL_CHARACTERS = /[.*+?^${}()|[\]\\]/g;
@@ -3303,6 +3933,13 @@ class AbsoluteMobilePageProtocolError extends Error {
3303
3933
  this.code = code;
3304
3934
  }
3305
3935
  }
3936
+ var disposeAbsoluteMobilePage = async (target = window) => {
3937
+ const dispose = target.__ABSOLUTE_PAGE_DISPOSE__;
3938
+ target.__ABSOLUTE_PAGE_DISPOSE__ = undefined;
3939
+ target.__ABSOLUTE_PAGE_READY__ = undefined;
3940
+ if (dispose)
3941
+ await dispose();
3942
+ };
3306
3943
  var frameworks4 = new Set([
3307
3944
  "angular",
3308
3945
  "ember",
@@ -3358,12 +3995,17 @@ var activateAbsoluteMobilePage = async (value, options) => {
3358
3995
  throw new AbsoluteMobilePageProtocolError("invalid-envelope", "Expected a renderable mobile page response.");
3359
3996
  }
3360
3997
  const target = options.target ?? window;
3998
+ await disposeAbsoluteMobilePage(target);
3361
3999
  target.__INITIAL_PROPS__ = envelope.response.props;
4000
+ target.__ABS_ANGULAR_REQUEST_CONTEXT__ = envelope.response.props;
3362
4001
  target.__ABSOLUTE_PAGE_RENDER_MODE__ = "client";
3363
4002
  await options.loadPage({
3364
4003
  contract: envelope.response.contract,
3365
4004
  pageId: envelope.response.pageId
3366
4005
  });
4006
+ if (target.__ABSOLUTE_PAGE_READY__) {
4007
+ await target.__ABSOLUTE_PAGE_READY__;
4008
+ }
3367
4009
  return {
3368
4010
  contract: envelope.response.contract,
3369
4011
  kind: "rendered",
@@ -3456,19 +4098,51 @@ var resolveAbsoluteMobileDeepLink = (manifest, value) => {
3456
4098
  }
3457
4099
  return `${url.pathname || "/"}${url.search}${url.hash}`;
3458
4100
  };
4101
+ var resolveAbsoluteMobileNavigation = (manifest, value, localOrigin) => {
4102
+ const url = new URL(value, `${localOrigin}/`);
4103
+ const local = new URL(localOrigin);
4104
+ const production = new URL(manifest.productionOrigin);
4105
+ const matches = (candidate, allowed) => candidate.protocol === allowed.protocol && candidate.host === allowed.host;
4106
+ if (!matches(url, local) && !matches(url, production)) {
4107
+ return;
4108
+ }
4109
+ return `${url.pathname}${url.search}${url.hash}`;
4110
+ };
3459
4111
 
3460
4112
  // src/mobile/capacitorBundle.ts
3461
4113
  var MANIFEST_FILE = "absolute-mobile-manifest.json";
3462
4114
  var BOOTSTRAP_FILE = "absolute-mobile-bootstrap.js";
3463
4115
  var INDEX_FILE = "index.html";
3464
- var CLIENT_IMPORT_PATTERN = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s*)["'](\/[^"']+)["']/gu;
4116
+ var CLIENT_CSS_DEPENDENCY_PATTERN = /(?:@import\s+(?:url\(\s*)?|url\(\s*)["']?((?:\/|\.\.\/|\.\/)[^"')\s]+)["']?\s*\)?/gu;
4117
+ var CLIENT_MARKUP_DEPENDENCY_PATTERN = /<(?:script\b[^>]*\bsrc|link\b[^>]*\bhref|img\b[^>]*\bsrc|source\b[^>]*\bsrcset)\s*=\s*["']((?:\/|\.\.\/|\.\/)[^"',\s]+)/giu;
4118
+ var CAPACITOR_CLIENT_FRAMEWORKS = new Set([
4119
+ "angular",
4120
+ "html",
4121
+ "htmx",
4122
+ "react",
4123
+ "svelte",
4124
+ "vue"
4125
+ ]);
4126
+ var CLIENT_ASSET_DIRECTORIES = ["assets", "html", "htmx", "indexes"];
3465
4127
  var errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code;
3466
4128
  var shellBootstrapModule = () => {
3467
- const candidate = ["js", "ts"].map((extension) => join8(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
4129
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellBootstrap.${extension}`)).find(existsSync2);
3468
4130
  if (candidate)
3469
4131
  return candidate;
3470
4132
  throw new TypeError("AbsoluteJS mobile shell bootstrap module is missing.");
3471
4133
  };
4134
+ var shellAuthModule = () => {
4135
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellAuth.${extension}`)).find(existsSync2);
4136
+ if (candidate)
4137
+ return candidate;
4138
+ throw new TypeError("AbsoluteJS mobile auth shell module is missing.");
4139
+ };
4140
+ var shellSyncModule = () => {
4141
+ const candidate = ["js", "ts"].map((extension) => join9(import.meta.dir, `shellSync.${extension}`)).find(existsSync2);
4142
+ if (candidate)
4143
+ return candidate;
4144
+ throw new TypeError("AbsoluteJS mobile Sync shell module is missing.");
4145
+ };
3472
4146
  var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
3473
4147
  var indexHtml = (appName) => `<!doctype html>
3474
4148
  <html>
@@ -3492,11 +4166,16 @@ var sourceAssetPath = (buildDirectory, bundlePath) => {
3492
4166
  }
3493
4167
  return asset;
3494
4168
  };
3495
- var buildShellBootstrap = async (staging) => {
4169
+ var buildShellBootstrap = async (staging, auth, sync) => {
3496
4170
  const modulePath = shellBootstrapModule();
3497
- const entryPath = join8(staging, ".absolute-mobile-entry.ts");
3498
- await writeFile8(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
3499
- void startAbsoluteMobileShell();
4171
+ const authImport = auth ? `import { createAbsoluteMobileShellAuth } from ${JSON.stringify(shellAuthModule())};
4172
+ ` : "";
4173
+ const options = auth ? `{ createAuth: createAbsoluteMobileShellAuth${sync ? ", installSync: installAbsoluteMobileShellSync" : ""} }` : "";
4174
+ const syncImport = sync ? `import { installAbsoluteMobileShellSync } from ${JSON.stringify(shellSyncModule())};
4175
+ ` : "";
4176
+ const entryPath = join9(staging, ".absolute-mobile-entry.ts");
4177
+ await writeFile10(entryPath, `import { startAbsoluteMobileShell } from ${JSON.stringify(modulePath)};
4178
+ ${authImport}${syncImport}void startAbsoluteMobileShell(${options});
3500
4179
  `);
3501
4180
  const build = await Bun.build({
3502
4181
  entrypoints: [entryPath],
@@ -3507,7 +4186,7 @@ void startAbsoluteMobileShell();
3507
4186
  if (!build.success || build.outputs.length !== 1) {
3508
4187
  throw new AggregateError(build.logs, "Failed to build the AbsoluteJS Capacitor shell.");
3509
4188
  }
3510
- await rename8(build.outputs[0]?.path ?? "", join8(staging, BOOTSTRAP_FILE));
4189
+ await rename9(build.outputs[0]?.path ?? "", join9(staging, BOOTSTRAP_FILE));
3511
4190
  await rm7(entryPath, { force: true });
3512
4191
  };
3513
4192
  var removePreviousBundle = async (backup, moved) => {
@@ -3518,20 +4197,20 @@ var removePreviousBundle = async (backup, moved) => {
3518
4197
  var restorePreviousBundle = async (backup, destination, moved) => {
3519
4198
  if (!moved)
3520
4199
  return;
3521
- await rename8(backup, destination);
4200
+ await rename9(backup, destination);
3522
4201
  };
3523
4202
  var installBundle = async (staging, destination) => {
3524
4203
  const backup = `${destination}.previous-${crypto.randomUUID()}`;
3525
4204
  let movedPrevious = false;
3526
4205
  try {
3527
- await rename8(destination, backup);
4206
+ await rename9(destination, backup);
3528
4207
  movedPrevious = true;
3529
4208
  } catch (error) {
3530
4209
  if (!errorHasCode2(error, "ENOENT"))
3531
4210
  throw error;
3532
4211
  }
3533
4212
  try {
3534
- await rename8(staging, destination);
4213
+ await rename9(staging, destination);
3535
4214
  await removePreviousBundle(backup, movedPrevious);
3536
4215
  } catch (error) {
3537
4216
  await restorePreviousBundle(backup, destination, movedPrevious);
@@ -3539,21 +4218,62 @@ var installBundle = async (staging, destination) => {
3539
4218
  }
3540
4219
  };
3541
4220
  var copyClientPage = async (page, buildDirectory, staging, copiedDependencies) => {
3542
- if (page.framework !== "react") {
3543
- throw new TypeError(`Capacitor spike currently supports React pages; ${page.pageId} is ${page.framework}.`);
4221
+ if (!CAPACITOR_CLIENT_FRAMEWORKS.has(page.framework)) {
4222
+ throw new TypeError(`Capacitor client rendering does not yet support ${page.framework} page ${page.pageId}.`);
3544
4223
  }
3545
- const extension = extname(page.bundlePath) || ".js";
4224
+ const extension = extname2(page.bundlePath) || ".js";
3546
4225
  const localBundlePath = `./pages/${page.bundleHash}${extension}`;
3547
4226
  const source = sourceAssetPath(buildDirectory, page.bundlePath);
3548
- await copyFile5(source, join8(staging, localBundlePath));
4227
+ await copyFile5(source, join9(staging, localBundlePath));
3549
4228
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copiedDependencies);
3550
- return { ...page, localBundlePath };
4229
+ let localStylePath;
4230
+ if (page.styleBundlePath && page.styleBundleHash) {
4231
+ const styleExtension = extname2(page.styleBundlePath) || ".css";
4232
+ localStylePath = `./styles/${page.styleBundleHash}${styleExtension}`;
4233
+ const styleSource = sourceAssetPath(buildDirectory, page.styleBundlePath);
4234
+ await mkdir9(dirname7(join9(staging, localStylePath)), {
4235
+ recursive: true
4236
+ });
4237
+ await copyFile5(styleSource, join9(staging, localStylePath));
4238
+ await copyAbsoluteClientDependencies(styleSource, buildDirectory, staging, copiedDependencies);
4239
+ }
4240
+ return {
4241
+ ...page,
4242
+ localBundlePath,
4243
+ ...localStylePath ? { localStylePath } : {}
4244
+ };
3551
4245
  };
3552
- var absoluteClientImports = async (sourcePath) => {
3553
- const source = await readFile10(sourcePath, "utf8");
3554
- return [...source.matchAll(CLIENT_IMPORT_PATTERN)].flatMap((match) => {
3555
- const [specifier] = match.slice(1);
3556
- return specifier ? [specifier.split(/[?#]/u, 1)[0] ?? specifier] : [];
4246
+ var absoluteClientImports = async (sourcePath, buildDirectory) => {
4247
+ const source = await readFile11(sourcePath, "utf8");
4248
+ const extension = extname2(sourcePath).toLowerCase();
4249
+ let scriptLoader;
4250
+ if (extension === ".tsx")
4251
+ scriptLoader = "tsx";
4252
+ else if (extension === ".ts")
4253
+ scriptLoader = "ts";
4254
+ else if (extension === ".jsx")
4255
+ scriptLoader = "jsx";
4256
+ else if ([".js", ".mjs", ".cjs"].includes(extension))
4257
+ scriptLoader = "js";
4258
+ const scriptImports = scriptLoader ? new Bun.Transpiler({ loader: scriptLoader }).scanImports(source).map(({ path }) => path) : [];
4259
+ const cssImports = extension === ".css" ? [...source.matchAll(CLIENT_CSS_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
4260
+ const markupImports = extension === ".html" ? [...source.matchAll(CLIENT_MARKUP_DEPENDENCY_PATTERN)].flatMap((match) => match[1] ?? []) : [];
4261
+ return [...scriptImports, ...cssImports, ...markupImports].flatMap((specifier) => {
4262
+ if (!specifier)
4263
+ return [];
4264
+ if (!specifier.startsWith("/") && !specifier.startsWith("./") && !specifier.startsWith("../")) {
4265
+ return [];
4266
+ }
4267
+ const clean = specifier.split(/[?#]/u, 1)[0] ?? specifier;
4268
+ if (clean.startsWith("/"))
4269
+ return [clean];
4270
+ const resolved = resolve9(dirname7(sourcePath), clean);
4271
+ const root = resolve9(buildDirectory);
4272
+ const relativePath = relative8(root, resolved).replaceAll("\\", "/");
4273
+ if (relativePath === ".." || relativePath.startsWith("../")) {
4274
+ throw new TypeError(`Mobile client dependency escaped the build directory: ${specifier}`);
4275
+ }
4276
+ return [`/${relativePath}`];
3557
4277
  });
3558
4278
  };
3559
4279
  var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, copied) => {
@@ -3561,13 +4281,13 @@ var copyAbsoluteClientDependency = async (specifier, buildDirectory, staging, co
3561
4281
  return;
3562
4282
  copied.add(specifier);
3563
4283
  const source = sourceAssetPath(buildDirectory, specifier);
3564
- const destination = join8(staging, specifier.replace(/^\/+/, ""));
3565
- await mkdir7(dirname5(destination), { recursive: true });
4284
+ const destination = join9(staging, specifier.replace(/^\/+/, ""));
4285
+ await mkdir9(dirname7(destination), { recursive: true });
3566
4286
  await copyFile5(source, destination);
3567
4287
  await copyAbsoluteClientDependencies(source, buildDirectory, staging, copied);
3568
4288
  };
3569
4289
  var copyAbsoluteClientDependencies = async (sourcePath, buildDirectory, staging, copied) => {
3570
- const dependencies = await absoluteClientImports(sourcePath);
4290
+ const dependencies = await absoluteClientImports(sourcePath, buildDirectory);
3571
4291
  await Promise.all(dependencies.map((specifier) => copyAbsoluteClientDependency(specifier, buildDirectory, staging, copied)));
3572
4292
  };
3573
4293
  var materializeAbsoluteCapacitorWebBundle = async (options) => {
@@ -3575,15 +4295,20 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
3575
4295
  throw new TypeError(`mobile.entry ${options.config.entry} is not a captured mobile page route.`);
3576
4296
  }
3577
4297
  const destination = options.config.bundleDirectory;
3578
- await mkdir7(dirname5(destination), { recursive: true });
3579
- const staging = await mkdtemp4(join8(dirname5(destination), `.${basename2(destination)}.stage-`));
4298
+ await mkdir9(dirname7(destination), { recursive: true });
4299
+ const staging = await mkdtemp4(join9(dirname7(destination), `.${basename3(destination)}.stage-`));
3580
4300
  try {
3581
- const pageDirectory = join8(staging, "pages");
3582
- await mkdir7(pageDirectory, { recursive: true });
4301
+ const pageDirectory = join9(staging, "pages");
4302
+ await mkdir9(pageDirectory, { recursive: true });
4303
+ await Promise.all(CLIENT_ASSET_DIRECTORIES.map((directory) => ({
4304
+ destination: join9(staging, directory),
4305
+ source: join9(options.buildDirectory, directory)
4306
+ })).filter(({ source }) => existsSync2(source)).map(({ destination: assetDestination, source }) => cp(source, assetDestination, { recursive: true })));
3583
4307
  const copiedDependencies = new Set;
3584
4308
  const pages = await Promise.all(options.artifact.pages.map((page) => copyClientPage(page, options.buildDirectory, staging, copiedDependencies)));
3585
4309
  const manifest = {
3586
4310
  appBuild: options.artifact.appBuild,
4311
+ ...options.auth ? { auth: options.auth } : {},
3587
4312
  appId: options.config.appId,
3588
4313
  appName: options.config.appName,
3589
4314
  deepLinkHosts: options.config.deepLinkHosts,
@@ -3593,13 +4318,14 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
3593
4318
  pages,
3594
4319
  productionOrigin: options.config.productionOrigin,
3595
4320
  routes: options.artifact.routes,
3596
- runtime: options.artifact.runtime
4321
+ runtime: options.artifact.runtime,
4322
+ ...options.sync ? { sync: { socketTickets: true } } : {}
3597
4323
  };
3598
4324
  await Promise.all([
3599
- writeFile8(join8(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
4325
+ writeFile10(join9(staging, MANIFEST_FILE), `${JSON.stringify(manifest, null, "\t")}
3600
4326
  `),
3601
- writeFile8(join8(staging, INDEX_FILE), indexHtml(options.config.appName)),
3602
- buildShellBootstrap(staging)
4327
+ writeFile10(join9(staging, INDEX_FILE), indexHtml(options.config.appName)),
4328
+ buildShellBootstrap(staging, options.auth !== undefined, options.auth !== undefined && options.sync === true)
3603
4329
  ]);
3604
4330
  await installBundle(staging, destination);
3605
4331
  return manifest;
@@ -3610,17 +4336,17 @@ var materializeAbsoluteCapacitorWebBundle = async (options) => {
3610
4336
  };
3611
4337
 
3612
4338
  // src/mobile/materializedBundle.ts
3613
- import { createHash as createHash8 } from "crypto";
4339
+ import { createHash as createHash9 } from "crypto";
3614
4340
  import {
3615
4341
  access as access8,
3616
- mkdir as mkdir8,
4342
+ mkdir as mkdir10,
3617
4343
  mkdtemp as mkdtemp5,
3618
- readFile as readFile11,
3619
- rename as rename9,
4344
+ readFile as readFile12,
4345
+ rename as rename10,
3620
4346
  rm as rm8,
3621
- writeFile as writeFile9
4347
+ writeFile as writeFile11
3622
4348
  } from "fs/promises";
3623
- import { dirname as dirname6, join as join9, resolve as resolvePath2 } from "path";
4349
+ import { dirname as dirname8, join as join10, resolve as resolvePath3 } from "path";
3624
4350
  import { pathToFileURL } from "url";
3625
4351
  var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1;
3626
4352
  var CURRENT_BUNDLE_FILE = "current.json";
@@ -3634,7 +4360,7 @@ var bundleIdFor = (currentReleaseId, releases) => {
3634
4360
  currentReleaseId,
3635
4361
  releases: releases.map(({ releaseId }) => releaseId)
3636
4362
  });
3637
- return `amb_${createHash8("sha256").update(identity).digest("hex")}`;
4363
+ return `amb_${createHash9("sha256").update(identity).digest("hex")}`;
3638
4364
  };
3639
4365
  var parseBundleIndex = (value) => {
3640
4366
  if (!isRecord7(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
@@ -3660,17 +4386,17 @@ var parseBundleIndex = (value) => {
3660
4386
  };
3661
4387
  };
3662
4388
  var writeRelease = async (root, release) => {
3663
- const directory = join9(root, release.artifact.releaseId);
3664
- const producerPath = join9(directory, release.artifact.producer.module);
3665
- await mkdir8(dirname6(producerPath), { recursive: true });
4389
+ const directory = join10(root, release.artifact.releaseId);
4390
+ const producerPath = join10(directory, release.artifact.producer.module);
4391
+ await mkdir10(dirname8(producerPath), { recursive: true });
3666
4392
  await Promise.all([
3667
- writeFile9(join9(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
4393
+ writeFile11(join10(directory, ARTIFACT_FILE2), `${JSON.stringify(release.artifact, null, "\t")}
3668
4394
  `),
3669
- writeFile9(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
4395
+ writeFile11(producerPath, new Uint8Array(await release.producer.arrayBuffer()))
3670
4396
  ]);
3671
4397
  };
3672
4398
  var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
3673
- const destination = join9(bundlesRoot, bundleId);
4399
+ const destination = join10(bundlesRoot, bundleId);
3674
4400
  try {
3675
4401
  await access8(destination);
3676
4402
  return destination;
@@ -3678,10 +4404,10 @@ var installImmutableBundle = async (bundlesRoot, bundleId, releases) => {
3678
4404
  if (!errorHasCode3(error, "ENOENT"))
3679
4405
  throw error;
3680
4406
  }
3681
- const staging = await mkdtemp5(join9(bundlesRoot, ".stage-"));
4407
+ const staging = await mkdtemp5(join10(bundlesRoot, ".stage-"));
3682
4408
  try {
3683
4409
  await Promise.all(releases.map((release) => writeRelease(staging, release)));
3684
- await rename9(staging, destination);
4410
+ await rename10(staging, destination);
3685
4411
  } catch (error) {
3686
4412
  await rm8(staging, { force: true, recursive: true });
3687
4413
  if (errorHasCode3(error, "EEXIST") || errorHasCode3(error, "ENOTEMPTY")) {
@@ -3711,16 +4437,16 @@ var resolveProducerHandler = (loaded, exportName) => {
3711
4437
  };
3712
4438
  };
3713
4439
  var loadAbsoluteMobileMaterializedBundle = async (root) => {
3714
- const resolvedRoot = resolvePath2(root);
3715
- const serialized = await readFile11(join9(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
4440
+ const resolvedRoot = resolvePath3(root);
4441
+ const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
3716
4442
  const parsed = JSON.parse(serialized);
3717
4443
  const index = parseBundleIndex(parsed);
3718
- const bundleRoot = join9(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
4444
+ const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
3719
4445
  return {
3720
4446
  artifacts: index.releases,
3721
4447
  currentReleaseId: index.currentReleaseId,
3722
4448
  loadProducer: async (artifact) => {
3723
- const modulePath = join9(bundleRoot, artifact.releaseId, artifact.producer.module);
4449
+ const modulePath = join10(bundleRoot, artifact.releaseId, artifact.producer.module);
3724
4450
  await verifyAbsoluteMobileCompatibilityProducer({
3725
4451
  artifact,
3726
4452
  producer: Bun.file(modulePath)
@@ -3746,9 +4472,9 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
3746
4472
  }
3747
4473
  return release;
3748
4474
  });
3749
- const root = resolvePath2(input.root);
3750
- const bundlesRoot = join9(root, BUNDLES_DIRECTORY);
3751
- await mkdir8(bundlesRoot, { recursive: true });
4475
+ const root = resolvePath3(input.root);
4476
+ const bundlesRoot = join10(root, BUNDLES_DIRECTORY);
4477
+ await mkdir10(bundlesRoot, { recursive: true });
3752
4478
  const bundleId = bundleIdFor(input.currentReleaseId, artifacts);
3753
4479
  await installImmutableBundle(bundlesRoot, bundleId, orderedReleases);
3754
4480
  const index = {
@@ -3757,22 +4483,22 @@ var materializeAbsoluteMobileCompatibilityBundle = async (input) => {
3757
4483
  format: ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT,
3758
4484
  releases: artifacts
3759
4485
  };
3760
- const pointerPath = join9(root, CURRENT_BUNDLE_FILE);
3761
- const temporaryPointerPath = join9(root, `.current-${crypto.randomUUID()}.json`);
3762
- await writeFile9(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
4486
+ const pointerPath = join10(root, CURRENT_BUNDLE_FILE);
4487
+ const temporaryPointerPath = join10(root, `.current-${crypto.randomUUID()}.json`);
4488
+ await writeFile11(temporaryPointerPath, `${JSON.stringify(index, null, "\t")}
3763
4489
  `, { flag: "wx" });
3764
- await rename9(temporaryPointerPath, pointerPath);
4490
+ await rename10(temporaryPointerPath, pointerPath);
3765
4491
  return index;
3766
4492
  };
3767
4493
  var readAbsoluteMobileMaterializedReleases = async (root) => {
3768
- const resolvedRoot = resolvePath2(root);
4494
+ const resolvedRoot = resolvePath3(root);
3769
4495
  try {
3770
- const serialized = await readFile11(join9(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
4496
+ const serialized = await readFile12(join10(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
3771
4497
  const parsed = JSON.parse(serialized);
3772
4498
  const index = parseBundleIndex(parsed);
3773
- const bundleRoot = join9(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
4499
+ const bundleRoot = join10(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
3774
4500
  return Promise.all(index.releases.map(async (artifact) => {
3775
- const producer = Bun.file(join9(bundleRoot, artifact.releaseId, artifact.producer.module));
4501
+ const producer = Bun.file(join10(bundleRoot, artifact.releaseId, artifact.producer.module));
3776
4502
  await verifyAbsoluteMobileCompatibilityProducer({
3777
4503
  artifact,
3778
4504
  producer
@@ -3786,6 +4512,58 @@ var readAbsoluteMobileMaterializedReleases = async (root) => {
3786
4512
  }
3787
4513
  };
3788
4514
 
4515
+ // src/mobile/nativeAuth.ts
4516
+ import { readFileSync as readFileSync2 } from "fs";
4517
+ import { join as join11 } from "path";
4518
+ var ABSOLUTE_AUTH_PACKAGE = "@absolutejs/auth";
4519
+ var ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV = "ABSOLUTE_AUTH_NATIVE_CLIENTS";
4520
+ var ABSOLUTE_NATIVE_AUTH_SCOPES = ["openid", "profile"];
4521
+ var ABSOLUTE_SYNC_PACKAGE = "@absolutejs/sync";
4522
+ var readPackageManifest = (projectRoot) => {
4523
+ try {
4524
+ return JSON.parse(readFileSync2(join11(projectRoot, "package.json"), "utf8"));
4525
+ } catch {
4526
+ return;
4527
+ }
4528
+ };
4529
+ var packageManifestHas = (manifest, packageName) => {
4530
+ if (typeof manifest !== "object" || manifest === null)
4531
+ return false;
4532
+ return [
4533
+ Reflect.get(manifest, "dependencies"),
4534
+ Reflect.get(manifest, "devDependencies"),
4535
+ Reflect.get(manifest, "optionalDependencies"),
4536
+ Reflect.get(manifest, "peerDependencies")
4537
+ ].some((dependencies) => typeof dependencies === "object" && dependencies !== null && Object.hasOwn(dependencies, packageName));
4538
+ };
4539
+ var createAbsoluteMobileAuthManifest = (config) => {
4540
+ const scheme = config.deepLinkScheme ?? config.appId.toLowerCase();
4541
+ return {
4542
+ clientId: `absolutejs-native:${config.appId}`,
4543
+ issuer: config.productionOrigin,
4544
+ redirectUri: `${scheme}://auth/callback`,
4545
+ scopes: [...ABSOLUTE_NATIVE_AUTH_SCOPES]
4546
+ };
4547
+ };
4548
+ var installAbsoluteMobileAuthEnvironment = (projectRoot, config) => {
4549
+ const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
4550
+ const serialized = serializeAbsoluteMobileAuthEnvironment(config, auth);
4551
+ if (serialized === undefined)
4552
+ delete process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV];
4553
+ else
4554
+ process.env[ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV] = serialized;
4555
+ return auth;
4556
+ };
4557
+ var projectUsesAbsoluteAuth = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_AUTH_PACKAGE);
4558
+ var projectUsesAbsoluteSync = (projectRoot) => packageManifestHas(readPackageManifest(projectRoot), ABSOLUTE_SYNC_PACKAGE);
4559
+ var resolveAbsoluteMobileAuthManifest = (projectRoot, config) => projectUsesAbsoluteAuth(projectRoot) ? createAbsoluteMobileAuthManifest(config) : undefined;
4560
+ var serializeAbsoluteMobileAuthEnvironment = (config, auth) => auth === undefined ? undefined : JSON.stringify([
4561
+ {
4562
+ ...auth,
4563
+ name: `${config.appName} native app`
4564
+ }
4565
+ ]);
4566
+
3789
4567
  // src/mobile/buildPipeline.ts
3790
4568
  var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes"));
3791
4569
  var isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string");
@@ -3796,12 +4574,11 @@ var serverExportName = (loaded, app) => {
3796
4574
  return "app";
3797
4575
  return "default";
3798
4576
  };
3799
- var restoreBuildDirectory = (previous) => {
3800
- if (previous !== undefined) {
3801
- process.env.ABSOLUTE_BUILD_DIR = previous;
3802
- return;
3803
- }
3804
- delete process.env.ABSOLUTE_BUILD_DIR;
4577
+ var restoreEnvironmentVariable = (name, previous) => {
4578
+ if (previous !== undefined)
4579
+ process.env[name] = previous;
4580
+ else
4581
+ delete process.env[name];
3805
4582
  };
3806
4583
  var requireRelease = (releases, releaseId) => {
3807
4584
  const release = releases.get(releaseId);
@@ -3823,9 +4600,9 @@ var loadServerApp = async (producerPath) => {
3823
4600
  var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
3824
4601
  const buildDirectory = resolve10(options.buildDirectory);
3825
4602
  const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
3826
- const root = join10(buildDirectory, ".absolutejs", "mobile-compatibility");
4603
+ const root = join12(buildDirectory, ".absolutejs", "mobile-compatibility");
3827
4604
  const [manifestSource, previous] = await Promise.all([
3828
- readFile12(join10(buildDirectory, "manifest.json"), "utf8"),
4605
+ readFile13(join12(buildDirectory, "manifest.json"), "utf8"),
3829
4606
  readAbsoluteMobileMaterializedReleases(root)
3830
4607
  ]);
3831
4608
  const manifest = JSON.parse(manifestSource);
@@ -3833,12 +4610,20 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
3833
4610
  throw new TypeError("Invalid AbsoluteJS build manifest for mobile capture.");
3834
4611
  }
3835
4612
  const previousBuildDirectory = process.env.ABSOLUTE_BUILD_DIR;
4613
+ const previousCompiledRuntime = process.env.ABSOLUTE_COMPILED_RUNTIME;
4614
+ const previousConfigPath = process.env.ABSOLUTE_CONFIG;
3836
4615
  process.env.ABSOLUTE_BUILD_DIR = buildDirectory;
4616
+ process.env.ABSOLUTE_COMPILED_RUNTIME = "1";
4617
+ if (options.configPath) {
4618
+ process.env.ABSOLUTE_CONFIG = resolve10(options.projectRoot, options.configPath);
4619
+ }
3837
4620
  let loaded;
3838
4621
  try {
3839
4622
  loaded = await loadServerApp(resolve10(options.producerPath));
3840
4623
  } finally {
3841
- restoreBuildDirectory(previousBuildDirectory);
4624
+ restoreEnvironmentVariable("ABSOLUTE_BUILD_DIR", previousBuildDirectory);
4625
+ restoreEnvironmentVariable("ABSOLUTE_COMPILED_RUNTIME", previousCompiledRuntime);
4626
+ restoreEnvironmentVariable("ABSOLUTE_CONFIG", previousConfigPath);
3842
4627
  }
3843
4628
  const current = await buildAbsoluteMobileCompatibilityRelease({
3844
4629
  app: loaded.app,
@@ -3850,6 +4635,11 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
3850
4635
  producerPath: resolve10(options.producerPath),
3851
4636
  runtime: String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)
3852
4637
  });
4638
+ const auth = resolveAbsoluteMobileAuthManifest(options.projectRoot, mobile);
4639
+ const sync = auth !== undefined && projectUsesAbsoluteSync(options.projectRoot);
4640
+ if (auth && !loaded.app.routes.some((route) => route.path === "/.well-known/openid-configuration")) {
4641
+ throw new TypeError("@absolutejs/auth is installed, but its OIDC provider is not mounted. Native authentication requires the auth oidc configuration so AbsoluteJS can provision a public PKCE client.");
4642
+ }
3853
4643
  const releasesById = new Map([current, ...previous].map((release) => [
3854
4644
  release.artifact.releaseId,
3855
4645
  release
@@ -3862,8 +4652,10 @@ var finalizeAbsoluteMobileCompatibilityBuild = async (options) => {
3862
4652
  });
3863
4653
  await materializeAbsoluteCapacitorWebBundle({
3864
4654
  artifact: current.artifact,
4655
+ ...auth ? { auth } : {},
3865
4656
  buildDirectory,
3866
- config: mobile
4657
+ config: mobile,
4658
+ ...sync ? { sync: true } : {}
3867
4659
  });
3868
4660
  return current.artifact;
3869
4661
  };
@@ -3888,6 +4680,64 @@ var ensureProducerStorage = () => {
3888
4680
  var runWithAbsoluteMobileProducer = (context, callback) => ensureProducerStorage().run(context, callback);
3889
4681
 
3890
4682
  // src/mobile/compatibilityDispatcher.ts
4683
+ var MOBILE_WEBVIEW_ORIGINS = new Set([
4684
+ "capacitor://localhost",
4685
+ "http://localhost",
4686
+ "https://localhost"
4687
+ ]);
4688
+ var MOBILE_REQUEST_HEADER_NAMES = Object.values(MOBILE_PAGE_REQUEST_HEADERS);
4689
+ var MOBILE_CORS_ALLOW_HEADERS = [
4690
+ "accept",
4691
+ "content-type",
4692
+ "authorization",
4693
+ "hx-current-url",
4694
+ "hx-request",
4695
+ "hx-target",
4696
+ "hx-trigger",
4697
+ "hx-trigger-name",
4698
+ ...MOBILE_REQUEST_HEADER_NAMES
4699
+ ].join(", ");
4700
+ var MOBILE_CORS_METHODS = new Set([
4701
+ "DELETE",
4702
+ "GET",
4703
+ "HEAD",
4704
+ "OPTIONS",
4705
+ "PATCH",
4706
+ "POST",
4707
+ "PUT"
4708
+ ]);
4709
+ var mobileWebViewOrigin = (request) => {
4710
+ const origin = request.headers.get("origin");
4711
+ return origin && MOBILE_WEBVIEW_ORIGINS.has(origin) ? origin : undefined;
4712
+ };
4713
+ var applyMobileCorsHeaders = (response, origin) => {
4714
+ response.headers.set("access-control-allow-credentials", "true");
4715
+ response.headers.set("access-control-allow-origin", origin);
4716
+ response.headers.append("vary", "Origin");
4717
+ return response;
4718
+ };
4719
+ var mobilePreflightResponse = (request) => {
4720
+ if (request.method !== "OPTIONS")
4721
+ return;
4722
+ const origin = mobileWebViewOrigin(request);
4723
+ if (!origin)
4724
+ return;
4725
+ const requestedHeaders = request.headers.get("access-control-request-headers");
4726
+ const requestedMethod = request.headers.get("access-control-request-method")?.toUpperCase() ?? "";
4727
+ if (!MOBILE_CORS_METHODS.has(requestedMethod))
4728
+ return;
4729
+ return new Response(null, {
4730
+ headers: {
4731
+ "access-control-allow-credentials": "true",
4732
+ "access-control-allow-headers": requestedHeaders || MOBILE_CORS_ALLOW_HEADERS,
4733
+ "access-control-allow-methods": [...MOBILE_CORS_METHODS].join(", "),
4734
+ "access-control-allow-origin": origin,
4735
+ "access-control-max-age": "600",
4736
+ vary: "Origin, Access-Control-Request-Headers"
4737
+ },
4738
+ status: 204
4739
+ });
4740
+ };
3891
4741
  var artifactOwnsRequest = (artifact, pageId, request) => {
3892
4742
  const { pathname } = new URL(request.url);
3893
4743
  return artifact.routes.some((route) => route.pageId === pageId && route.method === request.method && matchesAbsoluteMobileRoutePattern(route.pattern, pathname));
@@ -3915,6 +4765,9 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
3915
4765
  return new Elysia2({ name: "absolutejs-mobile-compatibility-dispatcher" }).request(async ({ request }) => {
3916
4766
  if (getCurrentAbsoluteMobileProducerContext())
3917
4767
  return;
4768
+ const preflight = mobilePreflightResponse(request);
4769
+ if (preflight)
4770
+ return preflight;
3918
4771
  const parsed = parseAbsoluteMobilePageRequest(request);
3919
4772
  if (parsed.kind !== "mobile")
3920
4773
  return;
@@ -3938,23 +4791,28 @@ var createAbsoluteMobileCompatibilityDispatcher = (options) => {
3938
4791
  console.error(`[Mobile] Failed to load retained producer ${resolved.artifact.releaseId}:`, error);
3939
4792
  return createAbsoluteMobilePageErrorResponse(parsed.client.pageId);
3940
4793
  }
4794
+ }).afterHandle("global", ({ request, responseValue }) => {
4795
+ const origin = mobileWebViewOrigin(request);
4796
+ if (!origin || !(responseValue instanceof Response))
4797
+ return;
4798
+ applyMobileCorsHeaders(responseValue, origin);
3941
4799
  }).as("global");
3942
4800
  };
3943
4801
  // src/mobile/nativeDeepLinks.ts
3944
- import { readFile as readFile13, rename as rename10, writeFile as writeFile10 } from "fs/promises";
3945
- import { join as join11 } from "path";
4802
+ import { readFile as readFile14, rename as rename11, writeFile as writeFile12 } from "fs/promises";
4803
+ import { join as join13 } from "path";
3946
4804
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
3947
4805
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
3948
4806
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
3949
4807
  var NOT_FOUND = -1;
3950
4808
  var escapeXml = (value) => value.replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("'", "&apos;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
3951
4809
  var writeChangedFile = async (path, source) => {
3952
- const current = await readFile13(path, "utf8");
4810
+ const current = await readFile14(path, "utf8");
3953
4811
  if (current === source)
3954
4812
  return false;
3955
4813
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
3956
- await writeFile10(temporary, source, { flag: "wx" });
3957
- await rename10(temporary, path);
4814
+ await writeFile12(temporary, source, { flag: "wx" });
4815
+ await rename11(temporary, path);
3958
4816
  return true;
3959
4817
  };
3960
4818
  var replaceManagedRegion = (source, region, insertAt) => {
@@ -3999,8 +4857,8 @@ ${hosts}
3999
4857
  `;
4000
4858
  };
4001
4859
  var configureAndroid = async (config) => {
4002
- const path = join11(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
4003
- const source = await readFile13(path, "utf8");
4860
+ const path = join13(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
4861
+ const source = await readFile14(path, "utf8");
4004
4862
  const mainActivity = source.indexOf('android:name=".MainActivity"');
4005
4863
  if (mainActivity === NOT_FOUND) {
4006
4864
  throw new TypeError("Android MainActivity was not found.");
@@ -4025,8 +4883,8 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
4025
4883
  ${END_MARKER}
4026
4884
  `;
4027
4885
  var configureIosInfo = async (config) => {
4028
- const path = join11(config.nativeProjectDirectory, "ios/App/App/Info.plist");
4029
- const source = await readFile13(path, "utf8");
4886
+ const path = join13(config.nativeProjectDirectory, "ios/App/App/Info.plist");
4887
+ const source = await readFile14(path, "utf8");
4030
4888
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
4031
4889
  ${END_MARKER}
4032
4890
  `;
@@ -4049,10 +4907,10 @@ ${domains}
4049
4907
  `;
4050
4908
  };
4051
4909
  var configureIosEntitlements = async (config) => {
4052
- const path = join11(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
4910
+ const path = join13(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
4053
4911
  let current = "";
4054
4912
  try {
4055
- current = await readFile13(path, "utf8");
4913
+ current = await readFile14(path, "utf8");
4056
4914
  } catch (error) {
4057
4915
  if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
4058
4916
  throw error;
@@ -4062,13 +4920,13 @@ var configureIosEntitlements = async (config) => {
4062
4920
  if (current === source)
4063
4921
  return false;
4064
4922
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
4065
- await writeFile10(temporary, source, { flag: "wx" });
4066
- await rename10(temporary, path);
4923
+ await writeFile12(temporary, source, { flag: "wx" });
4924
+ await rename11(temporary, path);
4067
4925
  return true;
4068
4926
  };
4069
4927
  var configureIosProject = async (config) => {
4070
- const path = join11(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
4071
- const source = await readFile13(path, "utf8");
4928
+ const path = join13(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
4929
+ const source = await readFile14(path, "utf8");
4072
4930
  const declarations = [
4073
4931
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
4074
4932
  ].map((match) => match[1]);
@@ -4107,7 +4965,7 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
4107
4965
  };
4108
4966
  // src/mobile/releasePublisher.ts
4109
4967
  import { access as access9 } from "fs/promises";
4110
- import { isAbsolute as isAbsolute5, relative as relative7, resolve as resolve11, sep as sep5 } from "path";
4968
+ import { isAbsolute as isAbsolute6, relative as relative9, resolve as resolve11, sep as sep6 } from "path";
4111
4969
  import { pathToFileURL as pathToFileURL3 } from "url";
4112
4970
  var prepareAbsoluteIosRelease = async (publisher, options) => {
4113
4971
  if (typeof publisher.prepareIosRelease !== "function") {
@@ -4135,8 +4993,8 @@ var isPublisher = (value) => isRecord8(value) && typeof value.publish === "funct
4135
4993
  var publisherModulePath = (projectRoot, requested) => {
4136
4994
  const root = resolve11(projectRoot);
4137
4995
  const path = resolve11(root, requested);
4138
- const projectRelative = relative7(root, path);
4139
- if (projectRelative === ".." || projectRelative.startsWith(`..${sep5}`) || isAbsolute5(projectRelative)) {
4996
+ const projectRelative = relative9(root, path);
4997
+ if (projectRelative === ".." || projectRelative.startsWith(`..${sep6}`) || isAbsolute6(projectRelative)) {
4140
4998
  throw new TypeError("mobile publish --registry must remain inside the project.");
4141
4999
  }
4142
5000
  return path;
@@ -4204,14 +5062,59 @@ var publishAbsoluteIosRelease = async (options) => {
4204
5062
  return publication;
4205
5063
  };
4206
5064
  // src/mobile/routeMetadataTransform.ts
4207
- import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
4208
- import { dirname as dirname7, extname as extname2, relative as relative8, resolve as resolve12 } from "path";
5065
+ import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
5066
+ import { dirname as dirname9, extname as extname3, relative as relative10, resolve as resolve12 } from "path";
4209
5067
  import ts from "typescript";
4210
5068
  var ROUTE_METHODS = new Set(["get", "head"]);
4211
5069
  var SOURCE_FILTER = /\.[cm]?[jt]sx?$/;
4212
- var PAGE_HANDLER = "handleReactPageRequest";
5070
+ var PAGE_HANDLERS = new Map([
5071
+ [
5072
+ "handleHTMLPageRequest",
5073
+ { framework: "html", inputKind: "static", propsProperty: "props" }
5074
+ ],
5075
+ [
5076
+ "handleHTMXPageRequest",
5077
+ { framework: "htmx", inputKind: "static", propsProperty: "props" }
5078
+ ],
5079
+ [
5080
+ "handleAngularPageRequest",
5081
+ {
5082
+ bundleProperty: "indexPath",
5083
+ framework: "angular",
5084
+ propsProperty: "requestContext",
5085
+ sourceProperty: "pagePath"
5086
+ }
5087
+ ],
5088
+ [
5089
+ "handleReactPageRequest",
5090
+ {
5091
+ bundleProperty: "index",
5092
+ framework: "react",
5093
+ pageProperty: "Page",
5094
+ propsProperty: "props"
5095
+ }
5096
+ ],
5097
+ [
5098
+ "handleSveltePageRequest",
5099
+ {
5100
+ bundleProperty: "indexPath",
5101
+ framework: "svelte",
5102
+ propsProperty: "props",
5103
+ sourceProperty: "pagePath"
5104
+ }
5105
+ ],
5106
+ [
5107
+ "handleVuePageRequest",
5108
+ {
5109
+ bundleProperty: "indexPath",
5110
+ framework: "vue",
5111
+ propsProperty: "props",
5112
+ sourceProperty: "pagePath"
5113
+ }
5114
+ ]
5115
+ ]);
4213
5116
  var posixPath = (value) => value.replace(/\\/g, "/");
4214
- var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname7(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
5117
+ var findTsconfig = (entry, projectRoot) => ts.findConfigFile(dirname9(entry), existsSync3, "tsconfig.json") ?? ts.findConfigFile(projectRoot, existsSync3, "tsconfig.json");
4215
5118
  var createProgram = (entry, projectRoot) => {
4216
5119
  const configPath = findTsconfig(entry, projectRoot);
4217
5120
  if (!configPath) {
@@ -4223,7 +5126,7 @@ var createProgram = (entry, projectRoot) => {
4223
5126
  target: ts.ScriptTarget.ESNext
4224
5127
  });
4225
5128
  }
4226
- const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync2(path, "utf8")).config, ts.sys, dirname7(configPath));
5129
+ const parsed = ts.parseJsonConfigFileContent(ts.readConfigFile(configPath, (path) => readFileSync3(path, "utf8")).config, ts.sys, dirname9(configPath));
4227
5130
  if (!parsed.fileNames.includes(entry))
4228
5131
  parsed.fileNames.push(entry);
4229
5132
  return ts.createProgram(parsed.fileNames, parsed.options);
@@ -4329,7 +5232,7 @@ var resolvePageIdentity = (expression, sourceFile, checker, projectRoot) => {
4329
5232
  const declaration = symbol?.declarations?.[0];
4330
5233
  const file = declaration?.getSourceFile().fileName ?? sourceFile.fileName;
4331
5234
  const exportedName = symbol?.name ?? expression.getText(sourceFile);
4332
- const source = posixPath(relative8(projectRoot, file));
5235
+ const source = posixPath(relative10(projectRoot, file));
4333
5236
  return `${source}#${exportedName}`;
4334
5237
  };
4335
5238
  var resolveAlias = (symbol, checker) => {
@@ -4361,13 +5264,108 @@ var assetKey = (expression, checker, seen = new Set) => {
4361
5264
  const [, key] = expression.arguments;
4362
5265
  return key && ts.isStringLiteralLike(key) ? key.text : undefined;
4363
5266
  };
5267
+ var staticString = (expression, bindings) => {
5268
+ if (ts.isStringLiteralLike(expression))
5269
+ return expression.text;
5270
+ if (ts.isIdentifier(expression))
5271
+ return bindings.get(expression.text);
5272
+ if (ts.isNoSubstitutionTemplateLiteral(expression))
5273
+ return expression.text;
5274
+ if (!ts.isTemplateExpression(expression))
5275
+ return;
5276
+ let value = expression.head.text;
5277
+ for (const span of expression.templateSpans) {
5278
+ const substitution = staticString(span.expression, bindings);
5279
+ if (substitution === undefined)
5280
+ return;
5281
+ value += substitution + span.literal.text;
5282
+ }
5283
+ return value;
5284
+ };
5285
+ var assetKeyWithBindings = (expression, checker, bindings = new Map) => {
5286
+ if (!expression)
5287
+ return;
5288
+ if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression) && expression.expression.text === "asset") {
5289
+ const [, key] = expression.arguments;
5290
+ return key ? staticString(key, bindings) : undefined;
5291
+ }
5292
+ return assetKey(expression, checker);
5293
+ };
5294
+ var callableObject = (call, checker) => {
5295
+ const symbol = checker.getSymbolAtLocation(call.expression);
5296
+ const resolved = symbol ? resolveAlias(symbol, checker) : undefined;
5297
+ const declaration = resolved?.valueDeclaration ?? resolved?.declarations?.[0];
5298
+ let callable;
5299
+ if (declaration && ts.isFunctionDeclaration(declaration)) {
5300
+ callable = declaration;
5301
+ } else if (declaration && ts.isVariableDeclaration(declaration) && declaration.initializer && (ts.isArrowFunction(declaration.initializer) || ts.isFunctionExpression(declaration.initializer))) {
5302
+ callable = declaration.initializer;
5303
+ }
5304
+ if (!callable)
5305
+ return;
5306
+ const bindings = new Map;
5307
+ callable.parameters.forEach((parameter, index) => {
5308
+ if (!ts.isIdentifier(parameter.name))
5309
+ return;
5310
+ const argument = call.arguments[index];
5311
+ if (!argument)
5312
+ return;
5313
+ const value = staticString(argument, new Map);
5314
+ if (value !== undefined)
5315
+ bindings.set(parameter.name.text, value);
5316
+ });
5317
+ const { body } = callable;
5318
+ if (!body)
5319
+ return;
5320
+ const expressionBody = ts.isParenthesizedExpression(body) ? body.expression : body;
5321
+ if (ts.isObjectLiteralExpression(expressionBody)) {
5322
+ return { bindings, object: expressionBody };
5323
+ }
5324
+ if (ts.isBlock(body)) {
5325
+ const returned = body.statements.find(ts.isReturnStatement)?.expression;
5326
+ if (returned && ts.isObjectLiteralExpression(returned)) {
5327
+ return { bindings, object: returned };
5328
+ }
5329
+ }
5330
+ return;
5331
+ };
5332
+ var spreadObject = (expression, checker, bindings) => {
5333
+ if (ts.isObjectLiteralExpression(expression)) {
5334
+ return { bindings, object: expression };
5335
+ }
5336
+ if (!ts.isCallExpression(expression))
5337
+ return;
5338
+ return callableObject(expression, checker);
5339
+ };
5340
+ var objectAssetKey = (object, name, checker, bindings = new Map) => {
5341
+ for (const property of [...object.properties].reverse()) {
5342
+ if (propertyName(property) === name && ts.isShorthandPropertyAssignment(property)) {
5343
+ return assetKeyWithBindings(property.name, checker, bindings);
5344
+ }
5345
+ if (propertyName(property) === name && ts.isPropertyAssignment(property)) {
5346
+ return assetKeyWithBindings(property.initializer, checker, bindings);
5347
+ }
5348
+ if (!ts.isSpreadAssignment(property))
5349
+ continue;
5350
+ const nestedObject = spreadObject(property.expression, checker, bindings);
5351
+ if (!nestedObject)
5352
+ continue;
5353
+ const nested = objectAssetKey(nestedObject.object, name, checker, nestedObject.bindings);
5354
+ if (nested)
5355
+ return nested;
5356
+ }
5357
+ return;
5358
+ };
4364
5359
  var findPageCall = (nodes) => {
4365
5360
  let found;
4366
5361
  const visit = (candidate) => {
4367
5362
  if (found)
4368
5363
  return;
4369
- if (ts.isCallExpression(candidate) && ts.isIdentifier(candidate.expression) && candidate.expression.text === PAGE_HANDLER) {
4370
- found = candidate;
5364
+ if (ts.isCallExpression(candidate) && ts.isIdentifier(candidate.expression) && PAGE_HANDLERS.has(candidate.expression.text)) {
5365
+ const definition = PAGE_HANDLERS.get(candidate.expression.text);
5366
+ if (!definition)
5367
+ return;
5368
+ found = { definition, node: candidate };
4371
5369
  return;
4372
5370
  }
4373
5371
  ts.forEachChild(candidate, visit);
@@ -4386,30 +5384,67 @@ var analyzeRouteCall = (node, sourceFile, checker, projectRoot) => {
4386
5384
  const [routePath] = node.arguments;
4387
5385
  if (!routePath || !ts.isStringLiteralLike(routePath))
4388
5386
  return;
4389
- const pageCall = findPageCall(node.arguments.slice(1));
5387
+ const foundPageCall = findPageCall(node.arguments.slice(1));
5388
+ const pageCall = foundPageCall?.node;
5389
+ const definition = foundPageCall?.definition;
4390
5390
  const [input] = pageCall?.arguments ?? [];
4391
- if (!pageCall || !input || !ts.isObjectLiteralExpression(input)) {
5391
+ if (!pageCall || !input) {
4392
5392
  return;
4393
5393
  }
4394
- const page = objectPropertyExpression(input, "Page");
4395
- if (!page)
5394
+ if (!definition)
4396
5395
  return;
4397
- const props = objectPropertyExpression(input, "props");
4398
- const index = objectPropertyExpression(input, "index");
4399
- const bundleKey = assetKey(index, checker);
5396
+ if (definition.inputKind === "static") {
5397
+ const bundleKey2 = assetKey(input, checker);
5398
+ if (!bundleKey2)
5399
+ return;
5400
+ const pageId2 = `${definition.framework}:${bundleKey2}`;
5401
+ const propsSchemaHash2 = hashAbsoluteMobilePropsSchema({
5402
+ properties: {},
5403
+ type: "object"
5404
+ });
5405
+ return {
5406
+ inputKind: "static",
5407
+ metadata: {
5408
+ bundleKey: bundleKey2,
5409
+ contract: `${definition.framework}:${pageId2}:${propsSchemaHash2}`,
5410
+ framework: definition.framework,
5411
+ pageId: pageId2,
5412
+ propsSchemaHash: propsSchemaHash2
5413
+ },
5414
+ pageCallStart: pageCall.getStart(sourceFile),
5415
+ routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
5416
+ };
5417
+ }
5418
+ if (!ts.isObjectLiteralExpression(input) || !definition.bundleProperty) {
5419
+ return;
5420
+ }
5421
+ const page = definition.pageProperty ? objectPropertyExpression(input, definition.pageProperty) : undefined;
5422
+ const source = definition.sourceProperty ? objectAssetKey(input, definition.sourceProperty, checker) : undefined;
5423
+ if (definition.pageProperty && !page)
5424
+ return;
5425
+ if (definition.sourceProperty && !source)
5426
+ return;
5427
+ const props = objectPropertyExpression(input, definition.propsProperty);
5428
+ const bundleKey = objectAssetKey(input, definition.bundleProperty, checker);
4400
5429
  if (!bundleKey)
4401
5430
  return;
4402
- const pageId = resolvePageIdentity(page, sourceFile, checker, projectRoot);
4403
- const schema = serializeType(pagePropsType(page, props, checker), checker);
5431
+ const pageId = page ? resolvePageIdentity(page, sourceFile, checker, projectRoot) : `${definition.framework}:${source}`;
5432
+ let propsType;
5433
+ if (page)
5434
+ propsType = pagePropsType(page, props, checker);
5435
+ else if (props)
5436
+ propsType = checker.getTypeAtLocation(props);
5437
+ const schema = propsType ? serializeType(propsType, checker) : { properties: {}, type: "object" };
4404
5438
  const propsSchemaHash = hashAbsoluteMobilePropsSchema(schema);
4405
5439
  const metadata = {
4406
5440
  bundleKey,
4407
- contract: `react:${pageId}:${propsSchemaHash}`,
4408
- framework: "react",
5441
+ contract: `${definition.framework}:${pageId}:${propsSchemaHash}`,
5442
+ framework: definition.framework,
4409
5443
  pageId,
4410
5444
  propsSchemaHash
4411
5445
  };
4412
5446
  const result = {
5447
+ inputKind: "object",
4413
5448
  metadata,
4414
5449
  pageCallStart: pageCall.getStart(sourceFile),
4415
5450
  routeCallSpan: `${node.getStart(sourceFile)}:${node.end}`
@@ -4466,6 +5501,16 @@ var routeOptions = (existing, metadata) => {
4466
5501
  var transformPageCall = (node, page) => {
4467
5502
  if (!page)
4468
5503
  return;
5504
+ if (page.inputKind === "static") {
5505
+ const [pagePath, existingOptions, ...rest] = node.arguments;
5506
+ if (!pagePath)
5507
+ return;
5508
+ const options = ts.factory.createObjectLiteralExpression([
5509
+ ...existingOptions ? [ts.factory.createSpreadAssignment(existingOptions)] : [],
5510
+ ts.factory.createPropertyAssignment("__absoluteMobile", metadataExpression(page.metadata))
5511
+ ]);
5512
+ return ts.factory.updateCallExpression(node, node.expression, node.typeArguments, [pagePath, options, ...rest]);
5513
+ }
4469
5514
  const [input] = node.arguments;
4470
5515
  if (!input || !ts.isObjectLiteralExpression(input))
4471
5516
  return;
@@ -4532,7 +5577,7 @@ var createAbsoluteMobileRouteMetadataPlugin = (options) => {
4532
5577
  const source = await Bun.file(path).text();
4533
5578
  return {
4534
5579
  contents: transformFile(source, path, analysis),
4535
- loader: extname2(path).endsWith("x") ? "tsx" : "ts"
5580
+ loader: extname3(path).endsWith("x") ? "tsx" : "ts"
4536
5581
  };
4537
5582
  });
4538
5583
  }
@@ -4543,7 +5588,7 @@ var inspectAbsoluteMobileRouteMetadata = (options) => {
4543
5588
  const entry = resolve12(options.entry);
4544
5589
  const analyzed = analyzeProgram(createProgram(entry, projectRoot), projectRoot);
4545
5590
  return [...analyzed.entries()].flatMap(([file, analysis]) => [...analysis.byRouteCall.values()].map(({ metadata }) => ({
4546
- file: posixPath(relative8(projectRoot, file)),
5591
+ file: posixPath(relative10(projectRoot, file)),
4547
5592
  metadata
4548
5593
  })));
4549
5594
  };
@@ -4552,17 +5597,27 @@ export {
4552
5597
  waitForAbsoluteIosHmrLog,
4553
5598
  verifyAbsoluteMobileCompatibilityProducer,
4554
5599
  verifyAbsoluteMobileAssociationFiles,
5600
+ validateAbsoluteSshDestination,
5601
+ validateAbsoluteRemoteMacProfileName,
5602
+ syncAbsoluteRemoteMacProject,
5603
+ startAbsoluteRemoteIosDevSession,
4555
5604
  startAbsoluteIosDevSession,
5605
+ serializeAbsoluteMobileAuthEnvironment,
4556
5606
  runWithAbsoluteMobileProducer,
4557
5607
  retainAbsoluteMobileCompatibilityArtifacts,
4558
5608
  resolveAbsoluteMobileRoute,
5609
+ resolveAbsoluteMobileNavigation,
4559
5610
  resolveAbsoluteMobileDeepLink,
4560
5611
  resolveAbsoluteMobileCompatibilityRelease,
5612
+ resolveAbsoluteMobileAuthManifest,
4561
5613
  repairAbsoluteIosDevSession,
5614
+ removeAbsoluteRemoteMacProfile,
4562
5615
  redactAbsoluteIosLog,
4563
5616
  readAbsoluteMobileMaterializedReleases,
4564
5617
  publishAbsoluteIosRelease,
4565
5618
  publishAbsoluteAndroidRelease,
5619
+ projectUsesAbsoluteSync,
5620
+ projectUsesAbsoluteAuth,
4566
5621
  prepareAbsoluteIosRelease,
4567
5622
  prepareAbsoluteIosDevProject,
4568
5623
  prepareAbsoluteAndroidRelease,
@@ -4575,23 +5630,32 @@ export {
4575
5630
  parseAbsoluteMobileBuildPageMetadata,
4576
5631
  parseAbsoluteIosLogLine,
4577
5632
  parseAbsoluteIosHmrLog,
5633
+ pairAbsoluteRemoteMac,
4578
5634
  normalizeAbsoluteMobileConfig,
4579
5635
  navigateAbsoluteMobilePage,
5636
+ materializeAbsoluteRemoteMacAgent,
4580
5637
  materializeAbsoluteMobileCompatibilityBundle,
4581
5638
  materializeAbsoluteMobileAssociationFiles,
4582
5639
  materializeAbsoluteCapacitorWebBundle,
4583
5640
  matchesAbsoluteMobileRoutePattern,
4584
5641
  loadAbsoluteNativeReleasePublisher,
4585
5642
  loadAbsoluteMobileMaterializedBundle,
5643
+ listAbsoluteRemoteMacProfiles,
4586
5644
  isAbsoluteIosNativeRootInput,
5645
+ installAbsoluteRemoteMacAgent,
5646
+ installAbsoluteMobileAuthEnvironment,
5647
+ inspectAbsoluteRemoteMac,
4587
5648
  inspectAbsoluteMobileRouteMetadata,
4588
5649
  hashAbsoluteMobilePropsSchema,
4589
5650
  getCurrentAbsoluteMobileProducerContext,
5651
+ getAbsoluteRemoteMacProfile,
4590
5652
  fingerprintAbsoluteIosNativeProject,
4591
5653
  fingerprintAbsoluteIosDevProject,
4592
5654
  finalizeAbsoluteMobilePage,
4593
5655
  finalizeAbsoluteMobileCompatibilityBuild,
4594
5656
  fetchAbsoluteMobilePage,
5657
+ disposeAbsoluteMobilePage,
5658
+ createAbsoluteRemoteIosDevProject,
4595
5659
  createAbsoluteMobileUpgradeResponse,
4596
5660
  createAbsoluteMobileRouteMetadataPlugin,
4597
5661
  createAbsoluteMobilePageRequest,
@@ -4601,6 +5665,7 @@ export {
4601
5665
  createAbsoluteMobileCompatibilityDispatcher,
4602
5666
  createAbsoluteMobileCompatibilityArtifact,
4603
5667
  createAbsoluteMobileBlobArtifactStore,
5668
+ createAbsoluteMobileAuthManifest,
4604
5669
  createAbsoluteMobileAssociationPlugin,
4605
5670
  createAbsoluteMobileAssociationDocuments,
4606
5671
  createAbsoluteIosNativeWatcher,
@@ -4612,10 +5677,17 @@ export {
4612
5677
  applyAbsoluteNativeDeepLinks,
4613
5678
  activateAbsoluteMobilePage,
4614
5679
  acceptsAbsoluteMobilePage,
5680
+ absoluteRemoteProjectSyncCommands,
5681
+ absoluteRemoteMacSshBase,
4615
5682
  MOBILE_PAGE_REQUEST_HEADERS,
4616
5683
  AbsoluteMobilePageProtocolError,
4617
5684
  APPLE_ASSOCIATION_PATH,
4618
5685
  ANDROID_ASSOCIATION_PATH,
5686
+ ABSOLUTE_SYNC_PACKAGE,
5687
+ ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION,
5688
+ ABSOLUTE_REMOTE_MAC_EVENT_PREFIX,
5689
+ ABSOLUTE_NATIVE_AUTH_SCOPES,
5690
+ ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
4619
5691
  ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
4620
5692
  ABSOLUTE_MOBILE_ROUTE_DETAIL,
4621
5693
  ABSOLUTE_MOBILE_RETAINED_GENERATIONS,
@@ -4626,8 +5698,9 @@ export {
4626
5698
  ABSOLUTE_MOBILE_CLIENT_MANIFEST_FORMAT,
4627
5699
  ABSOLUTE_IOS_SIMULATOR_NAME,
4628
5700
  ABSOLUTE_IOS_RELEASE_FORMAT,
5701
+ ABSOLUTE_AUTH_PACKAGE,
4629
5702
  ABSOLUTE_ANDROID_RELEASE_FORMAT
4630
5703
  };
4631
5704
 
4632
- //# debugId=048422942BB9C32864756E2164756E21
5705
+ //# debugId=7AFC88B0A927AFE564756E2164756E21
4633
5706
  //# sourceMappingURL=index.js.map