@absolutejs/absolute 0.20.0-beta.32 → 0.20.0-beta.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/cli/index.js +777 -217
- package/dist/index.js +41 -27
- package/dist/index.js.map +4 -4
- package/dist/mobile/index.js +449 -91
- package/dist/mobile/index.js.map +8 -7
- package/dist/mobile/remoteMacAgentEntry.js +14 -14
- package/dist/src/cli/scripts/dev.d.ts +2 -0
- package/dist/src/dev/devCert.d.ts +6 -4
- package/dist/src/mobile/androidEmulatorController.d.ts +8 -1
- package/dist/src/mobile/index.d.ts +1 -0
- package/dist/src/mobile/iosPhysicalDeviceTransport.d.ts +16 -0
- package/dist/src/mobile/iosSimulatorController.d.ts +14 -1
- package/dist/src/mobile/remoteMacProtocol.d.ts +4 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1290,11 +1290,16 @@ import {
|
|
|
1290
1290
|
sep,
|
|
1291
1291
|
win32
|
|
1292
1292
|
} from "path";
|
|
1293
|
-
var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_FORMAT = 1, NATIVE_CACHE_FORMAT = 1, HASH_RADIX = 16, EXECUTABLE_MODE_MASK = 73, NATIVE_PUBLIC_PATH_SEGMENTS = 5, CAPACITOR_PROJECT_DIRECTORY_PATTERN, ANDROID_TIMING_PHASES, androidTimingSummary = (timings) => ANDROID_TIMING_PHASES.map(([phase, label]) => {
|
|
1293
|
+
var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_FORMAT = 1, NATIVE_CACHE_FORMAT = 1, HASH_RADIX = 16, EXECUTABLE_MODE_MASK = 73, NATIVE_PUBLIC_PATH_SEGMENTS = 5, CAPACITOR_PROJECT_DIRECTORY_PATTERN, ANDROID_TIMING_PHASES, androidTimingSummary = (timings, physicalDevice = false) => ANDROID_TIMING_PHASES.map(([phase, label]) => {
|
|
1294
1294
|
const duration = timings[phase];
|
|
1295
1295
|
if (duration === undefined || label === undefined)
|
|
1296
1296
|
return null;
|
|
1297
|
-
|
|
1297
|
+
let phaseLabel = label;
|
|
1298
|
+
if (physicalDevice && phase === "booting")
|
|
1299
|
+
phaseLabel = "device selection";
|
|
1300
|
+
if (physicalDevice && phase === "forwarding")
|
|
1301
|
+
phaseLabel = "LAN transport";
|
|
1302
|
+
return `${phaseLabel} ${getDurationString(duration)}`;
|
|
1298
1303
|
}).filter((value) => value !== null).join(", "), pathExists2 = async (path) => {
|
|
1299
1304
|
try {
|
|
1300
1305
|
await access3(path);
|
|
@@ -1454,8 +1459,10 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1454
1459
|
const root = join7(projectRoot, ".absolutejs", "mobile", "dev-session");
|
|
1455
1460
|
return {
|
|
1456
1461
|
backup: join7(root, "capacitor.config.backup.json"),
|
|
1462
|
+
caBackup: join7(root, "absolutejs_dev_ca.backup.pem"),
|
|
1457
1463
|
journal: join7(root, "journal.json"),
|
|
1458
1464
|
manifestBackup: join7(root, "AndroidManifest.backup.xml"),
|
|
1465
|
+
networkConfigBackup: join7(root, "absolutejs_dev_network_security.backup.xml"),
|
|
1459
1466
|
root
|
|
1460
1467
|
};
|
|
1461
1468
|
}, isInside = (root, path) => {
|
|
@@ -1565,19 +1572,31 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1565
1572
|
const manifestBackupPath = Reflect.get(value, "manifestBackupPath");
|
|
1566
1573
|
const nativeConfigPath = Reflect.get(value, "nativeConfigPath");
|
|
1567
1574
|
const nativeManifestPath = Reflect.get(value, "nativeManifestPath");
|
|
1575
|
+
const projectedFiles = Reflect.get(value, "projectedFiles");
|
|
1568
1576
|
if (typeof backupPath !== "string" || format !== DEV_JOURNAL_FORMAT || typeof nativeConfigPath !== "string") {
|
|
1569
1577
|
return null;
|
|
1570
1578
|
}
|
|
1571
1579
|
if ((manifestBackupPath !== undefined || nativeManifestPath !== undefined) && (typeof manifestBackupPath !== "string" || typeof nativeManifestPath !== "string")) {
|
|
1572
1580
|
return null;
|
|
1573
1581
|
}
|
|
1582
|
+
if (projectedFiles !== undefined && (!Array.isArray(projectedFiles) || !projectedFiles.every((file) => isRecord(file) && typeof file.path === "string" && (file.backupPath === undefined || typeof file.backupPath === "string")))) {
|
|
1583
|
+
return null;
|
|
1584
|
+
}
|
|
1574
1585
|
return {
|
|
1575
1586
|
backupPath,
|
|
1576
1587
|
format,
|
|
1577
1588
|
manifestBackupPath,
|
|
1578
1589
|
nativeConfigPath,
|
|
1579
|
-
nativeManifestPath
|
|
1590
|
+
nativeManifestPath,
|
|
1591
|
+
projectedFiles
|
|
1580
1592
|
};
|
|
1593
|
+
}, restoreAndroidProjectedFile = async (file) => {
|
|
1594
|
+
if (!file.backupPath || !await pathExists2(file.backupPath)) {
|
|
1595
|
+
await rm(file.path, { force: true });
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
await mkdir(dirname3(file.path), { recursive: true });
|
|
1599
|
+
await copyFile(file.backupPath, file.path);
|
|
1581
1600
|
}, repairAbsoluteAndroidDevSession = async (projectRoot) => {
|
|
1582
1601
|
const paths = journalPaths(projectRoot);
|
|
1583
1602
|
if (!await pathExists2(paths.journal)) {
|
|
@@ -1585,7 +1604,7 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1585
1604
|
return false;
|
|
1586
1605
|
}
|
|
1587
1606
|
const journal = await readFile2(paths.journal, "utf8").then((source) => parseJournal(JSON.parse(source))).catch(() => null);
|
|
1588
|
-
if (!journal || !isInside(projectRoot, journal.nativeConfigPath) || !isInside(paths.root, journal.backupPath) || journal.nativeManifestPath !== undefined && !isInside(projectRoot, journal.nativeManifestPath) || journal.manifestBackupPath !== undefined && !isInside(paths.root, journal.manifestBackupPath)) {
|
|
1607
|
+
if (!journal || !isInside(projectRoot, journal.nativeConfigPath) || !isInside(paths.root, journal.backupPath) || journal.nativeManifestPath !== undefined && !isInside(projectRoot, journal.nativeManifestPath) || journal.manifestBackupPath !== undefined && !isInside(paths.root, journal.manifestBackupPath) || journal.projectedFiles?.some((file) => !isInside(projectRoot, file.path) || file.backupPath !== undefined && !isInside(paths.root, file.backupPath))) {
|
|
1589
1608
|
throw new Error(`Refusing unsafe or invalid mobile dev journal at ${paths.journal}.`);
|
|
1590
1609
|
}
|
|
1591
1610
|
if (await pathExists2(journal.backupPath)) {
|
|
@@ -1596,17 +1615,51 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1596
1615
|
await mkdir(dirname3(journal.nativeManifestPath), { recursive: true });
|
|
1597
1616
|
await copyFile(journal.manifestBackupPath, journal.nativeManifestPath);
|
|
1598
1617
|
}
|
|
1618
|
+
await Promise.all((journal.projectedFiles ?? []).map(restoreAndroidProjectedFile));
|
|
1599
1619
|
await rm(paths.root, { force: true, recursive: true });
|
|
1600
1620
|
return true;
|
|
1601
|
-
},
|
|
1621
|
+
}, setAndroidApplicationAttribute = (source, attribute, value) => {
|
|
1622
|
+
const pattern = new RegExp(`android:${attribute}=["'][^"']*["']`, "u");
|
|
1623
|
+
if (pattern.test(source))
|
|
1624
|
+
return source.replace(pattern, `android:${attribute}="${value}"`);
|
|
1625
|
+
return source.replace("<application", `<application
|
|
1626
|
+
android:${attribute}="${value}"`);
|
|
1627
|
+
}, androidDevelopmentManifest = (source, cleartext, developmentNetworkConfig) => {
|
|
1628
|
+
let projected = source;
|
|
1629
|
+
if (developmentNetworkConfig) {
|
|
1630
|
+
projected = setAndroidApplicationAttribute(projected, "networkSecurityConfig", `@xml/${developmentNetworkConfig}`);
|
|
1631
|
+
}
|
|
1602
1632
|
if (!cleartext)
|
|
1603
|
-
return
|
|
1633
|
+
return projected;
|
|
1604
1634
|
if (/android:usesCleartextTraffic=["'][^"']*["']/u.test(source)) {
|
|
1605
|
-
return
|
|
1635
|
+
return projected.replace(/android:usesCleartextTraffic=["'][^"']*["']/u, 'android:usesCleartextTraffic="true"');
|
|
1606
1636
|
}
|
|
1607
|
-
return
|
|
1608
|
-
|
|
1609
|
-
|
|
1637
|
+
return setAndroidApplicationAttribute(projected, "usesCleartextTraffic", "true");
|
|
1638
|
+
}, withAndroidDebugCertificateAuthority = (source) => {
|
|
1639
|
+
if (source.includes("@raw/absolutejs_dev_ca"))
|
|
1640
|
+
return source;
|
|
1641
|
+
const debugOverrides = /<debug-overrides(?:\s[^>]*)?>([\s\S]*?)<\/debug-overrides>/u;
|
|
1642
|
+
const match = source.match(debugOverrides);
|
|
1643
|
+
if (match) {
|
|
1644
|
+
const [block] = match;
|
|
1645
|
+
const trustAnchors = /<trust-anchors(?:\s[^>]*)?>/u;
|
|
1646
|
+
const projectedBlock = trustAnchors.test(block) ? block.replace(trustAnchors, `$&
|
|
1647
|
+
<certificates src="@raw/absolutejs_dev_ca" />`) : block.replace("</debug-overrides>", ` <trust-anchors>
|
|
1648
|
+
<certificates src="@raw/absolutejs_dev_ca" />
|
|
1649
|
+
</trust-anchors>
|
|
1650
|
+
</debug-overrides>`);
|
|
1651
|
+
return source.replace(block, projectedBlock);
|
|
1652
|
+
}
|
|
1653
|
+
if (!source.includes("</network-security-config>")) {
|
|
1654
|
+
throw new Error("Android Network Security Configuration is invalid.");
|
|
1655
|
+
}
|
|
1656
|
+
return source.replace("</network-security-config>", ` <debug-overrides>
|
|
1657
|
+
<trust-anchors>
|
|
1658
|
+
<certificates src="@raw/absolutejs_dev_ca" />
|
|
1659
|
+
</trust-anchors>
|
|
1660
|
+
</debug-overrides>
|
|
1661
|
+
</network-security-config>`);
|
|
1662
|
+
}, writeDevConfig = async (projectRoot, nativeConfigPath, nativeManifestPath, port, https, entry, embeddedBundle, serverHost, certificateAuthorityPath) => {
|
|
1610
1663
|
const paths = journalPaths(projectRoot);
|
|
1611
1664
|
await repairAbsoluteAndroidDevSession(projectRoot);
|
|
1612
1665
|
const source = await readFile2(nativeConfigPath, "utf8");
|
|
@@ -1618,12 +1671,28 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1618
1671
|
await mkdir(paths.root, { recursive: true });
|
|
1619
1672
|
await writeFile2(paths.backup, source, { flag: "wx" });
|
|
1620
1673
|
await writeFile2(paths.manifestBackup, manifestSource, { flag: "wx" });
|
|
1674
|
+
const resourceRoot = join7(dirname3(nativeManifestPath), "res");
|
|
1675
|
+
const caPath = join7(resourceRoot, "raw", "absolutejs_dev_ca.pem");
|
|
1676
|
+
const existingNetworkConfig = manifestSource.match(/android:networkSecurityConfig=["']@xml\/([a-z0-9_]+)["']/u)?.[1];
|
|
1677
|
+
const networkConfigName = existingNetworkConfig ?? "absolutejs_dev_network_security";
|
|
1678
|
+
const networkConfigPath = join7(resourceRoot, "xml", `${networkConfigName}.xml`);
|
|
1679
|
+
const backupProjectedFile = async ([path, backupPath]) => {
|
|
1680
|
+
if (!await pathExists2(path))
|
|
1681
|
+
return { path };
|
|
1682
|
+
await copyFile(path, backupPath);
|
|
1683
|
+
return { backupPath, path };
|
|
1684
|
+
};
|
|
1685
|
+
const projectedFiles = https && certificateAuthorityPath ? await Promise.all([
|
|
1686
|
+
[caPath, paths.caBackup],
|
|
1687
|
+
[networkConfigPath, paths.networkConfigBackup]
|
|
1688
|
+
].map(backupProjectedFile)) : [];
|
|
1621
1689
|
const journal = {
|
|
1622
1690
|
backupPath: paths.backup,
|
|
1623
1691
|
format: DEV_JOURNAL_FORMAT,
|
|
1624
1692
|
manifestBackupPath: paths.manifestBackup,
|
|
1625
1693
|
nativeConfigPath,
|
|
1626
|
-
nativeManifestPath
|
|
1694
|
+
nativeManifestPath,
|
|
1695
|
+
projectedFiles
|
|
1627
1696
|
};
|
|
1628
1697
|
await writeFile2(paths.journal, `${JSON.stringify(journal, null, "\t")}
|
|
1629
1698
|
`, {
|
|
@@ -1631,7 +1700,7 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1631
1700
|
});
|
|
1632
1701
|
if (!embeddedBundle) {
|
|
1633
1702
|
const currentServer = parsed.server;
|
|
1634
|
-
const developmentUrl = new URL(`${https ? "https" : "http"}
|
|
1703
|
+
const developmentUrl = new URL(`${https ? "https" : "http"}://${serverHost}:${port}${entry}`);
|
|
1635
1704
|
developmentUrl.searchParams.set("__absolute_target", "capacitor-android");
|
|
1636
1705
|
parsed.server = {
|
|
1637
1706
|
...typeof currentServer === "object" && currentServer !== null ? currentServer : {},
|
|
@@ -1641,7 +1710,19 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1641
1710
|
}
|
|
1642
1711
|
await writeFile2(nativeConfigPath, `${JSON.stringify(parsed, null, "\t")}
|
|
1643
1712
|
`);
|
|
1644
|
-
|
|
1713
|
+
if (https && certificateAuthorityPath) {
|
|
1714
|
+
await Promise.all([
|
|
1715
|
+
mkdir(dirname3(caPath), { recursive: true }),
|
|
1716
|
+
mkdir(dirname3(networkConfigPath), { recursive: true })
|
|
1717
|
+
]);
|
|
1718
|
+
await copyFile(certificateAuthorityPath, caPath);
|
|
1719
|
+
const existingNetworkConfigSource = existingNetworkConfig ? await readFile2(networkConfigPath, "utf8") : `<?xml version="1.0" encoding="utf-8"?>
|
|
1720
|
+
<network-security-config>
|
|
1721
|
+
</network-security-config>
|
|
1722
|
+
`;
|
|
1723
|
+
await writeFile2(networkConfigPath, withAndroidDebugCertificateAuthority(existingNetworkConfigSource));
|
|
1724
|
+
}
|
|
1725
|
+
await writeFile2(nativeManifestPath, androidDevelopmentManifest(manifestSource, !https, https && certificateAuthorityPath !== undefined ? networkConfigName : undefined));
|
|
1645
1726
|
}, parseAdbDevices = (output) => output.split(/\r?\n/).slice(1).map((line) => line.trim().split(/\s+/, 2)).filter((parts) => parts.length === 2 && parts[1] === "device").map(([serial]) => serial), isManagedEmulatorSerial = (serial, adb, capture, env) => {
|
|
1646
1727
|
if (!serial.startsWith("emulator-"))
|
|
1647
1728
|
return false;
|
|
@@ -1652,6 +1733,11 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1652
1733
|
if (devices.exitCode !== 0)
|
|
1653
1734
|
return;
|
|
1654
1735
|
return parseAdbDevices(devices.stdout).find((serial) => isManagedEmulatorSerial(serial, adb, capture, env));
|
|
1736
|
+
}, requireConnectedAndroidDevice = (adb, serial, capture, env) => {
|
|
1737
|
+
const devices = capture([adb, "devices"], { env });
|
|
1738
|
+
if (devices.exitCode !== 0 || !parseAdbDevices(devices.stdout).includes(serial)) {
|
|
1739
|
+
throw new Error(`Android device ${serial} is not connected and authorized. Confirm the USB debugging prompt, then retry.`);
|
|
1740
|
+
}
|
|
1655
1741
|
}, completedBootSerial = (project, capture, env) => {
|
|
1656
1742
|
const serial = managedEmulatorSerial(project.adb, capture, env);
|
|
1657
1743
|
if (!serial)
|
|
@@ -1663,15 +1749,13 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1663
1749
|
return booted.exitCode === 0 && booted.stdout.trim() === "1" ? serial : undefined;
|
|
1664
1750
|
}, waitForManagedEmulator = async (project, capture, sleep, env, signal, preferredSerial) => {
|
|
1665
1751
|
const deadline = Date.now() + ANDROID_BOOT_TIMEOUT_MS;
|
|
1666
|
-
let firstSerial = preferredSerial;
|
|
1667
1752
|
const poll = async () => {
|
|
1668
1753
|
throwIfAborted(signal);
|
|
1669
|
-
const serial =
|
|
1670
|
-
firstSerial = undefined;
|
|
1754
|
+
const serial = preferredSerial ? preferredCompletedBootSerial(project, preferredSerial, capture, env) : completedBootSerial(project, capture, env);
|
|
1671
1755
|
if (serial)
|
|
1672
1756
|
return serial;
|
|
1673
1757
|
if (Date.now() >= deadline) {
|
|
1674
|
-
throw new Error(`Android emulator ${ABSOLUTE_ANDROID_AVD_NAME} did not finish booting within ${ANDROID_BOOT_TIMEOUT_MS / ANDROID_BOOT_POLL_MS}s.`);
|
|
1758
|
+
throw new Error(`Android ${preferredSerial ? `target ${preferredSerial}` : `emulator ${ABSOLUTE_ANDROID_AVD_NAME}`} did not finish booting within ${ANDROID_BOOT_TIMEOUT_MS / ANDROID_BOOT_POLL_MS}s.`);
|
|
1675
1759
|
}
|
|
1676
1760
|
await sleep(ANDROID_BOOT_POLL_MS);
|
|
1677
1761
|
return poll();
|
|
@@ -1821,6 +1905,9 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1821
1905
|
await persistInstalledAndroidNativeCache(options, updated);
|
|
1822
1906
|
return { nativeCacheHit: false, uid: updated?.uid };
|
|
1823
1907
|
}, startManagedEmulatorIfNeeded = (project, capture, spawn, log, env) => {
|
|
1908
|
+
if (!project.emulator) {
|
|
1909
|
+
throw new Error("Android Emulator is unavailable. Select a physical device or run `absolute mobile doctor android --fix`.");
|
|
1910
|
+
}
|
|
1824
1911
|
const serial = managedEmulatorSerial(project.adb, capture, env);
|
|
1825
1912
|
if (serial)
|
|
1826
1913
|
return { serial, started: false };
|
|
@@ -1838,7 +1925,7 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1838
1925
|
}, logHttpsCertificateRequirement = (https, log) => {
|
|
1839
1926
|
if (https !== true)
|
|
1840
1927
|
return;
|
|
1841
|
-
log("Android HMR is using HTTPS;
|
|
1928
|
+
log("Android HMR is using HTTPS; AbsoluteJS is projecting the local development CA into this debug app only.");
|
|
1842
1929
|
}, removeAdbReverse = async (project, serial, port, run, env) => {
|
|
1843
1930
|
if (!serial)
|
|
1844
1931
|
return;
|
|
@@ -1937,13 +2024,19 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1937
2024
|
const host = detectAbsoluteMobileHost();
|
|
1938
2025
|
const androidRoot = process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
|
|
1939
2026
|
const checks = await inspectAbsoluteMobileToolchain({ androidRoot, host });
|
|
1940
|
-
const
|
|
2027
|
+
const deviceOnlyChecks = new Set([
|
|
2028
|
+
"android.avd",
|
|
2029
|
+
"android.avdmanager",
|
|
2030
|
+
"android.emulator",
|
|
2031
|
+
"android.virtualization"
|
|
2032
|
+
]);
|
|
2033
|
+
const failed = checks.filter((check) => check.platform === "android" && (check.status === "fail" || check.status === "warn") && (options.target !== "device" || !deviceOnlyChecks.has(check.id)));
|
|
1941
2034
|
if (failed.length > 0) {
|
|
1942
|
-
throw new Error(`Android emulation is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
|
|
2035
|
+
throw new Error(`Android ${options.target === "device" ? "device development" : "emulation"} is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
|
|
1943
2036
|
}
|
|
1944
2037
|
const adb = checks.find((check) => check.id === "android.adb")?.path;
|
|
1945
2038
|
const emulator = checks.find((check) => check.id === "android.emulator")?.path;
|
|
1946
|
-
if (!adb || !emulator) {
|
|
2039
|
+
if (!adb || options.target !== "device" && !emulator) {
|
|
1947
2040
|
throw new Error("Android SDK tools disappeared after readiness checks.");
|
|
1948
2041
|
}
|
|
1949
2042
|
const cap = join7(projectRoot, "node_modules", ".bin", host === "windows" ? "cap.cmd" : "cap");
|
|
@@ -1974,7 +2067,7 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
1974
2067
|
androidRoot,
|
|
1975
2068
|
cap,
|
|
1976
2069
|
config,
|
|
1977
|
-
emulator,
|
|
2070
|
+
...emulator ? { emulator } : {},
|
|
1978
2071
|
host,
|
|
1979
2072
|
nativeDirectory,
|
|
1980
2073
|
projectRoot
|
|
@@ -2030,7 +2123,11 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
2030
2123
|
});
|
|
2031
2124
|
};
|
|
2032
2125
|
try {
|
|
2033
|
-
|
|
2126
|
+
const physicalDevice = options.deviceSerial !== undefined;
|
|
2127
|
+
if (options.https && !options.certificateAuthorityPath) {
|
|
2128
|
+
throw new Error("Android HTTPS development requires the AbsoluteJS development CA certificate.");
|
|
2129
|
+
}
|
|
2130
|
+
await writeDevConfig(project.projectRoot, nativeConfigPath, nativeManifestPath, options.port, options.https === true, project.config.entry, options.embeddedBundle === true, options.serverHost ?? "localhost", options.certificateAuthorityPath);
|
|
2034
2131
|
throwIfAborted(options.signal);
|
|
2035
2132
|
logHttpsCertificateRequirement(options.https, log);
|
|
2036
2133
|
const fingerprintStartedAt = performance.now();
|
|
@@ -2041,20 +2138,23 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
2041
2138
|
return fingerprint;
|
|
2042
2139
|
});
|
|
2043
2140
|
transition("booting");
|
|
2044
|
-
|
|
2141
|
+
if (options.deviceSerial)
|
|
2142
|
+
requireConnectedAndroidDevice(project.adb, options.deviceSerial, capture, env);
|
|
2143
|
+
const emulator = options.deviceSerial ? { serial: options.deviceSerial, started: false } : startManagedEmulatorIfNeeded(project, capture, spawn, log, env);
|
|
2045
2144
|
const { started: startedEmulator } = emulator;
|
|
2046
2145
|
transition("connecting");
|
|
2047
2146
|
const serial = await waitForManagedEmulator(project, capture, sleep, env, options.signal, emulator.serial);
|
|
2048
2147
|
connectedSerial = serial;
|
|
2049
2148
|
transition("forwarding");
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2149
|
+
if (!physicalDevice)
|
|
2150
|
+
await requireSuccess([
|
|
2151
|
+
project.adb,
|
|
2152
|
+
"-s",
|
|
2153
|
+
serial,
|
|
2154
|
+
"reverse",
|
|
2155
|
+
`tcp:${options.port}`,
|
|
2156
|
+
`tcp:${options.port}`
|
|
2157
|
+
], "ADB reverse port forwarding", run, { env, signal: options.signal });
|
|
2058
2158
|
throwIfAborted(options.signal);
|
|
2059
2159
|
const nativeFingerprint = await nativeFingerprintPromise;
|
|
2060
2160
|
throwIfAborted(options.signal);
|
|
@@ -2085,8 +2185,8 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
2085
2185
|
nativeLogs = attachAndroidNativeLogs(project, serial, capture, env, options, uid);
|
|
2086
2186
|
transition("ready");
|
|
2087
2187
|
phaseDurations.total = performance.now() - startupStartedAt;
|
|
2088
|
-
log(options.embeddedBundle === true ? `Android emulator connected (${serial}) with the embedded bundle and backend on port ${options.port} in ${getDurationString(phaseDurations.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).` : `Android emulator connected (${serial}) with HMR on port ${options.port} in ${getDurationString(phaseDurations.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
|
|
2089
|
-
log(`Android startup: ${androidTimingSummary(phaseDurations)}.`);
|
|
2188
|
+
log(options.embeddedBundle === true ? `Android ${physicalDevice ? "device" : "emulator"} connected (${serial}) with the embedded bundle and backend on port ${options.port} in ${getDurationString(phaseDurations.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).` : `Android ${physicalDevice ? "device" : "emulator"} connected (${serial}) with HMR on port ${options.port} in ${getDurationString(phaseDurations.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
|
|
2189
|
+
log(`Android startup: ${androidTimingSummary(phaseDurations, physicalDevice)}.`);
|
|
2090
2190
|
let closed = false;
|
|
2091
2191
|
const close = async () => {
|
|
2092
2192
|
if (closed)
|
|
@@ -2094,7 +2194,8 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
2094
2194
|
closed = true;
|
|
2095
2195
|
transition("closing");
|
|
2096
2196
|
await closeNativeLogs();
|
|
2097
|
-
|
|
2197
|
+
if (!physicalDevice)
|
|
2198
|
+
await removeAdbReverse(project, serial, options.port, run, env);
|
|
2098
2199
|
await repairAbsoluteAndroidDevSession(project.projectRoot);
|
|
2099
2200
|
transition("closed");
|
|
2100
2201
|
};
|
|
@@ -2133,7 +2234,8 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
|
|
|
2133
2234
|
} catch (error) {
|
|
2134
2235
|
transition("failed");
|
|
2135
2236
|
await closeNativeLogs();
|
|
2136
|
-
|
|
2237
|
+
if (!options.deviceSerial)
|
|
2238
|
+
await removeAdbReverse(project, connectedSerial, options.port, run, env);
|
|
2137
2239
|
await repairAbsoluteAndroidDevSession(project.projectRoot);
|
|
2138
2240
|
throw error;
|
|
2139
2241
|
}
|
|
@@ -2742,17 +2844,109 @@ var init_iosRelease = __esm(() => {
|
|
|
2742
2844
|
]);
|
|
2743
2845
|
});
|
|
2744
2846
|
|
|
2847
|
+
// src/mobile/iosPhysicalDeviceTransport.ts
|
|
2848
|
+
import { randomUUID as randomUUID2, X509Certificate } from "crypto";
|
|
2849
|
+
import { createServer as createServer3 } from "http";
|
|
2850
|
+
import {
|
|
2851
|
+
connect as connectTcp,
|
|
2852
|
+
createServer as createTcpServer,
|
|
2853
|
+
isIP
|
|
2854
|
+
} from "net";
|
|
2855
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
2856
|
+
var closeServer = (server) => new Promise((resolve7, reject) => {
|
|
2857
|
+
server.close((error) => {
|
|
2858
|
+
if (error)
|
|
2859
|
+
reject(error);
|
|
2860
|
+
else
|
|
2861
|
+
resolve7();
|
|
2862
|
+
});
|
|
2863
|
+
}), listen = (server, port) => new Promise((resolve7, reject) => {
|
|
2864
|
+
server.once("error", reject);
|
|
2865
|
+
server.listen(port, "0.0.0.0", () => {
|
|
2866
|
+
server.off("error", reject);
|
|
2867
|
+
const address = server.address();
|
|
2868
|
+
if (!address || typeof address === "string") {
|
|
2869
|
+
reject(new Error("Could not determine the iOS device helper port."));
|
|
2870
|
+
return;
|
|
2871
|
+
}
|
|
2872
|
+
resolve7(address.port);
|
|
2873
|
+
});
|
|
2874
|
+
}), findEphemeralPort = async () => {
|
|
2875
|
+
const probe = createTcpServer();
|
|
2876
|
+
const port = await new Promise((resolve7, reject) => {
|
|
2877
|
+
probe.once("error", reject);
|
|
2878
|
+
probe.listen(0, "127.0.0.1", () => {
|
|
2879
|
+
const address = probe.address();
|
|
2880
|
+
if (!address || typeof address === "string") {
|
|
2881
|
+
reject(new Error("Could not allocate the iOS CA enrollment port."));
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2884
|
+
resolve7(address.port);
|
|
2885
|
+
});
|
|
2886
|
+
});
|
|
2887
|
+
await closeServer(probe);
|
|
2888
|
+
return port;
|
|
2889
|
+
}, normalizeAbsoluteIosDeviceHost = (value) => {
|
|
2890
|
+
const normalized = value.trim();
|
|
2891
|
+
if (!normalized || normalized.length > 253 || /[\0\s/?#]/u.test(normalized))
|
|
2892
|
+
throw new TypeError("Physical iOS development requires a valid LAN host.");
|
|
2893
|
+
return normalized;
|
|
2894
|
+
}, normalizeAbsoluteIosDeviceIdentifier = (value) => {
|
|
2895
|
+
const normalized = value.trim();
|
|
2896
|
+
if (!normalized || normalized.length > 256 || /[\0\r\n]/u.test(normalized))
|
|
2897
|
+
throw new TypeError("--ios-device requires a valid Xcode device identifier or name.");
|
|
2898
|
+
return normalized;
|
|
2899
|
+
}, urlForHost = (protocol, host, port) => {
|
|
2900
|
+
const url = new URL(`${protocol}://localhost:${port}`);
|
|
2901
|
+
const normalizedHost = normalizeAbsoluteIosDeviceHost(host);
|
|
2902
|
+
url.hostname = isIP(normalizedHost) === 6 ? `[${normalizedHost}]` : normalizedHost;
|
|
2903
|
+
return url;
|
|
2904
|
+
}, startAbsoluteIosCaEnrollmentServer = async (options) => {
|
|
2905
|
+
const certificate = new X509Certificate(await readFile5(options.certificateAuthorityPath));
|
|
2906
|
+
const certificateBytes = certificate.raw;
|
|
2907
|
+
const token = randomUUID2().replaceAll("-", "");
|
|
2908
|
+
const certificatePath = `/${token}/absolutejs-development-ca.cer`;
|
|
2909
|
+
const server = createServer3((request, response) => {
|
|
2910
|
+
if (request.method !== "GET" || request.url !== certificatePath) {
|
|
2911
|
+
response.writeHead(404, {
|
|
2912
|
+
"Cache-Control": "no-store",
|
|
2913
|
+
"Content-Type": "text/plain; charset=utf-8"
|
|
2914
|
+
});
|
|
2915
|
+
response.end("Not found.");
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
response.writeHead(200, {
|
|
2919
|
+
"Cache-Control": "no-store",
|
|
2920
|
+
"Content-Disposition": 'attachment; filename="absolutejs-development-ca.cer"',
|
|
2921
|
+
"Content-Length": String(certificateBytes.byteLength),
|
|
2922
|
+
"Content-Type": "application/x-x509-ca-cert",
|
|
2923
|
+
"X-Content-Type-Options": "nosniff"
|
|
2924
|
+
});
|
|
2925
|
+
response.end(certificateBytes);
|
|
2926
|
+
});
|
|
2927
|
+
const port = await findEphemeralPort();
|
|
2928
|
+
await listen(server, port);
|
|
2929
|
+
const url = urlForHost("http", options.displayHost, port);
|
|
2930
|
+
url.pathname = certificatePath;
|
|
2931
|
+
return {
|
|
2932
|
+
url: url.href,
|
|
2933
|
+
close: () => closeServer(server)
|
|
2934
|
+
};
|
|
2935
|
+
};
|
|
2936
|
+
var init_iosPhysicalDeviceTransport = () => {};
|
|
2937
|
+
|
|
2745
2938
|
// src/mobile/iosSimulatorController.ts
|
|
2746
|
-
import { createHash as createHash4, randomUUID as
|
|
2939
|
+
import { createHash as createHash4, randomUUID as randomUUID3 } from "crypto";
|
|
2747
2940
|
import {
|
|
2748
2941
|
access as access5,
|
|
2749
2942
|
copyFile as copyFile3,
|
|
2750
2943
|
mkdir as mkdir4,
|
|
2751
|
-
readFile as
|
|
2944
|
+
readFile as readFile6,
|
|
2752
2945
|
rename as rename4,
|
|
2753
2946
|
rm as rm4,
|
|
2754
2947
|
writeFile as writeFile4
|
|
2755
2948
|
} from "fs/promises";
|
|
2949
|
+
import { isIP as isIP2 } from "net";
|
|
2756
2950
|
import { dirname as dirname5, isAbsolute as isAbsolute3, join as join10, relative as relative4, resolve as resolve7, sep as sep3 } from "path";
|
|
2757
2951
|
var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000, BOOT_POLL_MS = 1000, DEV_JOURNAL_FORMAT2 = 1, NATIVE_CACHE_FORMAT2 = 1, isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), pathExists4 = async (path) => {
|
|
2758
2952
|
try {
|
|
@@ -2848,7 +3042,39 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
2848
3042
|
const exitCode = await run(command, options);
|
|
2849
3043
|
if (exitCode !== 0)
|
|
2850
3044
|
throw new Error(`${label} failed with status ${exitCode}.`);
|
|
2851
|
-
},
|
|
3045
|
+
}, trustIosSimulatorDevelopmentCa = async (options, udid, run, log) => {
|
|
3046
|
+
if (!options.https)
|
|
3047
|
+
return;
|
|
3048
|
+
if (!options.certificateAuthorityPath) {
|
|
3049
|
+
throw new Error("iOS Simulator HTTPS requires the AbsoluteJS development CA certificate.");
|
|
3050
|
+
}
|
|
3051
|
+
await requireSuccess2([
|
|
3052
|
+
options.project.xcrun,
|
|
3053
|
+
"simctl",
|
|
3054
|
+
"keychain",
|
|
3055
|
+
udid,
|
|
3056
|
+
"add-root-cert",
|
|
3057
|
+
options.certificateAuthorityPath
|
|
3058
|
+
], "iOS Simulator development CA trust", run, { signal: options.signal });
|
|
3059
|
+
log("Installed the AbsoluteJS development CA into this iOS Simulator trust store.");
|
|
3060
|
+
}, iosLaunchCommand = (project, udid, physical) => physical ? [
|
|
3061
|
+
project.xcrun,
|
|
3062
|
+
"devicectl",
|
|
3063
|
+
"device",
|
|
3064
|
+
"process",
|
|
3065
|
+
"launch",
|
|
3066
|
+
"--terminate-existing",
|
|
3067
|
+
"--device",
|
|
3068
|
+
udid,
|
|
3069
|
+
project.config.appId
|
|
3070
|
+
] : [
|
|
3071
|
+
project.xcrun,
|
|
3072
|
+
"simctl",
|
|
3073
|
+
"launch",
|
|
3074
|
+
"--terminate-running-process",
|
|
3075
|
+
udid,
|
|
3076
|
+
project.config.appId
|
|
3077
|
+
], requireCapturedSuccess = (result, label) => {
|
|
2852
3078
|
if (result.exitCode !== 0) {
|
|
2853
3079
|
throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
|
|
2854
3080
|
}
|
|
@@ -2971,7 +3197,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
2971
3197
|
await rm4(paths.root, { force: true, recursive: true });
|
|
2972
3198
|
return false;
|
|
2973
3199
|
}
|
|
2974
|
-
const journal = await
|
|
3200
|
+
const journal = await readFile6(paths.journal, "utf8").then((source) => parseJournal2(JSON.parse(source))).catch(() => null);
|
|
2975
3201
|
if (!journal || !isInside2(projectRoot, journal.nativeConfigPath) || !isInside2(projectRoot, journal.infoPath) || !isInside2(paths.root, journal.configBackupPath) || !isInside2(paths.root, journal.infoBackupPath)) {
|
|
2976
3202
|
throw new Error(`Refusing unsafe or invalid iOS dev journal at ${paths.journal}.`);
|
|
2977
3203
|
}
|
|
@@ -3000,14 +3226,14 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3000
3226
|
<key>NSAllowsArbitraryLoads</key>
|
|
3001
3227
|
<true/>
|
|
3002
3228
|
</dict>`);
|
|
3003
|
-
}, writeDevProjection = async (project, port, https) => {
|
|
3229
|
+
}, writeDevProjection = async (project, port, https, serverHost = "localhost") => {
|
|
3004
3230
|
const paths = journalPaths2(project.projectRoot);
|
|
3005
3231
|
await repairAbsoluteIosDevSession(project.projectRoot);
|
|
3006
3232
|
const nativeConfigPath = join10(project.nativeDirectory, "App", "App", "capacitor.config.json");
|
|
3007
3233
|
const infoPath = join10(project.nativeDirectory, "App", "App", "Info.plist");
|
|
3008
3234
|
const [configSource, infoSource] = await Promise.all([
|
|
3009
|
-
|
|
3010
|
-
|
|
3235
|
+
readFile6(nativeConfigPath, "utf8"),
|
|
3236
|
+
readFile6(infoPath, "utf8")
|
|
3011
3237
|
]);
|
|
3012
3238
|
const parsed = JSON.parse(configSource);
|
|
3013
3239
|
if (!isRecord3(parsed))
|
|
@@ -3029,6 +3255,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3029
3255
|
flag: "wx"
|
|
3030
3256
|
});
|
|
3031
3257
|
const developmentUrl = new URL(`${https ? "https" : "http"}://localhost:${port}${project.config.entry}`);
|
|
3258
|
+
developmentUrl.hostname = isIP2(serverHost) === 6 ? `[${serverHost}]` : serverHost;
|
|
3032
3259
|
developmentUrl.searchParams.set("__absolute_target", "capacitor-ios");
|
|
3033
3260
|
const existingServer = parsed.server;
|
|
3034
3261
|
parsed.server = {
|
|
@@ -3056,9 +3283,9 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3056
3283
|
String(identity)
|
|
3057
3284
|
]))
|
|
3058
3285
|
};
|
|
3059
|
-
}, readNativeCache2 = (projectRoot) =>
|
|
3286
|
+
}, readNativeCache2 = (projectRoot) => readFile6(nativeCachePath2(projectRoot), "utf8").then((source) => parseNativeCache2(JSON.parse(source))).catch(() => null), writeNativeCache2 = async (projectRoot, cache) => {
|
|
3060
3287
|
const destination = nativeCachePath2(projectRoot);
|
|
3061
|
-
const temporary = `${destination}.${process.pid}.${
|
|
3288
|
+
const temporary = `${destination}.${process.pid}.${randomUUID3()}.tmp`;
|
|
3062
3289
|
await mkdir4(dirname5(destination), { recursive: true });
|
|
3063
3290
|
try {
|
|
3064
3291
|
await writeFile4(temporary, `${JSON.stringify(cache, null, "\t")}
|
|
@@ -3140,6 +3367,29 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3140
3367
|
"app"
|
|
3141
3368
|
]);
|
|
3142
3369
|
return result.exitCode === 0 && result.stdout.trim() ? result.stdout.trim() : undefined;
|
|
3370
|
+
}, validatePhysicalIosDevice = (project, identifier, capture) => {
|
|
3371
|
+
const result = capture([
|
|
3372
|
+
project.xcrun,
|
|
3373
|
+
"devicectl",
|
|
3374
|
+
"device",
|
|
3375
|
+
"info",
|
|
3376
|
+
"details",
|
|
3377
|
+
"--device",
|
|
3378
|
+
identifier
|
|
3379
|
+
]);
|
|
3380
|
+
if (result.exitCode !== 0)
|
|
3381
|
+
throw new Error(`Physical iOS device ${JSON.stringify(identifier)} is unavailable: ${result.stderr.trim() || result.stdout.trim() || "pair it in Xcode Device Hub, trust this Mac, unlock it, and enable Developer Mode."}`);
|
|
3382
|
+
}, physicalIosAppIsInstalled = (project, identifier, capture) => {
|
|
3383
|
+
const result = capture([
|
|
3384
|
+
project.xcrun,
|
|
3385
|
+
"devicectl",
|
|
3386
|
+
"device",
|
|
3387
|
+
"info",
|
|
3388
|
+
"apps",
|
|
3389
|
+
"--device",
|
|
3390
|
+
identifier
|
|
3391
|
+
]);
|
|
3392
|
+
return result.exitCode === 0 && result.stdout.includes(project.config.appId);
|
|
3143
3393
|
}, buildIosDebugApp = async (project, udid, fingerprint, run, signal) => {
|
|
3144
3394
|
const derivedDataPath = join10(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash4("sha256").update(project.config.appId).digest("hex").slice(0, 16));
|
|
3145
3395
|
await mkdir4(derivedDataPath, { recursive: true });
|
|
@@ -3161,6 +3411,59 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3161
3411
|
if (!await pathExists4(appPath))
|
|
3162
3412
|
throw new Error(`Xcode did not produce the simulator app at ${appPath}.`);
|
|
3163
3413
|
return appPath;
|
|
3414
|
+
}, buildPhysicalIosDebugApp = async (project, identifier, run, signal) => {
|
|
3415
|
+
const derivedDataPath = join10(project.projectRoot, ".absolutejs", "mobile", "ios-derived-data", createHash4("sha256").update(project.config.appId).digest("hex").slice(0, 16));
|
|
3416
|
+
await mkdir4(derivedDataPath, { recursive: true });
|
|
3417
|
+
await requireSuccess2([
|
|
3418
|
+
project.xcodebuild,
|
|
3419
|
+
"-workspace",
|
|
3420
|
+
join10(project.nativeDirectory, "App", "App.xcworkspace"),
|
|
3421
|
+
"-scheme",
|
|
3422
|
+
"App",
|
|
3423
|
+
"-configuration",
|
|
3424
|
+
"Debug",
|
|
3425
|
+
"-destination",
|
|
3426
|
+
`platform=iOS,id=${identifier}`,
|
|
3427
|
+
"-derivedDataPath",
|
|
3428
|
+
derivedDataPath,
|
|
3429
|
+
"-allowProvisioningUpdates",
|
|
3430
|
+
"build"
|
|
3431
|
+
], "iOS physical-device build (configure automatic signing and a Development Team in Xcode if this is the first run)", run, { cwd: project.nativeDirectory, signal });
|
|
3432
|
+
const appPath = join10(derivedDataPath, "Build", "Products", "Debug-iphoneos", "App.app");
|
|
3433
|
+
if (!await pathExists4(appPath))
|
|
3434
|
+
throw new Error(`Xcode did not produce the physical-device app at ${appPath}.`);
|
|
3435
|
+
return appPath;
|
|
3436
|
+
}, ensurePhysicalIosDebugApp = async (options) => {
|
|
3437
|
+
const cacheHit = options.cache?.appId === options.project.config.appId && options.cache.fingerprint === options.fingerprint && options.cache.installations[options.identifier] === options.fingerprint && physicalIosAppIsInstalled(options.project, options.identifier, options.capture);
|
|
3438
|
+
if (cacheHit) {
|
|
3439
|
+
options.log(`iOS native app is unchanged on the selected physical device; skipped Xcode build and install.`);
|
|
3440
|
+
return true;
|
|
3441
|
+
}
|
|
3442
|
+
options.log("iOS native inputs changed or the physical-device install is stale; rebuilding.");
|
|
3443
|
+
options.transition("building");
|
|
3444
|
+
const appPath = await buildPhysicalIosDebugApp(options.project, options.identifier, options.run, options.signal);
|
|
3445
|
+
throwIfAborted2(options.signal);
|
|
3446
|
+
options.transition("installing");
|
|
3447
|
+
await requireSuccess2([
|
|
3448
|
+
options.project.xcrun,
|
|
3449
|
+
"devicectl",
|
|
3450
|
+
"device",
|
|
3451
|
+
"install",
|
|
3452
|
+
"app",
|
|
3453
|
+
"--device",
|
|
3454
|
+
options.identifier,
|
|
3455
|
+
appPath
|
|
3456
|
+
], "iOS physical-device app installation", options.run, { signal: options.signal });
|
|
3457
|
+
await writeNativeCache2(options.project.projectRoot, {
|
|
3458
|
+
appId: options.project.config.appId,
|
|
3459
|
+
fingerprint: options.fingerprint,
|
|
3460
|
+
format: NATIVE_CACHE_FORMAT2,
|
|
3461
|
+
installations: {
|
|
3462
|
+
...options.cache?.appId === options.project.config.appId ? options.cache.installations : {},
|
|
3463
|
+
[options.identifier]: options.fingerprint
|
|
3464
|
+
}
|
|
3465
|
+
}).catch((error) => options.log(`iOS native cache could not be saved: ${error instanceof Error ? error.message : String(error)}`));
|
|
3466
|
+
return false;
|
|
3164
3467
|
}, ensureIosDebugApp = async (options) => {
|
|
3165
3468
|
const installed = installedAppIdentity(options.project, options.udid, options.capture);
|
|
3166
3469
|
const cacheHit = options.cache?.appId === options.project.config.appId && options.cache.fingerprint === options.fingerprint && installed !== undefined && options.cache.installations[options.udid] === installed;
|
|
@@ -3200,7 +3503,18 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3200
3503
|
if (!options.nativeLog)
|
|
3201
3504
|
return null;
|
|
3202
3505
|
const start = options.startNativeLogs ?? defaultStartNativeLogs;
|
|
3203
|
-
|
|
3506
|
+
const command = options.deviceIdentifier ? [
|
|
3507
|
+
project.xcrun,
|
|
3508
|
+
"devicectl",
|
|
3509
|
+
"device",
|
|
3510
|
+
"process",
|
|
3511
|
+
"launch",
|
|
3512
|
+
"--console",
|
|
3513
|
+
"--terminate-existing",
|
|
3514
|
+
"--device",
|
|
3515
|
+
udid,
|
|
3516
|
+
project.config.appId
|
|
3517
|
+
] : [
|
|
3204
3518
|
project.xcrun,
|
|
3205
3519
|
"simctl",
|
|
3206
3520
|
"spawn",
|
|
@@ -3213,7 +3527,8 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3213
3527
|
"debug",
|
|
3214
3528
|
"--predicate",
|
|
3215
3529
|
'process == "App"'
|
|
3216
|
-
]
|
|
3530
|
+
];
|
|
3531
|
+
return start(command, { signal: options.signal }, (line) => {
|
|
3217
3532
|
const entry = parseAbsoluteIosLogLine(line);
|
|
3218
3533
|
if (entry)
|
|
3219
3534
|
options.nativeLog?.(entry);
|
|
@@ -3223,12 +3538,13 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3223
3538
|
return duration === undefined ? null : `${label} ${getDurationString(duration)}`;
|
|
3224
3539
|
}).filter((value) => value !== null).join(", "), prepareAbsoluteIosDevProject = async (config, options) => {
|
|
3225
3540
|
if (detectAbsoluteMobileHost() !== "macos")
|
|
3226
|
-
throw new Error("iOS
|
|
3541
|
+
throw new Error("iOS development requires macOS and Xcode.");
|
|
3542
|
+
const target = options.target ?? "simulator";
|
|
3227
3543
|
const projectRoot = resolve7(options.projectRoot);
|
|
3228
3544
|
const checks = await inspectAbsoluteMobileToolchain({ host: "macos" });
|
|
3229
|
-
const failed = checks.filter((check) => check.platform === "ios" && (check.status === "fail" || check.status === "warn"));
|
|
3545
|
+
const failed = checks.filter((check) => check.platform === "ios" && !(target === "device" && check.id === "ios.runtime") && (check.status === "fail" || check.status === "warn"));
|
|
3230
3546
|
if (failed.length > 0)
|
|
3231
|
-
throw new Error(`iOS
|
|
3547
|
+
throw new Error(`iOS ${target} development is not ready: ${failed.map(({ label }) => label).join(", ")}.`);
|
|
3232
3548
|
const xcrun = checks.find((check) => check.id === "ios.xcrun")?.path;
|
|
3233
3549
|
const xcodebuild = checks.find((check) => check.id === "ios.xcodebuild")?.path;
|
|
3234
3550
|
if (!xcrun || !xcodebuild)
|
|
@@ -3258,8 +3574,53 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3258
3574
|
xcodebuild,
|
|
3259
3575
|
xcrun
|
|
3260
3576
|
};
|
|
3577
|
+
}, preparePhysicalIosTarget = async (options) => {
|
|
3578
|
+
options.transition("connecting");
|
|
3579
|
+
validatePhysicalIosDevice(options.project, options.deviceIdentifier, options.capture);
|
|
3580
|
+
if (!options.startOptions.https)
|
|
3581
|
+
return {
|
|
3582
|
+
caEnrollmentServer: null,
|
|
3583
|
+
startedSimulator: false,
|
|
3584
|
+
udid: options.deviceIdentifier
|
|
3585
|
+
};
|
|
3586
|
+
if (!options.startOptions.certificateAuthorityPath)
|
|
3587
|
+
throw new Error("Physical iOS HTTPS development requires the AbsoluteJS development CA certificate.");
|
|
3588
|
+
options.transition("enrolling-trust");
|
|
3589
|
+
const startEnrollment = options.startOptions.startCaEnrollmentServer ?? startAbsoluteIosCaEnrollmentServer;
|
|
3590
|
+
const caEnrollmentServer = await startEnrollment({
|
|
3591
|
+
certificateAuthorityPath: options.startOptions.certificateAuthorityPath,
|
|
3592
|
+
displayHost: options.serverHost
|
|
3593
|
+
});
|
|
3594
|
+
options.log(`On the iOS device, open ${caEnrollmentServer.url}, install the AbsoluteJS development CA profile, then enable it under Settings > General > About > Certificate Trust Settings. This public CA endpoint exists only for this dev session.`);
|
|
3595
|
+
return {
|
|
3596
|
+
caEnrollmentServer,
|
|
3597
|
+
startedSimulator: false,
|
|
3598
|
+
udid: options.deviceIdentifier
|
|
3599
|
+
};
|
|
3600
|
+
}, prepareIosSimulatorTarget = async (options) => {
|
|
3601
|
+
options.transition("booting");
|
|
3602
|
+
const managed = await ensureManagedSimulator(options.project, options.capture);
|
|
3603
|
+
const { device } = managed;
|
|
3604
|
+
const startedSimulator = managed.created || device.state !== "Booted";
|
|
3605
|
+
bootSimulator(options.project, device, options.capture);
|
|
3606
|
+
options.spawn([
|
|
3607
|
+
"open",
|
|
3608
|
+
"-a",
|
|
3609
|
+
"Simulator",
|
|
3610
|
+
"--args",
|
|
3611
|
+
"-CurrentDeviceUDID",
|
|
3612
|
+
device.udid
|
|
3613
|
+
]);
|
|
3614
|
+
options.transition("connecting");
|
|
3615
|
+
await waitForBootedSimulator(options.project, device.udid, options.capture, options.sleep, options.startOptions.signal);
|
|
3616
|
+
await requireSuccess2([options.project.xcrun, "simctl", "bootstatus", device.udid, "-b"], "iOS simulator boot readiness", options.run, { signal: options.startOptions.signal });
|
|
3617
|
+
await trustIosSimulatorDevelopmentCa(options.startOptions, device.udid, options.run, options.log);
|
|
3618
|
+
return { caEnrollmentServer: null, startedSimulator, udid: device.udid };
|
|
3261
3619
|
}, startAbsoluteIosDevSession = async (options) => {
|
|
3262
3620
|
const { project } = options;
|
|
3621
|
+
const deviceIdentifier = options.deviceIdentifier ? normalizeAbsoluteIosDeviceIdentifier(options.deviceIdentifier) : undefined;
|
|
3622
|
+
const targetKind = deviceIdentifier ? "device" : "simulator";
|
|
3623
|
+
const serverHost = deviceIdentifier ? normalizeAbsoluteIosDeviceHost(options.serverHost ?? "") : "localhost";
|
|
3263
3624
|
const capture = options.capture ?? defaultCapture3;
|
|
3264
3625
|
const run = options.run ?? defaultRun3;
|
|
3265
3626
|
const sleep = options.sleep ?? Bun.sleep;
|
|
@@ -3287,6 +3648,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3287
3648
|
options.onStateChange?.(next);
|
|
3288
3649
|
};
|
|
3289
3650
|
let nativeLogs = null;
|
|
3651
|
+
let caEnrollmentServer = null;
|
|
3290
3652
|
const closeLogs = async () => {
|
|
3291
3653
|
const stream = nativeLogs;
|
|
3292
3654
|
nativeLogs = null;
|
|
@@ -3294,38 +3656,66 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3294
3656
|
return;
|
|
3295
3657
|
});
|
|
3296
3658
|
};
|
|
3659
|
+
const relaunchTarget = async (udid) => {
|
|
3660
|
+
if (deviceIdentifier && options.nativeLog) {
|
|
3661
|
+
await closeLogs();
|
|
3662
|
+
nativeLogs = attachNativeLogs(project, udid, options);
|
|
3663
|
+
return;
|
|
3664
|
+
}
|
|
3665
|
+
await requireSuccess2(iosLaunchCommand(project, udid, deviceIdentifier !== undefined), "iOS app relaunch", run, { signal: options.signal });
|
|
3666
|
+
};
|
|
3297
3667
|
try {
|
|
3298
3668
|
await repairAbsoluteIosDevSession(project.projectRoot);
|
|
3299
3669
|
throwIfAborted2(options.signal);
|
|
3300
3670
|
transition("syncing");
|
|
3301
3671
|
await requireSuccess2([project.cap, "sync", "ios"], "Capacitor iOS synchronization", run, { cwd: project.projectRoot, signal: options.signal });
|
|
3302
3672
|
transition("configuring");
|
|
3303
|
-
await writeDevProjection(project, options.port, options.https === true);
|
|
3673
|
+
await writeDevProjection(project, options.port, options.https === true, serverHost);
|
|
3304
3674
|
throwIfAborted2(options.signal);
|
|
3305
3675
|
const fingerprintStartedAt = performance.now();
|
|
3306
3676
|
const fingerprintPromise = fingerprintAbsoluteIosDevProject(project).then((fingerprint2) => {
|
|
3307
3677
|
timings.fingerprinting = performance.now() - fingerprintStartedAt;
|
|
3308
3678
|
return fingerprint2;
|
|
3309
3679
|
});
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3680
|
+
const target = deviceIdentifier ? await preparePhysicalIosTarget({
|
|
3681
|
+
capture,
|
|
3682
|
+
deviceIdentifier,
|
|
3683
|
+
log,
|
|
3684
|
+
project,
|
|
3685
|
+
serverHost,
|
|
3686
|
+
startOptions: options,
|
|
3687
|
+
transition
|
|
3688
|
+
}) : await prepareIosSimulatorTarget({
|
|
3689
|
+
capture,
|
|
3690
|
+
log,
|
|
3691
|
+
project,
|
|
3692
|
+
run,
|
|
3693
|
+
sleep,
|
|
3694
|
+
spawn,
|
|
3695
|
+
startOptions: options,
|
|
3696
|
+
transition
|
|
3697
|
+
});
|
|
3698
|
+
const {
|
|
3699
|
+
caEnrollmentServer: targetCaEnrollmentServer,
|
|
3700
|
+
startedSimulator,
|
|
3701
|
+
udid
|
|
3702
|
+
} = target;
|
|
3703
|
+
caEnrollmentServer = targetCaEnrollmentServer;
|
|
3325
3704
|
const fingerprint = await fingerprintPromise;
|
|
3326
3705
|
transition("checking-native");
|
|
3327
|
-
const
|
|
3328
|
-
|
|
3706
|
+
const cache = await readNativeCache2(project.projectRoot);
|
|
3707
|
+
const nativeCacheHit = deviceIdentifier ? await ensurePhysicalIosDebugApp({
|
|
3708
|
+
cache,
|
|
3709
|
+
capture,
|
|
3710
|
+
fingerprint,
|
|
3711
|
+
identifier: deviceIdentifier,
|
|
3712
|
+
log,
|
|
3713
|
+
project,
|
|
3714
|
+
run,
|
|
3715
|
+
signal: options.signal,
|
|
3716
|
+
transition
|
|
3717
|
+
}) : await ensureIosDebugApp({
|
|
3718
|
+
cache,
|
|
3329
3719
|
capture,
|
|
3330
3720
|
fingerprint,
|
|
3331
3721
|
log,
|
|
@@ -3333,24 +3723,18 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3333
3723
|
run,
|
|
3334
3724
|
signal: options.signal,
|
|
3335
3725
|
transition,
|
|
3336
|
-
udid
|
|
3726
|
+
udid
|
|
3337
3727
|
});
|
|
3338
3728
|
throwIfAborted2(options.signal);
|
|
3339
3729
|
if (options.nativeLog)
|
|
3340
3730
|
transition("streaming-logs");
|
|
3341
|
-
nativeLogs = attachNativeLogs(project,
|
|
3731
|
+
nativeLogs = attachNativeLogs(project, udid, options);
|
|
3342
3732
|
transition("launching");
|
|
3343
|
-
|
|
3344
|
-
project.
|
|
3345
|
-
"simctl",
|
|
3346
|
-
"launch",
|
|
3347
|
-
"--terminate-running-process",
|
|
3348
|
-
device.udid,
|
|
3349
|
-
project.config.appId
|
|
3350
|
-
], "iOS app launch", run, { signal: options.signal });
|
|
3733
|
+
if (!deviceIdentifier || !nativeLogs)
|
|
3734
|
+
await requireSuccess2(iosLaunchCommand(project, udid, deviceIdentifier !== undefined), "iOS app launch", run, { signal: options.signal });
|
|
3351
3735
|
transition("ready");
|
|
3352
3736
|
timings.total = performance.now() - startedAt;
|
|
3353
|
-
log(`iOS
|
|
3737
|
+
log(`iOS ${targetKind} connected with HMR on port ${options.port} in ${getDurationString(timings.total)} (${nativeCacheHit ? "native cache hit" : "native build installed"}).`);
|
|
3354
3738
|
log(`iOS startup: ${timingSummary(timings)}.`);
|
|
3355
3739
|
let closed = false;
|
|
3356
3740
|
const close = async () => {
|
|
@@ -3359,6 +3743,10 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3359
3743
|
closed = true;
|
|
3360
3744
|
transition("closing");
|
|
3361
3745
|
await closeLogs();
|
|
3746
|
+
await caEnrollmentServer?.close().catch(() => {
|
|
3747
|
+
return;
|
|
3748
|
+
});
|
|
3749
|
+
caEnrollmentServer = null;
|
|
3362
3750
|
await repairAbsoluteIosDevSession(project.projectRoot);
|
|
3363
3751
|
transition("closed");
|
|
3364
3752
|
};
|
|
@@ -3366,8 +3754,9 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3366
3754
|
close,
|
|
3367
3755
|
nativeCacheHit,
|
|
3368
3756
|
startedSimulator,
|
|
3757
|
+
targetKind,
|
|
3369
3758
|
timings: { ...timings },
|
|
3370
|
-
udid
|
|
3759
|
+
udid,
|
|
3371
3760
|
rebuild: async () => {
|
|
3372
3761
|
if (closed)
|
|
3373
3762
|
throw new Error("iOS development session is closed.");
|
|
@@ -3380,22 +3769,17 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3380
3769
|
throw new Error("iOS development session is closed.");
|
|
3381
3770
|
transition("launching");
|
|
3382
3771
|
try {
|
|
3383
|
-
await
|
|
3384
|
-
project.xcrun,
|
|
3385
|
-
"simctl",
|
|
3386
|
-
"launch",
|
|
3387
|
-
"--terminate-running-process",
|
|
3388
|
-
device.udid,
|
|
3389
|
-
project.config.appId
|
|
3390
|
-
], "iOS app relaunch", run, { signal: options.signal });
|
|
3772
|
+
await relaunchTarget(udid);
|
|
3391
3773
|
transition("ready");
|
|
3392
|
-
log(`iOS app relaunched on ${
|
|
3774
|
+
log(`iOS app relaunched on the selected ${targetKind}.`);
|
|
3393
3775
|
} catch (error) {
|
|
3394
3776
|
transition("failed");
|
|
3395
3777
|
throw error;
|
|
3396
3778
|
}
|
|
3397
3779
|
},
|
|
3398
3780
|
screenshot: async (destination) => {
|
|
3781
|
+
if (deviceIdentifier)
|
|
3782
|
+
throw new Error("Physical iOS screenshots are captured in Xcode Device Hub; the CLI never records a device screen automatically.");
|
|
3399
3783
|
const resolved = resolve7(project.projectRoot, destination);
|
|
3400
3784
|
if (!isInside2(project.projectRoot, resolved))
|
|
3401
3785
|
throw new Error("iOS screenshot destination must remain inside the project.");
|
|
@@ -3404,7 +3788,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3404
3788
|
project.xcrun,
|
|
3405
3789
|
"simctl",
|
|
3406
3790
|
"io",
|
|
3407
|
-
|
|
3791
|
+
udid,
|
|
3408
3792
|
"screenshot",
|
|
3409
3793
|
resolved
|
|
3410
3794
|
], "iOS simulator screenshot", run, { signal: options.signal });
|
|
@@ -3417,6 +3801,9 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3417
3801
|
} catch (error) {
|
|
3418
3802
|
transition("failed");
|
|
3419
3803
|
await closeLogs();
|
|
3804
|
+
await caEnrollmentServer?.close().catch(() => {
|
|
3805
|
+
return;
|
|
3806
|
+
});
|
|
3420
3807
|
await repairAbsoluteIosDevSession(project.projectRoot);
|
|
3421
3808
|
throw error;
|
|
3422
3809
|
}
|
|
@@ -3425,6 +3812,7 @@ var init_iosSimulatorController = __esm(() => {
|
|
|
3425
3812
|
init_emulatorDoctor();
|
|
3426
3813
|
init_capacitorProject();
|
|
3427
3814
|
init_iosRelease();
|
|
3815
|
+
init_iosPhysicalDeviceTransport();
|
|
3428
3816
|
init_getDurationString();
|
|
3429
3817
|
SECRET_VALUE = /((?:authorization|cookie|password|secret|token|oauth[_-]?code)\s*[:=]\s*)([^\s,;]+)/giu;
|
|
3430
3818
|
BEARER_VALUE = new RegExp(String.raw`\bBearer\s+[A-Za-z0-9._~+/-]+=*`, "giu");
|
|
@@ -3436,6 +3824,7 @@ var init_iosSimulatorController = __esm(() => {
|
|
|
3436
3824
|
["fingerprinting", "fingerprint"],
|
|
3437
3825
|
["booting", "simulator"],
|
|
3438
3826
|
["connecting", "device ready"],
|
|
3827
|
+
["enrolling-trust", "HTTPS trust"],
|
|
3439
3828
|
["checking-native", "app check"],
|
|
3440
3829
|
["building", "Xcode"],
|
|
3441
3830
|
["installing", "install"],
|
|
@@ -3448,9 +3837,10 @@ var init_iosSimulatorController = __esm(() => {
|
|
|
3448
3837
|
var ABSOLUTE_REMOTE_MAC_EVENT_PREFIX = "ABSOLUTE_REMOTE_MAC\t", ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION = 1;
|
|
3449
3838
|
|
|
3450
3839
|
// src/mobile/remoteMacProtocol.ts
|
|
3451
|
-
import { createHash as createHash5, randomUUID as
|
|
3452
|
-
import { chmod, mkdir as mkdir5, readFile as
|
|
3840
|
+
import { createHash as createHash5, randomUUID as randomUUID4 } from "crypto";
|
|
3841
|
+
import { chmod, mkdir as mkdir5, readFile as readFile7, rename as rename5, writeFile as writeFile5 } from "fs/promises";
|
|
3453
3842
|
import { homedir as homedir4 } from "os";
|
|
3843
|
+
import { isIP as isIP3 } from "net";
|
|
3454
3844
|
import {
|
|
3455
3845
|
dirname as dirname6,
|
|
3456
3846
|
isAbsolute as isAbsolute4,
|
|
@@ -3465,7 +3855,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3465
3855
|
profiles: {}
|
|
3466
3856
|
}), loadStore = async (path = defaultProfilePath()) => {
|
|
3467
3857
|
try {
|
|
3468
|
-
const parsed = JSON.parse(await
|
|
3858
|
+
const parsed = JSON.parse(await readFile7(path, "utf8"));
|
|
3469
3859
|
if (parsed.format !== PROFILE_FORMAT || typeof parsed.profiles !== "object" || parsed.profiles === null || Array.isArray(parsed.profiles))
|
|
3470
3860
|
throw new Error("Unsupported remote Mac profile format.");
|
|
3471
3861
|
for (const [key, profile] of Object.entries(parsed.profiles)) {
|
|
@@ -3482,7 +3872,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3482
3872
|
}
|
|
3483
3873
|
}, saveStore = async (store, path = defaultProfilePath()) => {
|
|
3484
3874
|
await mkdir5(dirname6(path), { recursive: true });
|
|
3485
|
-
const temporary = `${path}.${
|
|
3875
|
+
const temporary = `${path}.${randomUUID4()}.tmp`;
|
|
3486
3876
|
await writeFile5(temporary, `${JSON.stringify(store, null, 2)}
|
|
3487
3877
|
`, {
|
|
3488
3878
|
mode: 384
|
|
@@ -3564,6 +3954,18 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3564
3954
|
if (!xcodeVersion?.startsWith("Xcode "))
|
|
3565
3955
|
throw new Error("The remote Mac must have full Xcode installed and selected.");
|
|
3566
3956
|
return { bunPath, home, os: operatingSystem, xcodeVersion };
|
|
3957
|
+
}, inspectAbsoluteRemoteMacLanHost = async (profile, transport) => {
|
|
3958
|
+
const capture = transport?.capture ?? defaultTransport.capture;
|
|
3959
|
+
const script = `default_interface="$(/sbin/route -n get default 2>/dev/null | /usr/bin/awk '/interface:/{print $2; exit}')"; for interface in "$default_interface" en0 en1; do [ -n "$interface" ] || continue; address="$(/usr/sbin/ipconfig getifaddr "$interface" 2>/dev/null || true)"; if [ -n "$address" ]; then printf '%s\\n' "$address"; exit 0; fi; done; exit 1`;
|
|
3960
|
+
const result = await capture([
|
|
3961
|
+
...absoluteRemoteMacSshBase(profile),
|
|
3962
|
+
"/bin/sh -lc",
|
|
3963
|
+
shellQuote(script)
|
|
3964
|
+
]);
|
|
3965
|
+
const host = requireRemoteSuccess(result, "Remote Mac LAN address discovery").trim();
|
|
3966
|
+
if (isIP3(host) === 0)
|
|
3967
|
+
throw new Error("The Remote Mac did not report a device-reachable LAN address.");
|
|
3968
|
+
return host;
|
|
3567
3969
|
}, listAbsoluteRemoteMacProfiles = async (profilePath) => {
|
|
3568
3970
|
const store = await loadStore(profilePath);
|
|
3569
3971
|
return {
|
|
@@ -3630,7 +4032,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3630
4032
|
]);
|
|
3631
4033
|
if (verified.exitCode === 0)
|
|
3632
4034
|
return { ...artifact, remotePath, uploaded: false };
|
|
3633
|
-
const temporary = posix.join(directory, `.agent-${
|
|
4035
|
+
const temporary = posix.join(directory, `.agent-${randomUUID4()}.tmp`);
|
|
3634
4036
|
const installScript = [
|
|
3635
4037
|
"set -eu",
|
|
3636
4038
|
"umask 077",
|
|
@@ -3717,7 +4119,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3717
4119
|
}), absoluteRemoteProjectSyncCommands = (project) => {
|
|
3718
4120
|
const current = project.remoteProjectRoot;
|
|
3719
4121
|
const parent = posix.dirname(current);
|
|
3720
|
-
const staging = posix.join(parent, `.incoming-${
|
|
4122
|
+
const staging = posix.join(parent, `.incoming-${randomUUID4()}`);
|
|
3721
4123
|
const previous = posix.join(parent, ".previous");
|
|
3722
4124
|
const script = [
|
|
3723
4125
|
"set -eu",
|
|
@@ -3806,6 +4208,13 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3806
4208
|
await syncProject(options.project);
|
|
3807
4209
|
const syncDuration = performance.now() - syncStartedAt;
|
|
3808
4210
|
const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
|
|
4211
|
+
const encodedCertificateAuthority = options.certificateAuthorityPath ? (await readFile7(options.certificateAuthorityPath)).toString("base64url") : undefined;
|
|
4212
|
+
const physicalDevice = options.deviceIdentifier !== undefined;
|
|
4213
|
+
let relayPort;
|
|
4214
|
+
if (physicalDevice)
|
|
4215
|
+
relayPort = options.port <= 49151 ? options.port + 16384 : options.port - 16384;
|
|
4216
|
+
if (physicalDevice && !options.serverHost)
|
|
4217
|
+
throw new Error("Remote physical iOS development requires the Remote Mac LAN host.");
|
|
3809
4218
|
const remoteCommand = [
|
|
3810
4219
|
`cd ${shellQuote(options.project.remoteProjectRoot)}`,
|
|
3811
4220
|
"&&",
|
|
@@ -3816,14 +4225,26 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3816
4225
|
String(options.port),
|
|
3817
4226
|
"--mobile-config",
|
|
3818
4227
|
shellQuote(encodedConfig),
|
|
3819
|
-
...
|
|
4228
|
+
...encodedCertificateAuthority ? [
|
|
4229
|
+
"--certificate-authority",
|
|
4230
|
+
shellQuote(encodedCertificateAuthority)
|
|
4231
|
+
] : [],
|
|
4232
|
+
...options.https ? ["--https"] : [],
|
|
4233
|
+
...options.deviceIdentifier ? [
|
|
4234
|
+
"--ios-device",
|
|
4235
|
+
shellQuote(options.deviceIdentifier),
|
|
4236
|
+
"--server-host",
|
|
4237
|
+
shellQuote(options.serverHost ?? ""),
|
|
4238
|
+
"--relay-port",
|
|
4239
|
+
String(relayPort)
|
|
4240
|
+
] : []
|
|
3820
4241
|
].join(" ");
|
|
3821
4242
|
const command = [
|
|
3822
4243
|
...absoluteRemoteMacSshBase(options.project.profile),
|
|
3823
4244
|
"-o",
|
|
3824
4245
|
"ExitOnForwardFailure=yes",
|
|
3825
4246
|
"-R",
|
|
3826
|
-
`${options.port}:127.0.0.1:${options.port}`,
|
|
4247
|
+
physicalDevice ? `127.0.0.1:${relayPort}:127.0.0.1:${options.port}` : `${options.port}:127.0.0.1:${options.port}`,
|
|
3827
4248
|
"/bin/sh -lc",
|
|
3828
4249
|
shellQuote(remoteCommand)
|
|
3829
4250
|
];
|
|
@@ -3911,7 +4332,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3911
4332
|
};
|
|
3912
4333
|
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.`);
|
|
3913
4334
|
const request = (commandName) => {
|
|
3914
|
-
const id =
|
|
4335
|
+
const id = randomUUID4();
|
|
3915
4336
|
const response = new Promise((resolve8, reject) => pending.set(id, { reject, resolve: resolve8 }));
|
|
3916
4337
|
process2.stdin.write(`${JSON.stringify({ command: commandName, id, v: 1 })}
|
|
3917
4338
|
`);
|
|
@@ -3944,6 +4365,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3944
4365
|
close,
|
|
3945
4366
|
nativeCacheHit: currentReady.nativeCacheHit,
|
|
3946
4367
|
startedSimulator: currentReady.startedSimulator,
|
|
4368
|
+
targetKind: currentReady.targetKind,
|
|
3947
4369
|
timings: currentReady.timings,
|
|
3948
4370
|
udid: currentReady.udid,
|
|
3949
4371
|
rebuild: async () => {
|
|
@@ -3999,9 +4421,11 @@ var init_remoteMacProtocol = __esm(() => {
|
|
|
3999
4421
|
var exports_devCert = {};
|
|
4000
4422
|
__export(exports_devCert, {
|
|
4001
4423
|
setupMkcert: () => setupMkcert,
|
|
4424
|
+
normalizeDevCertificateHosts: () => normalizeDevCertificateHosts,
|
|
4002
4425
|
loadDevCert: () => loadDevCert,
|
|
4003
4426
|
hasMkcert: () => hasMkcert,
|
|
4004
4427
|
hasCert: () => hasCert,
|
|
4428
|
+
getDevCertificateAuthorityPath: () => getDevCertificateAuthorityPath,
|
|
4005
4429
|
ensureDevCert: () => ensureDevCert
|
|
4006
4430
|
});
|
|
4007
4431
|
import {
|
|
@@ -4011,20 +4435,31 @@ import {
|
|
|
4011
4435
|
readFileSync as readFileSync7,
|
|
4012
4436
|
rmSync
|
|
4013
4437
|
} from "fs";
|
|
4438
|
+
import { X509Certificate as X509Certificate2 } from "crypto";
|
|
4439
|
+
import { isIP as isIP4 } from "net";
|
|
4014
4440
|
import { platform as platform2 } from "os";
|
|
4015
4441
|
import { join as join12 } from "path";
|
|
4016
|
-
var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => console.log(`\x1B[2m${new Date().toLocaleTimeString()}\x1B[0m \x1B[36m[dev]\x1B[0m ${msg}`), devWarn = (msg) => console.log(`\x1B[2m${new Date().toLocaleTimeString()}\x1B[0m \x1B[33m[dev]\x1B[0m \x1B[33m${msg}\x1B[0m`), certFilesExist = () => existsSync4(CERT_PATH) && existsSync4(KEY_PATH),
|
|
4442
|
+
var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, DEFAULT_CERTIFICATE_HOSTS, CERTIFICATE_HOSTNAME_PATTERN, devLog = (msg) => console.log(`\x1B[2m${new Date().toLocaleTimeString()}\x1B[0m \x1B[36m[dev]\x1B[0m ${msg}`), devWarn = (msg) => console.log(`\x1B[2m${new Date().toLocaleTimeString()}\x1B[0m \x1B[33m[dev]\x1B[0m \x1B[33m${msg}\x1B[0m`), certFilesExist = () => existsSync4(CERT_PATH) && existsSync4(KEY_PATH), normalizeDevCertificateHosts = (hosts = []) => {
|
|
4443
|
+
const normalized = new Set(DEFAULT_CERTIFICATE_HOSTS);
|
|
4444
|
+
for (const host of hosts) {
|
|
4445
|
+
const value = host.trim().toLowerCase();
|
|
4446
|
+
if (!value || value === "0.0.0.0" || value === "::")
|
|
4447
|
+
continue;
|
|
4448
|
+
if (isIP4(value) === 0 && !CERTIFICATE_HOSTNAME_PATTERN.test(value)) {
|
|
4449
|
+
throw new TypeError(`Invalid development certificate host: ${host}`);
|
|
4450
|
+
}
|
|
4451
|
+
normalized.add(value);
|
|
4452
|
+
}
|
|
4453
|
+
return [...normalized];
|
|
4454
|
+
}, certificateIsUsable = (hosts) => {
|
|
4017
4455
|
try {
|
|
4018
4456
|
const certPem = readFileSync7(CERT_PATH, "utf-8");
|
|
4019
|
-
const
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
const dateStr = output.replace("notAfter=", "");
|
|
4024
|
-
const expiryDate = new Date(dateStr);
|
|
4025
|
-
return expiryDate.getTime() < Date.now();
|
|
4457
|
+
const certificate = new X509Certificate2(certPem);
|
|
4458
|
+
if (new Date(certificate.validTo).getTime() <= Date.now())
|
|
4459
|
+
return false;
|
|
4460
|
+
return normalizeDevCertificateHosts(hosts).every((host) => isIP4(host) ? certificate.checkIP(host) !== undefined : certificate.checkHost(host) !== undefined);
|
|
4026
4461
|
} catch {
|
|
4027
|
-
return
|
|
4462
|
+
return false;
|
|
4028
4463
|
}
|
|
4029
4464
|
}, hasMkcert = () => {
|
|
4030
4465
|
try {
|
|
@@ -4036,22 +4471,21 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4036
4471
|
} catch {
|
|
4037
4472
|
return false;
|
|
4038
4473
|
}
|
|
4039
|
-
}, generateWithMkcert = () => {
|
|
4474
|
+
}, generateWithMkcert = (hosts = []) => {
|
|
4040
4475
|
const result = Bun.spawnSync([
|
|
4041
4476
|
"mkcert",
|
|
4042
4477
|
"-cert-file",
|
|
4043
4478
|
CERT_PATH,
|
|
4044
4479
|
"-key-file",
|
|
4045
4480
|
KEY_PATH,
|
|
4046
|
-
|
|
4047
|
-
"127.0.0.1",
|
|
4048
|
-
"::1"
|
|
4481
|
+
...normalizeDevCertificateHosts(hosts)
|
|
4049
4482
|
], { stderr: "pipe", stdout: "pipe" });
|
|
4050
4483
|
if (result.exitCode !== 0) {
|
|
4051
4484
|
const err = new TextDecoder().decode(result.stderr);
|
|
4052
4485
|
throw new Error(`mkcert failed: ${err}`);
|
|
4053
4486
|
}
|
|
4054
|
-
}, generateSelfSigned = () => {
|
|
4487
|
+
}, generateSelfSigned = (hosts = []) => {
|
|
4488
|
+
const subjectAlternativeNames = normalizeDevCertificateHosts(hosts).map((host) => `${isIP4(host) ? "IP" : "DNS"}:${host}`).join(",");
|
|
4055
4489
|
const proc = Bun.spawnSync([
|
|
4056
4490
|
"openssl",
|
|
4057
4491
|
"req",
|
|
@@ -4070,36 +4504,36 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4070
4504
|
"-subj",
|
|
4071
4505
|
"/CN=localhost",
|
|
4072
4506
|
"-addext",
|
|
4073
|
-
|
|
4507
|
+
`subjectAltName=${subjectAlternativeNames}`
|
|
4074
4508
|
], { stderr: "pipe", stdout: "pipe" });
|
|
4075
4509
|
if (proc.exitCode !== 0) {
|
|
4076
4510
|
const err = new TextDecoder().decode(proc.stderr);
|
|
4077
4511
|
throw new Error(`openssl failed: ${err}`);
|
|
4078
4512
|
}
|
|
4079
4513
|
devLog("Using self-signed certificate \u2014 browser will show a one-time warning");
|
|
4080
|
-
}, generateCert = () => {
|
|
4514
|
+
}, generateCert = (hosts = []) => {
|
|
4081
4515
|
if (hasMkcert()) {
|
|
4082
|
-
generateWithMkcert();
|
|
4516
|
+
generateWithMkcert(hosts);
|
|
4083
4517
|
} else {
|
|
4084
|
-
generateSelfSigned();
|
|
4518
|
+
generateSelfSigned(hosts);
|
|
4085
4519
|
}
|
|
4086
|
-
}, ensureDevCert = () => {
|
|
4520
|
+
}, ensureDevCert = (hosts = []) => {
|
|
4087
4521
|
mkdirSync4(CERT_DIR, { recursive: true });
|
|
4088
|
-
if (hasCert()) {
|
|
4522
|
+
if (hasCert(hosts)) {
|
|
4089
4523
|
return { cert: CERT_PATH, key: KEY_PATH };
|
|
4090
4524
|
}
|
|
4091
4525
|
if (certFilesExist()) {
|
|
4092
|
-
devLog("Certificate expired, regenerating...");
|
|
4526
|
+
devLog("Certificate is expired or missing a required host, regenerating...");
|
|
4093
4527
|
}
|
|
4094
4528
|
try {
|
|
4095
|
-
generateCert();
|
|
4529
|
+
generateCert(hosts);
|
|
4096
4530
|
} catch (err) {
|
|
4097
4531
|
devWarn(`Failed to generate certificate: ${err instanceof Error ? err.message : err}`);
|
|
4098
4532
|
return null;
|
|
4099
4533
|
}
|
|
4100
4534
|
return { cert: CERT_PATH, key: KEY_PATH };
|
|
4101
|
-
}, hasCert = () => certFilesExist() &&
|
|
4102
|
-
const paths = ensureDevCert();
|
|
4535
|
+
}, hasCert = (hosts = []) => certFilesExist() && certificateIsUsable(hosts), loadDevCert = (hosts = []) => {
|
|
4536
|
+
const paths = ensureDevCert(hosts);
|
|
4103
4537
|
if (!paths)
|
|
4104
4538
|
return null;
|
|
4105
4539
|
try {
|
|
@@ -4215,7 +4649,15 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4215
4649
|
} catch {
|
|
4216
4650
|
return null;
|
|
4217
4651
|
}
|
|
4218
|
-
}, mkcertCaRoot = () => runCapture(["mkcert", "-CAROOT"]),
|
|
4652
|
+
}, mkcertCaRoot = () => runCapture(["mkcert", "-CAROOT"]), getDevCertificateAuthorityPath = () => {
|
|
4653
|
+
const caRoot = hasMkcert() ? mkcertCaRoot() : null;
|
|
4654
|
+
const rootCertificate = caRoot ? join12(caRoot, "rootCA.pem") : null;
|
|
4655
|
+
if (rootCertificate && existsSync4(rootCertificate))
|
|
4656
|
+
return rootCertificate;
|
|
4657
|
+
if (certFilesExist())
|
|
4658
|
+
return CERT_PATH;
|
|
4659
|
+
return null;
|
|
4660
|
+
}, toWindowsPath = (linuxPath) => runCapture(["wslpath", "-w", linuxPath]), windowsTempDir = () => {
|
|
4219
4661
|
const winTemp = runCapture(["cmd.exe", "/c", "echo %TEMP%"]);
|
|
4220
4662
|
if (!winTemp)
|
|
4221
4663
|
return null;
|
|
@@ -4249,7 +4691,7 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4249
4691
|
], { stderr: "pipe", stdout: "pipe" });
|
|
4250
4692
|
rmSync(staged, { force: true });
|
|
4251
4693
|
return result.exitCode === 0;
|
|
4252
|
-
}, setupMkcert = () => {
|
|
4694
|
+
}, setupMkcert = (hosts = []) => {
|
|
4253
4695
|
if (!ensureMkcert())
|
|
4254
4696
|
return false;
|
|
4255
4697
|
const installResult = Bun.spawnSync(["mkcert", "-install"], {
|
|
@@ -4276,7 +4718,7 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4276
4718
|
rmSync(CERT_PATH, { force: true });
|
|
4277
4719
|
rmSync(KEY_PATH, { force: true });
|
|
4278
4720
|
mkdirSync4(CERT_DIR, { recursive: true });
|
|
4279
|
-
generateWithMkcert();
|
|
4721
|
+
generateWithMkcert(hosts);
|
|
4280
4722
|
console.log("");
|
|
4281
4723
|
devLog("mkcert installed \u2014 HTTPS certificates are now locally trusted");
|
|
4282
4724
|
return true;
|
|
@@ -4285,6 +4727,8 @@ var init_devCert = __esm(() => {
|
|
|
4285
4727
|
CERT_DIR = join12(process.cwd(), ".absolutejs");
|
|
4286
4728
|
CERT_PATH = join12(CERT_DIR, "cert.pem");
|
|
4287
4729
|
KEY_PATH = join12(CERT_DIR, "key.pem");
|
|
4730
|
+
DEFAULT_CERTIFICATE_HOSTS = ["localhost", "127.0.0.1", "::1"];
|
|
4731
|
+
CERTIFICATE_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])?))*$/u;
|
|
4288
4732
|
});
|
|
4289
4733
|
|
|
4290
4734
|
// src/cli/scripts/eslintChunked.ts
|
|
@@ -5689,7 +6133,7 @@ var normalizeSlug = (str) => str.trim().replace(/\s+/g, "-").replace(/[^A-Za-z0-
|
|
|
5689
6133
|
|
|
5690
6134
|
// src/mobile/buildRelease.ts
|
|
5691
6135
|
import { createHash as createHash8 } from "crypto";
|
|
5692
|
-
import { mkdir as mkdir7, readFile as
|
|
6136
|
+
import { mkdir as mkdir7, readFile as readFile8, writeFile as writeFile6 } from "fs/promises";
|
|
5693
6137
|
import { basename as basename6, dirname as dirname9, extname as extname3, join as join16, relative as relative9, resolve as resolve13 } from "path";
|
|
5694
6138
|
var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATIC_SCRIPT_PATTERN, rewriteStaticScriptPaths = (source, manifest) => source.replace(STATIC_SCRIPT_PATTERN, (match, prefix, path, suffix) => {
|
|
5695
6139
|
if (path.endsWith("/htmx.min.js"))
|
|
@@ -5711,7 +6155,7 @@ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATI
|
|
|
5711
6155
|
}
|
|
5712
6156
|
let resolvedAssetPath = resolveAssetPath(buildDirectory, assetPath);
|
|
5713
6157
|
if (metadata.framework === "html" || metadata.framework === "htmx") {
|
|
5714
|
-
const source = await
|
|
6158
|
+
const source = await readFile8(resolvedAssetPath, "utf8");
|
|
5715
6159
|
const rewritten = rewriteStaticScriptPaths(source, manifest);
|
|
5716
6160
|
const documentHash = sha256(new TextEncoder().encode(rewritten));
|
|
5717
6161
|
resolvedAssetPath = join16(buildDirectory, ".absolutejs", "mobile-pages", `${documentHash}.html`);
|
|
@@ -5725,8 +6169,8 @@ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATI
|
|
|
5725
6169
|
].map((key) => manifest[key]).find((path) => typeof path === "string");
|
|
5726
6170
|
const resolvedStylePath = styleAssetPath ? resolveAssetPath(buildDirectory, styleAssetPath) : undefined;
|
|
5727
6171
|
const [bytes, styleBytes] = await Promise.all([
|
|
5728
|
-
|
|
5729
|
-
resolvedStylePath ?
|
|
6172
|
+
readFile8(resolvedAssetPath),
|
|
6173
|
+
resolvedStylePath ? readFile8(resolvedStylePath) : undefined
|
|
5730
6174
|
]);
|
|
5731
6175
|
const bundlePath = `/${relative9(resolve13(buildDirectory), resolvedAssetPath).replaceAll("\\", "/")}`;
|
|
5732
6176
|
const styleBundlePath = resolvedStylePath ? `/${relative9(resolve13(buildDirectory), resolvedStylePath).replaceAll("\\", "/")}` : undefined;
|
|
@@ -5745,7 +6189,7 @@ var sha256 = (bytes) => createHash8("sha256").update(bytes).digest("hex"), STATI
|
|
|
5745
6189
|
}, buildAbsoluteMobileCompatibilityRelease = async (options) => {
|
|
5746
6190
|
const [captured, producerBytes] = await Promise.all([
|
|
5747
6191
|
captureAbsoluteMobileRouteGraph(options.app),
|
|
5748
|
-
|
|
6192
|
+
readFile8(options.producerPath)
|
|
5749
6193
|
]);
|
|
5750
6194
|
if (captured.length === 0) {
|
|
5751
6195
|
throw new TypeError("No instrumented AbsoluteJS mobile page routes were found in the finalized Elysia route graph.");
|
|
@@ -5925,7 +6369,7 @@ import {
|
|
|
5925
6369
|
copyFile as copyFile4,
|
|
5926
6370
|
mkdir as mkdir8,
|
|
5927
6371
|
mkdtemp as mkdtemp3,
|
|
5928
|
-
readFile as
|
|
6372
|
+
readFile as readFile9,
|
|
5929
6373
|
rename as rename6,
|
|
5930
6374
|
rm as rm5,
|
|
5931
6375
|
writeFile as writeFile7
|
|
@@ -5983,7 +6427,7 @@ var MANIFEST_FILE = "absolute-mobile-manifest.json", BOOTSTRAP_FILE = "absolute-
|
|
|
5983
6427
|
const packageName = specifier.startsWith("@") ? segments.slice(0, 2).join("/") : segments[0] ?? "";
|
|
5984
6428
|
const subpath = specifier.slice(packageName.length);
|
|
5985
6429
|
const packageDirectory = join17(resolve14(projectRoot), "node_modules", packageName);
|
|
5986
|
-
const manifest = JSON.parse(await
|
|
6430
|
+
const manifest = JSON.parse(await readFile9(join17(packageDirectory, "package.json"), "utf8"));
|
|
5987
6431
|
const exports = typeof manifest === "object" && manifest !== null ? Reflect.get(manifest, "exports") : undefined;
|
|
5988
6432
|
const entry = typeof exports === "object" && exports !== null ? Reflect.get(exports, subpath ? `.${subpath}` : ".") : undefined;
|
|
5989
6433
|
const target = importEntryTarget(entry);
|
|
@@ -6085,7 +6529,7 @@ void startAbsoluteMobileShell(${push ? `{ createAuth: (config, options) => creat
|
|
|
6085
6529
|
...localStylePath ? { localStylePath } : {}
|
|
6086
6530
|
};
|
|
6087
6531
|
}, absoluteClientImports = async (sourcePath, buildDirectory) => {
|
|
6088
|
-
const source = await
|
|
6532
|
+
const source = await readFile9(sourcePath, "utf8");
|
|
6089
6533
|
const extension = extname4(sourcePath).toLowerCase();
|
|
6090
6534
|
let scriptLoader;
|
|
6091
6535
|
if (extension === ".tsx")
|
|
@@ -6227,7 +6671,7 @@ import {
|
|
|
6227
6671
|
access as access6,
|
|
6228
6672
|
mkdir as mkdir9,
|
|
6229
6673
|
mkdtemp as mkdtemp4,
|
|
6230
|
-
readFile as
|
|
6674
|
+
readFile as readFile10,
|
|
6231
6675
|
rename as rename7,
|
|
6232
6676
|
rm as rm6,
|
|
6233
6677
|
writeFile as writeFile8
|
|
@@ -6327,7 +6771,7 @@ var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1, CURRENT_BUNDLE_FILE = "curre
|
|
|
6327
6771
|
}, readAbsoluteMobileMaterializedReleases = async (root) => {
|
|
6328
6772
|
const resolvedRoot = resolvePath2(root);
|
|
6329
6773
|
try {
|
|
6330
|
-
const serialized = await
|
|
6774
|
+
const serialized = await readFile10(join18(resolvedRoot, CURRENT_BUNDLE_FILE), "utf8");
|
|
6331
6775
|
const parsed = JSON.parse(serialized);
|
|
6332
6776
|
const index = parseBundleIndex(parsed);
|
|
6333
6777
|
const bundleRoot = join18(resolvedRoot, BUNDLES_DIRECTORY, index.bundleId);
|
|
@@ -7029,7 +7473,7 @@ var init_deviceCapabilities = __esm(() => {
|
|
|
7029
7473
|
});
|
|
7030
7474
|
|
|
7031
7475
|
// src/mobile/buildPipeline.ts
|
|
7032
|
-
import { readFile as
|
|
7476
|
+
import { readFile as readFile11 } from "fs/promises";
|
|
7033
7477
|
import { join as join21, resolve as resolve17 } from "path";
|
|
7034
7478
|
import { pathToFileURL } from "url";
|
|
7035
7479
|
var isElysiaApp = (value) => typeof value === "object" && value !== null && typeof Reflect.get(value, "compile") === "function" && Array.isArray(Reflect.get(value, "routes")), isStringRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "string"), serverExportName = (loaded, app) => {
|
|
@@ -7063,7 +7507,7 @@ var isElysiaApp = (value) => typeof value === "object" && value !== null && type
|
|
|
7063
7507
|
const mobile = normalizeAbsoluteMobileConfig(options.mobile, options.projectRoot);
|
|
7064
7508
|
const root = join21(buildDirectory, ".absolutejs", "mobile-compatibility");
|
|
7065
7509
|
const [manifestSource, previous] = await Promise.all([
|
|
7066
|
-
|
|
7510
|
+
readFile11(join21(buildDirectory, "manifest.json"), "utf8"),
|
|
7067
7511
|
readAbsoluteMobileMaterializedReleases(root)
|
|
7068
7512
|
]);
|
|
7069
7513
|
const manifest = JSON.parse(manifestSource);
|
|
@@ -10769,8 +11213,8 @@ export { value };
|
|
|
10769
11213
|
host2.getSourceFile = (fileName, languageVersion, onError, shouldCreate) => fileName === virtualPath ? ts6.createSourceFile(fileName, source, languageVersion, true) : getSourceFile(fileName, languageVersion, onError, shouldCreate);
|
|
10770
11214
|
const fileExists = host2.fileExists.bind(host2);
|
|
10771
11215
|
host2.fileExists = (fileName) => fileName === virtualPath ? true : fileExists(fileName);
|
|
10772
|
-
const
|
|
10773
|
-
host2.readFile = (fileName) => fileName === virtualPath ? source :
|
|
11216
|
+
const readFile12 = host2.readFile.bind(host2);
|
|
11217
|
+
host2.readFile = (fileName) => fileName === virtualPath ? source : readFile12(fileName);
|
|
10774
11218
|
const program = ts6.createProgram([virtualPath], options, host2);
|
|
10775
11219
|
const checker = program.getTypeChecker();
|
|
10776
11220
|
const sourceFile = program.getSourceFile(virtualPath);
|
|
@@ -16393,10 +16837,10 @@ var init_compile = __esm(() => {
|
|
|
16393
16837
|
});
|
|
16394
16838
|
|
|
16395
16839
|
// src/mobile/nativeDeepLinks.ts
|
|
16396
|
-
import { readFile as
|
|
16840
|
+
import { readFile as readFile12, rename as rename8, writeFile as writeFile9 } from "fs/promises";
|
|
16397
16841
|
import { join as join46 } from "path";
|
|
16398
16842
|
var START_MARKER = "<!-- absolutejs:deep-links:start -->", END_MARKER = "<!-- absolutejs:deep-links:end -->", IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements", NOT_FOUND = -1, escapeXml = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">"), writeChangedFile = async (path, source) => {
|
|
16399
|
-
const current = await
|
|
16843
|
+
const current = await readFile12(path, "utf8");
|
|
16400
16844
|
if (current === source)
|
|
16401
16845
|
return false;
|
|
16402
16846
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
@@ -16444,7 +16888,7 @@ ${hosts}
|
|
|
16444
16888
|
`;
|
|
16445
16889
|
}, configureAndroid = async (config) => {
|
|
16446
16890
|
const path = join46(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
16447
|
-
const source = await
|
|
16891
|
+
const source = await readFile12(path, "utf8");
|
|
16448
16892
|
const mainActivity = source.indexOf('android:name=".MainActivity"');
|
|
16449
16893
|
if (mainActivity === NOT_FOUND) {
|
|
16450
16894
|
throw new TypeError("Android MainActivity was not found.");
|
|
@@ -16468,7 +16912,7 @@ ${hosts}
|
|
|
16468
16912
|
${END_MARKER}
|
|
16469
16913
|
`, configureIosInfo = async (config) => {
|
|
16470
16914
|
const path = join46(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
16471
|
-
const source = await
|
|
16915
|
+
const source = await readFile12(path, "utf8");
|
|
16472
16916
|
const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
|
|
16473
16917
|
${END_MARKER}
|
|
16474
16918
|
`;
|
|
@@ -16492,7 +16936,7 @@ ${domains}
|
|
|
16492
16936
|
const path = join46(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
|
|
16493
16937
|
let current = "";
|
|
16494
16938
|
try {
|
|
16495
|
-
current = await
|
|
16939
|
+
current = await readFile12(path, "utf8");
|
|
16496
16940
|
} catch (error) {
|
|
16497
16941
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
16498
16942
|
throw error;
|
|
@@ -16507,7 +16951,7 @@ ${domains}
|
|
|
16507
16951
|
return true;
|
|
16508
16952
|
}, configureIosProject = async (config) => {
|
|
16509
16953
|
const path = join46(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
16510
|
-
const source = await
|
|
16954
|
+
const source = await readFile12(path, "utf8");
|
|
16511
16955
|
const declarations = [
|
|
16512
16956
|
...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
|
|
16513
16957
|
].map((match) => match[1]);
|
|
@@ -16545,10 +16989,10 @@ ${domains}
|
|
|
16545
16989
|
var init_nativeDeepLinks = () => {};
|
|
16546
16990
|
|
|
16547
16991
|
// src/mobile/nativeDeviceCapabilities.ts
|
|
16548
|
-
import { readFile as
|
|
16992
|
+
import { readFile as readFile13, rename as rename9, writeFile as writeFile10 } from "fs/promises";
|
|
16549
16993
|
import { join as join47 } from "path";
|
|
16550
16994
|
var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->", NOT_FOUND2 = -1, IOS_PRIVACY_FILE_REFERENCE = "A85D0C000000000000000001", IOS_PRIVACY_BUILD_FILE = "A85D0C000000000000000002", PUSH_START_MARKER = "absolutejs:push-notifications:start", PUSH_END_MARKER = "absolutejs:push-notifications:end", escapeXml2 = (value) => value.replaceAll("&", "&").replaceAll('"', """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">"), writeChangedFile2 = async (path, source) => {
|
|
16551
|
-
const current = await
|
|
16995
|
+
const current = await readFile13(path, "utf8");
|
|
16552
16996
|
if (current === source)
|
|
16553
16997
|
return false;
|
|
16554
16998
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
@@ -16566,7 +17010,7 @@ var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->", END_MARKER2
|
|
|
16566
17010
|
return writeChangedFile2(path, source);
|
|
16567
17011
|
}, optionalSource = async (path) => {
|
|
16568
17012
|
try {
|
|
16569
|
-
return await
|
|
17013
|
+
return await readFile13(path, "utf8");
|
|
16570
17014
|
} catch (error) {
|
|
16571
17015
|
if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT")
|
|
16572
17016
|
return null;
|
|
@@ -16674,7 +17118,7 @@ ${entries}
|
|
|
16674
17118
|
if (requirements.iosPrivacyAccessedApis.length === 0)
|
|
16675
17119
|
return false;
|
|
16676
17120
|
const projectPath = join47(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
|
|
16677
|
-
const project = await
|
|
17121
|
+
const project = await readFile13(projectPath, "utf8");
|
|
16678
17122
|
return writeChangedFile2(projectPath, addIosPrivacyProjectReference(project));
|
|
16679
17123
|
}, addIosPrivacyProjectReference = (source) => {
|
|
16680
17124
|
const fileMatch = source.match(/([A-F0-9]{24}) \/\* PrivacyInfo\.xcprivacy \*\/ = \{isa = PBXFileReference;/u);
|
|
@@ -16725,7 +17169,7 @@ ${next.slice(index)}`;
|
|
|
16725
17169
|
return next;
|
|
16726
17170
|
}, configureIos2 = async (config, plan) => {
|
|
16727
17171
|
const path = join47(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
16728
|
-
const source = await
|
|
17172
|
+
const source = await readFile13(path, "utf8");
|
|
16729
17173
|
const requirements = absoluteDeviceNativeRequirements(plan);
|
|
16730
17174
|
const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
|
|
16731
17175
|
const ownedStart = source.indexOf(START_MARKER2);
|
|
@@ -16815,7 +17259,7 @@ ${content}
|
|
|
16815
17259
|
return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
|
|
16816
17260
|
}, configureAndroid2 = async (config, plan) => {
|
|
16817
17261
|
const path = join47(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
16818
|
-
const source = await
|
|
17262
|
+
const source = await readFile13(path, "utf8");
|
|
16819
17263
|
const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
|
|
16820
17264
|
const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
|
|
16821
17265
|
`);
|
|
@@ -16874,10 +17318,10 @@ var init_nativeDeviceCapabilities = __esm(() => {
|
|
|
16874
17318
|
});
|
|
16875
17319
|
|
|
16876
17320
|
// src/mobile/nativeBackgroundSync.ts
|
|
16877
|
-
import { readFile as
|
|
17321
|
+
import { readFile as readFile14, rename as rename10, writeFile as writeFile11 } from "fs/promises";
|
|
16878
17322
|
import { join as join48 } from "path";
|
|
16879
17323
|
var writeChanged = async (path, source) => {
|
|
16880
|
-
const current = await
|
|
17324
|
+
const current = await readFile14(path, "utf8");
|
|
16881
17325
|
if (current === source)
|
|
16882
17326
|
return false;
|
|
16883
17327
|
const temporary = `${path}.${crypto.randomUUID()}.tmp`;
|
|
@@ -16959,10 +17403,10 @@ ${makeRegion(values)} </array>
|
|
|
16959
17403
|
return { changed: false };
|
|
16960
17404
|
const identifier = `${config.appId}.absolutejs.background-sync`;
|
|
16961
17405
|
const infoPath = join48(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
16962
|
-
const info2 = await
|
|
17406
|
+
const info2 = await readFile14(infoPath, "utf8");
|
|
16963
17407
|
const nextInfo = ensurePlistArrayValues(ensurePlistArrayValues(info2, "BGTaskSchedulerPermittedIdentifiers", [identifier], "background-sync-identifiers"), "UIBackgroundModes", ["fetch", "processing"], "background-sync-modes");
|
|
16964
17408
|
const delegatePath = join48(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
|
|
16965
|
-
let delegate = await
|
|
17409
|
+
let delegate = await readFile14(delegatePath, "utf8");
|
|
16966
17410
|
if (!delegate.includes("import AbsoluteSyncCapacitor")) {
|
|
16967
17411
|
const importIndex = delegate.lastIndexOf("import Capacitor");
|
|
16968
17412
|
if (importIndex < 0)
|
|
@@ -16996,7 +17440,7 @@ var init_nativeBackgroundSync = __esm(() => {
|
|
|
16996
17440
|
import {
|
|
16997
17441
|
access as access7,
|
|
16998
17442
|
mkdir as mkdir10,
|
|
16999
|
-
readFile as
|
|
17443
|
+
readFile as readFile15,
|
|
17000
17444
|
rename as rename11,
|
|
17001
17445
|
rm as rm7,
|
|
17002
17446
|
writeFile as writeFile12
|
|
@@ -17054,7 +17498,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
|
|
|
17054
17498
|
}, writeAtomic = async (path, source) => {
|
|
17055
17499
|
let current;
|
|
17056
17500
|
try {
|
|
17057
|
-
current = await
|
|
17501
|
+
current = await readFile15(path, "utf8");
|
|
17058
17502
|
} catch (error) {
|
|
17059
17503
|
if (!(error instanceof Error) || !("code" in error) || error.code !== "ENOENT") {
|
|
17060
17504
|
throw error;
|
|
@@ -17077,7 +17521,7 @@ var OWNERSHIP_FILE = ".absolutejs-mobile-associations.json", HTTP_OK = 200, VERI
|
|
|
17077
17521
|
const path = resolve37(root, OWNERSHIP_FILE);
|
|
17078
17522
|
let ownership;
|
|
17079
17523
|
try {
|
|
17080
|
-
ownership = JSON.parse(await
|
|
17524
|
+
ownership = JSON.parse(await readFile15(path, "utf8"));
|
|
17081
17525
|
} catch {
|
|
17082
17526
|
throw new TypeError(`Association output ${root} already exists and is not owned by AbsoluteJS.`);
|
|
17083
17527
|
}
|
|
@@ -17590,8 +18034,8 @@ var DEFAULT_ROUTE_TIMEOUT_MS = 30000, DEFAULT_HMR_TIMEOUT_MS = 30000, routeExpre
|
|
|
17590
18034
|
};
|
|
17591
18035
|
|
|
17592
18036
|
// src/mobile/releaseDoctor.ts
|
|
17593
|
-
import { access as access8, readFile as
|
|
17594
|
-
import { extname as extname8, join as join49, relative as relative24 } from "path";
|
|
18037
|
+
import { access as access8, readFile as readFile16, readdir as readdir4 } from "fs/promises";
|
|
18038
|
+
import { dirname as dirname29, extname as extname8, join as join49, relative as relative24 } from "path";
|
|
17595
18039
|
var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
17596
18040
|
try {
|
|
17597
18041
|
await access8(path);
|
|
@@ -17604,7 +18048,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
17604
18048
|
return findHmrAsset(path);
|
|
17605
18049
|
if (!isFile2 || !RELEASE_ASSET_EXTENSIONS.has(extname8(path)))
|
|
17606
18050
|
return;
|
|
17607
|
-
const source = await
|
|
18051
|
+
const source = await readFile16(path, "utf8");
|
|
17608
18052
|
return HMR_ASSET_PATTERN.test(source) ? path : undefined;
|
|
17609
18053
|
}, findHmrAsset = async (root) => {
|
|
17610
18054
|
if (!await pathExists5(root))
|
|
@@ -17643,14 +18087,20 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
17643
18087
|
if (!await pathExists5(nativeConfigPath)) {
|
|
17644
18088
|
return fail5("android.capacitor-config", "The generated Android Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync android` before release validation.");
|
|
17645
18089
|
}
|
|
17646
|
-
const unsafe = isUnsafeCapacitorConfig(await
|
|
18090
|
+
const unsafe = isUnsafeCapacitorConfig(await readFile16(nativeConfigPath, "utf8"));
|
|
17647
18091
|
return unsafe ? fail5("android.capacitor-config", "Android Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync android`; do not ship development transport overrides.") : pass("android.capacitor-config", "Android Capacitor config contains no development transport overrides.", nativeConfigPath);
|
|
17648
18092
|
}, manifestReleaseCheck = async (manifestPath) => {
|
|
17649
18093
|
if (!await pathExists5(manifestPath)) {
|
|
17650
18094
|
return fail5("android.cleartext", "The Android manifest is missing.", manifestPath, "Run `absolute mobile sync android` before release validation.");
|
|
17651
18095
|
}
|
|
17652
|
-
const source = await
|
|
17653
|
-
|
|
18096
|
+
const source = await readFile16(manifestPath, "utf8");
|
|
18097
|
+
const cleartext = /android:usesCleartextTraffic=["']true["']/u.test(source);
|
|
18098
|
+
const networkConfigName = source.match(/android:networkSecurityConfig=["']@xml\/([a-z0-9_]+)["']/u)?.[1];
|
|
18099
|
+
const networkConfigPath = networkConfigName ? join49(dirname29(manifestPath), "res", "xml", `${networkConfigName}.xml`) : undefined;
|
|
18100
|
+
const developmentTrustReference = /android:networkSecurityConfig=["']@xml\/absolutejs_dev_network_security["']/u.test(source);
|
|
18101
|
+
const developmentTrustContents = networkConfigPath ? await readFile16(networkConfigPath, "utf8").then((value) => value.includes("@raw/absolutejs_dev_ca")).catch(() => false) : false;
|
|
18102
|
+
const developmentTrust = developmentTrustReference || developmentTrustContents;
|
|
18103
|
+
return cleartext || developmentTrust ? fail5("android.cleartext", developmentTrust ? "Android still references the AbsoluteJS development certificate authority." : "Android explicitly permits cleartext traffic.", manifestPath, "Run `absolute mobile sync android`; do not ship development transport or trust overrides.") : pass("android.cleartext", "Android does not explicitly permit cleartext traffic.", manifestPath);
|
|
17654
18104
|
}, hmrAssetsReleaseCheck = async (publicRoot) => {
|
|
17655
18105
|
const hmrAsset = await findHmrAsset(publicRoot);
|
|
17656
18106
|
return hmrAsset ? fail5("android.hmr-assets", "A packaged Android asset contains the development HMR client.", hmrAsset, "Rebuild the production mobile bundle and run Capacitor sync again.") : pass("android.hmr-assets", "Packaged Android assets contain no development HMR markers.", publicRoot);
|
|
@@ -17680,7 +18130,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
17680
18130
|
if (!config.platforms.includes("android") || permissions.length === 0)
|
|
17681
18131
|
return;
|
|
17682
18132
|
const path = join49(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
|
|
17683
|
-
const source = await
|
|
18133
|
+
const source = await readFile16(path, "utf8");
|
|
17684
18134
|
const missing = permissions.filter((permission) => !source.includes(`android:name="${permission}"`) && !source.includes(`android:name='${permission}'`));
|
|
17685
18135
|
if (missing.length === 0)
|
|
17686
18136
|
return;
|
|
@@ -17689,7 +18139,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
17689
18139
|
if (!config.platforms.includes("ios") || purposes.length === 0)
|
|
17690
18140
|
return;
|
|
17691
18141
|
const path = join49(config.nativeProjectDirectory, "ios/App/App/Info.plist");
|
|
17692
|
-
const source = await
|
|
18142
|
+
const source = await readFile16(path, "utf8");
|
|
17693
18143
|
const missing = purposes.filter((purpose) => !source.includes(`<key>${IOS_USAGE_KEYS[purpose]}</key>`));
|
|
17694
18144
|
if (missing.length === 0)
|
|
17695
18145
|
return;
|
|
@@ -17742,7 +18192,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
17742
18192
|
}
|
|
17743
18193
|
if (!await pathExists5(nativeConfigPath)) {
|
|
17744
18194
|
checks.push(fail5("ios.capacitor-config", "The generated iOS Capacitor config is missing.", nativeConfigPath, "Run `absolute mobile sync ios` before release validation."));
|
|
17745
|
-
} else if (isUnsafeCapacitorConfig(await
|
|
18195
|
+
} else if (isUnsafeCapacitorConfig(await readFile16(nativeConfigPath, "utf8"))) {
|
|
17746
18196
|
checks.push(fail5("ios.capacitor-config", "iOS Capacitor config contains a development server URL, cleartext transport, navigation allowlist, or invalid JSON.", nativeConfigPath, "Run `absolute mobile sync ios`; do not ship development transport overrides."));
|
|
17747
18197
|
} else {
|
|
17748
18198
|
checks.push(pass("ios.capacitor-config", "iOS Capacitor config contains no development transport overrides.", nativeConfigPath));
|
|
@@ -17750,7 +18200,7 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
17750
18200
|
if (!await pathExists5(infoPath)) {
|
|
17751
18201
|
checks.push(fail5("ios.transport-security", "The iOS Info.plist is missing.", infoPath, "Run `absolute mobile sync ios` before release validation."));
|
|
17752
18202
|
} else {
|
|
17753
|
-
const info2 = await
|
|
18203
|
+
const info2 = await readFile16(infoPath, "utf8");
|
|
17754
18204
|
checks.push(/<key>NSAllowsArbitraryLoads<\/key>\s*<true\s*\/>/u.test(info2) ? fail5("ios.transport-security", "iOS App Transport Security permits arbitrary network loads.", infoPath, "Remove NSAllowsArbitraryLoads from the release Info.plist.") : pass("ios.transport-security", "iOS App Transport Security does not permit arbitrary loads.", infoPath));
|
|
17755
18205
|
}
|
|
17756
18206
|
const hmrAsset = await findHmrAsset(publicRoot);
|
|
@@ -17803,13 +18253,13 @@ import {
|
|
|
17803
18253
|
copyFile as copyFile5,
|
|
17804
18254
|
mkdir as mkdir12,
|
|
17805
18255
|
mkdtemp as mkdtemp5,
|
|
17806
|
-
readFile as
|
|
18256
|
+
readFile as readFile17,
|
|
17807
18257
|
rename as rename12,
|
|
17808
18258
|
rm as rm8,
|
|
17809
18259
|
stat as stat2,
|
|
17810
18260
|
writeFile as writeFile14
|
|
17811
18261
|
} from "fs/promises";
|
|
17812
|
-
import { dirname as
|
|
18262
|
+
import { dirname as dirname30, isAbsolute as isAbsolute7, join as join50, relative as relative25, resolve as resolve39, sep as sep6 } from "path";
|
|
17813
18263
|
var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
|
|
17814
18264
|
if (!isRecord13(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
|
|
17815
18265
|
throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
|
|
@@ -17859,7 +18309,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
17859
18309
|
artifactPath
|
|
17860
18310
|
]);
|
|
17861
18311
|
return result.exitCode === 0 && /jar verified/iu.test(result.stdout);
|
|
17862
|
-
}, sha256File2 = async (path) => createHash12("sha256").update(await
|
|
18312
|
+
}, sha256File2 = async (path) => createHash12("sha256").update(await readFile17(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
|
|
17863
18313
|
const root = resolve39(projectRoot);
|
|
17864
18314
|
const output = resolve39(root, requested ?? ".absolutejs/mobile/releases/android");
|
|
17865
18315
|
const projectRelative = relative25(root, output);
|
|
@@ -17872,7 +18322,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
17872
18322
|
const artifactName = "app-release.aab";
|
|
17873
18323
|
const destination = join50(releaseRoot, artifactName);
|
|
17874
18324
|
if (await pathExists6(releaseRoot)) {
|
|
17875
|
-
const existing = requireManifestIdentity(JSON.parse(await
|
|
18325
|
+
const existing = requireManifestIdentity(JSON.parse(await readFile17(join50(releaseRoot, "release.json"), "utf8")), metadata);
|
|
17876
18326
|
const [installedBytes, installedSha256] = await Promise.all([
|
|
17877
18327
|
stat2(destination).then(({ size }) => size),
|
|
17878
18328
|
sha256File2(destination)
|
|
@@ -17882,8 +18332,8 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
17882
18332
|
}
|
|
17883
18333
|
return { artifactPath: destination, metadata: existing, releaseRoot };
|
|
17884
18334
|
}
|
|
17885
|
-
await mkdir12(
|
|
17886
|
-
const staging = await mkdtemp5(join50(
|
|
18335
|
+
await mkdir12(dirname30(releaseRoot), { recursive: true });
|
|
18336
|
+
const staging = await mkdtemp5(join50(dirname30(releaseRoot), ".android-stage-"));
|
|
17887
18337
|
try {
|
|
17888
18338
|
await copyFile5(artifactPath, join50(staging, artifactName));
|
|
17889
18339
|
const complete = {
|
|
@@ -17919,7 +18369,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
17919
18369
|
const host2 = options.host ?? detectAbsoluteMobileHost();
|
|
17920
18370
|
const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host2);
|
|
17921
18371
|
const nativeDirectory = join50(options.config.nativeProjectDirectory, "android");
|
|
17922
|
-
const manifest = requireManifest2(JSON.parse(await
|
|
18372
|
+
const manifest = requireManifest2(JSON.parse(await readFile17(join50(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
|
|
17923
18373
|
if (manifest.appId !== options.config.appId) {
|
|
17924
18374
|
throw new TypeError("Embedded mobile manifest appId does not match mobile.appId.");
|
|
17925
18375
|
}
|
|
@@ -17983,7 +18433,7 @@ var init_androidRelease = __esm(() => {
|
|
|
17983
18433
|
});
|
|
17984
18434
|
|
|
17985
18435
|
// src/mobile/iosConformance.ts
|
|
17986
|
-
import { readFile as
|
|
18436
|
+
import { readFile as readFile18, stat as stat3 } from "fs/promises";
|
|
17987
18437
|
var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
|
|
17988
18438
|
const match = HMR_LINE.exec(line);
|
|
17989
18439
|
if (!match)
|
|
@@ -18018,7 +18468,7 @@ var HMR_LINE, parseAbsoluteIosHmrLog = (line) => {
|
|
|
18018
18468
|
if (Date.now() > deadline)
|
|
18019
18469
|
throw new Error(`No iOS native HMR acknowledgement was observed within ${timeoutMs}ms.`);
|
|
18020
18470
|
options.signal?.throwIfAborted();
|
|
18021
|
-
const contents = await
|
|
18471
|
+
const contents = await readFile18(options.logPath).catch(() => Buffer.alloc(0));
|
|
18022
18472
|
if (contents.byteLength < offset) {
|
|
18023
18473
|
offset = 0;
|
|
18024
18474
|
buffered = "";
|
|
@@ -18042,7 +18492,7 @@ var init_iosConformance = __esm(() => {
|
|
|
18042
18492
|
});
|
|
18043
18493
|
|
|
18044
18494
|
// src/mobile/nativeTestReport.ts
|
|
18045
|
-
import { mkdir as mkdir13, readFile as
|
|
18495
|
+
import { mkdir as mkdir13, readFile as readFile19, writeFile as writeFile15 } from "fs/promises";
|
|
18046
18496
|
import { join as join51 } from "path";
|
|
18047
18497
|
var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sanitizeNativeReportText = (value) => value.replace(nativeCredentialPattern, "[REDACTED]").replace(bearerPattern, "Bearer [REDACTED]").replace(secretPattern, "$1$2[REDACTED]").replace(coordinatePattern, "$1$2[REDACTED]").replace(/(https?:\/\/[^\s?#]+)[?#][^\s]*/giu, "$1?[REDACTED]"), markdownCell = (value) => sanitizeNativeReportText(value).replaceAll("|", "\\|").replaceAll(`
|
|
18048
18498
|
`, "<br>"), createAbsoluteNativeAutomatedChecks = (run) => {
|
|
@@ -18121,7 +18571,7 @@ var secretPattern, bearerPattern, coordinatePattern, nativeCredentialPattern, sa
|
|
|
18121
18571
|
reportVersion: 1,
|
|
18122
18572
|
run: options.run
|
|
18123
18573
|
}), readPackageVersionForNativeReport = async (packageJsonPath) => {
|
|
18124
|
-
const manifest = JSON.parse(await
|
|
18574
|
+
const manifest = JSON.parse(await readFile19(packageJsonPath, "utf8"));
|
|
18125
18575
|
if (typeof manifest !== "object" || manifest === null)
|
|
18126
18576
|
return "unknown";
|
|
18127
18577
|
const version2 = Reflect.get(manifest, "version");
|
|
@@ -18462,11 +18912,11 @@ var exports_mobile = {};
|
|
|
18462
18912
|
__export(exports_mobile, {
|
|
18463
18913
|
runMobile: () => runMobile
|
|
18464
18914
|
});
|
|
18465
|
-
import { access as access11, mkdir as mkdir14, readFile as
|
|
18915
|
+
import { access as access11, mkdir as mkdir14, readFile as readFile20, writeFile as writeFile16 } from "fs/promises";
|
|
18466
18916
|
import { join as join52, resolve as resolve41 } from "path";
|
|
18467
18917
|
import { createInterface } from "readline/promises";
|
|
18468
18918
|
var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
|
|
18469
|
-
const manifest = JSON.parse(await
|
|
18919
|
+
const manifest = JSON.parse(await readFile20(join52(projectRoot, "package.json"), "utf8"));
|
|
18470
18920
|
if (!isRecord15(manifest))
|
|
18471
18921
|
throw new TypeError("Application package.json must contain an object.");
|
|
18472
18922
|
const names = new Set;
|
|
@@ -18479,7 +18929,7 @@ var NOT_FOUND3 = -1, isRecord15 = (value) => typeof value === "object" && value
|
|
|
18479
18929
|
return names;
|
|
18480
18930
|
}, resolvedPackageVersion = async (projectRoot, packageName) => {
|
|
18481
18931
|
try {
|
|
18482
|
-
const manifest = JSON.parse(await
|
|
18932
|
+
const manifest = JSON.parse(await readFile20(join52(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
|
|
18483
18933
|
return isRecord15(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
|
|
18484
18934
|
} catch {
|
|
18485
18935
|
return;
|
|
@@ -20678,6 +21128,24 @@ var resolveDevPort = async (requestedPort, options = {}) => {
|
|
|
20678
21128
|
` + `Set \`dev.port\` to a different value in absolute.config.ts (or via the ABSOLUTE_PORT env var), or extend \`dev.portRange\`.`);
|
|
20679
21129
|
};
|
|
20680
21130
|
|
|
21131
|
+
// src/utils/networking.ts
|
|
21132
|
+
import os from "os";
|
|
21133
|
+
var getAllNetworkIPs = () => {
|
|
21134
|
+
const interfaces = os.networkInterfaces();
|
|
21135
|
+
const addresses = Object.values(interfaces).flat().filter((iface) => iface !== undefined);
|
|
21136
|
+
const ipv4Addresses = [];
|
|
21137
|
+
addresses.filter((addr) => !addr.internal && addr.family === "IPv4").forEach((addr) => ipv4Addresses.push(addr.address));
|
|
21138
|
+
return ipv4Addresses;
|
|
21139
|
+
};
|
|
21140
|
+
var getLocalIPAddress = () => {
|
|
21141
|
+
const allIPs = getAllNetworkIPs();
|
|
21142
|
+
if (allIPs.length > 0 && allIPs[0]) {
|
|
21143
|
+
return allIPs[0];
|
|
21144
|
+
}
|
|
21145
|
+
console.warn("No IP address found, falling back to localhost");
|
|
21146
|
+
return "localhost";
|
|
21147
|
+
};
|
|
21148
|
+
|
|
20681
21149
|
// src/cli/scripts/dev.ts
|
|
20682
21150
|
init_config();
|
|
20683
21151
|
init_nativeAuth();
|
|
@@ -20982,25 +21450,41 @@ var confirmPrompt = (message, defaultYes = true) => {
|
|
|
20982
21450
|
process.stdin.on("data", onData);
|
|
20983
21451
|
return promise;
|
|
20984
21452
|
};
|
|
20985
|
-
var setupCertWithPrompt = async (ensureDevCert2, setupMkcert2) => {
|
|
21453
|
+
var setupCertWithPrompt = async (ensureDevCert2, setupMkcert2, hosts) => {
|
|
20986
21454
|
const install = await confirmPrompt("Install mkcert for trusted HTTPS? (no browser warning)");
|
|
20987
21455
|
if (install) {
|
|
20988
|
-
setupMkcert2();
|
|
21456
|
+
setupMkcert2(hosts);
|
|
20989
21457
|
} else {
|
|
20990
|
-
ensureDevCert2();
|
|
21458
|
+
ensureDevCert2(hosts);
|
|
20991
21459
|
}
|
|
20992
21460
|
};
|
|
20993
|
-
var setupHttpsCert = async () => {
|
|
20994
|
-
const {
|
|
20995
|
-
|
|
20996
|
-
|
|
20997
|
-
|
|
21461
|
+
var setupHttpsCert = async (hosts = []) => {
|
|
21462
|
+
const {
|
|
21463
|
+
getDevCertificateAuthorityPath: getDevCertificateAuthorityPath2,
|
|
21464
|
+
hasCert: hasCert2,
|
|
21465
|
+
hasMkcert: hasMkcert2,
|
|
21466
|
+
ensureDevCert: ensureDevCert2,
|
|
21467
|
+
setupMkcert: setupMkcert2
|
|
21468
|
+
} = await Promise.resolve().then(() => (init_devCert(), exports_devCert));
|
|
21469
|
+
if (hasCert2(hosts)) {
|
|
21470
|
+
ensureDevCert2(hosts);
|
|
21471
|
+
return getDevCertificateAuthorityPath2();
|
|
20998
21472
|
}
|
|
20999
21473
|
if (hasMkcert2()) {
|
|
21000
|
-
ensureDevCert2();
|
|
21001
|
-
return;
|
|
21474
|
+
ensureDevCert2(hosts);
|
|
21475
|
+
return getDevCertificateAuthorityPath2();
|
|
21002
21476
|
}
|
|
21003
|
-
await setupCertWithPrompt(ensureDevCert2, setupMkcert2);
|
|
21477
|
+
await setupCertWithPrompt(ensureDevCert2, setupMkcert2, hosts);
|
|
21478
|
+
return getDevCertificateAuthorityPath2();
|
|
21479
|
+
};
|
|
21480
|
+
var mobileReachableHost = (host) => {
|
|
21481
|
+
if (host !== "0.0.0.0" && host !== "::")
|
|
21482
|
+
return host;
|
|
21483
|
+
const address = getLocalIPAddress();
|
|
21484
|
+
if (address === "localhost") {
|
|
21485
|
+
throw new Error("No LAN address is available for the selected physical device. Connect this computer to the device network or set dev.host explicitly.");
|
|
21486
|
+
}
|
|
21487
|
+
return address;
|
|
21004
21488
|
};
|
|
21005
21489
|
var resolveDevConfig = (configDev) => {
|
|
21006
21490
|
const relay = env.ABSOLUTE_TUNNEL_RELAY ?? configDev?.tunnel?.relay;
|
|
@@ -21014,13 +21498,24 @@ var resolveDevConfig = (configDev) => {
|
|
|
21014
21498
|
...relay && token ? { tunnel: { relay, token } } : {}
|
|
21015
21499
|
};
|
|
21016
21500
|
};
|
|
21017
|
-
var androidToolchainReady = (checks
|
|
21018
|
-
|
|
21501
|
+
var androidToolchainReady = (checks, target = "emulator") => {
|
|
21502
|
+
const deviceOnlyChecks = new Set([
|
|
21503
|
+
"android.avd",
|
|
21504
|
+
"android.avdmanager",
|
|
21505
|
+
"android.emulator",
|
|
21506
|
+
"android.virtualization"
|
|
21507
|
+
]);
|
|
21508
|
+
return checks.every((check) => check.platform !== "android" || target === "device" && deviceOnlyChecks.has(check.id) || check.status !== "fail" && check.status !== "warn");
|
|
21509
|
+
};
|
|
21510
|
+
var iosToolchainReady = (checks, target = "simulator") => checks.every((check) => check.platform !== "ios" || target === "device" && check.id === "ios.runtime" || check.status !== "fail" && check.status !== "warn");
|
|
21019
21511
|
var dev = async (serverEntry, configPath2, options = {}) => {
|
|
21020
21512
|
let httpsEnabled = false;
|
|
21513
|
+
let devCertificateAuthorityPath = null;
|
|
21021
21514
|
let resolvedDev;
|
|
21022
21515
|
let buildDirectory = resolve8(process.cwd(), "build");
|
|
21023
21516
|
let mobileConfig;
|
|
21517
|
+
let iosPhysicalServerHost;
|
|
21518
|
+
let selectedRemoteMacProfile;
|
|
21024
21519
|
try {
|
|
21025
21520
|
const config = await loadConfig(configPath2);
|
|
21026
21521
|
mobileConfig = config?.mobile;
|
|
@@ -21031,12 +21526,39 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21031
21526
|
if (config?.buildDirectory) {
|
|
21032
21527
|
buildDirectory = resolve8(process.cwd(), config.buildDirectory);
|
|
21033
21528
|
}
|
|
21034
|
-
if (httpsEnabled)
|
|
21035
|
-
await setupHttpsCert();
|
|
21036
21529
|
} catch {
|
|
21037
21530
|
resolvedDev = resolveDevConfig(undefined);
|
|
21038
21531
|
httpsEnabled = resolvedDev.https;
|
|
21039
21532
|
}
|
|
21533
|
+
if ((options.androidDevice || options.iosDevice && detectAbsoluteMobileHost() === "macos") && ["localhost", "127.0.0.1", "::1"].includes(resolvedDev.host)) {
|
|
21534
|
+
resolvedDev.host = "0.0.0.0";
|
|
21535
|
+
}
|
|
21536
|
+
if ((options.androidDevice || options.iosDevice) && !mobileConfig) {
|
|
21537
|
+
throw new TypeError("Physical-device development requires an absolute.config.ts mobile configuration.");
|
|
21538
|
+
}
|
|
21539
|
+
if (options.androidDevice && mobileConfig?.platforms && !mobileConfig.platforms.includes("android")) {
|
|
21540
|
+
throw new TypeError("--android-device requires android in mobile.platforms.");
|
|
21541
|
+
}
|
|
21542
|
+
if (options.iosDevice && mobileConfig?.platforms && !mobileConfig.platforms.includes("ios")) {
|
|
21543
|
+
throw new TypeError("--ios-device requires ios in mobile.platforms.");
|
|
21544
|
+
}
|
|
21545
|
+
if (options.iosDevice) {
|
|
21546
|
+
if (detectAbsoluteMobileHost() === "macos")
|
|
21547
|
+
iosPhysicalServerHost = mobileReachableHost(resolvedDev.host);
|
|
21548
|
+
else {
|
|
21549
|
+
selectedRemoteMacProfile = await getAbsoluteRemoteMacProfile();
|
|
21550
|
+
if (!selectedRemoteMacProfile)
|
|
21551
|
+
throw new Error("--ios-device requires macOS or a paired Remote Mac.");
|
|
21552
|
+
iosPhysicalServerHost = await inspectAbsoluteRemoteMacLanHost(selectedRemoteMacProfile);
|
|
21553
|
+
}
|
|
21554
|
+
}
|
|
21555
|
+
if (httpsEnabled) {
|
|
21556
|
+
const certificateHosts = [
|
|
21557
|
+
...options.androidDevice ? [mobileReachableHost(resolvedDev.host)] : [],
|
|
21558
|
+
...iosPhysicalServerHost ? [iosPhysicalServerHost] : []
|
|
21559
|
+
];
|
|
21560
|
+
devCertificateAuthorityPath = await setupHttpsCert(certificateHosts.length > 0 ? certificateHosts : [resolvedDev.host]);
|
|
21561
|
+
}
|
|
21040
21562
|
let androidDevProject = null;
|
|
21041
21563
|
let iosDevProject = null;
|
|
21042
21564
|
const mobileInteractive = options.mobile !== false && process.env.ABSOLUTE_NO_MOBILE !== "1" && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -21044,14 +21566,15 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21044
21566
|
try {
|
|
21045
21567
|
const normalized = normalizeAbsoluteMobileConfig(mobileConfig, process.cwd());
|
|
21046
21568
|
if (normalized.platforms.includes("android")) {
|
|
21047
|
-
|
|
21569
|
+
const androidTarget = options.androidDevice ? "device" : "emulator";
|
|
21570
|
+
let ready = androidToolchainReady(await inspectAbsoluteMobileToolchain(), androidTarget);
|
|
21048
21571
|
if (!ready) {
|
|
21049
|
-
const install = await confirmPrompt("Android
|
|
21572
|
+
const install = await confirmPrompt("Android development is not configured. Install the tested toolchain now?");
|
|
21050
21573
|
if (install) {
|
|
21051
21574
|
await fixAbsoluteMobileEmulatorToolchain("android");
|
|
21052
|
-
ready = androidToolchainReady(await inspectAbsoluteMobileToolchain());
|
|
21575
|
+
ready = androidToolchainReady(await inspectAbsoluteMobileToolchain(), androidTarget);
|
|
21053
21576
|
} else {
|
|
21054
|
-
console.log(cliTag("\x1B[33m", "
|
|
21577
|
+
console.log(cliTag("\x1B[33m", "Android target skipped. Run `absolute mobile doctor android --fix` when ready."));
|
|
21055
21578
|
}
|
|
21056
21579
|
}
|
|
21057
21580
|
if (ready) {
|
|
@@ -21063,16 +21586,17 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21063
21586
|
if (existsSync5(nativeDirectory) || createNativeProject) {
|
|
21064
21587
|
androidDevProject = await prepareAbsoluteAndroidDevProject(normalized, {
|
|
21065
21588
|
createNativeProject,
|
|
21066
|
-
projectRoot: process.cwd()
|
|
21589
|
+
projectRoot: process.cwd(),
|
|
21590
|
+
target: androidTarget
|
|
21067
21591
|
});
|
|
21068
21592
|
}
|
|
21069
21593
|
}
|
|
21070
21594
|
}
|
|
21071
21595
|
if (normalized.platforms.includes("ios")) {
|
|
21072
21596
|
if (detectAbsoluteMobileHost() !== "macos") {
|
|
21073
|
-
const remote = await getAbsoluteRemoteMacProfile();
|
|
21597
|
+
const remote = selectedRemoteMacProfile ?? await getAbsoluteRemoteMacProfile();
|
|
21074
21598
|
if (!remote) {
|
|
21075
|
-
console.log(cliTag("\x1B[33m", "iOS
|
|
21599
|
+
console.log(cliTag("\x1B[33m", "iOS target skipped. Pair a Mac with `absolute mobile pair mac <name> <user@host>`."));
|
|
21076
21600
|
} else {
|
|
21077
21601
|
const nativeDirectory = join13(normalized.nativeProjectDirectory, "ios");
|
|
21078
21602
|
if (!existsSync5(nativeDirectory)) {
|
|
@@ -21083,12 +21607,13 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21083
21607
|
}
|
|
21084
21608
|
}
|
|
21085
21609
|
} else {
|
|
21086
|
-
|
|
21610
|
+
const iosTarget = options.iosDevice ? "device" : "simulator";
|
|
21611
|
+
let ready = iosToolchainReady(await inspectAbsoluteMobileToolchain(), iosTarget);
|
|
21087
21612
|
if (!ready) {
|
|
21088
|
-
const install = await confirmPrompt("iOS simulation is not configured. Install the missing simulator runtime now?");
|
|
21613
|
+
const install = await confirmPrompt(options.iosDevice ? "Physical iOS development is not configured. Open the guided Xcode setup now?" : "iOS simulation is not configured. Install the missing simulator runtime now?");
|
|
21089
21614
|
if (install) {
|
|
21090
21615
|
await fixAbsoluteMobileEmulatorToolchain("ios");
|
|
21091
|
-
ready = iosToolchainReady(await inspectAbsoluteMobileToolchain());
|
|
21616
|
+
ready = iosToolchainReady(await inspectAbsoluteMobileToolchain(), iosTarget);
|
|
21092
21617
|
} else {
|
|
21093
21618
|
console.log(cliTag("\x1B[33m", "Mobile simulator skipped. Run `absolute mobile doctor ios --fix` when ready."));
|
|
21094
21619
|
}
|
|
@@ -21102,7 +21627,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21102
21627
|
if (existsSync5(nativeDirectory) || createNativeProject) {
|
|
21103
21628
|
iosDevProject = await prepareAbsoluteIosDevProject(normalized, {
|
|
21104
21629
|
createNativeProject,
|
|
21105
|
-
projectRoot: process.cwd()
|
|
21630
|
+
projectRoot: process.cwd(),
|
|
21631
|
+
target: iosTarget
|
|
21106
21632
|
});
|
|
21107
21633
|
}
|
|
21108
21634
|
}
|
|
@@ -21145,7 +21671,9 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21145
21671
|
"dev",
|
|
21146
21672
|
serverEntry,
|
|
21147
21673
|
...configPath2 ? ["--config", configPath2] : [],
|
|
21148
|
-
...options.mobile === false ? ["--no-mobile"] : []
|
|
21674
|
+
...options.mobile === false ? ["--no-mobile"] : [],
|
|
21675
|
+
...options.androidDevice ? ["--android-device", options.androidDevice] : [],
|
|
21676
|
+
...options.iosDevice ? ["--ios-device", options.iosDevice] : []
|
|
21149
21677
|
].filter((part) => part.length > 0);
|
|
21150
21678
|
registerInstance({
|
|
21151
21679
|
command: relaunchCommand,
|
|
@@ -21201,6 +21729,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21201
21729
|
platform: "android",
|
|
21202
21730
|
provider: "capacitor",
|
|
21203
21731
|
startedEmulator: session.startedEmulator,
|
|
21732
|
+
target: options.androidDevice ? "device" : "emulator",
|
|
21204
21733
|
timings: session.timings
|
|
21205
21734
|
});
|
|
21206
21735
|
if (session.timings.building === undefined)
|
|
@@ -21215,9 +21744,12 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21215
21744
|
});
|
|
21216
21745
|
};
|
|
21217
21746
|
const openAndroidDevSession = (androidProject) => startAbsoluteAndroidDevSession({
|
|
21747
|
+
certificateAuthorityPath: devCertificateAuthorityPath ?? undefined,
|
|
21748
|
+
deviceSerial: options.androidDevice,
|
|
21218
21749
|
https: httpsEnabled,
|
|
21219
21750
|
port,
|
|
21220
21751
|
project: androidProject,
|
|
21752
|
+
serverHost: options.androidDevice ? mobileReachableHost(resolvedDev.host) : "localhost",
|
|
21221
21753
|
signal: androidDevAbort.signal,
|
|
21222
21754
|
log: (message) => printNativeOutput(cliTag("\x1B[36m", message)),
|
|
21223
21755
|
nativeLog: (entry) => printNativeOutput(androidLogTag(entry)),
|
|
@@ -21313,6 +21845,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21313
21845
|
platform: "ios",
|
|
21314
21846
|
provider: "capacitor",
|
|
21315
21847
|
startedSimulator: session.startedSimulator,
|
|
21848
|
+
target: session.targetKind,
|
|
21316
21849
|
timings: session.timings
|
|
21317
21850
|
});
|
|
21318
21851
|
if (session.timings.building === undefined)
|
|
@@ -21323,13 +21856,17 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21323
21856
|
installMs: session.timings.installing,
|
|
21324
21857
|
platform: "ios",
|
|
21325
21858
|
provider: "capacitor",
|
|
21326
|
-
success: true
|
|
21859
|
+
success: true,
|
|
21860
|
+
target: session.targetKind
|
|
21327
21861
|
});
|
|
21328
21862
|
};
|
|
21329
21863
|
const openIosDevSession = (iosProject) => {
|
|
21330
21864
|
const sessionOptions = {
|
|
21865
|
+
certificateAuthorityPath: devCertificateAuthorityPath ?? undefined,
|
|
21866
|
+
deviceIdentifier: options.iosDevice,
|
|
21331
21867
|
https: httpsEnabled,
|
|
21332
21868
|
port,
|
|
21869
|
+
serverHost: iosPhysicalServerHost,
|
|
21333
21870
|
signal: iosDevAbort.signal,
|
|
21334
21871
|
log: (message) => printNativeOutput(cliTag("\x1B[35m", message)),
|
|
21335
21872
|
nativeLog: (entry) => printNativeOutput(iosLogTag(entry)),
|
|
@@ -21343,7 +21880,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21343
21880
|
iosDevState = state;
|
|
21344
21881
|
if (state === "ready" || state === "closed")
|
|
21345
21882
|
return;
|
|
21346
|
-
printNativeOutput(cliTag("\x1B[35m", `iOS simulator: ${state}.`));
|
|
21883
|
+
printNativeOutput(cliTag("\x1B[35m", `iOS ${options.iosDevice ? "device" : "simulator"}: ${state}.`));
|
|
21347
21884
|
}
|
|
21348
21885
|
};
|
|
21349
21886
|
if (iosProject.remote)
|
|
@@ -21382,6 +21919,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21382
21919
|
provider: "capacitor",
|
|
21383
21920
|
rootInputChanged: change.rootInputChanged,
|
|
21384
21921
|
success: true,
|
|
21922
|
+
target: replacement.targetKind,
|
|
21385
21923
|
timings: replacement.timings
|
|
21386
21924
|
});
|
|
21387
21925
|
},
|
|
@@ -21392,7 +21930,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21392
21930
|
host: iosTelemetryHost(iosProject),
|
|
21393
21931
|
platform: "ios",
|
|
21394
21932
|
provider: "capacitor",
|
|
21395
|
-
success: false
|
|
21933
|
+
success: false,
|
|
21934
|
+
target: options.iosDevice ? "device" : "simulator"
|
|
21396
21935
|
});
|
|
21397
21936
|
}
|
|
21398
21937
|
});
|
|
@@ -21419,9 +21958,10 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21419
21958
|
phase: iosDevState,
|
|
21420
21959
|
platform: "ios",
|
|
21421
21960
|
provider: "capacitor",
|
|
21961
|
+
target: options.iosDevice ? "device" : "simulator",
|
|
21422
21962
|
timings: iosPhaseTimings
|
|
21423
21963
|
});
|
|
21424
|
-
console.error(cliTag("\x1B[31m", `iOS simulator failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
21964
|
+
console.error(cliTag("\x1B[31m", `iOS ${options.iosDevice ? "device" : "simulator"} failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
21425
21965
|
}).finally(() => {
|
|
21426
21966
|
iosDevStart = null;
|
|
21427
21967
|
});
|
|
@@ -21488,6 +22028,9 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21488
22028
|
return;
|
|
21489
22029
|
}
|
|
21490
22030
|
const refreshedDevConfig = resolveDevConfig(cfg?.dev);
|
|
22031
|
+
if (options.androidDevice && ["localhost", "127.0.0.1", "::1"].includes(refreshedDevConfig.host)) {
|
|
22032
|
+
refreshedDevConfig.host = "0.0.0.0";
|
|
22033
|
+
}
|
|
21491
22034
|
const desiredBuildDir = cfg?.buildDirectory ? resolve8(process.cwd(), cfg.buildDirectory) : resolve8(process.cwd(), "build");
|
|
21492
22035
|
if (desiredBuildDir !== buildDirectory && desiredBuildDir !== lastBuildDirectoryWarned) {
|
|
21493
22036
|
lastBuildDirectoryWarned = desiredBuildDir;
|
|
@@ -21570,6 +22113,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21570
22113
|
env: {
|
|
21571
22114
|
...process.env,
|
|
21572
22115
|
...readDotenvFiles(),
|
|
22116
|
+
ABSOLUTE_HOST: resolvedDev.host,
|
|
21573
22117
|
ABSOLUTE_INSTANCE_MANAGED: "1",
|
|
21574
22118
|
ABSOLUTE_PORT: String(port),
|
|
21575
22119
|
ABSOLUTE_SERVER_ENTRY: resolve8(serverEntry),
|
|
@@ -23766,9 +24310,25 @@ var stripNamedArgs = (...flags) => args.filter((_, idx) => flags.every((flag) =>
|
|
|
23766
24310
|
if (command === "dev") {
|
|
23767
24311
|
sendTelemetryEvent("cli:command", { command });
|
|
23768
24312
|
const configPath2 = parseNamedArg("--config");
|
|
23769
|
-
const
|
|
24313
|
+
const androidDevice = parseNamedArg("--android-device");
|
|
24314
|
+
const iosDevice = parseNamedArg("--ios-device");
|
|
24315
|
+
if (args.includes("--android-device") && !androidDevice) {
|
|
24316
|
+
throw new TypeError("--android-device requires an ADB device serial.");
|
|
24317
|
+
}
|
|
24318
|
+
if (androidDevice && args.includes("--no-mobile")) {
|
|
24319
|
+
throw new TypeError("--android-device cannot be combined with --no-mobile.");
|
|
24320
|
+
}
|
|
24321
|
+
if (args.includes("--ios-device") && !iosDevice) {
|
|
24322
|
+
throw new TypeError("--ios-device requires an Xcode device identifier or name.");
|
|
24323
|
+
}
|
|
24324
|
+
if (iosDevice && args.includes("--no-mobile")) {
|
|
24325
|
+
throw new TypeError("--ios-device cannot be combined with --no-mobile.");
|
|
24326
|
+
}
|
|
24327
|
+
const positionalArgs2 = stripNamedArgs("--config", "--android-device", "--ios-device").filter((arg) => arg !== "--no-mobile");
|
|
23770
24328
|
const serverEntry = positionalArgs2[0] ?? DEFAULT_SERVER_ENTRY;
|
|
23771
24329
|
await dev(serverEntry, configPath2, {
|
|
24330
|
+
androidDevice,
|
|
24331
|
+
iosDevice,
|
|
23772
24332
|
mobile: !args.includes("--no-mobile")
|
|
23773
24333
|
});
|
|
23774
24334
|
} else if (command === "start") {
|
|
@@ -23927,13 +24487,13 @@ if (command === "dev") {
|
|
|
23927
24487
|
console.error(message);
|
|
23928
24488
|
console.error("Usage: absolute <command>");
|
|
23929
24489
|
console.error("Commands:");
|
|
23930
|
-
console.error(" dev [entry] [--no-mobile] Start web and configured mobile development");
|
|
24490
|
+
console.error(" dev [entry] [--no-mobile] [--android-device serial] [--ios-device identifier] Start web and configured mobile development");
|
|
23931
24491
|
console.error(" workspace dev [--no-tui] Start multi-service workspace dev");
|
|
23932
24492
|
console.error(" build [--outdir dir] [--profile] Build production assets");
|
|
23933
24493
|
console.error(" prepare [entry] [--outdir dir] Build production assets and server without launching");
|
|
23934
24494
|
console.error(" start [entry] [--outdir dir] [--prebuilt] Start production server");
|
|
23935
24495
|
console.error(" compile [entry] [--outdir dir] [--outfile path] Compile standalone executable");
|
|
23936
|
-
console.error(" mobile <init|sync|pair|remotes|doctor|test> Manage Capacitor projects,
|
|
24496
|
+
console.error(" mobile <init|sync|pair|remotes|doctor|test> Manage Capacitor projects, simulators, physical devices, Remote Macs, guided setup, and deep links");
|
|
23937
24497
|
console.error(" config [--port n] Open the unified config UI (ESLint, tsconfig, Prettier)");
|
|
23938
24498
|
console.error(" db <backup|restore|seed> Backup/restore any Postgres DB (ORM-agnostic, upsert by PK) or run the seed script");
|
|
23939
24499
|
console.error(" doctor [--fix] [--json] Diagnose the project (bun, type graph, config, framework dirs, env, port)");
|