@absolutejs/absolute 0.20.0-beta.31 → 0.20.0-beta.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/angular/index.js +5 -1
- package/dist/angular/index.js.map +3 -3
- package/dist/angular/server.js +5 -1
- package/dist/angular/server.js.map +3 -3
- package/dist/build.js +7 -1
- package/dist/build.js.map +4 -4
- package/dist/cli/config/server.js +4 -0
- package/dist/cli/index.js +314 -90
- package/dist/dev/client/hmrClient.ts +5 -2
- package/dist/dev/client/hmrTiming.ts +2 -0
- package/dist/index.js +324 -230
- package/dist/index.js.map +11 -10
- package/dist/mobile/index.js +212 -31
- package/dist/mobile/index.js.map +11 -8
- package/dist/mobile/remoteMacAgentEntry.js +8 -8
- package/dist/react/index.js +5 -1
- package/dist/react/index.js.map +3 -3
- package/dist/react/server.js +5 -1
- package/dist/react/server.js.map +3 -3
- package/dist/src/cli/config/server.d.ts +1 -1
- package/dist/src/cli/scripts/dev.d.ts +1 -0
- package/dist/src/core/prepare.d.ts +340 -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/iosSimulatorController.d.ts +2 -0
- package/dist/src/mobile/mobilePreview.d.ts +174 -0
- package/dist/src/mobile/mobilePreviewClient.d.ts +1 -0
- package/dist/src/mobile/remoteMacProtocol.d.ts +1 -0
- package/dist/src/plugins/imageOptimizer.d.ts +1 -1
- package/dist/src/react/hooks/useMediaQuery.d.ts +1 -1
- package/dist/src/utils/logger.d.ts +1 -0
- package/dist/src/utils/startupBanner.d.ts +1 -0
- package/dist/src/utils/userAgentFunctions.d.ts +1 -1
- package/dist/svelte/index.js +5 -1
- package/dist/svelte/index.js.map +3 -3
- package/dist/svelte/server.js +5 -1
- package/dist/svelte/server.js.map +3 -3
- package/dist/types/messages.d.ts +1 -1
- package/dist/vue/index.js +5 -1
- package/dist/vue/index.js.map +3 -3
- package/dist/vue/server.js +5 -1
- package/dist/vue/server.js.map +3 -3
- 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
|
}
|
|
@@ -2848,6 +2950,21 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
2848
2950
|
const exitCode = await run(command, options);
|
|
2849
2951
|
if (exitCode !== 0)
|
|
2850
2952
|
throw new Error(`${label} failed with status ${exitCode}.`);
|
|
2953
|
+
}, trustIosSimulatorDevelopmentCa = async (options, udid, run, log) => {
|
|
2954
|
+
if (!options.https)
|
|
2955
|
+
return;
|
|
2956
|
+
if (!options.certificateAuthorityPath) {
|
|
2957
|
+
throw new Error("iOS Simulator HTTPS requires the AbsoluteJS development CA certificate.");
|
|
2958
|
+
}
|
|
2959
|
+
await requireSuccess2([
|
|
2960
|
+
options.project.xcrun,
|
|
2961
|
+
"simctl",
|
|
2962
|
+
"keychain",
|
|
2963
|
+
udid,
|
|
2964
|
+
"add-root-cert",
|
|
2965
|
+
options.certificateAuthorityPath
|
|
2966
|
+
], "iOS Simulator development CA trust", run, { signal: options.signal });
|
|
2967
|
+
log("Installed the AbsoluteJS development CA into this iOS Simulator trust store.");
|
|
2851
2968
|
}, requireCapturedSuccess = (result, label) => {
|
|
2852
2969
|
if (result.exitCode !== 0) {
|
|
2853
2970
|
throw new Error(`${label} failed: ${result.stderr.trim() || result.stdout.trim() || `status ${result.exitCode}`}`);
|
|
@@ -3322,6 +3439,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
|
|
|
3322
3439
|
transition("connecting");
|
|
3323
3440
|
await waitForBootedSimulator(project, device.udid, capture, sleep, options.signal);
|
|
3324
3441
|
await requireSuccess2([project.xcrun, "simctl", "bootstatus", device.udid, "-b"], "iOS simulator boot readiness", run, { signal: options.signal });
|
|
3442
|
+
await trustIosSimulatorDevelopmentCa(options, device.udid, run, log);
|
|
3325
3443
|
const fingerprint = await fingerprintPromise;
|
|
3326
3444
|
transition("checking-native");
|
|
3327
3445
|
const nativeCacheHit = await ensureIosDebugApp({
|
|
@@ -3806,6 +3924,7 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3806
3924
|
await syncProject(options.project);
|
|
3807
3925
|
const syncDuration = performance.now() - syncStartedAt;
|
|
3808
3926
|
const encodedConfig = Buffer.from(JSON.stringify(portableMobileConfig(options.project))).toString("base64url");
|
|
3927
|
+
const encodedCertificateAuthority = options.certificateAuthorityPath ? (await readFile6(options.certificateAuthorityPath)).toString("base64url") : undefined;
|
|
3809
3928
|
const remoteCommand = [
|
|
3810
3929
|
`cd ${shellQuote(options.project.remoteProjectRoot)}`,
|
|
3811
3930
|
"&&",
|
|
@@ -3816,6 +3935,10 @@ var PROFILE_FORMAT = 1, REMOTE_STDIN_FLUSH_ATTEMPTS = 3, PROFILE_NAME, SSH_DESTI
|
|
|
3816
3935
|
String(options.port),
|
|
3817
3936
|
"--mobile-config",
|
|
3818
3937
|
shellQuote(encodedConfig),
|
|
3938
|
+
...encodedCertificateAuthority ? [
|
|
3939
|
+
"--certificate-authority",
|
|
3940
|
+
shellQuote(encodedCertificateAuthority)
|
|
3941
|
+
] : [],
|
|
3819
3942
|
...options.https ? ["--https"] : []
|
|
3820
3943
|
].join(" ");
|
|
3821
3944
|
const command = [
|
|
@@ -3999,9 +4122,11 @@ var init_remoteMacProtocol = __esm(() => {
|
|
|
3999
4122
|
var exports_devCert = {};
|
|
4000
4123
|
__export(exports_devCert, {
|
|
4001
4124
|
setupMkcert: () => setupMkcert,
|
|
4125
|
+
normalizeDevCertificateHosts: () => normalizeDevCertificateHosts,
|
|
4002
4126
|
loadDevCert: () => loadDevCert,
|
|
4003
4127
|
hasMkcert: () => hasMkcert,
|
|
4004
4128
|
hasCert: () => hasCert,
|
|
4129
|
+
getDevCertificateAuthorityPath: () => getDevCertificateAuthorityPath,
|
|
4005
4130
|
ensureDevCert: () => ensureDevCert
|
|
4006
4131
|
});
|
|
4007
4132
|
import {
|
|
@@ -4011,20 +4136,31 @@ import {
|
|
|
4011
4136
|
readFileSync as readFileSync7,
|
|
4012
4137
|
rmSync
|
|
4013
4138
|
} from "fs";
|
|
4139
|
+
import { X509Certificate } from "crypto";
|
|
4140
|
+
import { isIP } from "net";
|
|
4014
4141
|
import { platform as platform2 } from "os";
|
|
4015
4142
|
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),
|
|
4143
|
+
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 = []) => {
|
|
4144
|
+
const normalized = new Set(DEFAULT_CERTIFICATE_HOSTS);
|
|
4145
|
+
for (const host of hosts) {
|
|
4146
|
+
const value = host.trim().toLowerCase();
|
|
4147
|
+
if (!value || value === "0.0.0.0" || value === "::")
|
|
4148
|
+
continue;
|
|
4149
|
+
if (isIP(value) === 0 && !CERTIFICATE_HOSTNAME_PATTERN.test(value)) {
|
|
4150
|
+
throw new TypeError(`Invalid development certificate host: ${host}`);
|
|
4151
|
+
}
|
|
4152
|
+
normalized.add(value);
|
|
4153
|
+
}
|
|
4154
|
+
return [...normalized];
|
|
4155
|
+
}, certificateIsUsable = (hosts) => {
|
|
4017
4156
|
try {
|
|
4018
4157
|
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();
|
|
4158
|
+
const certificate = new X509Certificate(certPem);
|
|
4159
|
+
if (new Date(certificate.validTo).getTime() <= Date.now())
|
|
4160
|
+
return false;
|
|
4161
|
+
return normalizeDevCertificateHosts(hosts).every((host) => isIP(host) ? certificate.checkIP(host) !== undefined : certificate.checkHost(host) !== undefined);
|
|
4026
4162
|
} catch {
|
|
4027
|
-
return
|
|
4163
|
+
return false;
|
|
4028
4164
|
}
|
|
4029
4165
|
}, hasMkcert = () => {
|
|
4030
4166
|
try {
|
|
@@ -4036,22 +4172,21 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4036
4172
|
} catch {
|
|
4037
4173
|
return false;
|
|
4038
4174
|
}
|
|
4039
|
-
}, generateWithMkcert = () => {
|
|
4175
|
+
}, generateWithMkcert = (hosts = []) => {
|
|
4040
4176
|
const result = Bun.spawnSync([
|
|
4041
4177
|
"mkcert",
|
|
4042
4178
|
"-cert-file",
|
|
4043
4179
|
CERT_PATH,
|
|
4044
4180
|
"-key-file",
|
|
4045
4181
|
KEY_PATH,
|
|
4046
|
-
|
|
4047
|
-
"127.0.0.1",
|
|
4048
|
-
"::1"
|
|
4182
|
+
...normalizeDevCertificateHosts(hosts)
|
|
4049
4183
|
], { stderr: "pipe", stdout: "pipe" });
|
|
4050
4184
|
if (result.exitCode !== 0) {
|
|
4051
4185
|
const err = new TextDecoder().decode(result.stderr);
|
|
4052
4186
|
throw new Error(`mkcert failed: ${err}`);
|
|
4053
4187
|
}
|
|
4054
|
-
}, generateSelfSigned = () => {
|
|
4188
|
+
}, generateSelfSigned = (hosts = []) => {
|
|
4189
|
+
const subjectAlternativeNames = normalizeDevCertificateHosts(hosts).map((host) => `${isIP(host) ? "IP" : "DNS"}:${host}`).join(",");
|
|
4055
4190
|
const proc = Bun.spawnSync([
|
|
4056
4191
|
"openssl",
|
|
4057
4192
|
"req",
|
|
@@ -4070,36 +4205,36 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4070
4205
|
"-subj",
|
|
4071
4206
|
"/CN=localhost",
|
|
4072
4207
|
"-addext",
|
|
4073
|
-
|
|
4208
|
+
`subjectAltName=${subjectAlternativeNames}`
|
|
4074
4209
|
], { stderr: "pipe", stdout: "pipe" });
|
|
4075
4210
|
if (proc.exitCode !== 0) {
|
|
4076
4211
|
const err = new TextDecoder().decode(proc.stderr);
|
|
4077
4212
|
throw new Error(`openssl failed: ${err}`);
|
|
4078
4213
|
}
|
|
4079
4214
|
devLog("Using self-signed certificate \u2014 browser will show a one-time warning");
|
|
4080
|
-
}, generateCert = () => {
|
|
4215
|
+
}, generateCert = (hosts = []) => {
|
|
4081
4216
|
if (hasMkcert()) {
|
|
4082
|
-
generateWithMkcert();
|
|
4217
|
+
generateWithMkcert(hosts);
|
|
4083
4218
|
} else {
|
|
4084
|
-
generateSelfSigned();
|
|
4219
|
+
generateSelfSigned(hosts);
|
|
4085
4220
|
}
|
|
4086
|
-
}, ensureDevCert = () => {
|
|
4221
|
+
}, ensureDevCert = (hosts = []) => {
|
|
4087
4222
|
mkdirSync4(CERT_DIR, { recursive: true });
|
|
4088
|
-
if (hasCert()) {
|
|
4223
|
+
if (hasCert(hosts)) {
|
|
4089
4224
|
return { cert: CERT_PATH, key: KEY_PATH };
|
|
4090
4225
|
}
|
|
4091
4226
|
if (certFilesExist()) {
|
|
4092
|
-
devLog("Certificate expired, regenerating...");
|
|
4227
|
+
devLog("Certificate is expired or missing a required host, regenerating...");
|
|
4093
4228
|
}
|
|
4094
4229
|
try {
|
|
4095
|
-
generateCert();
|
|
4230
|
+
generateCert(hosts);
|
|
4096
4231
|
} catch (err) {
|
|
4097
4232
|
devWarn(`Failed to generate certificate: ${err instanceof Error ? err.message : err}`);
|
|
4098
4233
|
return null;
|
|
4099
4234
|
}
|
|
4100
4235
|
return { cert: CERT_PATH, key: KEY_PATH };
|
|
4101
|
-
}, hasCert = () => certFilesExist() &&
|
|
4102
|
-
const paths = ensureDevCert();
|
|
4236
|
+
}, hasCert = (hosts = []) => certFilesExist() && certificateIsUsable(hosts), loadDevCert = (hosts = []) => {
|
|
4237
|
+
const paths = ensureDevCert(hosts);
|
|
4103
4238
|
if (!paths)
|
|
4104
4239
|
return null;
|
|
4105
4240
|
try {
|
|
@@ -4215,7 +4350,15 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4215
4350
|
} catch {
|
|
4216
4351
|
return null;
|
|
4217
4352
|
}
|
|
4218
|
-
}, mkcertCaRoot = () => runCapture(["mkcert", "-CAROOT"]),
|
|
4353
|
+
}, mkcertCaRoot = () => runCapture(["mkcert", "-CAROOT"]), getDevCertificateAuthorityPath = () => {
|
|
4354
|
+
const caRoot = hasMkcert() ? mkcertCaRoot() : null;
|
|
4355
|
+
const rootCertificate = caRoot ? join12(caRoot, "rootCA.pem") : null;
|
|
4356
|
+
if (rootCertificate && existsSync4(rootCertificate))
|
|
4357
|
+
return rootCertificate;
|
|
4358
|
+
if (certFilesExist())
|
|
4359
|
+
return CERT_PATH;
|
|
4360
|
+
return null;
|
|
4361
|
+
}, toWindowsPath = (linuxPath) => runCapture(["wslpath", "-w", linuxPath]), windowsTempDir = () => {
|
|
4219
4362
|
const winTemp = runCapture(["cmd.exe", "/c", "echo %TEMP%"]);
|
|
4220
4363
|
if (!winTemp)
|
|
4221
4364
|
return null;
|
|
@@ -4249,7 +4392,7 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4249
4392
|
], { stderr: "pipe", stdout: "pipe" });
|
|
4250
4393
|
rmSync(staged, { force: true });
|
|
4251
4394
|
return result.exitCode === 0;
|
|
4252
|
-
}, setupMkcert = () => {
|
|
4395
|
+
}, setupMkcert = (hosts = []) => {
|
|
4253
4396
|
if (!ensureMkcert())
|
|
4254
4397
|
return false;
|
|
4255
4398
|
const installResult = Bun.spawnSync(["mkcert", "-install"], {
|
|
@@ -4276,7 +4419,7 @@ var CERT_DIR, CERT_PATH, KEY_PATH, CERT_VALIDITY_DAYS = 365, devLog = (msg) => c
|
|
|
4276
4419
|
rmSync(CERT_PATH, { force: true });
|
|
4277
4420
|
rmSync(KEY_PATH, { force: true });
|
|
4278
4421
|
mkdirSync4(CERT_DIR, { recursive: true });
|
|
4279
|
-
generateWithMkcert();
|
|
4422
|
+
generateWithMkcert(hosts);
|
|
4280
4423
|
console.log("");
|
|
4281
4424
|
devLog("mkcert installed \u2014 HTTPS certificates are now locally trusted");
|
|
4282
4425
|
return true;
|
|
@@ -4285,6 +4428,8 @@ var init_devCert = __esm(() => {
|
|
|
4285
4428
|
CERT_DIR = join12(process.cwd(), ".absolutejs");
|
|
4286
4429
|
CERT_PATH = join12(CERT_DIR, "cert.pem");
|
|
4287
4430
|
KEY_PATH = join12(CERT_DIR, "key.pem");
|
|
4431
|
+
DEFAULT_CERTIFICATE_HOSTS = ["localhost", "127.0.0.1", "::1"];
|
|
4432
|
+
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
4433
|
});
|
|
4289
4434
|
|
|
4290
4435
|
// src/cli/scripts/eslintChunked.ts
|
|
@@ -17591,7 +17736,7 @@ var DEFAULT_ROUTE_TIMEOUT_MS = 30000, DEFAULT_HMR_TIMEOUT_MS = 30000, routeExpre
|
|
|
17591
17736
|
|
|
17592
17737
|
// src/mobile/releaseDoctor.ts
|
|
17593
17738
|
import { access as access8, readFile as readFile15, readdir as readdir4 } from "fs/promises";
|
|
17594
|
-
import { extname as extname8, join as join49, relative as relative24 } from "path";
|
|
17739
|
+
import { dirname as dirname29, extname as extname8, join as join49, relative as relative24 } from "path";
|
|
17595
17740
|
var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
17596
17741
|
try {
|
|
17597
17742
|
await access8(path);
|
|
@@ -17650,7 +17795,13 @@ var HMR_ASSET_PATTERN, RELEASE_ASSET_EXTENSIONS, pathExists5 = async (path) => {
|
|
|
17650
17795
|
return fail5("android.cleartext", "The Android manifest is missing.", manifestPath, "Run `absolute mobile sync android` before release validation.");
|
|
17651
17796
|
}
|
|
17652
17797
|
const source = await readFile15(manifestPath, "utf8");
|
|
17653
|
-
|
|
17798
|
+
const cleartext = /android:usesCleartextTraffic=["']true["']/u.test(source);
|
|
17799
|
+
const networkConfigName = source.match(/android:networkSecurityConfig=["']@xml\/([a-z0-9_]+)["']/u)?.[1];
|
|
17800
|
+
const networkConfigPath = networkConfigName ? join49(dirname29(manifestPath), "res", "xml", `${networkConfigName}.xml`) : undefined;
|
|
17801
|
+
const developmentTrustReference = /android:networkSecurityConfig=["']@xml\/absolutejs_dev_network_security["']/u.test(source);
|
|
17802
|
+
const developmentTrustContents = networkConfigPath ? await readFile15(networkConfigPath, "utf8").then((value) => value.includes("@raw/absolutejs_dev_ca")).catch(() => false) : false;
|
|
17803
|
+
const developmentTrust = developmentTrustReference || developmentTrustContents;
|
|
17804
|
+
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
17805
|
}, hmrAssetsReleaseCheck = async (publicRoot) => {
|
|
17655
17806
|
const hmrAsset = await findHmrAsset(publicRoot);
|
|
17656
17807
|
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);
|
|
@@ -17809,7 +17960,7 @@ import {
|
|
|
17809
17960
|
stat as stat2,
|
|
17810
17961
|
writeFile as writeFile14
|
|
17811
17962
|
} from "fs/promises";
|
|
17812
|
-
import { dirname as
|
|
17963
|
+
import { dirname as dirname30, isAbsolute as isAbsolute7, join as join50, relative as relative25, resolve as resolve39, sep as sep6 } from "path";
|
|
17813
17964
|
var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
|
|
17814
17965
|
if (!isRecord13(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
|
|
17815
17966
|
throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
|
|
@@ -17882,8 +18033,8 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
|
|
|
17882
18033
|
}
|
|
17883
18034
|
return { artifactPath: destination, metadata: existing, releaseRoot };
|
|
17884
18035
|
}
|
|
17885
|
-
await mkdir12(
|
|
17886
|
-
const staging = await mkdtemp5(join50(
|
|
18036
|
+
await mkdir12(dirname30(releaseRoot), { recursive: true });
|
|
18037
|
+
const staging = await mkdtemp5(join50(dirname30(releaseRoot), ".android-stage-"));
|
|
17887
18038
|
try {
|
|
17888
18039
|
await copyFile5(artifactPath, join50(staging, artifactName));
|
|
17889
18040
|
const complete = {
|
|
@@ -20678,6 +20829,24 @@ var resolveDevPort = async (requestedPort, options = {}) => {
|
|
|
20678
20829
|
` + `Set \`dev.port\` to a different value in absolute.config.ts (or via the ABSOLUTE_PORT env var), or extend \`dev.portRange\`.`);
|
|
20679
20830
|
};
|
|
20680
20831
|
|
|
20832
|
+
// src/utils/networking.ts
|
|
20833
|
+
import os from "os";
|
|
20834
|
+
var getAllNetworkIPs = () => {
|
|
20835
|
+
const interfaces = os.networkInterfaces();
|
|
20836
|
+
const addresses = Object.values(interfaces).flat().filter((iface) => iface !== undefined);
|
|
20837
|
+
const ipv4Addresses = [];
|
|
20838
|
+
addresses.filter((addr) => !addr.internal && addr.family === "IPv4").forEach((addr) => ipv4Addresses.push(addr.address));
|
|
20839
|
+
return ipv4Addresses;
|
|
20840
|
+
};
|
|
20841
|
+
var getLocalIPAddress = () => {
|
|
20842
|
+
const allIPs = getAllNetworkIPs();
|
|
20843
|
+
if (allIPs.length > 0 && allIPs[0]) {
|
|
20844
|
+
return allIPs[0];
|
|
20845
|
+
}
|
|
20846
|
+
console.warn("No IP address found, falling back to localhost");
|
|
20847
|
+
return "localhost";
|
|
20848
|
+
};
|
|
20849
|
+
|
|
20681
20850
|
// src/cli/scripts/dev.ts
|
|
20682
20851
|
init_config();
|
|
20683
20852
|
init_nativeAuth();
|
|
@@ -20982,25 +21151,41 @@ var confirmPrompt = (message, defaultYes = true) => {
|
|
|
20982
21151
|
process.stdin.on("data", onData);
|
|
20983
21152
|
return promise;
|
|
20984
21153
|
};
|
|
20985
|
-
var setupCertWithPrompt = async (ensureDevCert2, setupMkcert2) => {
|
|
21154
|
+
var setupCertWithPrompt = async (ensureDevCert2, setupMkcert2, hosts) => {
|
|
20986
21155
|
const install = await confirmPrompt("Install mkcert for trusted HTTPS? (no browser warning)");
|
|
20987
21156
|
if (install) {
|
|
20988
|
-
setupMkcert2();
|
|
21157
|
+
setupMkcert2(hosts);
|
|
20989
21158
|
} else {
|
|
20990
|
-
ensureDevCert2();
|
|
21159
|
+
ensureDevCert2(hosts);
|
|
20991
21160
|
}
|
|
20992
21161
|
};
|
|
20993
|
-
var setupHttpsCert = async () => {
|
|
20994
|
-
const {
|
|
20995
|
-
|
|
20996
|
-
|
|
20997
|
-
|
|
21162
|
+
var setupHttpsCert = async (hosts = []) => {
|
|
21163
|
+
const {
|
|
21164
|
+
getDevCertificateAuthorityPath: getDevCertificateAuthorityPath2,
|
|
21165
|
+
hasCert: hasCert2,
|
|
21166
|
+
hasMkcert: hasMkcert2,
|
|
21167
|
+
ensureDevCert: ensureDevCert2,
|
|
21168
|
+
setupMkcert: setupMkcert2
|
|
21169
|
+
} = await Promise.resolve().then(() => (init_devCert(), exports_devCert));
|
|
21170
|
+
if (hasCert2(hosts)) {
|
|
21171
|
+
ensureDevCert2(hosts);
|
|
21172
|
+
return getDevCertificateAuthorityPath2();
|
|
20998
21173
|
}
|
|
20999
21174
|
if (hasMkcert2()) {
|
|
21000
|
-
ensureDevCert2();
|
|
21001
|
-
return;
|
|
21175
|
+
ensureDevCert2(hosts);
|
|
21176
|
+
return getDevCertificateAuthorityPath2();
|
|
21002
21177
|
}
|
|
21003
|
-
await setupCertWithPrompt(ensureDevCert2, setupMkcert2);
|
|
21178
|
+
await setupCertWithPrompt(ensureDevCert2, setupMkcert2, hosts);
|
|
21179
|
+
return getDevCertificateAuthorityPath2();
|
|
21180
|
+
};
|
|
21181
|
+
var mobileReachableHost = (host) => {
|
|
21182
|
+
if (host !== "0.0.0.0" && host !== "::")
|
|
21183
|
+
return host;
|
|
21184
|
+
const address = getLocalIPAddress();
|
|
21185
|
+
if (address === "localhost") {
|
|
21186
|
+
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.");
|
|
21187
|
+
}
|
|
21188
|
+
return address;
|
|
21004
21189
|
};
|
|
21005
21190
|
var resolveDevConfig = (configDev) => {
|
|
21006
21191
|
const relay = env.ABSOLUTE_TUNNEL_RELAY ?? configDev?.tunnel?.relay;
|
|
@@ -21014,10 +21199,19 @@ var resolveDevConfig = (configDev) => {
|
|
|
21014
21199
|
...relay && token ? { tunnel: { relay, token } } : {}
|
|
21015
21200
|
};
|
|
21016
21201
|
};
|
|
21017
|
-
var androidToolchainReady = (checks
|
|
21202
|
+
var androidToolchainReady = (checks, target = "emulator") => {
|
|
21203
|
+
const deviceOnlyChecks = new Set([
|
|
21204
|
+
"android.avd",
|
|
21205
|
+
"android.avdmanager",
|
|
21206
|
+
"android.emulator",
|
|
21207
|
+
"android.virtualization"
|
|
21208
|
+
]);
|
|
21209
|
+
return checks.every((check) => check.platform !== "android" || target === "device" && deviceOnlyChecks.has(check.id) || check.status !== "fail" && check.status !== "warn");
|
|
21210
|
+
};
|
|
21018
21211
|
var iosToolchainReady = (checks) => checks.every((check) => check.platform !== "ios" || check.status !== "fail" && check.status !== "warn");
|
|
21019
21212
|
var dev = async (serverEntry, configPath2, options = {}) => {
|
|
21020
21213
|
let httpsEnabled = false;
|
|
21214
|
+
let devCertificateAuthorityPath = null;
|
|
21021
21215
|
let resolvedDev;
|
|
21022
21216
|
let buildDirectory = resolve8(process.cwd(), "build");
|
|
21023
21217
|
let mobileConfig;
|
|
@@ -21031,12 +21225,21 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21031
21225
|
if (config?.buildDirectory) {
|
|
21032
21226
|
buildDirectory = resolve8(process.cwd(), config.buildDirectory);
|
|
21033
21227
|
}
|
|
21034
|
-
if (httpsEnabled)
|
|
21035
|
-
await setupHttpsCert();
|
|
21036
21228
|
} catch {
|
|
21037
21229
|
resolvedDev = resolveDevConfig(undefined);
|
|
21038
21230
|
httpsEnabled = resolvedDev.https;
|
|
21039
21231
|
}
|
|
21232
|
+
if (options.androidDevice && ["localhost", "127.0.0.1", "::1"].includes(resolvedDev.host)) {
|
|
21233
|
+
resolvedDev.host = "0.0.0.0";
|
|
21234
|
+
}
|
|
21235
|
+
if (httpsEnabled)
|
|
21236
|
+
devCertificateAuthorityPath = await setupHttpsCert(options.androidDevice ? [mobileReachableHost(resolvedDev.host)] : [resolvedDev.host]);
|
|
21237
|
+
if (options.androidDevice && !mobileConfig) {
|
|
21238
|
+
throw new TypeError("--android-device requires an absolute.config.ts mobile configuration.");
|
|
21239
|
+
}
|
|
21240
|
+
if (options.androidDevice && mobileConfig?.platforms && !mobileConfig.platforms.includes("android")) {
|
|
21241
|
+
throw new TypeError("--android-device requires android in mobile.platforms.");
|
|
21242
|
+
}
|
|
21040
21243
|
let androidDevProject = null;
|
|
21041
21244
|
let iosDevProject = null;
|
|
21042
21245
|
const mobileInteractive = options.mobile !== false && process.env.ABSOLUTE_NO_MOBILE !== "1" && process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -21044,14 +21247,15 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21044
21247
|
try {
|
|
21045
21248
|
const normalized = normalizeAbsoluteMobileConfig(mobileConfig, process.cwd());
|
|
21046
21249
|
if (normalized.platforms.includes("android")) {
|
|
21047
|
-
|
|
21250
|
+
const androidTarget = options.androidDevice ? "device" : "emulator";
|
|
21251
|
+
let ready = androidToolchainReady(await inspectAbsoluteMobileToolchain(), androidTarget);
|
|
21048
21252
|
if (!ready) {
|
|
21049
|
-
const install = await confirmPrompt("Android
|
|
21253
|
+
const install = await confirmPrompt("Android development is not configured. Install the tested toolchain now?");
|
|
21050
21254
|
if (install) {
|
|
21051
21255
|
await fixAbsoluteMobileEmulatorToolchain("android");
|
|
21052
|
-
ready = androidToolchainReady(await inspectAbsoluteMobileToolchain());
|
|
21256
|
+
ready = androidToolchainReady(await inspectAbsoluteMobileToolchain(), androidTarget);
|
|
21053
21257
|
} else {
|
|
21054
|
-
console.log(cliTag("\x1B[33m", "
|
|
21258
|
+
console.log(cliTag("\x1B[33m", "Android target skipped. Run `absolute mobile doctor android --fix` when ready."));
|
|
21055
21259
|
}
|
|
21056
21260
|
}
|
|
21057
21261
|
if (ready) {
|
|
@@ -21063,7 +21267,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21063
21267
|
if (existsSync5(nativeDirectory) || createNativeProject) {
|
|
21064
21268
|
androidDevProject = await prepareAbsoluteAndroidDevProject(normalized, {
|
|
21065
21269
|
createNativeProject,
|
|
21066
|
-
projectRoot: process.cwd()
|
|
21270
|
+
projectRoot: process.cwd(),
|
|
21271
|
+
target: androidTarget
|
|
21067
21272
|
});
|
|
21068
21273
|
}
|
|
21069
21274
|
}
|
|
@@ -21145,7 +21350,8 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21145
21350
|
"dev",
|
|
21146
21351
|
serverEntry,
|
|
21147
21352
|
...configPath2 ? ["--config", configPath2] : [],
|
|
21148
|
-
...options.mobile === false ? ["--no-mobile"] : []
|
|
21353
|
+
...options.mobile === false ? ["--no-mobile"] : [],
|
|
21354
|
+
...options.androidDevice ? ["--android-device", options.androidDevice] : []
|
|
21149
21355
|
].filter((part) => part.length > 0);
|
|
21150
21356
|
registerInstance({
|
|
21151
21357
|
command: relaunchCommand,
|
|
@@ -21201,6 +21407,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21201
21407
|
platform: "android",
|
|
21202
21408
|
provider: "capacitor",
|
|
21203
21409
|
startedEmulator: session.startedEmulator,
|
|
21410
|
+
target: options.androidDevice ? "device" : "emulator",
|
|
21204
21411
|
timings: session.timings
|
|
21205
21412
|
});
|
|
21206
21413
|
if (session.timings.building === undefined)
|
|
@@ -21215,9 +21422,12 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21215
21422
|
});
|
|
21216
21423
|
};
|
|
21217
21424
|
const openAndroidDevSession = (androidProject) => startAbsoluteAndroidDevSession({
|
|
21425
|
+
certificateAuthorityPath: devCertificateAuthorityPath ?? undefined,
|
|
21426
|
+
deviceSerial: options.androidDevice,
|
|
21218
21427
|
https: httpsEnabled,
|
|
21219
21428
|
port,
|
|
21220
21429
|
project: androidProject,
|
|
21430
|
+
serverHost: options.androidDevice ? mobileReachableHost(resolvedDev.host) : "localhost",
|
|
21221
21431
|
signal: androidDevAbort.signal,
|
|
21222
21432
|
log: (message) => printNativeOutput(cliTag("\x1B[36m", message)),
|
|
21223
21433
|
nativeLog: (entry) => printNativeOutput(androidLogTag(entry)),
|
|
@@ -21328,6 +21538,7 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21328
21538
|
};
|
|
21329
21539
|
const openIosDevSession = (iosProject) => {
|
|
21330
21540
|
const sessionOptions = {
|
|
21541
|
+
certificateAuthorityPath: devCertificateAuthorityPath ?? undefined,
|
|
21331
21542
|
https: httpsEnabled,
|
|
21332
21543
|
port,
|
|
21333
21544
|
signal: iosDevAbort.signal,
|
|
@@ -21488,6 +21699,9 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21488
21699
|
return;
|
|
21489
21700
|
}
|
|
21490
21701
|
const refreshedDevConfig = resolveDevConfig(cfg?.dev);
|
|
21702
|
+
if (options.androidDevice && ["localhost", "127.0.0.1", "::1"].includes(refreshedDevConfig.host)) {
|
|
21703
|
+
refreshedDevConfig.host = "0.0.0.0";
|
|
21704
|
+
}
|
|
21491
21705
|
const desiredBuildDir = cfg?.buildDirectory ? resolve8(process.cwd(), cfg.buildDirectory) : resolve8(process.cwd(), "build");
|
|
21492
21706
|
if (desiredBuildDir !== buildDirectory && desiredBuildDir !== lastBuildDirectoryWarned) {
|
|
21493
21707
|
lastBuildDirectoryWarned = desiredBuildDir;
|
|
@@ -21570,9 +21784,11 @@ var dev = async (serverEntry, configPath2, options = {}) => {
|
|
|
21570
21784
|
env: {
|
|
21571
21785
|
...process.env,
|
|
21572
21786
|
...readDotenvFiles(),
|
|
21787
|
+
ABSOLUTE_HOST: resolvedDev.host,
|
|
21573
21788
|
ABSOLUTE_INSTANCE_MANAGED: "1",
|
|
21574
21789
|
ABSOLUTE_PORT: String(port),
|
|
21575
21790
|
ABSOLUTE_SERVER_ENTRY: resolve8(serverEntry),
|
|
21791
|
+
...mobileConfig ? { ABSOLUTE_MOBILE_PREVIEW: "1" } : {},
|
|
21576
21792
|
FORCE_COLOR: "1",
|
|
21577
21793
|
NODE_ENV: "development",
|
|
21578
21794
|
PORT: String(port),
|
|
@@ -23765,9 +23981,17 @@ var stripNamedArgs = (...flags) => args.filter((_, idx) => flags.every((flag) =>
|
|
|
23765
23981
|
if (command === "dev") {
|
|
23766
23982
|
sendTelemetryEvent("cli:command", { command });
|
|
23767
23983
|
const configPath2 = parseNamedArg("--config");
|
|
23768
|
-
const
|
|
23984
|
+
const androidDevice = parseNamedArg("--android-device");
|
|
23985
|
+
if (args.includes("--android-device") && !androidDevice) {
|
|
23986
|
+
throw new TypeError("--android-device requires an ADB device serial.");
|
|
23987
|
+
}
|
|
23988
|
+
if (androidDevice && args.includes("--no-mobile")) {
|
|
23989
|
+
throw new TypeError("--android-device cannot be combined with --no-mobile.");
|
|
23990
|
+
}
|
|
23991
|
+
const positionalArgs2 = stripNamedArgs("--config", "--android-device").filter((arg) => arg !== "--no-mobile");
|
|
23769
23992
|
const serverEntry = positionalArgs2[0] ?? DEFAULT_SERVER_ENTRY;
|
|
23770
23993
|
await dev(serverEntry, configPath2, {
|
|
23994
|
+
androidDevice,
|
|
23771
23995
|
mobile: !args.includes("--no-mobile")
|
|
23772
23996
|
});
|
|
23773
23997
|
} else if (command === "start") {
|
|
@@ -23926,7 +24150,7 @@ if (command === "dev") {
|
|
|
23926
24150
|
console.error(message);
|
|
23927
24151
|
console.error("Usage: absolute <command>");
|
|
23928
24152
|
console.error("Commands:");
|
|
23929
|
-
console.error(" dev [entry] [--no-mobile] Start web and configured mobile development");
|
|
24153
|
+
console.error(" dev [entry] [--no-mobile] [--android-device serial] Start web and configured mobile development");
|
|
23930
24154
|
console.error(" workspace dev [--no-tui] Start multi-service workspace dev");
|
|
23931
24155
|
console.error(" build [--outdir dir] [--profile] Build production assets");
|
|
23932
24156
|
console.error(" prepare [entry] [--outdir dir] Build production assets and server without launching");
|