@absolutejs/absolute 0.20.0-beta.113 → 0.20.0-beta.115
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/CHANGELOG.md +14 -0
- package/README.md +6 -0
- package/changelog.json +44 -0
- package/dist/angular/components/core/streamingSlotRegistrar.js +1 -1
- package/dist/angular/components/core/streamingSlotRegistry.js +2 -2
- package/dist/build.js +54 -2
- package/dist/build.js.map +3 -3
- package/dist/cli/{compile-1d5arbqj.js → compile-ya4kd2sg.js} +3 -3
- package/dist/cli/{config-vq2qcc4p.js → config-rjeexvza.js} +1 -1
- package/dist/cli/{dev-f0safhsr.js → dev-5cqmf7za.js} +2 -2
- package/dist/cli/{expoProject-zkprmjjz.js → expoProject-f5j20xms.js} +1 -1
- package/dist/cli/{index-gps0zk0m.js → index-5fmgn0nx.js} +2 -2
- package/dist/cli/{index-dfqzrznk.js → index-6mbm4n3g.js} +2 -2
- package/dist/cli/{index-0zcebhm4.js → index-dr21p6ae.js} +566 -83
- package/dist/cli/{index-df2mjbdy.js → index-exdd5k1g.js} +55 -0
- package/dist/cli/index.js +6 -6
- package/dist/cli/{mobile-v11nwcj9.js → mobile-fg2226vv.js} +153 -8
- package/dist/cli/{start-8v6g2y31.js → start-jt61f40d.js} +4 -4
- package/dist/index.js +54 -2
- package/dist/index.js.map +3 -3
- package/dist/mobile/index.js +1478 -895
- package/dist/mobile/index.js.map +9 -7
- package/dist/mobile/remoteMacAgentEntry.js +243 -242
- package/dist/react/hooks/index.js +6 -2
- package/dist/react/hooks/index.js.map +3 -3
- package/dist/src/mobile/androidServiceReadiness.d.ts +14 -0
- package/dist/src/mobile/branding.d.ts +92 -0
- package/dist/src/mobile/config.d.ts +20 -0
- package/dist/src/mobile/index.d.ts +1 -0
- package/dist/src/react/hooks/useMediaQuery.d.ts +3 -1
- package/dist/src/utils/userAgentFunctions.d.ts +1 -1
- package/dist/types/build.d.ts +36 -0
- package/package.json +2 -1
|
@@ -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
|
|
25
|
-
import { basename, dirname as
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
|
|
2155
|
+
var exists2 = async (path) => {
|
|
1680
2156
|
try {
|
|
1681
|
-
await
|
|
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
|
|
1738
|
-
const value =
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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(
|
|
3000
|
-
import { createAbsoluteNativeRoute, createAbsoluteNativeRouteErrorBoundary } from ${JSON.stringify(
|
|
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
|
-
`${
|
|
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
|
|
3043
|
-
if (await
|
|
3044
|
-
const current = await
|
|
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
|
|
3053
|
-
await
|
|
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
|
|
3540
|
+
if (!await exists2(path))
|
|
3059
3541
|
return false;
|
|
3060
|
-
const source = await
|
|
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
|
|
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 =
|
|
3070
|
-
if (!await
|
|
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
|
|
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 }) =>
|
|
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 =
|
|
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
|
|
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 =
|
|
3118
|
-
if (await
|
|
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
|
|
3125
|
-
await
|
|
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
|
-
|
|
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
|
-
|
|
3620
|
+
join6(project, "app.json"),
|
|
3139
3621
|
jsonSource(expoAppConfig(config, authEnabled, syncEnabled, devices, runtimeFingerprint))
|
|
3140
3622
|
],
|
|
3141
|
-
[
|
|
3623
|
+
[join6(project, "app.config.js"), expoDynamicAppConfig],
|
|
3142
3624
|
[
|
|
3143
|
-
|
|
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
|
-
[
|
|
3628
|
+
[join6(project, "metro.config.js"), metroConfig(projectRoot)],
|
|
3147
3629
|
[
|
|
3148
|
-
|
|
3630
|
+
join6(project, "plugins", "withAbsoluteDevelopmentCa.js"),
|
|
3149
3631
|
expoDevelopmentCaPlugin
|
|
3150
3632
|
],
|
|
3151
3633
|
[
|
|
3152
|
-
|
|
3634
|
+
join6(project, "plugins", "withAbsoluteActivityResultRecovery.js"),
|
|
3153
3635
|
expoActivityResultRecoveryPlugin
|
|
3154
3636
|
],
|
|
3155
3637
|
[
|
|
3156
|
-
|
|
3638
|
+
join6(project, "tsconfig.json"),
|
|
3157
3639
|
jsonSource(expoTsConfig(projectRoot, project, authEnabled, syncEnabled))
|
|
3158
3640
|
],
|
|
3159
3641
|
[
|
|
3160
|
-
|
|
3642
|
+
join6(project, "app", "_layout.tsx"),
|
|
3161
3643
|
layoutSource(authEnabled, syncEnabled, config.updates !== undefined, config.observability !== undefined)
|
|
3162
3644
|
],
|
|
3163
3645
|
[
|
|
3164
|
-
|
|
3646
|
+
join6(project, "app", "__absolute", "native", "index.tsx"),
|
|
3165
3647
|
nativeDiagnosticSource
|
|
3166
3648
|
],
|
|
3167
3649
|
[
|
|
3168
|
-
|
|
3650
|
+
join6(project, "src", "generated", "AbsoluteDevices.ts"),
|
|
3169
3651
|
devicesRuntimeSource(config, devices, authEnabled)
|
|
3170
3652
|
],
|
|
3171
3653
|
[
|
|
3172
|
-
|
|
3654
|
+
join6(project, "src", "generated", "AbsoluteNativeRoute.tsx"),
|
|
3173
3655
|
nativeRouteRuntimeSource(authEnabled)
|
|
3174
3656
|
],
|
|
3175
3657
|
[
|
|
3176
|
-
|
|
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(
|
|
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(
|
|
3673
|
+
files.set(join6(project, "certs", "absolute-mobile-update.pem"), expoCodeSigning.certificatePem);
|
|
3192
3674
|
if (auth) {
|
|
3193
|
-
files.set(
|
|
3675
|
+
files.set(join6(project, "src", "generated", "AbsoluteAuth.ts"), authRuntimeSource(auth, config.appId));
|
|
3194
3676
|
}
|
|
3195
3677
|
if (syncSchema) {
|
|
3196
|
-
files.set(
|
|
3678
|
+
files.set(join6(project, "src", "generated", "AbsoluteSync.ts"), syncRuntimeSource(config, syncSchema));
|
|
3197
3679
|
}
|
|
3198
|
-
const webAssetsPath =
|
|
3199
|
-
if (!await
|
|
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(
|
|
3685
|
+
files.set(join6(project, "app", "index.tsx"), webRouteSource);
|
|
3204
3686
|
}
|
|
3205
|
-
files.set(
|
|
3206
|
-
const nativeRouteRuntime =
|
|
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 === "/" ?
|
|
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(`${
|
|
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 =
|
|
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
|
|
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
|
|
3727
|
+
await rename2(staging, destination);
|
|
3245
3728
|
if (moved)
|
|
3246
|
-
await
|
|
3729
|
+
await rm2(backup, { force: true, recursive: true });
|
|
3247
3730
|
} catch (error) {
|
|
3248
3731
|
if (moved)
|
|
3249
|
-
await
|
|
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 =
|
|
3325
|
-
if (!await
|
|
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 =
|
|
3329
|
-
const manifest = JSON.parse(await
|
|
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 =
|
|
3334
|
-
const filesWithContents = await Promise.all(files.map(async (file) => ({ contents: await
|
|
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(
|
|
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 =
|
|
3343
|
-
await
|
|
3344
|
-
const staging = await mkdtemp(
|
|
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:
|
|
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
|
|
3360
|
-
archiveAsset =
|
|
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
|
|
3846
|
+
await rm2(staging, { force: true, recursive: true });
|
|
3364
3847
|
throw error;
|
|
3365
3848
|
}
|
|
3366
|
-
const generated =
|
|
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 };
|