@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.
@@ -1653,6 +1653,7 @@ import {
1653
1653
  mkdir as mkdir3,
1654
1654
  mkdtemp as mkdtemp2,
1655
1655
  readFile as readFile4,
1656
+ realpath as realpath2,
1656
1657
  rename as rename4,
1657
1658
  rm as rm3,
1658
1659
  stat,
@@ -2283,6 +2284,18 @@ var signAab = (artifactPath, capture, jarsigner, signing) => {
2283
2284
  throw new TypeError("jarsigner could not sign the Android App Bundle with the configured CI identity.");
2284
2285
  };
2285
2286
  var sha256File = async (path) => createHash4("sha256").update(await readFile4(path)).digest("hex");
2287
+ var fingerprintExpoAndroidProject = async (nativeDirectory) => {
2288
+ const root = await realpath2(nativeDirectory);
2289
+ const files = await Array.fromAsync(new Bun.Glob("**/*").scan({ cwd: root, onlyFiles: true }));
2290
+ const records = await Promise.all(files.filter((path) => {
2291
+ const parts = path.replaceAll("\\", "/").split("/");
2292
+ return !parts.includes(".gradle") && !parts.includes("build");
2293
+ }).sort().map(async (path) => {
2294
+ const contents = await readFile4(join4(root, path));
2295
+ return `${path.replaceAll("\\", "/")}\x00${createHash4("sha256").update(contents).digest("hex")}\x00`;
2296
+ }));
2297
+ return createHash4("sha256").update(records.join("")).digest("hex");
2298
+ };
2286
2299
  var safeOutputDirectory = (projectRoot, requested) => {
2287
2300
  const root = resolve3(projectRoot);
2288
2301
  const output = resolve3(root, requested ?? ".absolutejs/mobile/releases/android");
@@ -2344,6 +2357,9 @@ var buildAbsoluteAndroidRelease = async (options) => {
2344
2357
  }
2345
2358
  const projectRoot = resolve3(options.projectRoot);
2346
2359
  const host = options.host ?? detectAbsoluteMobileHost();
2360
+ if (options.config.engine === "expo" && host === "wsl") {
2361
+ 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.");
2362
+ }
2347
2363
  const androidRoot = options.androidRoot ?? process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? absoluteManagedAndroidSdkRoot(host);
2348
2364
  const nativeDirectory = join4(options.config.nativeProjectDirectory, "android");
2349
2365
  const manifest = requireManifest(JSON.parse(await readFile4(join4(options.config.bundleDirectory, "absolute-mobile-manifest.json"), "utf8")));
@@ -2352,7 +2368,9 @@ var buildAbsoluteAndroidRelease = async (options) => {
2352
2368
  }
2353
2369
  let { versionCode } = options;
2354
2370
  if (options.prepareVersionCode) {
2355
- const nativeFingerprint = await fingerprintAbsoluteAndroidNativeProject({ nativeDirectory });
2371
+ const nativeFingerprint = options.config.engine === "expo" ? await fingerprintExpoAndroidProject(nativeDirectory) : await fingerprintAbsoluteAndroidNativeProject({
2372
+ nativeDirectory
2373
+ });
2356
2374
  const buildIdentity = createHash4("sha256").update(`${manifest.appBuild}\x00${nativeFingerprint}`).digest("hex");
2357
2375
  versionCode = await options.prepareVersionCode(buildIdentity);
2358
2376
  }
@@ -2361,6 +2379,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
2361
2379
  }
2362
2380
  const { artifactPath } = await buildAbsoluteAndroidGradleArtifact({
2363
2381
  capture: options.capture,
2382
+ env: options.env,
2364
2383
  gradleArguments: versionCode === undefined ? [] : [`-Pandroid.injected.version.code=${versionCode}`],
2365
2384
  project: {
2366
2385
  androidRoot,
@@ -2401,7 +2420,7 @@ var buildAbsoluteAndroidRelease = async (options) => {
2401
2420
  appBuild: manifest.appBuild,
2402
2421
  appId: manifest.appId,
2403
2422
  bytes,
2404
- engine: "capacitor",
2423
+ engine: options.config.engine,
2405
2424
  format: ABSOLUTE_ANDROID_RELEASE_FORMAT,
2406
2425
  platform: "android",
2407
2426
  releaseId,
@@ -5231,6 +5250,7 @@ var getCurrentAbsoluteMobileProducerContext = () => {
5231
5250
  // src/mobile/pageProtocol.ts
5232
5251
  var ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE = "application/vnd.absolute.page+json";
5233
5252
  var ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION = 1;
5253
+ var ABSOLUTE_NATIVE_ROUTE_DATA_MEDIA_TYPE = "application/vnd.absolute.native-route+json";
5234
5254
  var BAD_REQUEST_STATUS = 400;
5235
5255
  var OK_STATUS = 200;
5236
5256
  var SERVER_ERROR_STATUS = 500;
@@ -5249,6 +5269,11 @@ var acceptsAbsoluteMobilePage = (request) => {
5249
5269
  return false;
5250
5270
  return (request.headers.get("accept") ?? "").split(",").some((value) => parseMediaType(value) === ABSOLUTE_MOBILE_PAGE_MEDIA_TYPE);
5251
5271
  };
5272
+ var acceptsAbsoluteNativeRouteData = (request) => {
5273
+ if (!request)
5274
+ return false;
5275
+ return (request.headers.get("accept") ?? "").split(",").some((value) => parseMediaType(value) === ABSOLUTE_NATIVE_ROUTE_DATA_MEDIA_TYPE);
5276
+ };
5252
5277
  var readRequiredHeader = (request, name) => {
5253
5278
  const value = request.headers.get(name)?.trim();
5254
5279
  return value ? value : undefined;
@@ -5379,6 +5404,32 @@ var finalizeArchivedMobilePage = (context, input) => {
5379
5404
  return createAbsoluteMobilePageErrorResponse(page.pageId);
5380
5405
  }
5381
5406
  };
5407
+ var finalizeAbsoluteNativeRouteDevelopmentPage = (input) => {
5408
+ if (false) {}
5409
+ if (input.request?.method !== "GET") {
5410
+ return createAbsoluteMobileInvalidRequestResponse("Native-route data requests must use GET.");
5411
+ }
5412
+ if (input.request.headers.get(MOBILE_PAGE_REQUEST_HEADERS.protocol) !== String(ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION)) {
5413
+ return createAbsoluteMobileInvalidRequestResponse(`Native-route data requires protocol ${ABSOLUTE_MOBILE_PAGE_PROTOCOL_VERSION}.`);
5414
+ }
5415
+ const [representation] = input.compatibility.representations;
5416
+ if (!representation) {
5417
+ return createAbsoluteMobilePageErrorResponse(input.compatibility.pageId);
5418
+ }
5419
+ try {
5420
+ return envelopeResponse({
5421
+ contract: representation.contract,
5422
+ framework: input.compatibility.framework,
5423
+ kind: "page",
5424
+ pageId: input.compatibility.pageId,
5425
+ props: normalizeJsonValue(representation.mapProps(input.props)),
5426
+ status: input.status ?? OK_STATUS
5427
+ }, input.status ?? OK_STATUS);
5428
+ } catch (error) {
5429
+ console.error(`[Mobile] Failed to produce development native route ${input.compatibility.pageId}:`, error);
5430
+ return createAbsoluteMobilePageErrorResponse(input.compatibility.pageId);
5431
+ }
5432
+ };
5382
5433
  var createAbsoluteMobileInvalidRequestResponse = (message) => envelopeResponse({ kind: "invalid-request", message }, BAD_REQUEST_STATUS);
5383
5434
  var createAbsoluteMobilePageErrorResponse = (pageId) => envelopeResponse({
5384
5435
  code: "representation-failed",
@@ -5388,6 +5439,9 @@ var createAbsoluteMobilePageErrorResponse = (pageId) => envelopeResponse({
5388
5439
  }, SERVER_ERROR_STATUS);
5389
5440
  var createAbsoluteMobileUpgradeResponse = (result) => envelopeResponse(result, UPGRADE_REQUIRED_STATUS);
5390
5441
  var finalizeAbsoluteMobilePage = (input) => {
5442
+ if (acceptsAbsoluteNativeRouteData(input.request)) {
5443
+ return finalizeAbsoluteNativeRouteDevelopmentPage(input);
5444
+ }
5391
5445
  const parsed = parseAbsoluteMobilePageRequest(input.request);
5392
5446
  if (parsed.kind === "not-mobile")
5393
5447
  return;
@@ -6179,6 +6233,48 @@ var exists3 = async (path) => {
6179
6233
  return false;
6180
6234
  }
6181
6235
  };
6236
+ var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6237
+ var requireManifestString = (value, field2) => {
6238
+ if (typeof value !== "string" || !value) {
6239
+ throw new TypeError(`AbsoluteJS mobile manifest ${field2} is invalid.`);
6240
+ }
6241
+ return value;
6242
+ };
6243
+ var expoNativeDataManifest = (value) => {
6244
+ if (!isRecord8(value) || !Array.isArray(value.pages) || !Array.isArray(value.routes)) {
6245
+ throw new TypeError("AbsoluteJS mobile manifest is invalid.");
6246
+ }
6247
+ const pages = value.pages.map((page) => {
6248
+ if (!isRecord8(page))
6249
+ throw new TypeError("AbsoluteJS mobile manifest page is invalid.");
6250
+ return {
6251
+ bundleHash: requireManifestString(page.bundleHash, "page.bundleHash"),
6252
+ contract: requireManifestString(page.contract, "page.contract"),
6253
+ pageId: requireManifestString(page.pageId, "page.pageId")
6254
+ };
6255
+ });
6256
+ const routes = value.routes.flatMap((route) => {
6257
+ if (!isRecord8(route) || typeof route.method !== "string") {
6258
+ throw new TypeError("AbsoluteJS mobile manifest route is invalid.");
6259
+ }
6260
+ if (route.method !== "GET")
6261
+ return [];
6262
+ return [
6263
+ {
6264
+ method: "GET",
6265
+ pageId: requireManifestString(route.pageId, "route.pageId"),
6266
+ pattern: requireManifestString(route.pattern, "route.pattern")
6267
+ }
6268
+ ];
6269
+ });
6270
+ return {
6271
+ appBuild: requireManifestString(value.appBuild, "appBuild"),
6272
+ pages,
6273
+ productionOrigin: requireManifestString(value.productionOrigin, "productionOrigin"),
6274
+ routes,
6275
+ runtime: requireManifestString(value.runtime, "runtime")
6276
+ };
6277
+ };
6182
6278
  var portableRelative2 = (from, destination) => {
6183
6279
  const value = relative10(from, destination).replaceAll("\\", "/");
6184
6280
  return value.startsWith(".") ? value : `./${value}`;
@@ -6973,7 +7069,134 @@ var catchAllRouteSource = `${EXPO_GENERATED_HEADER}import { AbsoluteWebHost } fr
6973
7069
 
6974
7070
  export default AbsoluteWebHost;
6975
7071
  `;
6976
- var nativeWrapperSource = (wrapper, module) => `${EXPO_GENERATED_HEADER}export { default } from ${JSON.stringify(portableRelative2(dirname10(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
7072
+ var nativeRouteRuntimeSource = (auth) => `${EXPO_GENERATED_HEADER}import { useLocalSearchParams, usePathname } from 'expo-router';
7073
+ import { type ComponentType, useEffect, useMemo, useState } from 'react';
7074
+ import { ActivityIndicator, Platform, Pressable, StyleSheet, Text, View } from 'react-native';
7075
+ import { ABSOLUTE_MOBILE_MANIFEST } from './webAssets';
7076
+ ${auth ? "import { absoluteExpoAuth } from './AbsoluteAuth';" : ""}
7077
+
7078
+ const PAGE_MEDIA_TYPE = 'application/vnd.absolute.page+json';
7079
+ const NATIVE_DATA_MEDIA_TYPE = 'application/vnd.absolute.native-route+json';
7080
+ const PROTOCOL = 1;
7081
+ const MAX_DATA_BYTES = 1024 * 1024;
7082
+ const DEV_ORIGIN = Platform.OS === 'android'
7083
+ ? process.env.EXPO_PUBLIC_ABSOLUTE_DEV_ANDROID_ORIGIN
7084
+ : process.env.EXPO_PUBLIC_ABSOLUTE_DEV_IOS_ORIGIN;
7085
+
7086
+ type RouteParams = Record<string, string | string[] | undefined>;
7087
+ type NativeRouteProps<PageProps extends object, Params extends object> = {
7088
+ pageProps: Readonly<PageProps>;
7089
+ params: Readonly<Params>;
7090
+ reload: () => void;
7091
+ };
7092
+ type NativeRouteState<PageProps> =
7093
+ | { kind: 'loading' }
7094
+ | { kind: 'ready'; pageProps: PageProps }
7095
+ | { kind: 'error' };
7096
+
7097
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
7098
+ typeof value === 'object' && value !== null && !Array.isArray(value);
7099
+ const routeSegmentPattern = (segment: string) => {
7100
+ if (segment === '*') return '.*';
7101
+ if (segment.startsWith(':') && segment.endsWith('?')) return '[^/]*';
7102
+ if (segment.startsWith(':')) return '[^/]+';
7103
+ return segment.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&');
7104
+ };
7105
+ const matchesRoute = (pattern: string, pathname: string) =>
7106
+ new RegExp('^' + pattern.split('/').map(routeSegmentPattern).join('/') + '/?$').test(pathname);
7107
+ const requestPathFor = (pathname: string, params: RouteParams, pattern: string) => {
7108
+ const pathNames = new Set(pattern.split('/').filter(segment => segment.startsWith(':')).map(segment => segment.replace(/^:/, '').replace(/\\?$/, '')));
7109
+ if (pattern.endsWith('/*')) pathNames.add('absoluteWildcard');
7110
+ const query = new URLSearchParams();
7111
+ for (const [name, value] of Object.entries(params).sort(([left], [right]) => left.localeCompare(right))) {
7112
+ if (pathNames.has(name) || name === '#' || value === undefined) continue;
7113
+ for (const item of Array.isArray(value) ? value : [value]) query.append(name, item);
7114
+ }
7115
+ const search = query.toString();
7116
+ return pathname + (search ? '?' + search : '');
7117
+ };
7118
+ const productionPage = (pathname: string) => {
7119
+ if (!ABSOLUTE_MOBILE_MANIFEST) throw new Error('AbsoluteJS native route data is not prepared for production.');
7120
+ const route = ABSOLUTE_MOBILE_MANIFEST.routes.find(candidate => candidate.method === 'GET' && matchesRoute(candidate.pattern, pathname));
7121
+ if (!route) throw new Error('No trusted AbsoluteJS page route owns this native URL.');
7122
+ const page = ABSOLUTE_MOBILE_MANIFEST.pages.find(candidate => candidate.pageId === route.pageId);
7123
+ if (!page) throw new Error('The embedded AbsoluteJS page contract is incomplete.');
7124
+ return page;
7125
+ };
7126
+ const createDataRequest = (path: string) => {
7127
+ const development = typeof DEV_ORIGIN === 'string' && DEV_ORIGIN.length > 0;
7128
+ const origin = development ? DEV_ORIGIN : ABSOLUTE_MOBILE_MANIFEST?.productionOrigin;
7129
+ if (!origin) throw new Error('AbsoluteJS native route data has no trusted server origin.');
7130
+ const url = new URL(path, origin);
7131
+ if (url.origin !== new URL(origin).origin) throw new Error('AbsoluteJS native route data left its trusted origin.');
7132
+ const headers = new Headers();
7133
+ headers.set('accept', development ? NATIVE_DATA_MEDIA_TYPE : PAGE_MEDIA_TYPE);
7134
+ headers.set('x-absolute-mobile-protocol', String(PROTOCOL));
7135
+ let page: ReturnType<typeof productionPage> | undefined;
7136
+ if (!development) {
7137
+ page = productionPage(url.pathname);
7138
+ headers.set('x-absolute-mobile-app-build', ABSOLUTE_MOBILE_MANIFEST!.appBuild);
7139
+ headers.set('x-absolute-mobile-page-bundle', page.bundleHash);
7140
+ headers.set('x-absolute-mobile-page-contracts', page.contract);
7141
+ headers.set('x-absolute-mobile-page-id', page.pageId);
7142
+ headers.set('x-absolute-mobile-runtime', ABSOLUTE_MOBILE_MANIFEST!.runtime);
7143
+ }
7144
+ return { page, request: new Request(url, { headers, method: 'GET' }) };
7145
+ };
7146
+ const requestData = async <PageProps extends object>(path: string, signal: AbortSignal) => {
7147
+ const { page, request } = createDataRequest(path);
7148
+ const response = await ${auth ? "absoluteExpoAuth.fetchOptional" : "fetch"}(request, { redirect: 'manual', signal });
7149
+ 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.');
7150
+ const source = await response.text();
7151
+ if (new TextEncoder().encode(source).byteLength > MAX_DATA_BYTES) throw new Error('AbsoluteJS native route data exceeded 1 MiB.');
7152
+ let envelope: unknown;
7153
+ try { envelope = JSON.parse(source); } catch { throw new Error('The server did not return an AbsoluteJS page envelope.'); }
7154
+ if (!isRecord(envelope) || envelope.protocol !== PROTOCOL || !isRecord(envelope.response)) throw new Error('The server returned an invalid AbsoluteJS page envelope.');
7155
+ const result = envelope.response;
7156
+ if (result.kind === 'upgrade-required') throw new Error('This app version must be updated before opening this screen.');
7157
+ 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.');
7158
+ if (page && (result.pageId !== page.pageId || result.contract !== page.contract)) throw new Error('The server returned a different page contract than this app contains.');
7159
+ return result.props as PageProps;
7160
+ };
7161
+
7162
+ export const createAbsoluteNativeRoute = <
7163
+ PageProps extends object,
7164
+ Params extends object = RouteParams
7165
+ >(Component: ComponentType<NativeRouteProps<PageProps, Params>>, pattern: string) => {
7166
+ function AbsoluteNativeRouteScreen() {
7167
+ const pathname = usePathname() || '/';
7168
+ const params = useLocalSearchParams() as RouteParams;
7169
+ const parameterKey = JSON.stringify(params);
7170
+ const requestPath = useMemo(() => requestPathFor(pathname, params, pattern), [parameterKey, pathname]);
7171
+ const [revision, setRevision] = useState(0);
7172
+ const [state, setState] = useState<NativeRouteState<PageProps>>({ kind: 'loading' });
7173
+ useEffect(() => {
7174
+ const controller = new AbortController();
7175
+ setState({ kind: 'loading' });
7176
+ void requestData<PageProps>(requestPath, controller.signal).then(
7177
+ pageProps => { if (!controller.signal.aborted) setState({ kind: 'ready', pageProps }); },
7178
+ () => { if (!controller.signal.aborted) setState({ kind: 'error' }); }
7179
+ );
7180
+ return () => controller.abort();
7181
+ }, [requestPath, revision]);
7182
+ const reload = () => setRevision(value => value + 1);
7183
+ if (state.kind === 'loading') return <View style={styles.center}><ActivityIndicator accessibilityLabel="Loading screen" /></View>;
7184
+ 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>;
7185
+ return <Component pageProps={state.pageProps} params={params as Params} reload={reload} />;
7186
+ }
7187
+ return AbsoluteNativeRouteScreen;
7188
+ };
7189
+
7190
+ const styles = StyleSheet.create({
7191
+ button: { backgroundColor: '#e2e8f0', borderRadius: 10, paddingHorizontal: 16, paddingVertical: 12 },
7192
+ center: { alignItems: 'center', flex: 1, gap: 16, justifyContent: 'center', padding: 24 },
7193
+ error: { color: '#b91c1c', fontSize: 16, textAlign: 'center' }
7194
+ });
7195
+ `;
7196
+ var nativeWrapperSource = (wrapper, module, runtime, route) => `${EXPO_GENERATED_HEADER}import ApplicationNativeRoute from ${JSON.stringify(portableRelative2(dirname10(wrapper), module).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
7197
+ import { createAbsoluteNativeRoute } from ${JSON.stringify(portableRelative2(dirname10(wrapper), runtime).replace(/\.(?:[cm]?[jt]sx?)$/u, ""))};
7198
+
7199
+ export default createAbsoluteNativeRoute(ApplicationNativeRoute, ${JSON.stringify(route)});
6977
7200
  `;
6978
7201
  var expoTsConfig = (projectRoot, project, auth, sync) => ({
6979
7202
  compilerOptions: {
@@ -7040,9 +7263,19 @@ var pruneStaleManagedExpoRoutes = async (project, expected) => {
7040
7263
  };
7041
7264
  var jsonSource = (value) => `${JSON.stringify(value, null, "\t")}
7042
7265
  `;
7043
- var emptyWebAssetsSource = `${EXPO_GENERATED_HEADER}export const materializeAbsoluteWebBundle = async () => {
7266
+ var nativeDataManifestTypeSource = `type AbsoluteMobileManifest = {
7267
+ appBuild: string;
7268
+ pages: readonly { bundleHash: string; contract: string; pageId: string }[];
7269
+ productionOrigin: string;
7270
+ routes: readonly { method: 'GET'; pageId: string; pattern: string }[];
7271
+ runtime: string;
7272
+ };
7273
+ `;
7274
+ var emptyWebAssetsSource = `${EXPO_GENERATED_HEADER}${nativeDataManifestTypeSource}
7275
+ export const materializeAbsoluteWebBundle = async () => {
7044
7276
  throw new Error('The embedded AbsoluteJS bundle is unavailable. Run absolute prepare before a production Expo build.');
7045
7277
  };
7278
+ export const ABSOLUTE_MOBILE_MANIFEST: AbsoluteMobileManifest | undefined = undefined;
7046
7279
  `;
7047
7280
  var writeAbsoluteExpoProject = async (config, options) => {
7048
7281
  if (config.engine !== "expo")
@@ -7113,6 +7346,10 @@ node_modules/
7113
7346
  join14(project, "src", "generated", "AbsoluteDevices.ts"),
7114
7347
  devicesRuntimeSource(config, devices, authEnabled)
7115
7348
  ],
7349
+ [
7350
+ join14(project, "src", "generated", "AbsoluteNativeRoute.tsx"),
7351
+ nativeRouteRuntimeSource(authEnabled)
7352
+ ],
7116
7353
  [
7117
7354
  join14(project, "src", "generated", "AbsoluteWebHost.tsx"),
7118
7355
  webHostSource(config, auth, syncEnabled)
@@ -7132,9 +7369,10 @@ node_modules/
7132
7369
  files.set(join14(project, "app", "index.tsx"), webRouteSource);
7133
7370
  }
7134
7371
  files.set(join14(project, "app", "[...absolute].tsx"), catchAllRouteSource);
7372
+ const nativeRouteRuntime = join14(project, "src", "generated", "AbsoluteNativeRoute.tsx");
7135
7373
  for (const [route, module] of routeModules) {
7136
7374
  const wrapper = route === "/" ? join14(project, "app", "index.tsx") : routeFile(project, route);
7137
- files.set(wrapper, nativeWrapperSource(wrapper, module));
7375
+ files.set(wrapper, nativeWrapperSource(wrapper, module, nativeRouteRuntime, route));
7138
7376
  }
7139
7377
  const removed = await pruneStaleManagedExpoRoutes(project, new Set([...files.keys()].filter((path) => path.startsWith(`${join14(project, "app")}${sep6}`))));
7140
7378
  const changes = await Promise.all([...files].map(([path, source]) => writeManagedFile(path, source, true)));
@@ -7177,11 +7415,13 @@ var installStagedDirectory = async (staging, destination) => {
7177
7415
  throw error;
7178
7416
  }
7179
7417
  };
7180
- var assetModuleSource = (assets, bundleId) => `${EXPO_GENERATED_HEADER}import { Asset } from 'expo-asset';
7418
+ var assetModuleSource = (assets, bundleId, manifest) => `${EXPO_GENERATED_HEADER}import { Asset } from 'expo-asset';
7181
7419
  import { Directory, File, Paths } from 'expo-file-system';
7182
7420
 
7421
+ ${nativeDataManifestTypeSource}
7183
7422
  declare const require: (path: string) => number;
7184
7423
  const BUNDLE_ID = ${JSON.stringify(bundleId)};
7424
+ export const ABSOLUTE_MOBILE_MANIFEST: AbsoluteMobileManifest = ${JSON.stringify(manifest)};
7185
7425
  const ASSETS = [
7186
7426
  ${assets.map(({ asset, path }) => ` { module: require(${JSON.stringify(asset)}), path: ${JSON.stringify(path)} }`).join(`,
7187
7427
  `)}
@@ -7215,9 +7455,8 @@ var syncAbsoluteExpoWebAssets = async (config) => {
7215
7455
  }
7216
7456
  const manifestPath = join14(config.bundleDirectory, "absolute-mobile-manifest.json");
7217
7457
  const manifest = JSON.parse(await readFile14(manifestPath, "utf8"));
7218
- const appBuild = typeof manifest === "object" && manifest !== null && typeof Reflect.get(manifest, "appBuild") === "string" ? String(Reflect.get(manifest, "appBuild")) : undefined;
7219
- if (!appBuild)
7220
- throw new TypeError("AbsoluteJS mobile manifest has no appBuild.");
7458
+ const nativeDataManifest = expoNativeDataManifest(manifest);
7459
+ const { appBuild } = nativeDataManifest;
7221
7460
  const files = await walkFiles(config.bundleDirectory);
7222
7461
  const bundleHash = createHash10("sha256");
7223
7462
  const filesWithContents = await Promise.all(files.map(async (file) => ({ contents: await readFile14(file), file })));
@@ -7247,7 +7486,7 @@ var syncAbsoluteExpoWebAssets = async (config) => {
7247
7486
  throw error;
7248
7487
  }
7249
7488
  const generated = join14(config.nativeProjectDirectory, "src", "generated", "webAssets.ts");
7250
- await writeManagedFile(generated, assetModuleSource(assets, bundleId), true);
7489
+ await writeManagedFile(generated, assetModuleSource(assets, bundleId, nativeDataManifest), true);
7251
7490
  return { appBuild, assets: assets.length, bundleId, path: destination };
7252
7491
  };
7253
7492
 
@@ -7564,7 +7803,7 @@ var ABSOLUTE_EXPO_BRIDGE_METHODS = [
7564
7803
  "sync.socket.open",
7565
7804
  "sync.socket.sendChunk"
7566
7805
  ];
7567
- var isRecord8 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
7806
+ var isRecord9 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
7568
7807
  var validId = (value) => typeof value === "string" && /^[A-Za-z0-9_-]{1,80}$/u.test(value);
7569
7808
  var validPath = (value) => typeof value === "string" && value.startsWith("/") && !value.startsWith("//") && value.length <= 4096;
7570
7809
  var parseRequest = (value) => {
@@ -7574,7 +7813,7 @@ var parseRequest = (value) => {
7574
7813
  if (!method) {
7575
7814
  throw new TypeError("Expo bridge method is not allowed.");
7576
7815
  }
7577
- if (!isRecord8(value.params))
7816
+ if (!isRecord9(value.params))
7578
7817
  throw new TypeError("Expo bridge request params must be an object.");
7579
7818
  if (typeof value.path !== "string" || !validPath(value.path))
7580
7819
  throw new TypeError("Expo bridge request path is invalid.");
@@ -7610,7 +7849,7 @@ var parseRequest = (value) => {
7610
7849
  throw new TypeError("Expo bridge HTTP body exceeds 48 KiB.");
7611
7850
  if ((value.params.method === "GET" || value.params.method === "DELETE") && value.params.body !== undefined)
7612
7851
  throw new TypeError("Expo bridge HTTP method cannot contain a body.");
7613
- if (!isRecord8(value.params.headers))
7852
+ if (!isRecord9(value.params.headers))
7614
7853
  throw new TypeError("Expo bridge HTTP headers must be an object.");
7615
7854
  const headersAllowed = Object.entries(value.params.headers).every(([name, header]) => typeof header === "string" && (name.toLowerCase() === "accept" || name.toLowerCase() === "content-type" || name.toLowerCase().startsWith("x-absolute-mobile-")));
7616
7855
  if (!headersAllowed) {
@@ -7636,13 +7875,13 @@ var parseResponse = (value) => {
7636
7875
  if (typeof value.id !== "string" || !validId(value.id))
7637
7876
  throw new TypeError("Expo bridge response id is invalid.");
7638
7877
  if (value.error !== undefined) {
7639
- if (!isRecord8(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string") {
7878
+ if (!isRecord9(value.error) || typeof value.error.code !== "string" || typeof value.error.message !== "string") {
7640
7879
  throw new TypeError("Expo bridge response error is invalid.");
7641
7880
  }
7642
7881
  if (value.result !== undefined)
7643
7882
  throw new TypeError("Expo bridge response cannot contain result and error.");
7644
7883
  }
7645
- if (isRecord8(value.error)) {
7884
+ if (isRecord9(value.error)) {
7646
7885
  return {
7647
7886
  error: {
7648
7887
  code: String(value.error.code),
@@ -7666,7 +7905,7 @@ var parseEvent = (value) => {
7666
7905
  }
7667
7906
  if (typeof value.path !== "string" || !validPath(value.path))
7668
7907
  throw new TypeError("Expo bridge event path is invalid.");
7669
- if (value.payload !== undefined && !isRecord8(value.payload)) {
7908
+ if (value.payload !== undefined && !isRecord9(value.payload)) {
7670
7909
  throw new TypeError("Expo bridge event payload must be an object.");
7671
7910
  }
7672
7911
  return {
@@ -7700,7 +7939,7 @@ var parseAbsoluteExpoBridgeMessage = (source) => {
7700
7939
  cause
7701
7940
  });
7702
7941
  }
7703
- if (!isRecord8(parsed) || parsed.format !== ABSOLUTE_EXPO_BRIDGE_FORMAT) {
7942
+ if (!isRecord9(parsed) || parsed.format !== ABSOLUTE_EXPO_BRIDGE_FORMAT) {
7704
7943
  throw new TypeError("Expo bridge message format is unsupported.");
7705
7944
  }
7706
7945
  if (parsed.kind === "request")
@@ -8467,8 +8706,10 @@ var requiredSecrets = (platforms, includePublishing, custom) => [
8467
8706
  ];
8468
8707
  var createAbsoluteMobileGithubWorkflow = (options) => {
8469
8708
  const platforms = [
8470
- ...options.config.platforms
8709
+ ...options.config.engine === "expo" ? options.config.platforms.filter((platform) => platform === "android") : options.config.platforms
8471
8710
  ].sort();
8711
+ if (platforms.length === 0)
8712
+ throw new TypeError("Generated Expo production CI currently requires android in mobile.platforms; Expo iOS release automation is the next checkpoint.");
8472
8713
  const includePublishing = options.includePublishing === true;
8473
8714
  const customSecrets = normalizeSecretEnvironment(options.secretEnvironment);
8474
8715
  const serverEntry = projectPath(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
@@ -8544,7 +8785,7 @@ var writeAbsoluteMobileGithubWorkflow = async (options) => {
8544
8785
  changed: previous !== generated.workflow,
8545
8786
  format: ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT,
8546
8787
  path,
8547
- platforms: [...options.config.platforms].sort(),
8788
+ platforms: options.config.engine === "expo" ? options.config.platforms.filter((platform) => platform === "android") : [...options.config.platforms].sort(),
8548
8789
  publishing: options.includePublishing === true,
8549
8790
  requiredSecrets: generated.requiredSecrets
8550
8791
  };
@@ -8698,7 +8939,7 @@ init_telemetryEvent();
8698
8939
  import { Elysia as Elysia3 } from "elysia";
8699
8940
  var ABSOLUTE_MOBILE_PREVIEW_PATH = "/__absolute/mobile-preview";
8700
8941
  var escapeHtml2 = (value) => value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
8701
- var isRecord9 = (value) => typeof value === "object" && value !== null;
8942
+ var isRecord10 = (value) => typeof value === "object" && value !== null;
8702
8943
  var normalizeEntry2 = (entry) => {
8703
8944
  const parsed = new URL(entry ?? "/", "https://absolute.invalid");
8704
8945
  return `${parsed.pathname}${parsed.search}${parsed.hash}`;
@@ -8749,7 +8990,7 @@ var createAbsoluteMobilePreviewPlugin = (mobile) => {
8749
8990
  "X-Robots-Tag": "noindex, nofollow"
8750
8991
  }
8751
8992
  })).post("/__absolute/mobile-preview-telemetry", ({ body, status }) => {
8752
- const value = isRecord9(body) ? body : undefined;
8993
+ const value = isRecord10(body) ? body : undefined;
8753
8994
  const durationMs = value?.durationMs;
8754
8995
  const platform2 = value?.platform;
8755
8996
  if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0 || durationMs > 300000 || platform2 !== "android" && platform2 !== "ios") {
@@ -9306,8 +9547,8 @@ var prepareAbsoluteAndroidRelease = async (publisher, options) => {
9306
9547
  }
9307
9548
  return versionCode;
9308
9549
  };
9309
- var isRecord10 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9310
- var isPublisher = (value) => isRecord10(value) && typeof value.publish === "function";
9550
+ var isRecord11 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
9551
+ var isPublisher = (value) => isRecord11(value) && typeof value.publish === "function";
9311
9552
  var publisherModulePath = (projectRoot, requested) => {
9312
9553
  const root = resolve15(projectRoot);
9313
9554
  const path = resolve15(root, requested);
@@ -9323,7 +9564,7 @@ var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath
9323
9564
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
9324
9565
  });
9325
9566
  const loaded = await import(pathToFileURL3(modulePath).href);
9326
- const publisher = isRecord10(loaded) ? loaded.default ?? loaded.registry : undefined;
9567
+ const publisher = isRecord11(loaded) ? loaded.default ?? loaded.registry : undefined;
9327
9568
  if (!isPublisher(publisher)) {
9328
9569
  throw new TypeError("Native release registry module must default-export a registry with publish(options).");
9329
9570
  }
@@ -10139,6 +10380,7 @@ export {
10139
10380
  ABSOLUTE_MOBILE_TRANSFORM_PROTOCOL,
10140
10381
  ABSOLUTE_NATIVE_AUTH_CLIENTS_ENV,
10141
10382
  ABSOLUTE_NATIVE_AUTH_SCOPES,
10383
+ ABSOLUTE_NATIVE_ROUTE_DATA_MEDIA_TYPE,
10142
10384
  ABSOLUTE_REMOTE_MAC_EVENT_PREFIX,
10143
10385
  ABSOLUTE_REMOTE_MAC_PROTOCOL_VERSION,
10144
10386
  ABSOLUTE_SYNC_PACKAGE,
@@ -10153,6 +10395,7 @@ export {
10153
10395
  absoluteRemoteMacSshBase,
10154
10396
  absoluteRemoteProjectSyncCommands,
10155
10397
  acceptsAbsoluteMobilePage,
10398
+ acceptsAbsoluteNativeRouteData,
10156
10399
  activateAbsoluteMobilePage,
10157
10400
  applyAbsoluteNativeDeepLinks,
10158
10401
  applyAbsoluteNativeDeviceCapabilities,
@@ -10272,5 +10515,5 @@ export {
10272
10515
  writeAbsoluteMobileGithubWorkflow
10273
10516
  };
10274
10517
 
10275
- //# debugId=CFD74BC8ED7A634C64756E2164756E21
10518
+ //# debugId=9EFFDF4D91097A2464756E2164756E21
10276
10519
  //# sourceMappingURL=index.js.map