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