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