@absolutejs/absolute 0.20.0-beta.112 → 0.20.0-beta.114

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.
@@ -12,17 +12,17 @@ import {
12
12
 
13
13
  // src/mobile/expoProject.ts
14
14
  import {
15
- access,
16
- mkdir,
15
+ access as access2,
16
+ mkdir as mkdir2,
17
17
  mkdtemp,
18
18
  readdir,
19
- readFile,
20
- rename,
21
- rm,
22
- writeFile
19
+ readFile as readFile2,
20
+ rename as rename2,
21
+ rm as rm2,
22
+ writeFile as writeFile2
23
23
  } from "fs/promises";
24
- import { createHash as createHash2 } from "crypto";
25
- import { basename, dirname as dirname2, join as join5, relative as relative2, resolve as resolve3, sep } from "path";
24
+ import { createHash as createHash3 } from "crypto";
25
+ import { basename, dirname as dirname3, join as join6, relative as relative3, resolve as resolve4, sep } from "path";
26
26
 
27
27
  // src/mobile/syncSchema.ts
28
28
  import { readFileSync } from "fs";
@@ -1671,14 +1671,490 @@ var absoluteExpoActivityResultRecoveryFiles = (project) => new Map([
1671
1671
  ]
1672
1672
  ]);
1673
1673
 
1674
+ // src/mobile/branding.ts
1675
+ import { createHash as createHash2 } from "crypto";
1676
+ import {
1677
+ access,
1678
+ copyFile,
1679
+ mkdir,
1680
+ readFile,
1681
+ rename,
1682
+ rm,
1683
+ writeFile
1684
+ } from "fs/promises";
1685
+ import { dirname as dirname2, join as join5, relative as relative2, resolve as resolve3 } from "path";
1686
+ var ABSOLUTE_MOBILE_BRANDING_FORMAT = 1;
1687
+ var ABSOLUTE_CAPACITOR_ASSETS_VERSION = "3.0.5";
1688
+ var ABSOLUTE_CAPACITOR_ASSETS_SPEC = `@capacitor/assets@${ABSOLUTE_CAPACITOR_ASSETS_VERSION}`;
1689
+ var MAX_SOURCE_BYTES = 32 * 1024 * 1024;
1690
+ var ICON_SIZE = 1024;
1691
+ var SPLASH_SIZE = 2732;
1692
+ var ANDROID_ADAPTIVE_SIZES = [
1693
+ ["ldpi", 81],
1694
+ ["mdpi", 108],
1695
+ ["hdpi", 162],
1696
+ ["xhdpi", 216],
1697
+ ["xxhdpi", 324],
1698
+ ["xxxhdpi", 432]
1699
+ ];
1700
+ var exists = async (path) => {
1701
+ try {
1702
+ await access(path);
1703
+ return true;
1704
+ } catch {
1705
+ return false;
1706
+ }
1707
+ };
1708
+ var portableRelative = (root, path) => relative2(root, path).replaceAll("\\", "/");
1709
+ var absoluteMobileBrandingManifestPath = (projectRoot) => join5(resolve3(projectRoot), ".absolutejs", "mobile", "branding", "manifest.json");
1710
+ var brandingEntries = (branding) => [
1711
+ { kind: "icon", path: branding.icon, size: ICON_SIZE },
1712
+ branding.android.foreground ? {
1713
+ kind: "android-foreground",
1714
+ path: branding.android.foreground,
1715
+ size: ICON_SIZE
1716
+ } : undefined,
1717
+ branding.android.backgroundImage ? {
1718
+ kind: "android-background",
1719
+ path: branding.android.backgroundImage,
1720
+ size: ICON_SIZE
1721
+ } : undefined,
1722
+ branding.android.monochrome ? {
1723
+ kind: "android-monochrome",
1724
+ path: branding.android.monochrome,
1725
+ size: ICON_SIZE
1726
+ } : undefined,
1727
+ branding.ios.darkIcon ? {
1728
+ kind: "ios-dark",
1729
+ path: branding.ios.darkIcon,
1730
+ size: ICON_SIZE
1731
+ } : undefined,
1732
+ branding.ios.tintedIcon ? {
1733
+ kind: "ios-tinted",
1734
+ path: branding.ios.tintedIcon,
1735
+ size: ICON_SIZE
1736
+ } : undefined,
1737
+ branding.splash.image ? {
1738
+ kind: "splash-light",
1739
+ path: branding.splash.image,
1740
+ size: SPLASH_SIZE
1741
+ } : undefined,
1742
+ branding.splash.darkImage ? {
1743
+ kind: "splash-dark",
1744
+ path: branding.splash.darkImage,
1745
+ size: SPLASH_SIZE
1746
+ } : undefined
1747
+ ].filter((entry) => entry !== undefined);
1748
+ var inspectSource = async (projectRoot, entry) => {
1749
+ const buffer = await readFile(entry.path);
1750
+ if (buffer.byteLength > MAX_SOURCE_BYTES)
1751
+ throw new TypeError(`mobile.branding ${entry.kind} exceeds the 32 MiB source limit.`);
1752
+ let metadata;
1753
+ try {
1754
+ metadata = await new Bun.Image(buffer, {
1755
+ maxPixels: SPLASH_SIZE * SPLASH_SIZE * 4
1756
+ }).metadata();
1757
+ } catch (error) {
1758
+ throw new TypeError(`mobile.branding ${entry.kind} must be a readable PNG image.`, { cause: error });
1759
+ }
1760
+ if (metadata.format !== "png")
1761
+ throw new TypeError(`mobile.branding ${entry.kind} must be PNG.`);
1762
+ if (metadata.width !== metadata.height || metadata.width < entry.size || metadata.height < entry.size)
1763
+ throw new TypeError(`mobile.branding ${entry.kind} must be square and at least ${entry.size}x${entry.size}.`);
1764
+ return {
1765
+ bytes: buffer.byteLength,
1766
+ height: metadata.height,
1767
+ kind: entry.kind,
1768
+ path: portableRelative(projectRoot, entry.path),
1769
+ sha256: createHash2("sha256").update(buffer).digest("hex"),
1770
+ width: metadata.width
1771
+ };
1772
+ };
1773
+ var manifestFingerprint = (config, sources) => {
1774
+ const { branding } = config;
1775
+ if (!branding)
1776
+ throw new TypeError("mobile.branding is not configured.");
1777
+ return createHash2("sha256").update(JSON.stringify({
1778
+ androidBackgroundColor: branding.android.backgroundColor,
1779
+ engine: config.engine,
1780
+ format: ABSOLUTE_MOBILE_BRANDING_FORMAT,
1781
+ platforms: config.platforms,
1782
+ sources: sources.map(({ kind, sha256 }) => ({ kind, sha256 })),
1783
+ splashBackgroundColor: branding.splash.backgroundColor,
1784
+ splashDarkBackgroundColor: branding.splash.darkBackgroundColor,
1785
+ splashLogoScale: branding.splash.logoScale
1786
+ })).digest("hex");
1787
+ };
1788
+ var createAbsoluteMobileBrandingManifest = async (config, projectRoot) => {
1789
+ if (!config.branding)
1790
+ throw new TypeError("absolute.config.ts must define mobile.branding before generating mobile assets.");
1791
+ const sources = await Promise.all(brandingEntries(config.branding).map((entry) => inspectSource(projectRoot, entry)));
1792
+ return {
1793
+ engine: config.engine,
1794
+ fingerprint: manifestFingerprint(config, sources),
1795
+ format: ABSOLUTE_MOBILE_BRANDING_FORMAT,
1796
+ platforms: [...config.platforms],
1797
+ sources
1798
+ };
1799
+ };
1800
+ var writeManifest = async (projectRoot, manifest) => {
1801
+ const destination = absoluteMobileBrandingManifestPath(projectRoot);
1802
+ await mkdir(dirname2(destination), { recursive: true });
1803
+ const temporary = `${destination}.${crypto.randomUUID()}.tmp`;
1804
+ await writeFile(temporary, `${JSON.stringify(manifest, null, 2)}
1805
+ `, {
1806
+ flag: "wx"
1807
+ });
1808
+ await rename(temporary, destination);
1809
+ return destination;
1810
+ };
1811
+ var copyManaged = async (source, destination) => {
1812
+ await mkdir(dirname2(destination), { recursive: true });
1813
+ await copyFile(source, destination);
1814
+ };
1815
+ var absoluteExpoBrandingConfig = (config) => {
1816
+ const { branding } = config;
1817
+ if (!branding)
1818
+ return;
1819
+ const root = "./assets/absolute-branding";
1820
+ const iosIcon = branding.ios.darkIcon || branding.ios.tintedIcon ? {
1821
+ ...branding.ios.darkIcon ? { dark: `${root}/ios-dark.png` } : {},
1822
+ light: `${root}/icon.png`,
1823
+ ...branding.ios.tintedIcon ? { tinted: `${root}/ios-tinted.png` } : {}
1824
+ } : `${root}/icon.png`;
1825
+ return {
1826
+ android: {
1827
+ adaptiveIcon: {
1828
+ backgroundColor: branding.android.backgroundColor,
1829
+ ...branding.android.backgroundImage ? { backgroundImage: `${root}/android-background.png` } : {},
1830
+ foregroundImage: branding.android.foreground ? `${root}/android-foreground.png` : `${root}/icon.png`,
1831
+ ...branding.android.monochrome ? { monochromeImage: `${root}/android-monochrome.png` } : {}
1832
+ },
1833
+ icon: `${root}/icon.png`
1834
+ },
1835
+ icon: `${root}/icon.png`,
1836
+ ios: { icon: iosIcon },
1837
+ splashPlugin: [
1838
+ "expo-splash-screen",
1839
+ {
1840
+ backgroundColor: branding.splash.backgroundColor,
1841
+ dark: {
1842
+ backgroundColor: branding.splash.darkBackgroundColor,
1843
+ image: branding.splash.darkImage ? `${root}/splash-dark.png` : `${root}/icon.png`
1844
+ },
1845
+ image: branding.splash.image ? `${root}/splash-light.png` : `${root}/icon.png`,
1846
+ imageWidth: Math.round(1024 * branding.splash.logoScale)
1847
+ }
1848
+ ]
1849
+ };
1850
+ };
1851
+ var writeAbsoluteExpoBrandingInputs = async (config, projectRoot) => {
1852
+ const { branding } = config;
1853
+ if (!branding)
1854
+ return;
1855
+ const manifest = await createAbsoluteMobileBrandingManifest(config, projectRoot);
1856
+ const destination = join5(config.nativeProjectDirectory, "assets", "absolute-branding");
1857
+ await rm(destination, { force: true, recursive: true });
1858
+ await mkdir(destination, { recursive: true });
1859
+ await copyManaged(branding.icon, join5(destination, "icon.png"));
1860
+ const copies = [
1861
+ [branding.android.backgroundImage, "android-background.png"],
1862
+ [branding.android.foreground, "android-foreground.png"],
1863
+ [branding.android.monochrome, "android-monochrome.png"],
1864
+ [branding.ios.darkIcon, "ios-dark.png"],
1865
+ [branding.ios.tintedIcon, "ios-tinted.png"],
1866
+ [branding.splash.image, "splash-light.png"],
1867
+ [branding.splash.darkImage, "splash-dark.png"]
1868
+ ];
1869
+ await Promise.all(copies.map(([source, name]) => source ? copyManaged(source, join5(destination, name)) : undefined));
1870
+ await writeManifest(projectRoot, manifest);
1871
+ return { manifest, path: destination };
1872
+ };
1873
+ var stageCapacitorInputs = async (branding, projectRoot) => {
1874
+ const destination = join5(projectRoot, ".absolutejs", "mobile", "branding", "input");
1875
+ await rm(destination, { force: true, recursive: true });
1876
+ await mkdir(destination, { recursive: true });
1877
+ await copyManaged(branding.icon, join5(destination, "logo.png"));
1878
+ if (branding.splash.image)
1879
+ await copyManaged(branding.splash.image, join5(destination, "splash.png"));
1880
+ if (branding.splash.darkImage)
1881
+ await copyManaged(branding.splash.darkImage, join5(destination, "splash-dark.png"));
1882
+ return destination;
1883
+ };
1884
+ var defaultRunner = async (command, options) => {
1885
+ const child = Bun.spawn(command, {
1886
+ cwd: options.cwd,
1887
+ stderr: "pipe",
1888
+ stdin: "inherit",
1889
+ stdout: "pipe"
1890
+ });
1891
+ const [exitCode, stdout, stderr] = await Promise.all([
1892
+ child.exited,
1893
+ new Response(child.stdout).text(),
1894
+ new Response(child.stderr).text()
1895
+ ]);
1896
+ if (stdout)
1897
+ process.stdout.write(stdout);
1898
+ if (stderr)
1899
+ process.stderr.write(stderr);
1900
+ if (exitCode !== 0)
1901
+ throw new TypeError(`Capacitor asset generation exited with status ${exitCode}.`);
1902
+ if (/Unable to generate assets|Unable to load source image|Asset directory not found|No assets found|No platforms found|skipping (?:iOS|android) generation/iu.test(`${stdout}
1903
+ ${stderr}`))
1904
+ throw new TypeError("Capacitor asset generation reported an incomplete projection.");
1905
+ };
1906
+ var resizePng = async (source, destination, size) => {
1907
+ const output = await new Bun.Image(await readFile(source)).resize(size, size).png({ compressionLevel: 9 }).toBuffer();
1908
+ await mkdir(dirname2(destination), { recursive: true });
1909
+ await writeFile(destination, output);
1910
+ };
1911
+ var applyAndroidLayers = async (branding, nativeDirectory) => {
1912
+ const resources = join5(nativeDirectory, "android", "app", "src", "main", "res");
1913
+ await Promise.all(ANDROID_ADAPTIVE_SIZES.flatMap(([density, size]) => [
1914
+ ...branding.android.foreground ? [
1915
+ resizePng(branding.android.foreground, join5(resources, `mipmap-${density}`, "ic_launcher_foreground.png"), size)
1916
+ ] : [],
1917
+ ...branding.android.backgroundImage ? [
1918
+ resizePng(branding.android.backgroundImage, join5(resources, `mipmap-${density}`, "ic_launcher_background.png"), size)
1919
+ ] : [],
1920
+ ...branding.android.monochrome ? [
1921
+ resizePng(branding.android.monochrome, join5(resources, `mipmap-${density}`, "ic_launcher_monochrome.png"), size)
1922
+ ] : []
1923
+ ]));
1924
+ if (!branding.android.monochrome)
1925
+ return;
1926
+ const iconXml = `<?xml version="1.0" encoding="utf-8"?>
1927
+ <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
1928
+ <background android:drawable="@mipmap/ic_launcher_background" />
1929
+ <foreground android:drawable="@mipmap/ic_launcher_foreground" />
1930
+ <monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
1931
+ </adaptive-icon>
1932
+ `;
1933
+ await Promise.all(["ic_launcher.xml", "ic_launcher_round.xml"].map((name) => writeFile(join5(resources, "mipmap-anydpi-v26", name), iconXml)));
1934
+ };
1935
+ var applyIosAppearances = async (branding, nativeDirectory) => {
1936
+ if (!branding.ios.darkIcon && !branding.ios.tintedIcon)
1937
+ return;
1938
+ const assetSet = join5(nativeDirectory, "ios", "App", "App", "Assets.xcassets", "AppIcon.appiconset");
1939
+ const contentsPath = join5(assetSet, "Contents.json");
1940
+ const contents = JSON.parse(await readFile(contentsPath, "utf8"));
1941
+ const images = typeof contents === "object" && contents !== null ? Reflect.get(contents, "images") : undefined;
1942
+ if (typeof contents !== "object" || contents === null || !Array.isArray(images))
1943
+ throw new TypeError("Generated iOS AppIcon Contents.json is invalid.");
1944
+ const appearanceSources = [
1945
+ ["dark", branding.ios.darkIcon],
1946
+ ["tinted", branding.ios.tintedIcon]
1947
+ ];
1948
+ const generated = await Promise.all(appearanceSources.map(async ([appearance, source]) => {
1949
+ if (!source)
1950
+ return;
1951
+ const filename = `AppIcon-1024-${appearance}.png`;
1952
+ await resizePng(source, join5(assetSet, filename), ICON_SIZE);
1953
+ return {
1954
+ appearances: [{ appearance: "luminosity", value: appearance }],
1955
+ filename,
1956
+ idiom: "universal",
1957
+ platform: "ios",
1958
+ size: "1024x1024"
1959
+ };
1960
+ }));
1961
+ const retained = images.filter((entry) => {
1962
+ if (typeof entry !== "object" || entry === null)
1963
+ return true;
1964
+ const filename = Reflect.get(entry, "filename");
1965
+ return filename !== "AppIcon-1024-dark.png" && filename !== "AppIcon-1024-tinted.png";
1966
+ });
1967
+ Reflect.set(contents, "images", [
1968
+ ...retained,
1969
+ ...generated.filter((entry) => entry !== undefined)
1970
+ ]);
1971
+ await writeFile(contentsPath, `${JSON.stringify(contents, null, 2)}
1972
+ `);
1973
+ };
1974
+ var nativeBrandingSentinels = (config, platforms) => platforms.map((platform) => platform === "android" ? join5(config.nativeProjectDirectory, "android", "app", "src", "main", "res", "mipmap-xxxhdpi", "ic_launcher.png") : join5(config.nativeProjectDirectory, "ios", "App", "App", "Assets.xcassets", "AppIcon.appiconset", "AppIcon-512@2x.png"));
1975
+ var requireGeneratedBranding = async (config, platforms) => {
1976
+ const missing = (await Promise.all(nativeBrandingSentinels(config, platforms).map(async (path) => ({
1977
+ path,
1978
+ present: await exists(path)
1979
+ })))).filter(({ present }) => !present);
1980
+ if (missing.length > 0)
1981
+ throw new TypeError(`Capacitor asset generation did not produce ${missing.map(({ path }) => path).join(", ")}.`);
1982
+ };
1983
+ var reusableManifestPlatforms = (value, fingerprint) => {
1984
+ if (typeof value !== "object" || value === null || Reflect.get(value, "format") !== ABSOLUTE_MOBILE_BRANDING_FORMAT || Reflect.get(value, "fingerprint") !== fingerprint)
1985
+ return [];
1986
+ const platforms = Reflect.get(value, "platforms");
1987
+ if (!Array.isArray(platforms))
1988
+ return [];
1989
+ return platforms.flatMap((platform) => platform === "android" || platform === "ios" ? [platform] : []);
1990
+ };
1991
+ var completedManifestPlatforms = async (manifest, manifestPath, generated) => {
1992
+ let previousPlatforms = [];
1993
+ try {
1994
+ const previous = JSON.parse(await readFile(manifestPath, "utf8"));
1995
+ previousPlatforms = reusableManifestPlatforms(previous, manifest.fingerprint);
1996
+ } catch {}
1997
+ const completed = new Set([...generated, ...previousPlatforms]);
1998
+ return manifest.platforms.filter((platform) => completed.has(platform));
1999
+ };
2000
+ var generateAbsoluteCapacitorBranding = async (config, projectRoot, options = {}) => {
2001
+ const { branding } = config;
2002
+ if (!branding)
2003
+ throw new TypeError("absolute.config.ts must define mobile.branding before generating mobile assets.");
2004
+ if (config.engine !== "capacitor")
2005
+ throw new TypeError("Capacitor branding requires mobile.engine: capacitor.");
2006
+ const manifest = await createAbsoluteMobileBrandingManifest(config, projectRoot);
2007
+ const input = await stageCapacitorInputs(branding, projectRoot);
2008
+ const platforms = options.platforms ?? config.platforms;
2009
+ const command = [
2010
+ "bun",
2011
+ "x",
2012
+ "--no-install",
2013
+ "capacitor-assets",
2014
+ "generate",
2015
+ ...platforms.map((platform) => `--${platform}`),
2016
+ "--assetPath",
2017
+ portableRelative(projectRoot, input),
2018
+ "--androidProject",
2019
+ portableRelative(projectRoot, join5(config.nativeProjectDirectory, "android")),
2020
+ "--iosProject",
2021
+ portableRelative(projectRoot, join5(config.nativeProjectDirectory, "ios", "App")),
2022
+ "--iconBackgroundColor",
2023
+ branding.android.backgroundColor,
2024
+ "--splashBackgroundColor",
2025
+ branding.splash.backgroundColor,
2026
+ "--splashBackgroundColorDark",
2027
+ branding.splash.darkBackgroundColor,
2028
+ "--logoSplashScale",
2029
+ String(branding.splash.logoScale)
2030
+ ];
2031
+ await (options.runner ?? defaultRunner)(command, { cwd: projectRoot });
2032
+ await requireGeneratedBranding(config, platforms);
2033
+ if (platforms.includes("android"))
2034
+ await applyAndroidLayers(branding, config.nativeProjectDirectory);
2035
+ if (platforms.includes("ios"))
2036
+ await applyIosAppearances(branding, config.nativeProjectDirectory);
2037
+ const manifestPath = absoluteMobileBrandingManifestPath(projectRoot);
2038
+ const completedPlatforms = await completedManifestPlatforms(manifest, manifestPath, platforms);
2039
+ await writeManifest(projectRoot, {
2040
+ ...manifest,
2041
+ platforms: completedPlatforms
2042
+ });
2043
+ return {
2044
+ manifest: { ...manifest, platforms: completedPlatforms },
2045
+ manifestPath,
2046
+ platforms
2047
+ };
2048
+ };
2049
+ var parseManifestState = (value) => {
2050
+ const fingerprint = typeof value === "object" && value !== null ? Reflect.get(value, "fingerprint") : undefined;
2051
+ if (typeof value !== "object" || value === null || Reflect.get(value, "format") !== ABSOLUTE_MOBILE_BRANDING_FORMAT || typeof fingerprint !== "string")
2052
+ throw new TypeError("Mobile branding manifest is invalid.");
2053
+ const platforms = Reflect.get(value, "platforms");
2054
+ if (!Array.isArray(platforms) || platforms.some((platform) => platform !== "android" && platform !== "ios"))
2055
+ throw new TypeError("Mobile branding manifest platforms are invalid.");
2056
+ const validPlatforms = platforms.flatMap((platform) => platform === "android" || platform === "ios" ? [platform] : []);
2057
+ return { fingerprint, platforms: new Set(validPlatforms) };
2058
+ };
2059
+ var brandingInspection = (value) => value;
2060
+ var inspectAbsoluteMobileBranding = async (config, projectRoot) => {
2061
+ const manifestPath = absoluteMobileBrandingManifestPath(projectRoot);
2062
+ if (!config.branding)
2063
+ return brandingInspection({
2064
+ configured: false,
2065
+ detail: "Mobile branding is not configured; native template artwork may be shipped.",
2066
+ manifestPath,
2067
+ ready: false,
2068
+ status: "absent"
2069
+ });
2070
+ let expected;
2071
+ try {
2072
+ expected = await createAbsoluteMobileBrandingManifest(config, projectRoot);
2073
+ } catch (error) {
2074
+ return brandingInspection({
2075
+ configured: true,
2076
+ detail: error instanceof Error ? error.message : "Mobile branding sources are invalid.",
2077
+ manifestPath,
2078
+ ready: false,
2079
+ status: "invalid"
2080
+ });
2081
+ }
2082
+ try {
2083
+ const current = parseManifestState(JSON.parse(await readFile(manifestPath, "utf8")));
2084
+ if (current.fingerprint !== expected.fingerprint)
2085
+ return brandingInspection({
2086
+ configured: true,
2087
+ detail: "Mobile branding sources changed after the last native projection.",
2088
+ manifestPath,
2089
+ ready: false,
2090
+ status: "stale"
2091
+ });
2092
+ const missingPlatforms = config.platforms.filter((platform) => !current.platforms.has(platform));
2093
+ if (missingPlatforms.length > 0)
2094
+ return brandingInspection({
2095
+ configured: true,
2096
+ detail: `Mobile branding has not been projected for ${missingPlatforms.join(", ")}.`,
2097
+ manifestPath,
2098
+ ready: false,
2099
+ status: "stale"
2100
+ });
2101
+ const nativeSentinels = config.engine === "expo" ? [
2102
+ join5(config.nativeProjectDirectory, "assets", "absolute-branding", "icon.png")
2103
+ ] : nativeBrandingSentinels(config, config.platforms);
2104
+ if ((await Promise.all(nativeSentinels.map((path) => exists(path)))).some((present) => !present))
2105
+ return brandingInspection({
2106
+ configured: true,
2107
+ detail: "The generated native branding projection is missing.",
2108
+ manifestPath,
2109
+ ready: false,
2110
+ status: "stale"
2111
+ });
2112
+ return brandingInspection({
2113
+ configured: true,
2114
+ detail: `Mobile branding ${expected.fingerprint.slice(0, 12)} is current for ${config.engine}.`,
2115
+ manifestPath,
2116
+ ready: true,
2117
+ status: "ready"
2118
+ });
2119
+ } catch {
2120
+ return brandingInspection({
2121
+ configured: true,
2122
+ detail: "Mobile branding has not been projected into the native app.",
2123
+ manifestPath,
2124
+ ready: false,
2125
+ status: "stale"
2126
+ });
2127
+ }
2128
+ };
2129
+ var escapeHtml = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
2130
+ var renderAbsoluteMobileBrandingPreview = async (config, projectRoot) => {
2131
+ const manifest = await createAbsoluteMobileBrandingManifest(config, projectRoot);
2132
+ const { branding } = config;
2133
+ if (!branding)
2134
+ throw new TypeError("mobile.branding is not configured.");
2135
+ const destination = join5(projectRoot, ".absolutejs", "mobile", "branding", "preview.html");
2136
+ const icon = `data:image/png;base64,${(await readFile(branding.icon)).toString("base64")}`;
2137
+ const appName = escapeHtml(config.appName);
2138
+ const html = `<!doctype html>
2139
+ <meta charset="utf-8">
2140
+ <meta name="viewport" content="width=device-width">
2141
+ <title>${appName} mobile branding</title>
2142
+ <style>body{font:16px system-ui;margin:2rem;background:#eee;color:#111}main{display:flex;flex-wrap:wrap;gap:2rem}.card{background:white;border-radius:20px;padding:1.5rem;box-shadow:0 8px 30px #0002}.icon{width:192px;height:192px;object-fit:cover}.circle{border-radius:50%}.squircle{border-radius:24%}.splash{width:240px;height:480px;display:grid;place-items:center;background:${branding.splash.backgroundColor}}.splash img{width:${branding.splash.logoScale * 100}%}code{display:block;margin-top:1rem}</style>
2143
+ <h1>${appName}</h1><main><section class="card"><img class="icon squircle" src="${icon}" alt="Square app icon preview"><code>Apple / legacy Android</code></section><section class="card"><img class="icon circle" src="${icon}" alt="Circular Android icon preview"><code>Android circular mask</code></section><section class="card splash"><img src="${icon}" alt="Launch screen preview"></section></main><p>Fingerprint: <code>${manifest.fingerprint}</code></p>
2144
+ `;
2145
+ await mkdir(dirname2(destination), { recursive: true });
2146
+ await writeFile(destination, html);
2147
+ return destination;
2148
+ };
2149
+
1674
2150
  // src/mobile/expoProject.ts
1675
2151
  var EXPO_GENERATED_HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts, then run absolute mobile sync.
1676
2152
  `;
1677
2153
  var EXPO_ASSET_EXTENSION = ".absasset";
1678
2154
  var EXPO_PROJECT_MARKER = ".absolutejs-expo-project";
1679
- var exists = async (path) => {
2155
+ var exists2 = async (path) => {
1680
2156
  try {
1681
- await access(path);
2157
+ await access2(path);
1682
2158
  return true;
1683
2159
  } catch {
1684
2160
  return false;
@@ -1734,8 +2210,8 @@ var expoNativeDataManifest = (value) => {
1734
2210
  runtime: requireManifestString(value.runtime, "runtime")
1735
2211
  };
1736
2212
  };
1737
- var portableRelative = (from, destination) => {
1738
- const value = relative2(from, destination).replaceAll("\\", "/");
2213
+ var portableRelative2 = (from, destination) => {
2214
+ const value = relative3(from, destination).replaceAll("\\", "/");
1739
2215
  return value.startsWith(".") ? value : `./${value}`;
1740
2216
  };
1741
2217
  var routeSegments = (route) => {
@@ -1751,12 +2227,12 @@ var routeSegments = (route) => {
1751
2227
  return segment;
1752
2228
  });
1753
2229
  };
1754
- var routeFile = (project, route) => join5(project, "app", ...routeSegments(route), "index.tsx");
2230
+ var routeFile = (project, route) => join6(project, "app", ...routeSegments(route), "index.tsx");
1755
2231
  var packageDependencies = (plan) => Object.fromEntries(plan.requiredPackages.map((spec) => {
1756
2232
  const separator = spec.lastIndexOf("@");
1757
2233
  return [spec.slice(0, separator), spec.slice(separator + 1)];
1758
2234
  }));
1759
- var expoPackage = (auth, sync, devices, updates) => ({
2235
+ var expoPackage = (auth, branding, sync, devices, updates) => ({
1760
2236
  dependencies: {
1761
2237
  "@absolutejs/devices": "0.7.0",
1762
2238
  "@absolutejs/devices-expo": "0.0.11",
@@ -1784,6 +2260,7 @@ var expoPackage = (auth, sync, devices, updates) => ({
1784
2260
  } : {},
1785
2261
  ...updates || sync ? { "expo-updates": "~57.0.19" } : {},
1786
2262
  "expo-router": "~57.0.17",
2263
+ ...branding ? { "expo-splash-screen": "~57.0.9" } : {},
1787
2264
  ...auth || updates ? {
1788
2265
  "expo-secure-store": "~57.0.2",
1789
2266
  ...auth ? { "expo-web-browser": "~57.0.2" } : {}
@@ -1810,6 +2287,7 @@ var expoPackage = (auth, sync, devices, updates) => ({
1810
2287
  version: "0.0.0"
1811
2288
  });
1812
2289
  var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
2290
+ const branding = absoluteExpoBrandingConfig(config);
1813
2291
  const requirements = absoluteDeviceNativeRequirements(devices);
1814
2292
  const usageKey = (purpose) => {
1815
2293
  if (purpose === "camera")
@@ -1860,6 +2338,7 @@ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
1860
2338
  return {
1861
2339
  expo: {
1862
2340
  android: {
2341
+ ...branding?.android ?? {},
1863
2342
  ...devicePlugins.includes("expo-image-picker") ? {
1864
2343
  blockedPermissions: [
1865
2344
  "android.permission.READ_EXTERNAL_STORAGE",
@@ -1878,6 +2357,7 @@ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
1878
2357
  },
1879
2358
  experiments: { typedRoutes: true },
1880
2359
  ios: {
2360
+ ...branding?.ios ?? {},
1881
2361
  associatedDomains: config.deepLinkHosts.map((host2) => `applinks:${host2}`),
1882
2362
  bundleIdentifier: config.appId,
1883
2363
  infoPlist: descriptions,
@@ -1892,9 +2372,11 @@ var expoAppConfig = (config, auth, sync, devices, runtimeFingerprint) => {
1892
2372
  ...config.iosVersion ? { buildNumber: config.iosVersion } : {}
1893
2373
  },
1894
2374
  name: config.appName,
2375
+ ...branding ? { icon: branding.icon } : {},
1895
2376
  plugins: [
1896
2377
  "expo-router",
1897
2378
  ["expo-dev-client", { launchMode: "most-recent" }],
2379
+ ...branding ? [branding.splashPlugin] : [],
1898
2380
  "./plugins/withAbsoluteActivityResultRecovery",
1899
2381
  ...auth || config.updates ? ["expo-secure-store"] : [],
1900
2382
  ...sync ? [
@@ -2305,7 +2787,7 @@ ${push ? `absoluteExpoAuth.onPrincipalChange(principal => {
2305
2787
  `;
2306
2788
  };
2307
2789
  var authRuntimeSource = (auth, appId) => {
2308
- const storageIdentity = createHash2("sha256").update(appId).digest("hex").slice(0, 24);
2790
+ const storageIdentity = createHash3("sha256").update(appId).digest("hex").slice(0, 24);
2309
2791
  return `${EXPO_GENERATED_HEADER}import { createAbsoluteExpoAuthClient } from '@absolutejs/auth-expo';
2310
2792
  import { createMobileAuthTransport, installAuthClientRuntimeTransport } from '@absolutejs/auth/client/mobile';
2311
2793
 
@@ -2337,7 +2819,7 @@ export const startAbsoluteExpoAuth = () => {
2337
2819
  `;
2338
2820
  };
2339
2821
  var syncRuntimeSource = (config, schema) => {
2340
- const storageIdentity = createHash2("sha256").update(config.appId).digest("hex").slice(0, 24);
2822
+ const storageIdentity = createHash3("sha256").update(config.appId).digest("hex").slice(0, 24);
2341
2823
  const backgroundEndpoint = new URL("/__absolute/sync/background", config.productionOrigin).href;
2342
2824
  const backgroundTask = `${config.appId}.absolutejs.sync`;
2343
2825
  return `${EXPO_GENERATED_HEADER}import { runHeadlessSync } from '@absolutejs/sync/client/headless';
@@ -2996,8 +3478,8 @@ const styles = StyleSheet.create({
2996
3478
  error: { color: '#b91c1c', fontSize: 16, textAlign: 'center' }
2997
3479
  });
2998
3480
  `;
2999
- var nativeWrapperSource = (wrapper, module, runtime2, route) => `${EXPO_GENERATED_HEADER}import ApplicationNativeRoute from ${JSON.stringify(portableRelative(dirname2(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
3000
- import { createAbsoluteNativeRoute, createAbsoluteNativeRouteErrorBoundary } from ${JSON.stringify(portableRelative(dirname2(wrapper), runtime2).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
3481
+ var nativeWrapperSource = (wrapper, module, runtime2, route) => `${EXPO_GENERATED_HEADER}import ApplicationNativeRoute from ${JSON.stringify(portableRelative2(dirname3(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
3482
+ import { createAbsoluteNativeRoute, createAbsoluteNativeRouteErrorBoundary } from ${JSON.stringify(portableRelative2(dirname3(wrapper), runtime2).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
3001
3483
 
3002
3484
  export const ErrorBoundary = createAbsoluteNativeRouteErrorBoundary(${JSON.stringify(route)});
3003
3485
  export default createAbsoluteNativeRoute(ApplicationNativeRoute, ${JSON.stringify(route)});
@@ -3029,7 +3511,7 @@ var expoTsConfig = (projectRoot, project, auth, sync) => ({
3029
3511
  } : {},
3030
3512
  "*": [
3031
3513
  "./node_modules/*",
3032
- `${portableRelative(project, projectRoot)}/node_modules/*`
3514
+ `${portableRelative2(project, projectRoot)}/node_modules/*`
3033
3515
  ],
3034
3516
  react: ["./node_modules/@types/react/index.d.ts"],
3035
3517
  "react/*": ["./node_modules/@types/react/*"]
@@ -3039,9 +3521,9 @@ var expoTsConfig = (projectRoot, project, auth, sync) => ({
3039
3521
  extends: "expo/tsconfig.base"
3040
3522
  });
3041
3523
  var writeManagedFile = async (path, source, force) => {
3042
- await mkdir(dirname2(path), { recursive: true });
3043
- if (await exists(path)) {
3044
- const current = await readFile(path, "utf8");
3524
+ await mkdir2(dirname3(path), { recursive: true });
3525
+ if (await exists2(path)) {
3526
+ const current = await readFile2(path, "utf8");
3045
3527
  if (current === source)
3046
3528
  return false;
3047
3529
  if (!force && !current.startsWith(EXPO_GENERATED_HEADER)) {
@@ -3049,32 +3531,32 @@ var writeManagedFile = async (path, source, force) => {
3049
3531
  }
3050
3532
  }
3051
3533
  const temporary = `${path}.${crypto.randomUUID()}.tmp`;
3052
- await writeFile(temporary, source, { flag: "wx" });
3053
- await rename(temporary, path);
3534
+ await writeFile2(temporary, source, { flag: "wx" });
3535
+ await rename2(temporary, path);
3054
3536
  return true;
3055
3537
  };
3056
3538
  var pruneManagedFiles = async (paths) => {
3057
3539
  const removed = await Promise.all(paths.map(async (path) => {
3058
- if (!await exists(path))
3540
+ if (!await exists2(path))
3059
3541
  return false;
3060
- const source = await readFile(path, "utf8");
3542
+ const source = await readFile2(path, "utf8");
3061
3543
  if (!source.startsWith(EXPO_GENERATED_HEADER) && !path.endsWith("expo-module.config.json"))
3062
3544
  return false;
3063
- await rm(path, { force: true });
3545
+ await rm2(path, { force: true });
3064
3546
  return true;
3065
3547
  }));
3066
3548
  return removed.filter(Boolean).length;
3067
3549
  };
3068
3550
  var pruneStaleManagedExpoRoutes = async (project, expected) => {
3069
- const appDirectory = join5(project, "app");
3070
- if (!await exists(appDirectory))
3551
+ const appDirectory = join6(project, "app");
3552
+ if (!await exists2(appDirectory))
3071
3553
  return 0;
3072
3554
  const files = await walkFiles(appDirectory);
3073
3555
  const stale = (await Promise.all(files.map(async (path) => ({
3074
- managed: path.endsWith(".tsx") && (await readFile(path, "utf8")).startsWith(EXPO_GENERATED_HEADER),
3556
+ managed: path.endsWith(".tsx") && (await readFile2(path, "utf8")).startsWith(EXPO_GENERATED_HEADER),
3075
3557
  path
3076
3558
  })))).filter(({ managed, path }) => managed && !expected.has(path));
3077
- await Promise.all(stale.map(({ path }) => rm(path, { force: true })));
3559
+ await Promise.all(stale.map(({ path }) => rm2(path, { force: true })));
3078
3560
  return stale.length;
3079
3561
  };
3080
3562
  var jsonSource = (value) => `${JSON.stringify(value, null, "\t")}
@@ -3097,7 +3579,7 @@ export const ABSOLUTE_MOBILE_MANIFEST: AbsoluteMobileManifest | undefined = unde
3097
3579
  var writeAbsoluteExpoProject = async (config, options) => {
3098
3580
  if (config.engine !== "expo")
3099
3581
  throw new TypeError("Expo project generation requires mobile.engine: expo.");
3100
- const projectRoot = resolve3(options.projectRoot);
3582
+ const projectRoot = resolve4(options.projectRoot);
3101
3583
  const project = config.nativeProjectDirectory;
3102
3584
  const auth = resolveAbsoluteMobileAuthManifest(projectRoot, config);
3103
3585
  const devices = resolveAbsoluteDeviceCapabilityPlan(projectRoot, "expo");
@@ -3106,7 +3588,7 @@ var writeAbsoluteExpoProject = async (config, options) => {
3106
3588
  const runtimeFingerprint = resolveAbsoluteMobileUpdateRuntime(config, projectRoot).fingerprint;
3107
3589
  const routeModules = Object.entries(config.expoNativeRoutes);
3108
3590
  const moduleChecks = await Promise.all(routeModules.map(async ([route, module]) => ({
3109
- exists: await exists(module),
3591
+ exists: await exists2(module),
3110
3592
  module,
3111
3593
  route
3112
3594
  })));
@@ -3114,20 +3596,20 @@ var writeAbsoluteExpoProject = async (config, options) => {
3114
3596
  if (missing) {
3115
3597
  throw new TypeError(`Expo native route ${missing.route} references missing module ${missing.module}.`);
3116
3598
  }
3117
- const marker = join5(project, EXPO_PROJECT_MARKER);
3118
- if (await exists(project) && !await exists(marker)) {
3599
+ const marker = join6(project, EXPO_PROJECT_MARKER);
3600
+ if (await exists2(project) && !await exists2(marker)) {
3119
3601
  const entries = await readdir(project);
3120
3602
  if (entries.length > 0 && !options.force) {
3121
3603
  throw new TypeError(`Expo project directory ${project} is not AbsoluteJS-managed; rerun with --force only after reviewing it.`);
3122
3604
  }
3123
3605
  }
3124
- await mkdir(project, { recursive: true });
3125
- await writeFile(marker, `format=1
3606
+ await mkdir2(project, { recursive: true });
3607
+ await writeFile2(marker, `format=1
3126
3608
  `);
3127
3609
  const authEnabled = Boolean(auth);
3128
3610
  const files = new Map([
3129
3611
  [
3130
- join5(project, ".gitignore"),
3612
+ join6(project, ".gitignore"),
3131
3613
  `.expo/
3132
3614
  android/
3133
3615
  ios/
@@ -3135,45 +3617,45 @@ node_modules/
3135
3617
  `
3136
3618
  ],
3137
3619
  [
3138
- join5(project, "app.json"),
3620
+ join6(project, "app.json"),
3139
3621
  jsonSource(expoAppConfig(config, authEnabled, syncEnabled, devices, runtimeFingerprint))
3140
3622
  ],
3141
- [join5(project, "app.config.js"), expoDynamicAppConfig],
3623
+ [join6(project, "app.config.js"), expoDynamicAppConfig],
3142
3624
  [
3143
- join5(project, "package.json"),
3144
- jsonSource(expoPackage(authEnabled, syncEnabled, devices, config.updates !== undefined))
3625
+ join6(project, "package.json"),
3626
+ jsonSource(expoPackage(authEnabled, config.branding !== undefined, syncEnabled, devices, config.updates !== undefined))
3145
3627
  ],
3146
- [join5(project, "metro.config.js"), metroConfig(projectRoot)],
3628
+ [join6(project, "metro.config.js"), metroConfig(projectRoot)],
3147
3629
  [
3148
- join5(project, "plugins", "withAbsoluteDevelopmentCa.js"),
3630
+ join6(project, "plugins", "withAbsoluteDevelopmentCa.js"),
3149
3631
  expoDevelopmentCaPlugin
3150
3632
  ],
3151
3633
  [
3152
- join5(project, "plugins", "withAbsoluteActivityResultRecovery.js"),
3634
+ join6(project, "plugins", "withAbsoluteActivityResultRecovery.js"),
3153
3635
  expoActivityResultRecoveryPlugin
3154
3636
  ],
3155
3637
  [
3156
- join5(project, "tsconfig.json"),
3638
+ join6(project, "tsconfig.json"),
3157
3639
  jsonSource(expoTsConfig(projectRoot, project, authEnabled, syncEnabled))
3158
3640
  ],
3159
3641
  [
3160
- join5(project, "app", "_layout.tsx"),
3642
+ join6(project, "app", "_layout.tsx"),
3161
3643
  layoutSource(authEnabled, syncEnabled, config.updates !== undefined, config.observability !== undefined)
3162
3644
  ],
3163
3645
  [
3164
- join5(project, "app", "__absolute", "native", "index.tsx"),
3646
+ join6(project, "app", "__absolute", "native", "index.tsx"),
3165
3647
  nativeDiagnosticSource
3166
3648
  ],
3167
3649
  [
3168
- join5(project, "src", "generated", "AbsoluteDevices.ts"),
3650
+ join6(project, "src", "generated", "AbsoluteDevices.ts"),
3169
3651
  devicesRuntimeSource(config, devices, authEnabled)
3170
3652
  ],
3171
3653
  [
3172
- join5(project, "src", "generated", "AbsoluteNativeRoute.tsx"),
3654
+ join6(project, "src", "generated", "AbsoluteNativeRoute.tsx"),
3173
3655
  nativeRouteRuntimeSource(authEnabled)
3174
3656
  ],
3175
3657
  [
3176
- join5(project, "src", "generated", "AbsoluteWebHost.tsx"),
3658
+ join6(project, "src", "generated", "AbsoluteWebHost.tsx"),
3177
3659
  webHostSource(config, auth, syncEnabled)
3178
3660
  ]
3179
3661
  ]);
@@ -3184,40 +3666,41 @@ node_modules/
3184
3666
  files.set(path, source);
3185
3667
  }
3186
3668
  if (config.updates) {
3187
- files.set(join5(project, "src", "generated", "AbsoluteUpdates.ts"), updatesRuntimeSource(config));
3669
+ files.set(join6(project, "src", "generated", "AbsoluteUpdates.ts"), updatesRuntimeSource(config));
3188
3670
  }
3189
3671
  const expoCodeSigning = config.updates?.expoCodeSigning;
3190
3672
  if (expoCodeSigning)
3191
- files.set(join5(project, "certs", "absolute-mobile-update.pem"), expoCodeSigning.certificatePem);
3673
+ files.set(join6(project, "certs", "absolute-mobile-update.pem"), expoCodeSigning.certificatePem);
3192
3674
  if (auth) {
3193
- files.set(join5(project, "src", "generated", "AbsoluteAuth.ts"), authRuntimeSource(auth, config.appId));
3675
+ files.set(join6(project, "src", "generated", "AbsoluteAuth.ts"), authRuntimeSource(auth, config.appId));
3194
3676
  }
3195
3677
  if (syncSchema) {
3196
- files.set(join5(project, "src", "generated", "AbsoluteSync.ts"), syncRuntimeSource(config, syncSchema));
3678
+ files.set(join6(project, "src", "generated", "AbsoluteSync.ts"), syncRuntimeSource(config, syncSchema));
3197
3679
  }
3198
- const webAssetsPath = join5(project, "src", "generated", "webAssets.ts");
3199
- if (!await exists(webAssetsPath)) {
3680
+ const webAssetsPath = join6(project, "src", "generated", "webAssets.ts");
3681
+ if (!await exists2(webAssetsPath)) {
3200
3682
  files.set(webAssetsPath, emptyWebAssetsSource);
3201
3683
  }
3202
3684
  if (!config.expoNativeRoutes["/"]) {
3203
- files.set(join5(project, "app", "index.tsx"), webRouteSource);
3685
+ files.set(join6(project, "app", "index.tsx"), webRouteSource);
3204
3686
  }
3205
- files.set(join5(project, "app", "[...absolute].tsx"), catchAllRouteSource);
3206
- const nativeRouteRuntime = join5(project, "src", "generated", "AbsoluteNativeRoute.tsx");
3687
+ files.set(join6(project, "app", "[...absolute].tsx"), catchAllRouteSource);
3688
+ const nativeRouteRuntime = join6(project, "src", "generated", "AbsoluteNativeRoute.tsx");
3207
3689
  for (const [route, module] of routeModules) {
3208
- const wrapper = route === "/" ? join5(project, "app", "index.tsx") : routeFile(project, route);
3690
+ const wrapper = route === "/" ? join6(project, "app", "index.tsx") : routeFile(project, route);
3209
3691
  files.set(wrapper, nativeWrapperSource(wrapper, module, nativeRouteRuntime, route));
3210
3692
  }
3211
- const removedRoutes = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${join5(project, "app")}${sep}`))));
3693
+ const removedRoutes = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${join6(project, "app")}${sep}`))));
3212
3694
  const removedObservability = config.observability ? 0 : await pruneManagedFiles(absoluteExpoNativeObservabilityPaths(project));
3213
3695
  const changes = await Promise.all([...files].map(([path, source]) => writeManagedFile(path, source, true)));
3696
+ await writeAbsoluteExpoBrandingInputs(config, projectRoot);
3214
3697
  const changed = removedRoutes + removedObservability + changes.filter(Boolean).length;
3215
3698
  return { changed, path: project, written: [...files.keys()] };
3216
3699
  };
3217
3700
  var walkFiles = async (root, directory = root) => {
3218
3701
  const entries = await readdir(directory, { withFileTypes: true });
3219
3702
  const nested = await Promise.all(entries.map((entry) => {
3220
- const path = join5(directory, entry.name);
3703
+ const path = join6(directory, entry.name);
3221
3704
  if (entry.isDirectory())
3222
3705
  return walkFiles(root, path);
3223
3706
  if (entry.isFile())
@@ -3228,7 +3711,7 @@ var walkFiles = async (root, directory = root) => {
3228
3711
  };
3229
3712
  var renameIfPresent = async (source, destination) => {
3230
3713
  try {
3231
- await rename(source, destination);
3714
+ await rename2(source, destination);
3232
3715
  return true;
3233
3716
  } catch (error) {
3234
3717
  if (typeof error === "object" && error !== null && Reflect.get(error, "code") === "ENOENT") {
@@ -3241,12 +3724,12 @@ var installStagedDirectory = async (staging, destination) => {
3241
3724
  const backup = `${destination}.previous-${crypto.randomUUID()}`;
3242
3725
  const moved = await renameIfPresent(destination, backup);
3243
3726
  try {
3244
- await rename(staging, destination);
3727
+ await rename2(staging, destination);
3245
3728
  if (moved)
3246
- await rm(backup, { force: true, recursive: true });
3729
+ await rm2(backup, { force: true, recursive: true });
3247
3730
  } catch (error) {
3248
3731
  if (moved)
3249
- await rename(backup, destination);
3732
+ await rename2(backup, destination);
3250
3733
  throw error;
3251
3734
  }
3252
3735
  };
@@ -3321,27 +3804,27 @@ export const materializeAbsoluteWebBundle = async () => {
3321
3804
  var syncAbsoluteExpoWebAssets = async (config) => {
3322
3805
  if (config.engine !== "expo")
3323
3806
  throw new TypeError("Expo asset sync requires mobile.engine: expo.");
3324
- const marker = join5(config.nativeProjectDirectory, EXPO_PROJECT_MARKER);
3325
- if (!await exists(marker)) {
3807
+ const marker = join6(config.nativeProjectDirectory, EXPO_PROJECT_MARKER);
3808
+ if (!await exists2(marker)) {
3326
3809
  throw new TypeError("Expo asset sync requires an AbsoluteJS-managed Expo project. Run mobile init first.");
3327
3810
  }
3328
- const manifestPath = join5(config.bundleDirectory, "absolute-mobile-manifest.json");
3329
- const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
3811
+ const manifestPath = join6(config.bundleDirectory, "absolute-mobile-manifest.json");
3812
+ const manifest = JSON.parse(await readFile2(manifestPath, "utf8"));
3330
3813
  const nativeDataManifest = expoNativeDataManifest(manifest);
3331
3814
  const { appBuild } = nativeDataManifest;
3332
3815
  const files = await walkFiles(config.bundleDirectory);
3333
- const bundleHash = createHash2("sha256");
3334
- const filesWithContents = await Promise.all(files.map(async (file) => ({ contents: await readFile(file), file })));
3816
+ const bundleHash = createHash3("sha256");
3817
+ const filesWithContents = await Promise.all(files.map(async (file) => ({ contents: await readFile2(file), file })));
3335
3818
  filesWithContents.forEach(({ contents, file }) => {
3336
- bundleHash.update(relative2(config.bundleDirectory, file).replaceAll("\\", "/"));
3819
+ bundleHash.update(relative3(config.bundleDirectory, file).replaceAll("\\", "/"));
3337
3820
  bundleHash.update("\x00");
3338
3821
  bundleHash.update(contents);
3339
3822
  bundleHash.update("\x00");
3340
3823
  });
3341
3824
  const bundleId = `amexpo_${bundleHash.digest("hex")}`;
3342
- const destination = join5(config.nativeProjectDirectory, "assets", "absolute");
3343
- await mkdir(dirname2(destination), { recursive: true });
3344
- const staging = await mkdtemp(join5(dirname2(destination), `.${basename(destination)}.stage-`));
3825
+ const destination = join6(config.nativeProjectDirectory, "assets", "absolute");
3826
+ await mkdir2(dirname3(destination), { recursive: true });
3827
+ const staging = await mkdtemp(join6(dirname3(destination), `.${basename(destination)}.stage-`));
3345
3828
  let assets;
3346
3829
  let archiveAsset;
3347
3830
  try {
@@ -3350,22 +3833,22 @@ var syncAbsoluteExpoWebAssets = async (config) => {
3350
3833
  const entry = {
3351
3834
  length: contents.byteLength,
3352
3835
  offset,
3353
- path: relative2(config.bundleDirectory, file).replaceAll("\\", "/")
3836
+ path: relative3(config.bundleDirectory, file).replaceAll("\\", "/")
3354
3837
  };
3355
3838
  offset += contents.byteLength;
3356
3839
  return entry;
3357
3840
  });
3358
3841
  const name = `bundle${EXPO_ASSET_EXTENSION}`;
3359
- await writeFile(join5(staging, name), Buffer.concat(filesWithContents.map(({ contents }) => contents)));
3360
- archiveAsset = portableRelative(join5(config.nativeProjectDirectory, "src", "generated"), join5(destination, name));
3842
+ await writeFile2(join6(staging, name), Buffer.concat(filesWithContents.map(({ contents }) => contents)));
3843
+ archiveAsset = portableRelative2(join6(config.nativeProjectDirectory, "src", "generated"), join6(destination, name));
3361
3844
  await installStagedDirectory(staging, destination);
3362
3845
  } catch (error) {
3363
- await rm(staging, { force: true, recursive: true });
3846
+ await rm2(staging, { force: true, recursive: true });
3364
3847
  throw error;
3365
3848
  }
3366
- const generated = join5(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
3849
+ const generated = join6(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
3367
3850
  await writeManagedFile(generated, assetModuleSource(assets, archiveAsset, bundleId, nativeDataManifest), true);
3368
3851
  return { appBuild, assets: assets.length, bundleId, path: destination };
3369
3852
  };
3370
3853
 
3371
- export { discoverAbsoluteSyncSchema, absoluteDeviceNativeRequirements, assertAbsoluteDeviceCapabilityPackages, resolveAbsoluteDeviceCapabilityPlan, ABSOLUTE_MOBILE_UPDATE_FORMAT, absoluteMobileUpdateSigningPayload, canonicalizeAbsoluteMobileUpdate, parseAbsoluteMobileUpdateManifest, unsignedAbsoluteMobileUpdate, resolveAbsoluteMobileUpdateRuntime, writeAbsoluteExpoProject, syncAbsoluteExpoWebAssets };
3854
+ export { discoverAbsoluteSyncSchema, absoluteDeviceNativeRequirements, assertAbsoluteDeviceCapabilityPackages, resolveAbsoluteDeviceCapabilityPlan, ABSOLUTE_MOBILE_UPDATE_FORMAT, absoluteMobileUpdateSigningPayload, canonicalizeAbsoluteMobileUpdate, parseAbsoluteMobileUpdateManifest, unsignedAbsoluteMobileUpdate, resolveAbsoluteMobileUpdateRuntime, ABSOLUTE_CAPACITOR_ASSETS_SPEC, generateAbsoluteCapacitorBranding, inspectAbsoluteMobileBranding, renderAbsoluteMobileBrandingPreview, writeAbsoluteExpoProject, syncAbsoluteExpoWebAssets };