@absolutejs/absolute 0.20.0-beta.47 → 0.20.0-beta.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -1648,6 +1648,45 @@ var EXPO_GENERATED_HEADER = `// Generated by AbsoluteJS. Edit absolute.config.ts
1648
1648
  } catch {
1649
1649
  return false;
1650
1650
  }
1651
+ }, isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifestString = (value, field) => {
1652
+ if (typeof value !== "string" || !value) {
1653
+ throw new TypeError(`AbsoluteJS mobile manifest ${field} is invalid.`);
1654
+ }
1655
+ return value;
1656
+ }, expoNativeDataManifest = (value) => {
1657
+ if (!isRecord(value) || !Array.isArray(value.pages) || !Array.isArray(value.routes)) {
1658
+ throw new TypeError("AbsoluteJS mobile manifest is invalid.");
1659
+ }
1660
+ const pages = value.pages.map((page) => {
1661
+ if (!isRecord(page))
1662
+ throw new TypeError("AbsoluteJS mobile manifest page is invalid.");
1663
+ return {
1664
+ bundleHash: requireManifestString(page.bundleHash, "page.bundleHash"),
1665
+ contract: requireManifestString(page.contract, "page.contract"),
1666
+ pageId: requireManifestString(page.pageId, "page.pageId")
1667
+ };
1668
+ });
1669
+ const routes = value.routes.flatMap((route) => {
1670
+ if (!isRecord(route) || typeof route.method !== "string") {
1671
+ throw new TypeError("AbsoluteJS mobile manifest route is invalid.");
1672
+ }
1673
+ if (route.method !== "GET")
1674
+ return [];
1675
+ return [
1676
+ {
1677
+ method: "GET",
1678
+ pageId: requireManifestString(route.pageId, "route.pageId"),
1679
+ pattern: requireManifestString(route.pattern, "route.pattern")
1680
+ }
1681
+ ];
1682
+ });
1683
+ return {
1684
+ appBuild: requireManifestString(value.appBuild, "appBuild"),
1685
+ pages,
1686
+ productionOrigin: requireManifestString(value.productionOrigin, "productionOrigin"),
1687
+ routes,
1688
+ runtime: requireManifestString(value.runtime, "runtime")
1689
+ };
1651
1690
  }, portableRelative = (from, destination) => {
1652
1691
  const value = relative2(from, destination).replaceAll("\\", "/");
1653
1692
  return value.startsWith(".") ? value : `./${value}`;
@@ -2340,7 +2379,133 @@ export function AbsoluteWebHost() {
2340
2379
 
2341
2380
  const styles = StyleSheet.create({ loading: { alignItems: 'center', flex: 1, justifyContent: 'center' }, web: { flex: 1 } });
2342
2381
  `;
2343
- }, webRouteSource, catchAllRouteSource, nativeWrapperSource = (wrapper, module) => `${EXPO_GENERATED_HEADER}export { default } from ${JSON.stringify(portableRelative(dirname4(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
2382
+ }, webRouteSource, catchAllRouteSource, nativeRouteRuntimeSource = (auth) => `${EXPO_GENERATED_HEADER}import { useLocalSearchParams, usePathname } from 'expo-router';
2383
+ import { type ComponentType, useEffect, useMemo, useState } from 'react';
2384
+ import { ActivityIndicator, Platform, Pressable, StyleSheet, Text, View } from 'react-native';
2385
+ import { ABSOLUTE_MOBILE_MANIFEST } from './webAssets';
2386
+ ${auth ? "import { absoluteExpoAuth } from './AbsoluteAuth';" : ""}
2387
+
2388
+ const PAGE_MEDIA_TYPE = 'application/vnd.absolute.page+json';
2389
+ const NATIVE_DATA_MEDIA_TYPE = 'application/vnd.absolute.native-route+json';
2390
+ const PROTOCOL = 1;
2391
+ const MAX_DATA_BYTES = 1024 * 1024;
2392
+ const DEV_ORIGIN = Platform.OS === 'android'
2393
+ ? process.env.EXPO_PUBLIC_ABSOLUTE_DEV_ANDROID_ORIGIN
2394
+ : process.env.EXPO_PUBLIC_ABSOLUTE_DEV_IOS_ORIGIN;
2395
+
2396
+ type RouteParams = Record<string, string | string[] | undefined>;
2397
+ type NativeRouteProps<PageProps extends object, Params extends object> = {
2398
+ pageProps: Readonly<PageProps>;
2399
+ params: Readonly<Params>;
2400
+ reload: () => void;
2401
+ };
2402
+ type NativeRouteState<PageProps> =
2403
+ | { kind: 'loading' }
2404
+ | { kind: 'ready'; pageProps: PageProps }
2405
+ | { kind: 'error' };
2406
+
2407
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
2408
+ typeof value === 'object' && value !== null && !Array.isArray(value);
2409
+ const routeSegmentPattern = (segment: string) => {
2410
+ if (segment === '*') return '.*';
2411
+ if (segment.startsWith(':') && segment.endsWith('?')) return '[^/]*';
2412
+ if (segment.startsWith(':')) return '[^/]+';
2413
+ return segment.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&');
2414
+ };
2415
+ const matchesRoute = (pattern: string, pathname: string) =>
2416
+ new RegExp('^' + pattern.split('/').map(routeSegmentPattern).join('/') + '/?$').test(pathname);
2417
+ const requestPathFor = (pathname: string, params: RouteParams, pattern: string) => {
2418
+ const pathNames = new Set(pattern.split('/').filter(segment => segment.startsWith(':')).map(segment => segment.replace(/^:/, '').replace(/\\?$/, '')));
2419
+ if (pattern.endsWith('/*')) pathNames.add('absoluteWildcard');
2420
+ const query = new URLSearchParams();
2421
+ for (const [name, value] of Object.entries(params).sort(([left], [right]) => left.localeCompare(right))) {
2422
+ if (pathNames.has(name) || name === '#' || value === undefined) continue;
2423
+ for (const item of Array.isArray(value) ? value : [value]) query.append(name, item);
2424
+ }
2425
+ const search = query.toString();
2426
+ return pathname + (search ? '?' + search : '');
2427
+ };
2428
+ const productionPage = (pathname: string) => {
2429
+ if (!ABSOLUTE_MOBILE_MANIFEST) throw new Error('AbsoluteJS native route data is not prepared for production.');
2430
+ const route = ABSOLUTE_MOBILE_MANIFEST.routes.find(candidate => candidate.method === 'GET' && matchesRoute(candidate.pattern, pathname));
2431
+ if (!route) throw new Error('No trusted AbsoluteJS page route owns this native URL.');
2432
+ const page = ABSOLUTE_MOBILE_MANIFEST.pages.find(candidate => candidate.pageId === route.pageId);
2433
+ if (!page) throw new Error('The embedded AbsoluteJS page contract is incomplete.');
2434
+ return page;
2435
+ };
2436
+ const createDataRequest = (path: string) => {
2437
+ const development = typeof DEV_ORIGIN === 'string' && DEV_ORIGIN.length > 0;
2438
+ const origin = development ? DEV_ORIGIN : ABSOLUTE_MOBILE_MANIFEST?.productionOrigin;
2439
+ if (!origin) throw new Error('AbsoluteJS native route data has no trusted server origin.');
2440
+ const url = new URL(path, origin);
2441
+ if (url.origin !== new URL(origin).origin) throw new Error('AbsoluteJS native route data left its trusted origin.');
2442
+ const headers = new Headers();
2443
+ headers.set('accept', development ? NATIVE_DATA_MEDIA_TYPE : PAGE_MEDIA_TYPE);
2444
+ headers.set('x-absolute-mobile-protocol', String(PROTOCOL));
2445
+ let page: ReturnType<typeof productionPage> | undefined;
2446
+ if (!development) {
2447
+ page = productionPage(url.pathname);
2448
+ headers.set('x-absolute-mobile-app-build', ABSOLUTE_MOBILE_MANIFEST!.appBuild);
2449
+ headers.set('x-absolute-mobile-page-bundle', page.bundleHash);
2450
+ headers.set('x-absolute-mobile-page-contracts', page.contract);
2451
+ headers.set('x-absolute-mobile-page-id', page.pageId);
2452
+ headers.set('x-absolute-mobile-runtime', ABSOLUTE_MOBILE_MANIFEST!.runtime);
2453
+ }
2454
+ return { page, request: new Request(url, { headers, method: 'GET' }) };
2455
+ };
2456
+ const requestData = async <PageProps extends object>(path: string, signal: AbortSignal) => {
2457
+ const { page, request } = createDataRequest(path);
2458
+ const response = await ${auth ? "absoluteExpoAuth.fetchOptional" : "fetch"}(request, { redirect: 'manual', signal });
2459
+ if (new URL(response.url || request.url).origin !== new URL(request.url).origin || response.status >= 300 && response.status < 400) throw new Error('AbsoluteJS native route data redirected outside its contract.');
2460
+ const source = await response.text();
2461
+ if (new TextEncoder().encode(source).byteLength > MAX_DATA_BYTES) throw new Error('AbsoluteJS native route data exceeded 1 MiB.');
2462
+ let envelope: unknown;
2463
+ try { envelope = JSON.parse(source); } catch { throw new Error('The server did not return an AbsoluteJS page envelope.'); }
2464
+ if (!isRecord(envelope) || envelope.protocol !== PROTOCOL || !isRecord(envelope.response)) throw new Error('The server returned an invalid AbsoluteJS page envelope.');
2465
+ const result = envelope.response;
2466
+ if (result.kind === 'upgrade-required') throw new Error('This app version must be updated before opening this screen.');
2467
+ if (result.kind !== 'page' || !isRecord(result.props) || typeof result.pageId !== 'string' || typeof result.contract !== 'string') throw new Error('The server did not produce native route page props.');
2468
+ if (page && (result.pageId !== page.pageId || result.contract !== page.contract)) throw new Error('The server returned a different page contract than this app contains.');
2469
+ return result.props as PageProps;
2470
+ };
2471
+
2472
+ export const createAbsoluteNativeRoute = <
2473
+ PageProps extends object,
2474
+ Params extends object = RouteParams
2475
+ >(Component: ComponentType<NativeRouteProps<PageProps, Params>>, pattern: string) => {
2476
+ function AbsoluteNativeRouteScreen() {
2477
+ const pathname = usePathname() || '/';
2478
+ const params = useLocalSearchParams() as RouteParams;
2479
+ const parameterKey = JSON.stringify(params);
2480
+ const requestPath = useMemo(() => requestPathFor(pathname, params, pattern), [parameterKey, pathname]);
2481
+ const [revision, setRevision] = useState(0);
2482
+ const [state, setState] = useState<NativeRouteState<PageProps>>({ kind: 'loading' });
2483
+ useEffect(() => {
2484
+ const controller = new AbortController();
2485
+ setState({ kind: 'loading' });
2486
+ void requestData<PageProps>(requestPath, controller.signal).then(
2487
+ pageProps => { if (!controller.signal.aborted) setState({ kind: 'ready', pageProps }); },
2488
+ () => { if (!controller.signal.aborted) setState({ kind: 'error' }); }
2489
+ );
2490
+ return () => controller.abort();
2491
+ }, [requestPath, revision]);
2492
+ const reload = () => setRevision(value => value + 1);
2493
+ if (state.kind === 'loading') return <View style={styles.center}><ActivityIndicator accessibilityLabel="Loading screen" /></View>;
2494
+ if (state.kind === 'error') return <View style={styles.center}><Text accessibilityRole="alert" style={styles.error}>This screen could not load.</Text><Pressable accessibilityRole="button" onPress={reload} style={styles.button}><Text>Try again</Text></Pressable></View>;
2495
+ return <Component pageProps={state.pageProps} params={params as Params} reload={reload} />;
2496
+ }
2497
+ return AbsoluteNativeRouteScreen;
2498
+ };
2499
+
2500
+ const styles = StyleSheet.create({
2501
+ button: { backgroundColor: '#e2e8f0', borderRadius: 10, paddingHorizontal: 16, paddingVertical: 12 },
2502
+ center: { alignItems: 'center', flex: 1, gap: 16, justifyContent: 'center', padding: 24 },
2503
+ error: { color: '#b91c1c', fontSize: 16, textAlign: 'center' }
2504
+ });
2505
+ `, nativeWrapperSource = (wrapper, module, runtime, route) => `${EXPO_GENERATED_HEADER}import ApplicationNativeRoute from ${JSON.stringify(portableRelative(dirname4(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
2506
+ import { createAbsoluteNativeRoute } from ${JSON.stringify(portableRelative(dirname4(wrapper), runtime).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
2507
+
2508
+ export default createAbsoluteNativeRoute(ApplicationNativeRoute, ${JSON.stringify(route)});
2344
2509
  `, expoTsConfig = (projectRoot, project, auth, sync) => ({
2345
2510
  compilerOptions: {
2346
2511
  paths: {
@@ -2402,6 +2567,13 @@ const styles = StyleSheet.create({ loading: { alignItems: 'center', flex: 1, jus
2402
2567
  await Promise.all(stale.map(({ path }) => rm(path, { force: true })));
2403
2568
  return stale.length;
2404
2569
  }, jsonSource = (value) => `${JSON.stringify(value, null, "\t")}
2570
+ `, nativeDataManifestTypeSource = `type AbsoluteMobileManifest = {
2571
+ appBuild: string;
2572
+ pages: readonly { bundleHash: string; contract: string; pageId: string }[];
2573
+ productionOrigin: string;
2574
+ routes: readonly { method: 'GET'; pageId: string; pattern: string }[];
2575
+ runtime: string;
2576
+ };
2405
2577
  `, emptyWebAssetsSource, writeAbsoluteExpoProject = async (config, options) => {
2406
2578
  if (config.engine !== "expo")
2407
2579
  throw new TypeError("Expo project generation requires mobile.engine: expo.");
@@ -2471,6 +2643,10 @@ node_modules/
2471
2643
  join8(project, "src", "generated", "AbsoluteDevices.ts"),
2472
2644
  devicesRuntimeSource(config, devices, authEnabled)
2473
2645
  ],
2646
+ [
2647
+ join8(project, "src", "generated", "AbsoluteNativeRoute.tsx"),
2648
+ nativeRouteRuntimeSource(authEnabled)
2649
+ ],
2474
2650
  [
2475
2651
  join8(project, "src", "generated", "AbsoluteWebHost.tsx"),
2476
2652
  webHostSource(config, auth, syncEnabled)
@@ -2490,9 +2666,10 @@ node_modules/
2490
2666
  files.set(join8(project, "app", "index.tsx"), webRouteSource);
2491
2667
  }
2492
2668
  files.set(join8(project, "app", "[...absolute].tsx"), catchAllRouteSource);
2669
+ const nativeRouteRuntime = join8(project, "src", "generated", "AbsoluteNativeRoute.tsx");
2493
2670
  for (const [route, module] of routeModules) {
2494
2671
  const wrapper = route === "/" ? join8(project, "app", "index.tsx") : routeFile(project, route);
2495
- files.set(wrapper, nativeWrapperSource(wrapper, module));
2672
+ files.set(wrapper, nativeWrapperSource(wrapper, module, nativeRouteRuntime, route));
2496
2673
  }
2497
2674
  const removed = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${join8(project, "app")}${sep}`))));
2498
2675
  const changes = await Promise.all([...files].map(([path, source]) => writeManagedFile(path, source, true)));
@@ -2531,11 +2708,13 @@ node_modules/
2531
2708
  await rename(backup, destination);
2532
2709
  throw error;
2533
2710
  }
2534
- }, assetModuleSource = (assets, bundleId) => `${EXPO_GENERATED_HEADER}import { Asset } from 'expo-asset';
2711
+ }, assetModuleSource = (assets, bundleId, manifest) => `${EXPO_GENERATED_HEADER}import { Asset } from 'expo-asset';
2535
2712
  import { Directory, File, Paths } from 'expo-file-system';
2536
2713
 
2714
+ ${nativeDataManifestTypeSource}
2537
2715
  declare const require: (path: string) => number;
2538
2716
  const BUNDLE_ID = ${JSON.stringify(bundleId)};
2717
+ export const ABSOLUTE_MOBILE_MANIFEST: AbsoluteMobileManifest = ${JSON.stringify(manifest)};
2539
2718
  const ASSETS = [
2540
2719
  ${assets.map(({ asset, path }) => ` { module: require(${JSON.stringify(asset)}), path: ${JSON.stringify(path)} }`).join(`,
2541
2720
  `)}
@@ -2568,9 +2747,8 @@ export const materializeAbsoluteWebBundle = async () => {
2568
2747
  }
2569
2748
  const manifestPath = join8(config.bundleDirectory, "absolute-mobile-manifest.json");
2570
2749
  const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
2571
- const appBuild = typeof manifest === "object" && manifest !== null && typeof Reflect.get(manifest, "appBuild") === "string" ? String(Reflect.get(manifest, "appBuild")) : undefined;
2572
- if (!appBuild)
2573
- throw new TypeError("AbsoluteJS mobile manifest has no appBuild.");
2750
+ const nativeDataManifest = expoNativeDataManifest(manifest);
2751
+ const { appBuild } = nativeDataManifest;
2574
2752
  const files = await walkFiles(config.bundleDirectory);
2575
2753
  const bundleHash = createHash("sha256");
2576
2754
  const filesWithContents = await Promise.all(files.map(async (file) => ({ contents: await readFile(file), file })));
@@ -2600,7 +2778,7 @@ export const materializeAbsoluteWebBundle = async () => {
2600
2778
  throw error;
2601
2779
  }
2602
2780
  const generated = join8(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
2603
- await writeManagedFile(generated, assetModuleSource(assets, bundleId), true);
2781
+ await writeManagedFile(generated, assetModuleSource(assets, bundleId, nativeDataManifest), true);
2604
2782
  return { appBuild, assets: assets.length, bundleId, path: destination };
2605
2783
  };
2606
2784
  var init_expoProject = __esm(() => {
@@ -2697,9 +2875,11 @@ export default AbsoluteWebHost;
2697
2875
 
2698
2876
  export default AbsoluteWebHost;
2699
2877
  `;
2700
- emptyWebAssetsSource = `${EXPO_GENERATED_HEADER}export const materializeAbsoluteWebBundle = async () => {
2878
+ emptyWebAssetsSource = `${EXPO_GENERATED_HEADER}${nativeDataManifestTypeSource}
2879
+ export const materializeAbsoluteWebBundle = async () => {
2701
2880
  throw new Error('The embedded AbsoluteJS bundle is unavailable. Run absolute prepare before a production Expo build.');
2702
2881
  };
2882
+ export const ABSOLUTE_MOBILE_MANIFEST: AbsoluteMobileManifest | undefined = undefined;
2703
2883
  `;
2704
2884
  });
2705
2885
 
@@ -3395,11 +3575,11 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
3395
3575
  const resolvedPath = resolve8(path);
3396
3576
  const relativePath = relative4(resolvedRoot, resolvedPath);
3397
3577
  return relativePath === "" || relativePath !== ".." && !relativePath.startsWith(`..${sep2}`) && !isAbsolute(relativePath);
3398
- }, isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value), nativeCachePath = (projectRoot) => join11(projectRoot, ".absolutejs", "mobile", "cache", "android-debug.json"), parseNativeCache = (value) => {
3399
- if (!isRecord(value))
3578
+ }, isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), nativeCachePath = (projectRoot) => join11(projectRoot, ".absolutejs", "mobile", "cache", "android-debug.json"), parseNativeCache = (value) => {
3579
+ if (!isRecord2(value))
3400
3580
  return null;
3401
3581
  const { appId, fingerprint, format, installations } = value;
3402
- if (format !== NATIVE_CACHE_FORMAT || typeof appId !== "string" || typeof fingerprint !== "string" || !isRecord(installations) || !Object.values(installations).every((identity) => typeof identity === "string")) {
3582
+ if (format !== NATIVE_CACHE_FORMAT || typeof appId !== "string" || typeof fingerprint !== "string" || !isRecord2(installations) || !Object.values(installations).every((identity) => typeof identity === "string")) {
3403
3583
  return null;
3404
3584
  }
3405
3585
  return {
@@ -3504,7 +3684,7 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
3504
3684
  if ((manifestBackupPath !== undefined || nativeManifestPath !== undefined) && (typeof manifestBackupPath !== "string" || typeof nativeManifestPath !== "string")) {
3505
3685
  return null;
3506
3686
  }
3507
- if (projectedFiles !== undefined && (!Array.isArray(projectedFiles) || !projectedFiles.every((file) => isRecord(file) && typeof file.path === "string" && (file.backupPath === undefined || typeof file.backupPath === "string")))) {
3687
+ if (projectedFiles !== undefined && (!Array.isArray(projectedFiles) || !projectedFiles.every((file) => isRecord2(file) && typeof file.path === "string" && (file.backupPath === undefined || typeof file.backupPath === "string")))) {
3508
3688
  return null;
3509
3689
  }
3510
3690
  return {
@@ -3590,7 +3770,7 @@ var ANDROID_BOOT_TIMEOUT_MS = 180000, ANDROID_BOOT_POLL_MS = 1000, DEV_JOURNAL_F
3590
3770
  const source = await readFile4(nativeConfigPath, "utf8");
3591
3771
  const manifestSource = await readFile4(nativeManifestPath, "utf8");
3592
3772
  const parsed = JSON.parse(source);
3593
- if (!isRecord(parsed)) {
3773
+ if (!isRecord2(parsed)) {
3594
3774
  throw new Error(`Invalid Capacitor native config at ${nativeConfigPath}.`);
3595
3775
  }
3596
3776
  await mkdir2(paths.root, { recursive: true });
@@ -4520,8 +4700,8 @@ var developmentTeamArgument = (value) => {
4520
4700
  if (!/^[A-Z0-9]{10}$/u.test(team))
4521
4701
  throw new TypeError("iOS development team must contain ten letters or digits.");
4522
4702
  return `DEVELOPMENT_TEAM=${team}`;
4523
- }, isRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest = (value) => {
4524
- if (!isRecord2(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
4703
+ }, isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest = (value) => {
4704
+ if (!isRecord3(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
4525
4705
  throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
4526
4706
  }
4527
4707
  return {
@@ -4630,7 +4810,7 @@ var developmentTeamArgument = (value) => {
4630
4810
  const destination = join13(releaseRoot, "App.ipa");
4631
4811
  if (await pathExists3(releaseRoot)) {
4632
4812
  const value = JSON.parse(await readFile6(join13(releaseRoot, "release.json"), "utf8"));
4633
- if (!isRecord2(value) || value.artifact !== "App.ipa" || Object.entries(metadata).some(([key, expected]) => Reflect.get(value, key) !== expected)) {
4813
+ if (!isRecord3(value) || value.artifact !== "App.ipa" || Object.entries(metadata).some(([key, expected]) => Reflect.get(value, key) !== expected)) {
4634
4814
  throw new TypeError(`Immutable iOS release ${metadata.releaseId} does not match its content.`);
4635
4815
  }
4636
4816
  const [bytes, sha256] = await Promise.all([
@@ -4791,7 +4971,7 @@ import {
4791
4971
  } from "fs/promises";
4792
4972
  import { isIP as isIP2 } from "net";
4793
4973
  import { dirname as dirname7, isAbsolute as isAbsolute3, join as join14, relative as relative6, resolve as resolve10, sep as sep4 } from "path";
4794
- var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000, BOOT_POLL_MS = 1000, DEV_JOURNAL_FORMAT2 = 1, NATIVE_CACHE_FORMAT2 = 1, isRecord3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), pathExists4 = async (path) => {
4974
+ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000, BOOT_POLL_MS = 1000, DEV_JOURNAL_FORMAT2 = 1, NATIVE_CACHE_FORMAT2 = 1, isRecord4 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), pathExists4 = async (path) => {
4795
4975
  try {
4796
4976
  await access7(path);
4797
4977
  return true;
@@ -4925,7 +5105,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
4925
5105
  }, parseJson = (source, label) => {
4926
5106
  try {
4927
5107
  const parsed = JSON.parse(source);
4928
- if (isRecord3(parsed))
5108
+ if (isRecord4(parsed))
4929
5109
  return parsed;
4930
5110
  } catch {}
4931
5111
  throw new Error(`Invalid ${label} JSON from simctl.`);
@@ -4935,7 +5115,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
4935
5115
  if (!Array.isArray(types))
4936
5116
  return [];
4937
5117
  return types.flatMap((type) => {
4938
- if (!isRecord3(type))
5118
+ if (!isRecord4(type))
4939
5119
  return [];
4940
5120
  const { identifier } = type;
4941
5121
  const { name } = type;
@@ -4947,7 +5127,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
4947
5127
  if (!Array.isArray(runtimes))
4948
5128
  return [];
4949
5129
  return runtimes.flatMap((runtime) => {
4950
- if (!isRecord3(runtime))
5130
+ if (!isRecord4(runtime))
4951
5131
  return [];
4952
5132
  const { identifier } = runtime;
4953
5133
  const { name } = runtime;
@@ -4966,13 +5146,13 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
4966
5146
  }, parseIosSimulators = (source) => {
4967
5147
  const parsed = parseJson(source, "device");
4968
5148
  const { devices } = parsed;
4969
- if (!isRecord3(devices))
5149
+ if (!isRecord4(devices))
4970
5150
  return [];
4971
5151
  return Object.entries(devices).flatMap(([runtime, values]) => {
4972
5152
  if (!Array.isArray(values))
4973
5153
  return [];
4974
5154
  return values.flatMap((device) => {
4975
- if (!isRecord3(device))
5155
+ if (!isRecord4(device))
4976
5156
  return [];
4977
5157
  const { name } = device;
4978
5158
  const { state } = device;
@@ -5019,7 +5199,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
5019
5199
  const value = relative6(resolve10(root), resolve10(path));
5020
5200
  return value === "" || !value.startsWith(`..${sep4}`) && value !== ".." && !isAbsolute3(value);
5021
5201
  }, parseJournal2 = (value) => {
5022
- if (!isRecord3(value) || value.format !== DEV_JOURNAL_FORMAT2)
5202
+ if (!isRecord4(value) || value.format !== DEV_JOURNAL_FORMAT2)
5023
5203
  return null;
5024
5204
  const { configBackupPath } = value;
5025
5205
  const { infoBackupPath } = value;
@@ -5079,7 +5259,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
5079
5259
  readFile7(infoPath, "utf8")
5080
5260
  ]);
5081
5261
  const parsed = JSON.parse(configSource);
5082
- if (!isRecord3(parsed))
5262
+ if (!isRecord4(parsed))
5083
5263
  throw new Error(`Invalid Capacitor native config at ${nativeConfigPath}.`);
5084
5264
  await mkdir5(paths.root, { recursive: true });
5085
5265
  await Promise.all([
@@ -5102,7 +5282,7 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
5102
5282
  developmentUrl.searchParams.set("__absolute_target", "capacitor-ios");
5103
5283
  const existingServer = parsed.server;
5104
5284
  parsed.server = {
5105
- ...isRecord3(existingServer) ? existingServer : {},
5285
+ ...isRecord4(existingServer) ? existingServer : {},
5106
5286
  cleartext: !https,
5107
5287
  url: developmentUrl.href
5108
5288
  };
@@ -5112,10 +5292,10 @@ var ABSOLUTE_IOS_SIMULATOR_NAME = "AbsoluteJS iPhone", BOOT_TIMEOUT_MS = 180000,
5112
5292
  writeFile5(infoPath, iosDevelopmentInfoPlist(infoSource, !https))
5113
5293
  ]);
5114
5294
  }, parseNativeCache2 = (value) => {
5115
- if (!isRecord3(value))
5295
+ if (!isRecord4(value))
5116
5296
  return null;
5117
5297
  const { appId, fingerprint, format, installations } = value;
5118
- if (format !== NATIVE_CACHE_FORMAT2 || typeof appId !== "string" || typeof fingerprint !== "string" || !isRecord3(installations) || !Object.values(installations).every((identity) => typeof identity === "string"))
5298
+ if (format !== NATIVE_CACHE_FORMAT2 || typeof appId !== "string" || typeof fingerprint !== "string" || !isRecord4(installations) || !Object.values(installations).every((identity) => typeof identity === "string"))
5119
5299
  return null;
5120
5300
  return {
5121
5301
  appId,
@@ -7370,23 +7550,23 @@ var stripStringsAndComments = (source) => {
7370
7550
  var toIslandFrameworkSegment = (framework) => framework[0]?.toUpperCase() + framework.slice(1), getIslandManifestKey = (framework, component2) => `Island${toIslandFrameworkSegment(framework)}${component2}`;
7371
7551
 
7372
7552
  // src/core/islands.ts
7373
- var isRecord4 = (value) => typeof value === "object" && value !== null, getIslandBuildReference = (component2) => {
7553
+ var isRecord5 = (value) => typeof value === "object" && value !== null, getIslandBuildReference = (component2) => {
7374
7554
  if (!isIslandComponentDefinition(component2))
7375
7555
  return null;
7376
7556
  return {
7377
7557
  export: component2.export,
7378
7558
  source: component2.source
7379
7559
  };
7380
- }, isIslandComponentDefinition = (value) => isRecord4(value) && ("component" in value) && ("source" in value) && typeof value.source === "string";
7560
+ }, isIslandComponentDefinition = (value) => isRecord5(value) && ("component" in value) && ("source" in value) && typeof value.source === "string";
7381
7561
  var init_islands = () => {};
7382
7562
 
7383
7563
  // src/build/islandEntries.ts
7384
7564
  import { dirname as dirname10, extname as extname2, join as join19, relative as relative10, resolve as resolve14 } from "path";
7385
7565
  import ts2 from "typescript";
7386
- var frameworks, isRecord5 = (value) => typeof value === "object" && value !== null, resolveRegistryExport = (mod) => {
7387
- if (isRecord5(mod.islandRegistry))
7566
+ var frameworks, isRecord6 = (value) => typeof value === "object" && value !== null, resolveRegistryExport = (mod) => {
7567
+ if (isRecord6(mod.islandRegistry))
7388
7568
  return mod.islandRegistry;
7389
- if (isRecord5(mod.default))
7569
+ if (isRecord6(mod.default))
7390
7570
  return mod.default;
7391
7571
  throw new Error("Island registry module must export `islandRegistry` or a default registry object.");
7392
7572
  }, hasSvelteImport = (source) => /from\s+['"][^'"]+\.svelte['"]/.test(source), resolveIslandSourcePath = (registryPath, sourcePath) => {
@@ -7536,7 +7716,7 @@ var frameworks, isRecord5 = (value) => typeof value === "object" && value !== nu
7536
7716
  const registry2 = resolveRegistryExport(registryModule);
7537
7717
  const definitions = frameworks.flatMap((framework) => {
7538
7718
  const frameworkRegistry = registry2[framework];
7539
- if (!isRecord5(frameworkRegistry))
7719
+ if (!isRecord6(frameworkRegistry))
7540
7720
  return [];
7541
7721
  return Object.entries(frameworkRegistry).map(([component2, value]) => ({
7542
7722
  buildReference: getIslandBuildReference(value),
@@ -7546,7 +7726,7 @@ var frameworks, isRecord5 = (value) => typeof value === "object" && value !== nu
7546
7726
  });
7547
7727
  return {
7548
7728
  definitions,
7549
- hasNamedExport: isRecord5(registryModule.islandRegistry),
7729
+ hasNamedExport: isRecord6(registryModule.islandRegistry),
7550
7730
  registry: registry2
7551
7731
  };
7552
7732
  }, loadIslandRegistryBuildInfo = async (registryPath) => {
@@ -7846,8 +8026,8 @@ var init_bunStringRawUnicodePlugin = __esm(() => {
7846
8026
  });
7847
8027
 
7848
8028
  // src/mobile/buildMetadata.ts
7849
- var ABSOLUTE_MOBILE_ROUTE_DETAIL = "x-absolute-mobile", frameworks2, isRecord6 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), isPageFramework = (value) => typeof value === "string" && frameworks2.has(value), parseAbsoluteMobileBuildPageMetadata = (value) => {
7850
- if (!isRecord6(value))
8029
+ var ABSOLUTE_MOBILE_ROUTE_DETAIL = "x-absolute-mobile", frameworks2, isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), isPageFramework = (value) => typeof value === "string" && frameworks2.has(value), parseAbsoluteMobileBuildPageMetadata = (value) => {
8030
+ if (!isRecord7(value))
7851
8031
  return;
7852
8032
  if (typeof value.bundleKey !== "string" || typeof value.contract !== "string" || !isPageFramework(value.framework) || typeof value.pageId !== "string" || typeof value.propsSchemaHash !== "string") {
7853
8033
  return;
@@ -8667,14 +8847,14 @@ import {
8667
8847
  writeFile as writeFile9
8668
8848
  } from "fs/promises";
8669
8849
  import { dirname as dirname13, join as join22, resolve as resolvePath3 } from "path";
8670
- var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1, CURRENT_BUNDLE_FILE = "current.json", BUNDLES_DIRECTORY = "bundles", ARTIFACT_FILE = "artifact.json", BUNDLE_ID_PATTERN, isRecord7 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code, bundleIdFor = (currentReleaseId, releases) => {
8850
+ var ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT = 1, CURRENT_BUNDLE_FILE = "current.json", BUNDLES_DIRECTORY = "bundles", ARTIFACT_FILE = "artifact.json", BUNDLE_ID_PATTERN, isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), errorHasCode2 = (error, code) => typeof error === "object" && error !== null && Reflect.get(error, "code") === code, bundleIdFor = (currentReleaseId, releases) => {
8671
8851
  const identity = JSON.stringify({
8672
8852
  currentReleaseId,
8673
8853
  releases: releases.map(({ releaseId }) => releaseId)
8674
8854
  });
8675
8855
  return `amb_${createHash11("sha256").update(identity).digest("hex")}`;
8676
8856
  }, parseBundleIndex = (value) => {
8677
- if (!isRecord7(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
8857
+ if (!isRecord8(value) || value.format !== ABSOLUTE_MOBILE_MATERIALIZED_BUNDLE_FORMAT || typeof value.bundleId !== "string" || !BUNDLE_ID_PATTERN.test(value.bundleId) || typeof value.currentReleaseId !== "string" || !Array.isArray(value.releases)) {
8678
8858
  throw new TypeError("Invalid materialized mobile compatibility bundle.");
8679
8859
  }
8680
8860
  const releases = value.releases.map(parseAbsoluteMobileCompatibilityArtifact);
@@ -10980,8 +11160,8 @@ var DEFAULT_PROOF_LOCATION = ".absolutejs/lint-proof.json", PROOF_CONTRACT_VERSI
10980
11160
  `);
10981
11161
  renameSync2(temporary, path);
10982
11162
  return proof;
10983
- }, isRecord8 = (value) => value !== null && typeof value === "object", isLintProofAttestation = (value) => {
10984
- if (!isRecord8(value))
11163
+ }, isRecord9 = (value) => value !== null && typeof value === "object", isLintProofAttestation = (value) => {
11164
+ if (!isRecord9(value))
10985
11165
  return false;
10986
11166
  return Reflect.get(value, "algorithm") === "ed25519" && typeof Reflect.get(value, "keyId") === "string" && typeof Reflect.get(value, "signature") === "string";
10987
11167
  }, isLintProof = (value) => {
@@ -12368,7 +12548,7 @@ var init_mem = __esm(() => {
12368
12548
  });
12369
12549
 
12370
12550
  // src/cli/config/guards.ts
12371
- var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
12551
+ var isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
12372
12552
 
12373
12553
  // src/cli/config/schema/fromType.ts
12374
12554
  import ts6 from "typescript";
@@ -12431,7 +12611,7 @@ var VIRTUAL_NAME = "__absolute_type_introspect__.ts", MAX_DEPTH = 6, isFramework
12431
12611
  }, readDiskCache = (cwd, typeName, signature, specifier) => {
12432
12612
  try {
12433
12613
  const cached = JSON.parse(readFileSync21(cacheFile(cwd, typeName, specifier), "utf-8"));
12434
- if (isRecord9(cached) && cached.signature === signature && Array.isArray(cached.fields)) {
12614
+ if (isRecord10(cached) && cached.signature === signature && Array.isArray(cached.fields)) {
12435
12615
  return cached.fields;
12436
12616
  }
12437
12617
  } catch {}
@@ -12838,11 +13018,11 @@ var init_frameworks = __esm(() => {
12838
13018
 
12839
13019
  // src/cli/generate/context.ts
12840
13020
  import { dirname as dirname17, isAbsolute as isAbsolute5, join as join29, relative as relative16, resolve as resolve27 } from "path";
12841
- var asString = (value) => typeof value === "string" ? value : undefined, isRecord10 = (value) => typeof value === "object" && value !== null, resolveDir = (cwd, value) => isAbsolute5(value) ? value : resolve27(cwd, value), resolveStylesDir = (cwd, config) => {
13021
+ var asString = (value) => typeof value === "string" ? value : undefined, isRecord11 = (value) => typeof value === "object" && value !== null, resolveDir = (cwd, value) => isAbsolute5(value) ? value : resolve27(cwd, value), resolveStylesDir = (cwd, config) => {
12842
13022
  const styles = config.stylesConfig;
12843
13023
  if (typeof styles === "string")
12844
13024
  return resolveDir(cwd, styles);
12845
- if (isRecord10(styles)) {
13025
+ if (isRecord11(styles)) {
12846
13026
  const indexes = asString(styles.indexes);
12847
13027
  if (indexes)
12848
13028
  return resolveDir(cwd, indexes);
@@ -12853,7 +13033,7 @@ var asString = (value) => typeof value === "string" ? value : undefined, isRecor
12853
13033
  return dir ? dirname17(dir) : resolve27(project.cwd, "src/frontend");
12854
13034
  }, resolveProject = async (cwd, configOverride) => {
12855
13035
  const loaded = await loadConfig(configOverride);
12856
- const config = isRecord10(loaded) ? loaded : {};
13036
+ const config = isRecord11(loaded) ? loaded : {};
12857
13037
  const frameworkDirs = {};
12858
13038
  for (const key of FRAMEWORK_KEYS2) {
12859
13039
  const dir = asString(config[frameworks6[key].configDirKey]);
@@ -13833,7 +14013,7 @@ ${value.map((item) => `${pad}${serializeValue(item, level + 1, indent)}`).join(`
13833
14013
  `)}
13834
14014
  ${indent.repeat(level)}]`;
13835
14015
  }
13836
- if (isRecord9(value)) {
14016
+ if (isRecord10(value)) {
13837
14017
  const keys = Object.keys(value);
13838
14018
  if (keys.length === 0)
13839
14019
  return "{}";
@@ -14046,18 +14226,18 @@ var init_catalog = __esm(() => {
14046
14226
  // src/cli/integrations/addPlugin.ts
14047
14227
  import { existsSync as existsSync27, readFileSync as readFileSync27 } from "fs";
14048
14228
  import { join as join35 } from "path";
14049
- var isRecord11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readPackageJson = (cwd) => {
14229
+ var isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readPackageJson = (cwd) => {
14050
14230
  const path = join35(cwd, "package.json");
14051
14231
  if (!existsSync27(path))
14052
14232
  return null;
14053
14233
  try {
14054
14234
  const parsed = JSON.parse(readFileSync27(path, "utf-8"));
14055
- return isRecord11(parsed) ? parsed : null;
14235
+ return isRecord12(parsed) ? parsed : null;
14056
14236
  } catch {
14057
14237
  return null;
14058
14238
  }
14059
14239
  }, addGroupKeys = (group, names) => {
14060
- if (!isRecord11(group))
14240
+ if (!isRecord12(group))
14061
14241
  return;
14062
14242
  for (const name of Object.keys(group))
14063
14243
  names.add(name);
@@ -14650,12 +14830,12 @@ var init_resolveAuthSettings = __esm(() => {
14650
14830
  import ts13 from "typescript";
14651
14831
  import { existsSync as existsSync29, readdirSync as readdirSync6, readFileSync as readFileSync29 } from "fs";
14652
14832
  import { join as join36, relative as relative19, resolve as resolve29 } from "path";
14653
- var AUTH_PACKAGE2 = "@absolutejs/auth", REPO_URL = "https://github.com/absolutejs/absolute-auth", NPM_URL = "https://www.npmjs.com/package/@absolutejs/auth", SKIP_DIRS, MAX_FILES = 4000, SETUP_EXPORTS, isRecord12 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson2 = (path) => {
14833
+ var AUTH_PACKAGE2 = "@absolutejs/auth", REPO_URL = "https://github.com/absolutejs/absolute-auth", NPM_URL = "https://www.npmjs.com/package/@absolutejs/auth", SKIP_DIRS, MAX_FILES = 4000, SETUP_EXPORTS, isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJson2 = (path) => {
14654
14834
  if (!existsSync29(path))
14655
14835
  return null;
14656
14836
  try {
14657
14837
  const parsed = JSON.parse(readFileSync29(path, "utf-8"));
14658
- return isRecord12(parsed) ? parsed : null;
14838
+ return isRecord13(parsed) ? parsed : null;
14659
14839
  } catch {
14660
14840
  return null;
14661
14841
  }
@@ -14668,7 +14848,7 @@ var AUTH_PACKAGE2 = "@absolutejs/auth", REPO_URL = "https://github.com/absolutej
14668
14848
  return null;
14669
14849
  for (const field of ["dependencies", "devDependencies"]) {
14670
14850
  const group = pkg[field];
14671
- if (!isRecord12(group))
14851
+ if (!isRecord13(group))
14672
14852
  continue;
14673
14853
  const version2 = group[AUTH_PACKAGE2];
14674
14854
  if (typeof version2 === "string")
@@ -15961,13 +16141,13 @@ import { dirname as dirname25, join as join43, resolve as resolve32, sep as sep6
15961
16141
  var DEPENDENCY_FIELDS, TYPE_GRAPH_PACKAGES, readManifest = (path) => {
15962
16142
  try {
15963
16143
  const parsed = JSON.parse(readFileSync35(path, "utf-8"));
15964
- return isRecord9(parsed) ? parsed : null;
16144
+ return isRecord10(parsed) ? parsed : null;
15965
16145
  } catch {
15966
16146
  return null;
15967
16147
  }
15968
16148
  }, dependencyRecord = (manifest, field) => {
15969
16149
  const value = Reflect.get(manifest, field);
15970
- return isRecord9(value) ? value : {};
16150
+ return isRecord10(value) ? value : {};
15971
16151
  }, dependencyNames2 = (manifest) => [
15972
16152
  ...new Set(DEPENDENCY_FIELDS.flatMap((field) => Object.keys(dependencyRecord(manifest, field))))
15973
16153
  ], declaresPackage = (manifest, name) => DEPENDENCY_FIELDS.some((field) => Object.hasOwn(dependencyRecord(manifest, field), name)), manifestName = (manifest, fallback) => {
@@ -16098,7 +16278,7 @@ var DEPENDENCY_FIELDS, TYPE_GRAPH_PACKAGES, readManifest = (path) => {
16098
16278
  if (!manifest)
16099
16279
  return [];
16100
16280
  const existing = Reflect.get(manifest, "overrides");
16101
- const overrides = isRecord9(existing) ? existing : {};
16281
+ const overrides = isRecord10(existing) ? existing : {};
16102
16282
  const changes = [];
16103
16283
  const rootName = manifestName(manifest, "<workspace>");
16104
16284
  for (const duplicate of duplicates) {
@@ -17045,7 +17225,7 @@ var cliTag4 = (color, message) => `\x1B[2m${formatTimestamp()}\x1B[0m ${color}[c
17045
17225
  if (!encoded)
17046
17226
  return;
17047
17227
  const map = JSON.parse(Buffer.from(encoded, "base64").toString("utf-8"));
17048
- if (!isRecord9(map))
17228
+ if (!isRecord10(map))
17049
17229
  return;
17050
17230
  if (!Array.isArray(map.sources))
17051
17231
  return;
@@ -17296,7 +17476,7 @@ var cliTag4 = (color, message) => `\x1B[2m${formatTimestamp()}\x1B[0m ${color}[c
17296
17476
  }, pickExportEntry = (value) => {
17297
17477
  if (typeof value === "string")
17298
17478
  return value;
17299
- if (!isRecord9(value))
17479
+ if (!isRecord10(value))
17300
17480
  return;
17301
17481
  for (const key of ["bun", "node", "import", "module", "default"]) {
17302
17482
  const entry = pickExportEntry(value[key]);
@@ -19537,7 +19717,7 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19537
19717
  path,
19538
19718
  remediation,
19539
19719
  status: "warn"
19540
- }), readJsonObject = async (path) => {
19720
+ }), isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), readJsonObject = async (path) => {
19541
19721
  const value = JSON.parse(await readFile18(path, "utf8"));
19542
19722
  if (typeof value !== "object" || value === null || Array.isArray(value))
19543
19723
  throw new TypeError("JSON root must be an object.");
@@ -19579,6 +19759,37 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19579
19759
  } catch (error) {
19580
19760
  return fail5("mobile.capacitor-versions", error instanceof Error ? error.message : "Capacitor package versions could not be validated.", manifestPath, "Pin @capacitor/core, @capacitor/cli, and each configured platform to exact versions on the same major/minor line, then reinstall.");
19581
19761
  }
19762
+ }, satisfiesGeneratedVersion = (declared, installed) => {
19763
+ if (EXACT_VERSION_PATTERN.test(declared))
19764
+ return declared === installed;
19765
+ if (!declared.startsWith("~"))
19766
+ return false;
19767
+ const expected = declared.slice(1).split(".").map(Number);
19768
+ const actual = installed.split(".").map(Number);
19769
+ return actual[0] === expected[0] && actual[1] === expected[1] && (actual[2] ?? NOT_FOUND3) >= (expected[2] ?? 0);
19770
+ }, expoVersionCheck = async (config) => {
19771
+ const manifestPath = join52(config.nativeProjectDirectory, "package.json");
19772
+ try {
19773
+ const manifest = await readJsonObject(manifestPath);
19774
+ const declarations = packageDeclarations(manifest);
19775
+ const required = ["expo", "expo-router", "react", "react-native"];
19776
+ const missingRequired = required.find((name) => !declarations.has(name));
19777
+ if (missingRequired)
19778
+ throw new TypeError(`Generated Expo project is missing ${missingRequired}.`);
19779
+ const installedVersions = await Promise.all([...declarations].map(async ([name, declared]) => ({
19780
+ declared,
19781
+ installed: await readJsonObject(join52(config.nativeProjectDirectory, "node_modules", name, "package.json")),
19782
+ name
19783
+ })));
19784
+ const mismatch = installedVersions.find(({ declared, installed }) => typeof installed.version !== "string" || !satisfiesGeneratedVersion(declared, installed.version));
19785
+ if (mismatch)
19786
+ throw new TypeError(`Generated Expo dependency ${mismatch.name}@${mismatch.declared} does not match its installed version.`);
19787
+ if (!await pathExists6(join52(config.nativeProjectDirectory, "bun.lock")))
19788
+ throw new TypeError("Generated Expo dependency lockfile is missing.");
19789
+ return pass("mobile.expo-versions", `Generated Expo SDK dependencies are pinned, installed, and locked (${declarations.get("expo")}).`, manifestPath);
19790
+ } catch (error) {
19791
+ return fail5("mobile.expo-versions", error instanceof Error ? error.message : "Generated Expo dependency versions could not be validated.", manifestPath, "Run `absolute mobile init --yes`, then rebuild the production Expo project.");
19792
+ }
19582
19793
  }, dependencyLockCheck = async (projectRoot) => {
19583
19794
  const present = (await Promise.all(LOCK_FILES.map(async (name) => ({
19584
19795
  exists: await pathExists6(join52(projectRoot, name)),
@@ -19670,6 +19881,63 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19670
19881
  } catch (error) {
19671
19882
  return fail5(`${platform6}.content-security-policy`, error instanceof Error ? error.message : "Packaged shell CSP could not be validated.", path, "Rebuild the production mobile bundle with the AbsoluteJS-generated shell.");
19672
19883
  }
19884
+ }, expoApplicationConfigCheck = async (config) => {
19885
+ const path = join52(config.nativeProjectDirectory, "app.json");
19886
+ try {
19887
+ const root = await readJsonObject(path);
19888
+ if (!isRecord14(root.expo))
19889
+ throw new TypeError("Generated Expo application config is invalid.");
19890
+ const { expo } = root;
19891
+ if (expo.name !== config.appName || !isRecord14(expo.android) || expo.android.package !== config.appId) {
19892
+ throw new TypeError("Generated Expo Android identity does not match mobile config.");
19893
+ }
19894
+ if (!isRecord14(expo.runtimeVersion) || expo.runtimeVersion.policy !== "appVersion") {
19895
+ throw new TypeError("Generated Expo runtimeVersion must follow the native app version.");
19896
+ }
19897
+ if (Array.isArray(expo.plugins) && expo.plugins.some((plugin) => typeof plugin === "string" && plugin.includes("withAbsoluteDevelopmentCa"))) {
19898
+ throw new TypeError("Generated Expo production config includes the development CA plugin.");
19899
+ }
19900
+ return pass("expo.app-config", "Expo application identity and runtime policy match the production mobile config.", path);
19901
+ } catch (error) {
19902
+ return fail5("expo.app-config", error instanceof Error ? error.message : "Generated Expo application config could not be validated.", path, "Run `absolute mobile build android`; do not edit the generated Expo project.");
19903
+ }
19904
+ }, expoEmbeddedAssetsCheck = async (config) => {
19905
+ const generated = join52(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
19906
+ const assetsRoot = join52(config.nativeProjectDirectory, "assets", "absolute");
19907
+ try {
19908
+ const [source, manifest] = await Promise.all([
19909
+ readFile18(generated, "utf8"),
19910
+ readJsonObject(join52(config.bundleDirectory, "absolute-mobile-manifest.json"))
19911
+ ]);
19912
+ if (source.includes("embedded AbsoluteJS bundle is unavailable") || typeof manifest.appBuild !== "string" || !source.includes(JSON.stringify(manifest.appBuild)) || !source.includes(JSON.stringify(config.productionOrigin))) {
19913
+ throw new TypeError("Generated Expo assets do not contain the prepared production release identity.");
19914
+ }
19915
+ const sourceFiles = (await Array.fromAsync(new Bun.Glob("**/*").scan({
19916
+ cwd: config.bundleDirectory,
19917
+ onlyFiles: true
19918
+ }))).sort();
19919
+ const embeddedFiles = (await Array.fromAsync(new Bun.Glob("*.absasset").scan({
19920
+ cwd: assetsRoot,
19921
+ onlyFiles: true
19922
+ }))).sort();
19923
+ if (sourceFiles.length === 0 || sourceFiles.length !== embeddedFiles.length)
19924
+ throw new TypeError("Generated Expo asset count does not match the prepared mobile bundle.");
19925
+ const matches = await Promise.all(sourceFiles.map(async (path, index) => {
19926
+ const embedded = embeddedFiles[index];
19927
+ if (!embedded)
19928
+ return false;
19929
+ const [left, right] = await Promise.all([
19930
+ readFile18(join52(config.bundleDirectory, path)),
19931
+ readFile18(join52(assetsRoot, embedded))
19932
+ ]);
19933
+ return left.equals(right);
19934
+ }));
19935
+ if (matches.some((value) => !value))
19936
+ throw new TypeError("Generated Expo asset bytes differ from the prepared mobile bundle.");
19937
+ return pass("expo.bundle-projection", `Expo embeds the complete signed mobile bundle as ${embeddedFiles.length} opaque asset(s).`, generated);
19938
+ } catch (error) {
19939
+ return fail5("expo.bundle-projection", error instanceof Error ? error.message : "Generated Expo assets could not be validated.", generated, "Run `absolute mobile build android` to regenerate and verify the production asset projection.");
19940
+ }
19673
19941
  }, sourceFiles = async (root, extensions) => {
19674
19942
  if (!await pathExists6(root))
19675
19943
  return [];
@@ -19702,13 +19970,8 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19702
19970
  }, androidDeepLinkProjectionCheck = async (config, manifestPath) => {
19703
19971
  try {
19704
19972
  const source = await readFile18(manifestPath, "utf8");
19705
- const required = [
19706
- 'android:autoVerify="true"',
19707
- "android.intent.category.BROWSABLE",
19708
- ...config.deepLinkHosts.map((host2) => `android:scheme="https" android:host="${host2}"`),
19709
- ...config.deepLinkScheme ? [`android:scheme="${config.deepLinkScheme}"`] : []
19710
- ];
19711
- if (required.some((value) => !source.includes(value)))
19973
+ const hasWebHost = (host2) => [...source.matchAll(/<data\b[^>]*>/giu)].some(([tag]) => tag.includes('android:scheme="https"') && tag.includes(`android:host="${host2}"`));
19974
+ if (!source.includes('android:autoVerify="true"') || !source.includes("android.intent.category.BROWSABLE") || config.deepLinkHosts.some((host2) => !hasWebHost(host2)) || config.deepLinkScheme && !source.includes(`android:scheme="${config.deepLinkScheme}"`))
19712
19975
  throw new TypeError("Android App Link or custom-scheme projection does not match mobile config.");
19713
19976
  return pass("android.deep-links", "Android verified links and custom scheme match the effective mobile config.", manifestPath);
19714
19977
  } catch (error) {
@@ -19817,8 +20080,21 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19817
20080
  }, deviceCapabilityReleaseCheck = async (config, projectRoot) => {
19818
20081
  const manifestPath = join52(projectRoot, "package.json");
19819
20082
  try {
19820
- const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot);
19821
- assertAbsoluteDeviceCapabilityPackages(projectRoot, plan);
20083
+ const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot, config.engine);
20084
+ const assertPackages = async () => {
20085
+ if (config.engine === "capacitor")
20086
+ return assertAbsoluteDeviceCapabilityPackages(projectRoot, plan);
20087
+ const generated = await readJsonObject(join52(config.nativeProjectDirectory, "package.json"));
20088
+ const declarations = packageDeclarations(generated);
20089
+ const missing = plan.requiredPackages.filter((spec) => {
20090
+ const separator = spec.lastIndexOf("@");
20091
+ return declarations.get(spec.slice(0, separator)) !== spec.slice(separator + 1);
20092
+ });
20093
+ if (missing.length > 0)
20094
+ throw new TypeError(`Generated Expo project is missing detected capability packages: ${missing.join(", ")}.`);
20095
+ return;
20096
+ };
20097
+ await assertPackages();
19822
20098
  const requirements = absoluteDeviceNativeRequirements(plan);
19823
20099
  const androidCheck = await androidDevicePermissionCheck(config, requirements.androidPermissions);
19824
20100
  if (androidCheck)
@@ -19855,6 +20131,26 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19855
20131
  ...check2,
19856
20132
  path: check2.path ? relative26(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
19857
20133
  }));
20134
+ }, inspectExpoAndroidRelease = async (config, projectRoot) => {
20135
+ const androidRoot = join52(config.nativeProjectDirectory, "android");
20136
+ const manifestPath = join52(androidRoot, "app", "src", "main", "AndroidManifest.xml");
20137
+ const journalPath = join52(projectRoot, ".absolutejs", "mobile", "expo-dev-session", "journal.json");
20138
+ const checks = await Promise.all([
20139
+ journalReleaseCheck(journalPath, "android"),
20140
+ expoApplicationConfigCheck(config),
20141
+ expoEmbeddedAssetsCheck(config),
20142
+ manifestReleaseCheck(manifestPath),
20143
+ hmrAssetsReleaseCheck(config.bundleDirectory),
20144
+ embeddedBundleReleaseCheck(config, projectRoot, "android", config.bundleDirectory),
20145
+ contentSecurityPolicyCheck(config, "android", config.bundleDirectory),
20146
+ androidNativeSecurityCheck(androidRoot),
20147
+ androidExportedComponentsCheck(manifestPath),
20148
+ androidDeepLinkProjectionCheck(config, manifestPath)
20149
+ ]);
20150
+ return checks.map((check2) => ({
20151
+ ...check2,
20152
+ path: check2.path ? relative26(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
20153
+ }));
19858
20154
  }, inspectIosRelease = async (config, projectRoot) => {
19859
20155
  const iosAppRoot = join52(config.nativeProjectDirectory, "ios", "App", "App");
19860
20156
  const nativeConfigPath = join52(iosAppRoot, "capacitor.config.json");
@@ -19914,17 +20210,18 @@ var ABSOLUTE_MOBILE_COMPLIANCE_REPORT_FORMAT = 1, HMR_ASSET_PATTERN, RELEASE_ASS
19914
20210
  Promise.resolve(productionOriginCheck(config, projectRoot)),
19915
20211
  Promise.resolve(associationIdentityCheck(config, projectRoot)),
19916
20212
  dependencyLockCheck(projectRoot),
19917
- capacitorVersionCheck(config, projectRoot)
20213
+ config.engine === "expo" ? expoVersionCheck(config) : capacitorVersionCheck(config, projectRoot)
19918
20214
  ]);
19919
20215
  const checks = globalChecks.map((check2) => ({
19920
20216
  ...check2,
19921
20217
  path: check2.path ? relative26(projectRoot, check2.path).replaceAll("\\", "/") || "." : undefined
19922
20218
  }));
19923
20219
  if (config.platforms.includes("android"))
19924
- checks.push(...await inspectAndroidRelease(config, projectRoot));
19925
- if (config.platforms.includes("ios")) {
19926
- checks.push(...await inspectIosRelease(config, projectRoot));
19927
- }
20220
+ checks.push(...config.engine === "expo" ? await inspectExpoAndroidRelease(config, projectRoot) : await inspectAndroidRelease(config, projectRoot));
20221
+ if (config.platforms.includes("ios"))
20222
+ checks.push(...config.engine === "expo" ? [
20223
+ fail5("expo.ios-release", "Expo iOS production release automation is not implemented yet.", config.nativeProjectDirectory, "Build Android independently or wait for the Expo iOS signing checkpoint.")
20224
+ ] : await inspectIosRelease(config, projectRoot));
19928
20225
  const syncSchema = syncSchemaReleaseCheck(projectRoot);
19929
20226
  if (syncSchema) {
19930
20227
  checks.push({
@@ -19981,14 +20278,15 @@ import {
19981
20278
  mkdir as mkdir13,
19982
20279
  mkdtemp as mkdtemp6,
19983
20280
  readFile as readFile19,
20281
+ realpath as realpath2,
19984
20282
  rename as rename13,
19985
20283
  rm as rm9,
19986
20284
  stat as stat3,
19987
20285
  writeFile as writeFile15
19988
20286
  } from "fs/promises";
19989
20287
  import { dirname as dirname31, isAbsolute as isAbsolute7, join as join53, relative as relative27, resolve as resolve40, sep as sep7 } from "path";
19990
- var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
19991
- if (!isRecord13(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
20288
+ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), requireManifest2 = (value) => {
20289
+ if (!isRecord15(value) || typeof value.appBuild !== "string" || typeof value.appId !== "string" || typeof value.runtime !== "string") {
19992
20290
  throw new TypeError("Invalid embedded AbsoluteJS mobile manifest.");
19993
20291
  }
19994
20292
  return {
@@ -20050,7 +20348,18 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
20050
20348
  ]);
20051
20349
  if (result.exitCode !== 0)
20052
20350
  throw new TypeError("jarsigner could not sign the Android App Bundle with the configured CI identity.");
20053
- }, sha256File2 = async (path) => createHash14("sha256").update(await readFile19(path)).digest("hex"), safeOutputDirectory2 = (projectRoot, requested) => {
20351
+ }, sha256File2 = async (path) => createHash14("sha256").update(await readFile19(path)).digest("hex"), fingerprintExpoAndroidProject = async (nativeDirectory) => {
20352
+ const root = await realpath2(nativeDirectory);
20353
+ const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: root, onlyFiles: true }));
20354
+ const records = await Promise.all(files.filter((path) => {
20355
+ const parts = path.replaceAll("\\", "/").split("/");
20356
+ return !parts.includes(".gradle") && !parts.includes("build");
20357
+ }).sort().map(async (path) => {
20358
+ const contents = await readFile19(join53(root, path));
20359
+ return `${path.replaceAll("\\", "/")}\x00${createHash14("sha256").update(contents).digest("hex")}\x00`;
20360
+ }));
20361
+ return createHash14("sha256").update(records.join("")).digest("hex");
20362
+ }, safeOutputDirectory2 = (projectRoot, requested) => {
20054
20363
  const root = resolve40(projectRoot);
20055
20364
  const output = resolve40(root, requested ?? ".absolutejs/mobile/releases/android");
20056
20365
  const projectRelative = relative27(root, output);
@@ -20091,7 +20400,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
20091
20400
  });
20092
20401
  }
20093
20402
  }, requireManifestIdentity = (value, expected) => {
20094
- if (!isRecord13(value)) {
20403
+ if (!isRecord15(value)) {
20095
20404
  throw new TypeError("Existing Android release metadata is invalid.");
20096
20405
  }
20097
20406
  const { artifact } = value;
@@ -20108,6 +20417,9 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
20108
20417
  }
20109
20418
  const projectRoot = resolve40(options.projectRoot);
20110
20419
  const host2 = options.host ?? detectAbsoluteMobileHost();
20420
+ if (options.config.engine === "expo" && host2 === "wsl") {
20421
+ throw new TypeError("Expo Android production builds from WSL are not available yet. Run the generated CI workflow on Linux or build from native Windows while the WSL projection is completed.");
20422
+ }
20111
20423
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host2);
20112
20424
  const nativeDirectory = join53(options.config.nativeProjectDirectory, "android");
20113
20425
  const manifest = requireManifest2(JSON.parse(await readFile19(join53(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
@@ -20116,7 +20428,9 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
20116
20428
  }
20117
20429
  let { versionCode } = options;
20118
20430
  if (options.prepareVersionCode) {
20119
- const nativeFingerprint = await fingerprintAbsoluteAndroidNativeProject({ nativeDirectory });
20431
+ const nativeFingerprint = options.config.engine === "expo" ? await fingerprintExpoAndroidProject(nativeDirectory) : await fingerprintAbsoluteAndroidNativeProject({
20432
+ nativeDirectory
20433
+ });
20120
20434
  const buildIdentity = createHash14("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
20121
20435
  versionCode = await options.prepareVersionCode(buildIdentity);
20122
20436
  }
@@ -20125,6 +20439,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
20125
20439
  }
20126
20440
  const { artifactPath } = await buildAbsoluteAndroidGradleArtifact({
20127
20441
  capture: options.capture,
20442
+ env: options.env,
20128
20443
  gradleArguments: versionCode === undefined ? [] : [`-Pandroid.injected.version.code=${versionCode}`],
20129
20444
  project: {
20130
20445
  androidRoot,
@@ -20165,7 +20480,7 @@ var ABSOLUTE_ANDROID_RELEASE_FORMAT = 1, isRecord13 = (value) => typeof value ==
20165
20480
  appBuild: manifest.appBuild,
20166
20481
  appId: manifest.appId,
20167
20482
  bytes,
20168
- engine: "capacitor",
20483
+ engine: options.config.engine,
20169
20484
  format: ABSOLUTE_ANDROID_RELEASE_FORMAT,
20170
20485
  platform: "android",
20171
20486
  releaseId,
@@ -20661,7 +20976,7 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
20661
20976
  throw new TypeError("Google Play publisher returned an invalid Android versionCode.");
20662
20977
  }
20663
20978
  return versionCode;
20664
- }, isRecord14 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), isPublisher = (value) => isRecord14(value) && typeof value.publish === "function", publisherModulePath = (projectRoot, requested) => {
20979
+ }, isRecord16 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), isPublisher = (value) => isRecord16(value) && typeof value.publish === "function", publisherModulePath = (projectRoot, requested) => {
20665
20980
  const root = resolve41(projectRoot);
20666
20981
  const path = resolve41(root, requested);
20667
20982
  const projectRelative = relative28(root, path);
@@ -20675,7 +20990,7 @@ var prepareAbsoluteIosRelease = async (publisher, options) => {
20675
20990
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
20676
20991
  });
20677
20992
  const loaded = await import(pathToFileURL2(modulePath).href);
20678
- const publisher = isRecord14(loaded) ? loaded.default ?? loaded.registry : undefined;
20993
+ const publisher = isRecord16(loaded) ? loaded.default ?? loaded.registry : undefined;
20679
20994
  if (!isPublisher(publisher)) {
20680
20995
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
20681
20996
  }
@@ -20777,7 +21092,7 @@ var ABSOLUTE_MOBILE_INSPECTION_FORMAT = 1, MOBILE_PACKAGE_NAMES, isObject3 = (va
20777
21092
  let capabilityIssue;
20778
21093
  let plugins = [];
20779
21094
  try {
20780
- const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot);
21095
+ const plan = resolveAbsoluteDeviceCapabilityPlan(projectRoot, config.engine);
20781
21096
  currentCapabilities = plan.capabilities;
20782
21097
  plugins = plan.requiredPackages;
20783
21098
  } catch {
@@ -21230,8 +21545,10 @@ ${releaseAuditSteps("ios")}
21230
21545
  ...custom
21231
21546
  ], createAbsoluteMobileGithubWorkflow = (options) => {
21232
21547
  const platforms = [
21233
- ...options.config.platforms
21548
+ ...options.config.engine === "expo" ? options.config.platforms.filter((platform6) => platform6 === "android") : options.config.platforms
21234
21549
  ].sort();
21550
+ if (platforms.length === 0)
21551
+ throw new TypeError("Generated Expo production CI currently requires android in mobile.platforms; Expo iOS release automation is the next checkpoint.");
21235
21552
  const includePublishing = options.includePublishing === true;
21236
21553
  const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
21237
21554
  const serverEntry = projectPath(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
@@ -21306,7 +21623,7 @@ ${bundleAuditSteps}${platforms.includes("android") ? androidJob({ customSecrets,
21306
21623
  changed: previous !== generated.workflow,
21307
21624
  format: ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
21308
21625
  path,
21309
- platforms: [...options.config.platforms].sort(),
21626
+ platforms: options.config.engine === "expo" ? options.config.platforms.filter((platform6) => platform6 === "android") : [...options.config.platforms].sort(),
21310
21627
  publishing: options.includePublishing === true,
21311
21628
  requiredSecrets: generated.requiredSecrets
21312
21629
  };
@@ -21365,14 +21682,14 @@ __export(exports_mobile, {
21365
21682
  import { access as access16, mkdir as mkdir16, readFile as readFile24, writeFile as writeFile18 } from "fs/promises";
21366
21683
  import { join as join56, relative as relative31, resolve as resolve44 } from "path";
21367
21684
  import { createInterface } from "readline/promises";
21368
- var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
21685
+ var NOT_FOUND4 = -1, isRecord17 = (value) => typeof value === "object" && value !== null && !Array.isArray(value), CAPACITOR_PACKAGES, CAPACITOR_PACKAGE_SPECS, CAPACITOR_SYNC_PACKAGE_SPECS, packageNameFromSpec = (spec) => spec.slice(0, spec.lastIndexOf("@")), directProjectPackages = async (projectRoot) => {
21369
21686
  const manifest = JSON.parse(await readFile24(join56(projectRoot, "package.json"), "utf8"));
21370
- if (!isRecord15(manifest))
21687
+ if (!isRecord17(manifest))
21371
21688
  throw new TypeError("Application package.json must contain an object.");
21372
21689
  const names = new Set;
21373
21690
  for (const field of ["dependencies", "devDependencies"]) {
21374
21691
  const dependencies = Reflect.get(manifest, field);
21375
- if (isRecord15(dependencies))
21692
+ if (isRecord17(dependencies))
21376
21693
  for (const name of Object.keys(dependencies))
21377
21694
  names.add(name);
21378
21695
  }
@@ -21380,7 +21697,7 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
21380
21697
  }, resolvedPackageVersion = async (projectRoot, packageName) => {
21381
21698
  try {
21382
21699
  const manifest = JSON.parse(await readFile24(join56(projectRoot, "node_modules", packageName, "package.json"), "utf8"));
21383
- return isRecord15(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
21700
+ return isRecord17(manifest) && typeof manifest.version === "string" ? manifest.version : undefined;
21384
21701
  } catch {
21385
21702
  return;
21386
21703
  }
@@ -21429,13 +21746,13 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
21429
21746
  }
21430
21747
  }, runCapacitor = async (projectRoot, args) => {
21431
21748
  const executable = await capacitorExecutable(projectRoot);
21432
- const process2 = Bun.spawn([executable, ...args], {
21749
+ const subprocess = Bun.spawn([executable, ...args], {
21433
21750
  cwd: projectRoot,
21434
21751
  stderr: "inherit",
21435
21752
  stdin: "inherit",
21436
21753
  stdout: "inherit"
21437
21754
  });
21438
- const exitCode = await process2.exited;
21755
+ const exitCode = await subprocess.exited;
21439
21756
  if (exitCode !== 0) {
21440
21757
  throw new TypeError(`Capacitor exited with status ${exitCode}.`);
21441
21758
  }
@@ -21447,15 +21764,26 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
21447
21764
  } catch {
21448
21765
  throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
21449
21766
  }
21450
- }, runExpo = async (project, args) => {
21767
+ }, expoProductionEnvironment = () => {
21768
+ const env6 = { ...process.env };
21769
+ delete env6.ABSOLUTE_EXPO_DEVELOPMENT;
21770
+ delete env6.ABSOLUTE_EXPO_DEVELOPMENT_CA_PATH;
21771
+ delete env6.EXPO_PUBLIC_ABSOLUTE_DEV_ANDROID_ORIGIN;
21772
+ delete env6.EXPO_PUBLIC_ABSOLUTE_DEV_IOS_ORIGIN;
21773
+ env6.BABEL_ENV = "production";
21774
+ env6.NODE_ENV = "production";
21775
+ return env6;
21776
+ }, runExpo = async (project, args, options = {}) => {
21451
21777
  const executable = await expoExecutable(project);
21452
- const process2 = Bun.spawn([executable, ...args], {
21778
+ const env6 = options.production ? expoProductionEnvironment() : { ...process.env };
21779
+ const subprocess = Bun.spawn([executable, ...args], {
21453
21780
  cwd: project,
21781
+ env: env6,
21454
21782
  stderr: "inherit",
21455
21783
  stdin: "inherit",
21456
21784
  stdout: "inherit"
21457
21785
  });
21458
- const exitCode = await process2.exited;
21786
+ const exitCode = await subprocess.exited;
21459
21787
  if (exitCode !== 0)
21460
21788
  throw new TypeError(`Expo exited with status ${exitCode}.`);
21461
21789
  }, ensureExpoPackages = async (project, args) => {
@@ -21502,10 +21830,9 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
21502
21830
  }, requireCapacitorEngine = (mobile, command) => {
21503
21831
  if (mobile.engine === "capacitor")
21504
21832
  return;
21505
- throw new TypeError(`${command} is not available for the experimental Expo engine yet. Use mobile init/sync and Expo CLI from the generated shell; Capacitor remains the release-capable engine.`);
21833
+ throw new TypeError(`${command} is not available for the Expo engine yet.`);
21506
21834
  }, inspectMobile = async (args) => {
21507
21835
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
21508
- requireCapacitorEngine(mobile, "mobile inspect");
21509
21836
  const report = await inspectAbsoluteMobileProject(mobile, projectRoot, {
21510
21837
  absolutejsVersion: await absolutejsVersionForReport()
21511
21838
  });
@@ -21561,7 +21888,7 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
21561
21888
  }, initialize = async (args) => {
21562
21889
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
21563
21890
  if (mobile.engine === "expo") {
21564
- console.warn("Experimental: Expo Auth and authenticated HTTP are available; Sync, release publishing, and physical-device acceptance are not complete.");
21891
+ console.warn("Experimental: Expo Android builds and publishing are available; iOS releases and physical-device acceptance are not complete.");
21565
21892
  const generated2 = await writeAbsoluteExpoProject(mobile, {
21566
21893
  force: args.includes("--force"),
21567
21894
  projectRoot
@@ -21655,7 +21982,6 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
21655
21982
  throw new TypeError("Usage: absolute mobile ci github [server-entry] [--publish] [--registry module] [--secret-env NAME] [--output path] [--force] [--json] [--config path]");
21656
21983
  const configPath2 = valueAfter(args, "--config");
21657
21984
  const { mobile, projectRoot } = await loadMobile(configPath2);
21658
- requireCapacitorEngine(mobile, "mobile ci github");
21659
21985
  const result = await writeAbsoluteMobileGithubWorkflow({
21660
21986
  config: mobile,
21661
21987
  configPath: configPath2,
@@ -21721,7 +22047,6 @@ var NOT_FOUND4 = -1, isRecord15 = (value) => typeof value === "object" && value
21721
22047
  }
21722
22048
  }, runReleaseDoctor = async (args) => {
21723
22049
  const { mobile, projectRoot } = await loadMobile(valueAfter(args, "--config"));
21724
- requireCapacitorEngine(mobile, "mobile doctor release");
21725
22050
  const platform6 = args.find((value) => value === "android" || value === "ios");
21726
22051
  const effectiveMobile = platform6 ? { ...mobile, platforms: [platform6] } : mobile;
21727
22052
  const result = await inspectAbsoluteMobileRelease(effectiveMobile, projectRoot);
@@ -21903,31 +22228,37 @@ Mobile release security and compliance checks failed.`);
21903
22228
  keystorePath,
21904
22229
  storePasswordEnvironment: "ABSOLUTE_ANDROID_KEYSTORE_PASSWORD"
21905
22230
  };
21906
- }, buildAndroid = async (args, prepareVersionCode) => {
22231
+ }, prepareExpoAndroidReleaseProject = async (mobile, projectRoot, args) => {
22232
+ await writeAbsoluteExpoProject(mobile, { projectRoot });
22233
+ await ensureExpoPackages(mobile.nativeProjectDirectory, [...args, "--yes"]);
22234
+ await syncAbsoluteExpoWebAssets(mobile);
22235
+ await runExpo(mobile.nativeProjectDirectory, ["prebuild", "--clean", "--no-install", "--platform", "android"], { production: true });
22236
+ }, prepareCapacitorAndroidReleaseProject = async (mobile, projectRoot) => {
22237
+ await writeAbsoluteCapacitorConfig(mobile, { projectRoot });
22238
+ await runCapacitorForPlatforms(projectRoot, "sync", ["android"]);
22239
+ await applyAbsoluteNativeDeepLinks(mobile, ["android"]);
22240
+ await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile, [
22241
+ "android"
22242
+ ]);
22243
+ await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, ["android"]);
22244
+ }, prepareAndroidReleaseProject = (mobile, projectRoot, args) => mobile.engine === "expo" ? prepareExpoAndroidReleaseProject(mobile, projectRoot, args) : prepareCapacitorAndroidReleaseProject(mobile, projectRoot), buildAndroid = async (args, prepareVersionCode) => {
21907
22245
  const configPath2 = valueAfter(args, "--config");
21908
22246
  const { mobile, projectRoot } = await loadMobile(configPath2);
21909
- requireCapacitorEngine(mobile, "mobile build android");
21910
22247
  if (!mobile.platforms.includes("android")) {
21911
22248
  throw new TypeError("mobile build android requires android in mobile.platforms.");
21912
22249
  }
21913
22250
  const startedAt = performance.now();
21914
22251
  let success = false;
21915
22252
  try {
21916
- await repairAbsoluteAndroidDevSession(projectRoot);
22253
+ if (mobile.engine === "capacitor")
22254
+ await repairAbsoluteAndroidDevSession(projectRoot);
21917
22255
  await start(mobileBuildServerEntry(args), valueAfter(args, "--web-outdir"), configPath2, { prepareOnly: true });
21918
- await writeAbsoluteCapacitorConfig(mobile, { projectRoot });
21919
- await runCapacitorForPlatforms(projectRoot, "sync", ["android"]);
21920
- await applyAbsoluteNativeDeepLinks(mobile, ["android"]);
21921
- await applyAbsoluteNativeDeviceCapabilities(projectRoot, mobile, [
21922
- "android"
21923
- ]);
21924
- await applyAbsoluteNativeBackgroundSync(projectRoot, mobile, [
21925
- "android"
21926
- ]);
22256
+ await prepareAndroidReleaseProject(mobile, projectRoot, args);
21927
22257
  await requireAndroidReleaseReady(mobile, projectRoot);
21928
22258
  const release = await buildAbsoluteAndroidRelease({
21929
22259
  allowUnsigned: args.includes("--unsigned"),
21930
22260
  config: mobile,
22261
+ ...mobile.engine === "expo" ? { env: expoProductionEnvironment() } : {},
21931
22262
  outputDirectory: valueAfter(args, "--outdir"),
21932
22263
  projectRoot,
21933
22264
  signing: androidCiSigning(),
@@ -21942,7 +22273,7 @@ Mobile release security and compliance checks failed.`);
21942
22273
  } finally {
21943
22274
  sendTelemetryEvent("mobile:android-release-build", {
21944
22275
  durationMs: Math.round(performance.now() - startedAt),
21945
- engine: "capacitor",
22276
+ engine: mobile.engine,
21946
22277
  platform: "android",
21947
22278
  success,
21948
22279
  type: "aab",
@@ -21960,7 +22291,6 @@ Mobile release security and compliance checks failed.`);
21960
22291
  const configPath2 = valueAfter(args, "--config");
21961
22292
  const googlePlay = googlePlayTarget(args);
21962
22293
  const { mobile, projectRoot } = await loadMobile(configPath2);
21963
- requireCapacitorEngine(mobile, "mobile publish android");
21964
22294
  const startedAt = performance.now();
21965
22295
  let reused = false;
21966
22296
  let success = false;
@@ -22473,7 +22803,7 @@ Emulator setup verification:`);
22473
22803
  return;
22474
22804
  });
22475
22805
  const status2 = response?.ok ? await response.json().catch(() => null) : null;
22476
- const targets = isRecord15(status2) && isRecord15(status2.connectedTargets) ? status2.connectedTargets : undefined;
22806
+ const targets = isRecord17(status2) && isRecord17(status2.connectedTargets) ? status2.connectedTargets : undefined;
22477
22807
  if (targets && typeof targets["capacitor-ios"] === "number" && targets["capacitor-ios"] > 0)
22478
22808
  return;
22479
22809
  await Bun.sleep(100);