@absolutejs/absolute 0.20.0-beta.40 → 0.20.0-beta.42

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.
@@ -862,7 +862,7 @@ var init_deviceCapabilities = __esm(() => {
862
862
  // src/cli/scripts/telemetry.ts
863
863
  import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync5, writeFileSync } from "fs";
864
864
  import { homedir as homedir3 } from "os";
865
- import { join as join16 } from "path";
865
+ import { join as join17 } from "path";
866
866
  var configDir, configPath, getTelemetryConfig = () => {
867
867
  try {
868
868
  if (!existsSync4(configPath))
@@ -875,14 +875,14 @@ var configDir, configPath, getTelemetryConfig = () => {
875
875
  }
876
876
  };
877
877
  var init_telemetry = __esm(() => {
878
- configDir = join16(homedir3(), ".absolutejs");
879
- configPath = join16(configDir, "telemetry.json");
878
+ configDir = join17(homedir3(), ".absolutejs");
879
+ configPath = join17(configDir, "telemetry.json");
880
880
  });
881
881
 
882
882
  // src/cli/telemetryEvent.ts
883
883
  import { existsSync as existsSync5, readFileSync as readFileSync6 } from "fs";
884
884
  import { arch, platform } from "os";
885
- import { dirname as dirname12, join as join17, parse } from "path";
885
+ import { dirname as dirname12, join as join18, parse } from "path";
886
886
  var checkCandidate = (candidate) => {
887
887
  if (!existsSync5(candidate)) {
888
888
  return null;
@@ -902,7 +902,7 @@ var checkCandidate = (candidate) => {
902
902
  }, findPackageVersion = () => {
903
903
  let { dir } = import.meta;
904
904
  while (dir !== parse(dir).root) {
905
- const candidate = join17(dir, "package.json");
905
+ const candidate = join18(dir, "package.json");
906
906
  const version = checkCandidate(candidate);
907
907
  if (version) {
908
908
  return version;
@@ -6037,6 +6037,7 @@ var expoPackage = () => ({
6037
6037
  expo: "~57.0.9",
6038
6038
  "expo-asset": "~57.0.15",
6039
6039
  "expo-constants": "~57.0.16",
6040
+ "expo-dev-client": "~57.0.16",
6040
6041
  "expo-file-system": "~57.0.6",
6041
6042
  "expo-haptics": "~57.0.2",
6042
6043
  "expo-linking": "~57.0.8",
@@ -6079,13 +6080,67 @@ var expoAppConfig = (config) => ({
6079
6080
  ...config.iosVersion ? { buildNumber: config.iosVersion } : {}
6080
6081
  },
6081
6082
  name: config.appName,
6082
- plugins: ["expo-router"],
6083
+ plugins: [
6084
+ "expo-router",
6085
+ ["expo-dev-client", { launchMode: "most-recent" }]
6086
+ ],
6083
6087
  runtimeVersion: { policy: "appVersion" },
6084
6088
  scheme: config.deepLinkScheme,
6085
6089
  slug: config.appId.toLowerCase().replaceAll(".", "-"),
6086
6090
  version: config.iosVersion ?? "0.1.0"
6087
6091
  }
6088
6092
  });
6093
+ var expoDynamicAppConfig = `${EXPO_GENERATED_HEADER}const config = require('./app.json');
6094
+
6095
+ if (process.env.ABSOLUTE_EXPO_DEVELOPMENT === '1' && process.env.ABSOLUTE_EXPO_DEVELOPMENT_CA_PATH) {
6096
+ config.expo.plugins = [
6097
+ ...config.expo.plugins,
6098
+ './plugins/withAbsoluteDevelopmentCa'
6099
+ ];
6100
+ }
6101
+
6102
+ module.exports = config;
6103
+ `;
6104
+ var expoDevelopmentCaPlugin = `${EXPO_GENERATED_HEADER}const { AndroidConfig, withAndroidManifest, withDangerousMod } = require('expo/config-plugins');
6105
+ const { copyFile, mkdir } = require('node:fs/promises');
6106
+ const path = require('node:path');
6107
+
6108
+ const NETWORK_CONFIG = [
6109
+ '<?xml version="1.0" encoding="utf-8"?>',
6110
+ '<network-security-config>',
6111
+ ' <debug-overrides>',
6112
+ ' <trust-anchors>',
6113
+ ' <certificates src="@raw/absolutejs_dev_ca" />',
6114
+ ' </trust-anchors>',
6115
+ ' </debug-overrides>',
6116
+ '</network-security-config>',
6117
+ ''
6118
+ ].join('\\n');
6119
+
6120
+ const withAbsoluteDevelopmentCa = config => {
6121
+ config = withAndroidManifest(config, value => {
6122
+ const application = AndroidConfig.Manifest.getMainApplicationOrThrow(value.modResults);
6123
+ application.$['android:networkSecurityConfig'] = '@xml/absolutejs_dev_network_security';
6124
+ return value;
6125
+ });
6126
+ return withDangerousMod(config, ['android', async value => {
6127
+ const certificate = process.env.ABSOLUTE_EXPO_DEVELOPMENT_CA_PATH;
6128
+ if (!certificate) throw new Error('AbsoluteJS Expo HTTPS development requires its development CA path.');
6129
+ const resources = path.join(value.modRequest.platformProjectRoot, 'app', 'src', 'main', 'res');
6130
+ await Promise.all([
6131
+ mkdir(path.join(resources, 'raw'), { recursive: true }),
6132
+ mkdir(path.join(resources, 'xml'), { recursive: true })
6133
+ ]);
6134
+ await Promise.all([
6135
+ copyFile(certificate, path.join(resources, 'raw', 'absolutejs_dev_ca.pem')),
6136
+ require('node:fs/promises').writeFile(path.join(resources, 'xml', 'absolutejs_dev_network_security.xml'), NETWORK_CONFIG)
6137
+ ]);
6138
+ return value;
6139
+ }]);
6140
+ };
6141
+
6142
+ module.exports = withAbsoluteDevelopmentCa;
6143
+ `;
6089
6144
  var metroConfig = (projectRoot) => `${EXPO_GENERATED_HEADER}const { getDefaultConfig } = require('expo/metro-config');
6090
6145
  const path = require('node:path');
6091
6146
 
@@ -6147,7 +6202,7 @@ var webHostSource = (config) => {
6147
6202
  import * as Linking from 'expo-linking';
6148
6203
  import { router, usePathname } from 'expo-router';
6149
6204
  import { useEffect, useRef, useState } from 'react';
6150
- import { ActivityIndicator, BackHandler, StyleSheet, View } from 'react-native';
6205
+ import { ActivityIndicator, BackHandler, Platform, StyleSheet, View } from 'react-native';
6151
6206
  import { WebView, type WebViewMessageEvent } from 'react-native-webview';
6152
6207
  import { materializeAbsoluteWebBundle } from './webAssets';
6153
6208
 
@@ -6156,11 +6211,19 @@ const MAX_MESSAGE_BYTES = 64 * 1024;
6156
6211
  const MAX_HTTP_BODY_BYTES = 48 * 1024;
6157
6212
  const NATIVE_ROUTES = new Set(${JSON.stringify(nativeRoutes)});
6158
6213
  const PRODUCTION_ORIGIN = ${JSON.stringify(config.productionOrigin)};
6214
+ const DEV_ORIGIN = Platform.OS === 'android'
6215
+ ? process.env.EXPO_PUBLIC_ABSOLUTE_DEV_ANDROID_ORIGIN
6216
+ : process.env.EXPO_PUBLIC_ABSOLUTE_DEV_IOS_ORIGIN;
6217
+ const HMR_TARGET = Platform.OS === 'android' ? 'expo-android' : 'expo-ios';
6159
6218
 
6160
- const bridgeBootstrap = (path: string) => \`(() => {
6219
+ const bridgeBootstrap = (path: string) => {
6220
+ const initialPath = DEV_ORIGIN
6221
+ ? 'location.pathname + location.search + location.hash'
6222
+ : JSON.stringify(path);
6223
+ return \`(() => {
6161
6224
  const pending = new Map();
6162
6225
  let sequence = 0;
6163
- let currentPath = \${JSON.stringify(path)};
6226
+ let currentPath = \${initialPath};
6164
6227
  const send = value => {
6165
6228
  const source = JSON.stringify(value);
6166
6229
  if (new TextEncoder().encode(source).byteLength > 65536) throw new Error('Expo bridge message exceeds 64 KiB.');
@@ -6191,6 +6254,24 @@ const bridgeBootstrap = (path: string) => \`(() => {
6191
6254
  send({ format: 1, kind: 'event', event: 'navigation', path });
6192
6255
  }
6193
6256
  };
6257
+ if (\${DEV_ORIGIN ? 'true' : 'false'}) {
6258
+ const publishPath = () => {
6259
+ const path = location.pathname + location.search + location.hash;
6260
+ if (path === currentPath) return;
6261
+ currentPath = path;
6262
+ send({ format: 1, kind: 'event', event: 'navigation', path });
6263
+ };
6264
+ for (const method of ['pushState', 'replaceState']) {
6265
+ const original = history[method];
6266
+ history[method] = function(...args) {
6267
+ const result = original.apply(this, args);
6268
+ publishPath();
6269
+ return result;
6270
+ };
6271
+ }
6272
+ addEventListener('popstate', publishPath);
6273
+ addEventListener('hashchange', publishPath);
6274
+ }
6194
6275
  document.addEventListener('click', event => {
6195
6276
  const anchor = event.target instanceof Element ? event.target.closest('a[href]') : null;
6196
6277
  if (!anchor) return;
@@ -6199,8 +6280,9 @@ const bridgeBootstrap = (path: string) => \`(() => {
6199
6280
  event.preventDefault();
6200
6281
  send({ format: 1, kind: 'event', event: 'navigation', path: url.pathname + url.search + url.hash });
6201
6282
  }, true);
6202
- send({ format: 1, kind: 'event', event: 'ready', path: \${JSON.stringify(path)} });
6283
+ send({ format: 1, kind: 'event', event: 'ready', path: currentPath });
6203
6284
  })(); true;\`;
6285
+ };
6204
6286
 
6205
6287
  const impact = async (params: Record<string, unknown>) => {
6206
6288
  const style = params.style;
@@ -6242,7 +6324,15 @@ export function AbsoluteWebHost() {
6242
6324
  const [canGoBack, setCanGoBack] = useState(false);
6243
6325
  const activeWebPath = useRef(pathname);
6244
6326
 
6245
- useEffect(() => { void materializeAbsoluteWebBundle().then(setIndexUri); }, []);
6327
+ useEffect(() => {
6328
+ if (DEV_ORIGIN) {
6329
+ const target = new URL(pathname, DEV_ORIGIN);
6330
+ target.searchParams.set('__absolute_target', HMR_TARGET);
6331
+ setIndexUri(target.href);
6332
+ return;
6333
+ }
6334
+ void materializeAbsoluteWebBundle().then(uri => setIndexUri(uri + '?absolutePath=' + encodeURIComponent(pathname)));
6335
+ }, [pathname]);
6246
6336
  useEffect(() => {
6247
6337
  const subscription = BackHandler.addEventListener('hardwareBackPress', () => {
6248
6338
  if (!canGoBack) return false;
@@ -6257,6 +6347,9 @@ export function AbsoluteWebHost() {
6257
6347
  if (new TextEncoder().encode(source).byteLength > MAX_MESSAGE_BYTES) throw new Error('Expo bridge response exceeds 64 KiB.');
6258
6348
  webView.current?.injectJavaScript(\`globalThis.__absoluteExpoReceive(\${JSON.stringify(source)}); true;\`);
6259
6349
  };
6350
+ const hasOrigin = (source: string, origin: string) => {
6351
+ try { return new URL(source).origin === origin; } catch { return false; }
6352
+ };
6260
6353
  const onMessage = async (event: WebViewMessageEvent) => {
6261
6354
  const source = event.nativeEvent.data;
6262
6355
  if (new TextEncoder().encode(source).byteLength > MAX_MESSAGE_BYTES) return;
@@ -6298,12 +6391,12 @@ export function AbsoluteWebHost() {
6298
6391
  onMessage={onMessage}
6299
6392
  onNavigationStateChange={state => setCanGoBack(state.canGoBack)}
6300
6393
  onShouldStartLoadWithRequest={request => {
6301
- if (request.url.startsWith('file:') || request.url.startsWith(PRODUCTION_ORIGIN)) return true;
6394
+ if (request.url.startsWith('file:') || hasOrigin(request.url, PRODUCTION_ORIGIN) || DEV_ORIGIN && hasOrigin(request.url, DEV_ORIGIN)) return true;
6302
6395
  void Linking.openURL(request.url);
6303
6396
  return false;
6304
6397
  }}
6305
6398
  ref={webView}
6306
- source={{ uri: indexUri + '?absolutePath=' + encodeURIComponent(pathname) }}
6399
+ source={{ uri: indexUri }}
6307
6400
  style={styles.web}
6308
6401
  />;
6309
6402
  }
@@ -6352,6 +6445,10 @@ var writeManagedFile = async (path, source, force) => {
6352
6445
  };
6353
6446
  var jsonSource = (value) => `${JSON.stringify(value, null, "\t")}
6354
6447
  `;
6448
+ var emptyWebAssetsSource = `${EXPO_GENERATED_HEADER}export const materializeAbsoluteWebBundle = async () => {
6449
+ throw new Error('The embedded AbsoluteJS bundle is unavailable. Run absolute prepare before a production Expo build.');
6450
+ };
6451
+ `;
6355
6452
  var writeAbsoluteExpoProject = async (config, options) => {
6356
6453
  if (config.engine !== "expo")
6357
6454
  throw new TypeError("Expo project generation requires mobile.engine: expo.");
@@ -6387,8 +6484,13 @@ node_modules/
6387
6484
  `
6388
6485
  ],
6389
6486
  [join14(project, "app.json"), jsonSource(expoAppConfig(config))],
6487
+ [join14(project, "app.config.js"), expoDynamicAppConfig],
6390
6488
  [join14(project, "package.json"), jsonSource(expoPackage())],
6391
6489
  [join14(project, "metro.config.js"), metroConfig(projectRoot)],
6490
+ [
6491
+ join14(project, "plugins", "withAbsoluteDevelopmentCa.js"),
6492
+ expoDevelopmentCaPlugin
6493
+ ],
6392
6494
  [
6393
6495
  join14(project, "tsconfig.json"),
6394
6496
  jsonSource(expoTsConfig(projectRoot, project))
@@ -6403,6 +6505,10 @@ node_modules/
6403
6505
  webHostSource(config)
6404
6506
  ]
6405
6507
  ]);
6508
+ const webAssetsPath = join14(project, "src", "generated", "webAssets.ts");
6509
+ if (!await exists3(webAssetsPath)) {
6510
+ files.set(webAssetsPath, emptyWebAssetsSource);
6511
+ }
6406
6512
  if (!config.expoNativeRoutes["/"]) {
6407
6513
  files.set(join14(project, "app", "index.tsx"), webRouteSource);
6408
6514
  }
@@ -6902,9 +7008,341 @@ var parseAbsoluteExpoBridgeMessage = (source) => {
6902
7008
  return parseEvent(parsed);
6903
7009
  throw new TypeError("Expo bridge message kind is unsupported.");
6904
7010
  };
7011
+ // src/mobile/expoDevController.ts
7012
+ import { spawn } from "child_process";
7013
+ import { access as access10 } from "fs/promises";
7014
+ import { join as join16 } from "path";
7015
+ var METRO_READY_TIMEOUT_MS = 60000;
7016
+ var PROCESS_CLOSE_TIMEOUT_MS = 2000;
7017
+ var commandEnvironment = (options) => ({
7018
+ ABSOLUTE_EXPO_DEVELOPMENT: "1",
7019
+ ...options.certificateAuthorityPath ? {
7020
+ ABSOLUTE_EXPO_DEVELOPMENT_CA_PATH: options.certificateAuthorityPath
7021
+ } : {},
7022
+ ...options.androidOrigin ? { EXPO_PUBLIC_ABSOLUTE_DEV_ANDROID_ORIGIN: options.androidOrigin } : {},
7023
+ ...options.iosOrigin ? { EXPO_PUBLIC_ABSOLUTE_DEV_IOS_ORIGIN: options.iosOrigin } : {}
7024
+ });
7025
+ var absoluteExpoExecutable = async (project) => {
7026
+ const executable = join16(project, "node_modules", ".bin", "expo");
7027
+ try {
7028
+ await access10(executable);
7029
+ return executable;
7030
+ } catch {
7031
+ throw new TypeError("Expo dependencies are not installed in the generated shell. Run `absolute mobile init --yes`.");
7032
+ }
7033
+ };
7034
+ var planAbsoluteExpoDevSession = (config, options) => {
7035
+ if (config.engine !== "expo")
7036
+ throw new TypeError("The Expo development controller requires Expo.");
7037
+ if (options.platforms.length === 0)
7038
+ throw new TypeError("Expo development requires a local target platform.");
7039
+ const secureOrigin = [options.androidOrigin, options.iosOrigin].some((origin) => origin && new URL(origin).protocol === "https:");
7040
+ if (secureOrigin && !options.certificateAuthorityPath)
7041
+ throw new TypeError("Expo HTTPS development requires the AbsoluteJS development CA certificate.");
7042
+ const env = commandEnvironment(options);
7043
+ const metro = {
7044
+ args: [
7045
+ "start",
7046
+ "--dev-client",
7047
+ "--host",
7048
+ "localhost",
7049
+ "--port",
7050
+ String(options.metroPort)
7051
+ ],
7052
+ env,
7053
+ role: "metro"
7054
+ };
7055
+ const prepare = {
7056
+ args: [
7057
+ "prebuild",
7058
+ "--clean",
7059
+ "--no-install",
7060
+ "--platform",
7061
+ options.platforms.length === 2 ? "all" : options.platforms[0] ?? "all"
7062
+ ],
7063
+ env,
7064
+ role: "native-prepare"
7065
+ };
7066
+ const native = options.platforms.map((platform) => {
7067
+ const device = platform === "android" ? options.androidDevice : options.iosDevice;
7068
+ const command = {
7069
+ args: [
7070
+ `run:${platform}`,
7071
+ "--no-bundler",
7072
+ "--port",
7073
+ String(options.metroPort),
7074
+ ...device ? ["--device", device] : []
7075
+ ],
7076
+ env,
7077
+ platform,
7078
+ role: "native-build"
7079
+ };
7080
+ return command;
7081
+ });
7082
+ return {
7083
+ commands: [prepare, metro, ...native],
7084
+ metroPort: options.metroPort,
7085
+ project: config.nativeProjectDirectory
7086
+ };
7087
+ };
7088
+ var abortError = () => new DOMException("Expo development aborted.", "AbortError");
7089
+ var forwardLines = (process2, onLine) => {
7090
+ const attach = (stream) => {
7091
+ if (!stream)
7092
+ return;
7093
+ let buffered = "";
7094
+ stream.on("data", (chunk) => {
7095
+ buffered += chunk.toString("utf8");
7096
+ let newline = buffered.indexOf(`
7097
+ `);
7098
+ while (newline >= 0) {
7099
+ onLine(buffered.slice(0, newline).replace(/\r$/u, ""));
7100
+ buffered = buffered.slice(newline + 1);
7101
+ newline = buffered.indexOf(`
7102
+ `);
7103
+ }
7104
+ });
7105
+ stream.on("end", () => {
7106
+ if (buffered)
7107
+ onLine(buffered);
7108
+ });
7109
+ };
7110
+ attach(process2.stdout);
7111
+ attach(process2.stderr);
7112
+ };
7113
+ var stopProcess = async (process2) => {
7114
+ if (process2.exitCode !== null || process2.killed)
7115
+ return;
7116
+ process2.kill("SIGTERM");
7117
+ await Promise.race([
7118
+ new Promise((resolve14) => process2.once("exit", () => resolve14())),
7119
+ new Promise((resolve14) => setTimeout(resolve14, PROCESS_CLOSE_TIMEOUT_MS))
7120
+ ]);
7121
+ if (process2.exitCode === null)
7122
+ process2.kill("SIGKILL");
7123
+ };
7124
+ var waitForExit = (process2) => new Promise((resolve14) => {
7125
+ if (process2.exitCode !== null) {
7126
+ resolve14(process2.exitCode);
7127
+ return;
7128
+ }
7129
+ process2.once("exit", (code) => resolve14(code ?? 1));
7130
+ });
7131
+ var runUtilityCommand = async (run, command, args, options) => {
7132
+ const child = run(command, args, {
7133
+ cwd: options.cwd,
7134
+ env: process.env,
7135
+ stdio: ["ignore", "pipe", "pipe"]
7136
+ });
7137
+ forwardLines(child, options.log);
7138
+ const abort = () => void stopProcess(child);
7139
+ options.signal?.addEventListener("abort", abort, { once: true });
7140
+ const exitCode = await waitForExit(child);
7141
+ options.signal?.removeEventListener("abort", abort);
7142
+ if (options.signal?.aborted)
7143
+ throw abortError();
7144
+ return exitCode;
7145
+ };
7146
+ var startAbsoluteExpoDevSession = async (options) => {
7147
+ const plan = planAbsoluteExpoDevSession(options.config, options);
7148
+ const executable = options.executable ?? await absoluteExpoExecutable(plan.project);
7149
+ const run = options.spawnProcess ?? spawn;
7150
+ const log = options.log ?? (() => {
7151
+ return;
7152
+ });
7153
+ const timings = {};
7154
+ let caEnrollmentServer = null;
7155
+ const closeEnrollmentServer = async () => {
7156
+ const server = caEnrollmentServer;
7157
+ caEnrollmentServer = null;
7158
+ await server?.close();
7159
+ };
7160
+ const publishTiming = (phase, durationMs) => {
7161
+ timings[phase] = (timings[phase] ?? 0) + durationMs;
7162
+ options.onPhaseTiming?.({ durationMs, phase });
7163
+ };
7164
+ const setState = (state) => {
7165
+ options.onStateChange?.(state);
7166
+ };
7167
+ if (options.signal?.aborted)
7168
+ throw abortError();
7169
+ const prepareCommand = plan.commands.find((command) => command.role === "native-prepare");
7170
+ const metroCommand = plan.commands.find((command) => command.role === "metro");
7171
+ const nativeCommands = plan.commands.filter((command) => command.role === "native-build");
7172
+ if (!prepareCommand)
7173
+ throw new TypeError("Expo native preparation command is missing.");
7174
+ if (!metroCommand)
7175
+ throw new TypeError("Expo Metro command is missing.");
7176
+ setState("preparing-native");
7177
+ const prepareStarted = performance.now();
7178
+ const prepareProcess = run(executable, prepareCommand.args, {
7179
+ cwd: plan.project,
7180
+ env: { ...process.env, ...prepareCommand.env },
7181
+ stdio: ["ignore", "pipe", "pipe"]
7182
+ });
7183
+ forwardLines(prepareProcess, (line) => {
7184
+ if (line)
7185
+ log(`[prebuild] ${line}`);
7186
+ });
7187
+ const abortPrepare = () => void stopProcess(prepareProcess);
7188
+ options.signal?.addEventListener("abort", abortPrepare, { once: true });
7189
+ const prepareExit = await waitForExit(prepareProcess);
7190
+ options.signal?.removeEventListener("abort", abortPrepare);
7191
+ if (options.signal?.aborted)
7192
+ throw abortError();
7193
+ if (prepareExit !== 0) {
7194
+ setState("failed");
7195
+ throw new Error(`Expo native preparation exited with status ${prepareExit}.`);
7196
+ }
7197
+ const prepareMs = performance.now() - prepareStarted;
7198
+ timings["preparing-native"] = prepareMs;
7199
+ options.onPhaseTiming?.({
7200
+ durationMs: prepareMs,
7201
+ phase: "preparing-native"
7202
+ });
7203
+ setState("starting-metro");
7204
+ const metroStarted = performance.now();
7205
+ const metro = run(executable, metroCommand.args, {
7206
+ cwd: plan.project,
7207
+ env: { ...process.env, ...metroCommand.env },
7208
+ stdio: ["ignore", "pipe", "pipe"]
7209
+ });
7210
+ let metroReady = false;
7211
+ let resolveMetro;
7212
+ const metroPromise = new Promise((resolve14, reject) => {
7213
+ const timeout = setTimeout(() => {
7214
+ reject(new Error("Expo Metro did not become ready within 60 seconds."));
7215
+ }, METRO_READY_TIMEOUT_MS);
7216
+ resolveMetro = () => {
7217
+ clearTimeout(timeout);
7218
+ resolve14();
7219
+ };
7220
+ metro.once("exit", (code) => {
7221
+ if (!metroReady) {
7222
+ clearTimeout(timeout);
7223
+ reject(new Error(`Expo Metro exited with status ${code ?? 1}.`));
7224
+ }
7225
+ });
7226
+ });
7227
+ forwardLines(metro, (line) => {
7228
+ if (line)
7229
+ log(`[metro] ${line}`);
7230
+ if (!metroReady && /(?:Waiting on|Metro waiting on|Dev server ready)/iu.test(line)) {
7231
+ metroReady = true;
7232
+ resolveMetro?.();
7233
+ }
7234
+ });
7235
+ const abort = () => void stopProcess(metro);
7236
+ options.signal?.addEventListener("abort", abort, { once: true });
7237
+ const runNativeCommand = async (command) => {
7238
+ if (options.signal?.aborted)
7239
+ throw abortError();
7240
+ const { platform } = command;
7241
+ if (!platform)
7242
+ throw new TypeError("Expo native build command is missing a platform.");
7243
+ const state = platform === "android" ? "building-android" : "building-ios";
7244
+ setState(state);
7245
+ const started = performance.now();
7246
+ const child = run(executable, command.args, {
7247
+ cwd: plan.project,
7248
+ env: { ...process.env, ...command.env },
7249
+ stdio: ["ignore", "pipe", "pipe"]
7250
+ });
7251
+ forwardLines(child, (line) => {
7252
+ if (line)
7253
+ log(`[${platform}] ${line}`);
7254
+ });
7255
+ const abortChild = () => void stopProcess(child);
7256
+ options.signal?.addEventListener("abort", abortChild, { once: true });
7257
+ const exitCode = await waitForExit(child);
7258
+ options.signal?.removeEventListener("abort", abortChild);
7259
+ if (options.signal?.aborted)
7260
+ throw abortError();
7261
+ if (exitCode !== 0) {
7262
+ throw new Error(`Expo ${platform} development build exited with status ${exitCode}.`);
7263
+ }
7264
+ if (platform === "ios" && options.certificateAuthorityPath && options.iosOrigin && new URL(options.iosOrigin).protocol === "https:" && !options.iosDevice) {
7265
+ setState("enrolling-trust");
7266
+ const trustStarted = performance.now();
7267
+ const utilityOptions = {
7268
+ cwd: plan.project,
7269
+ signal: options.signal,
7270
+ log: (line) => line && log(`[ios-trust] ${line}`)
7271
+ };
7272
+ const trustExit = await runUtilityCommand(run, "xcrun", [
7273
+ "simctl",
7274
+ "keychain",
7275
+ "booted",
7276
+ "add-root-cert",
7277
+ options.certificateAuthorityPath
7278
+ ], utilityOptions);
7279
+ if (trustExit !== 0)
7280
+ throw new Error(`Expo iOS Simulator development CA trust exited with status ${trustExit}.`);
7281
+ await runUtilityCommand(run, "xcrun", ["simctl", "terminate", "booted", options.config.appId], utilityOptions);
7282
+ const launchExit = await runUtilityCommand(run, "xcrun", ["simctl", "launch", "booted", options.config.appId], utilityOptions);
7283
+ if (launchExit !== 0)
7284
+ throw new Error(`Expo iOS Simulator relaunch exited with status ${launchExit}.`);
7285
+ log("Installed the AbsoluteJS development CA into the Expo iOS Simulator and relaunched the app.");
7286
+ publishTiming("enrolling-trust", performance.now() - trustStarted);
7287
+ }
7288
+ const durationMs = performance.now() - started;
7289
+ timings[state] = durationMs;
7290
+ options.onPhaseTiming?.({ durationMs, phase: state });
7291
+ };
7292
+ const runNativeCommands = async (commands) => {
7293
+ const [command, ...remaining] = commands;
7294
+ if (!command)
7295
+ return;
7296
+ await runNativeCommand(command);
7297
+ await runNativeCommands(remaining);
7298
+ };
7299
+ const startPhysicalIosEnrollment = async () => {
7300
+ if (!options.iosDevice || !options.certificateAuthorityPath || !options.iosOrigin || new URL(options.iosOrigin).protocol !== "https:") {
7301
+ return;
7302
+ }
7303
+ setState("enrolling-trust");
7304
+ const trustStarted = performance.now();
7305
+ const startEnrollment = options.startCaEnrollmentServer ?? startAbsoluteIosCaEnrollmentServer;
7306
+ caEnrollmentServer = await startEnrollment({
7307
+ certificateAuthorityPath: options.certificateAuthorityPath,
7308
+ displayHost: new URL(options.iosOrigin).hostname
7309
+ });
7310
+ log(`On the iOS device, open ${caEnrollmentServer.url}, install the AbsoluteJS development CA profile, then enable it under Settings > General > About > Certificate Trust Settings. This public CA endpoint exists only for this dev session.`);
7311
+ publishTiming("enrolling-trust", performance.now() - trustStarted);
7312
+ };
7313
+ try {
7314
+ await startPhysicalIosEnrollment();
7315
+ await metroPromise;
7316
+ const metroMs = performance.now() - metroStarted;
7317
+ timings["starting-metro"] = metroMs;
7318
+ options.onPhaseTiming?.({
7319
+ durationMs: metroMs,
7320
+ phase: "starting-metro"
7321
+ });
7322
+ await runNativeCommands(nativeCommands);
7323
+ setState("ready");
7324
+ return {
7325
+ metroPort: plan.metroPort,
7326
+ platforms: options.platforms,
7327
+ timings,
7328
+ close: async () => {
7329
+ options.signal?.removeEventListener("abort", abort);
7330
+ await stopProcess(metro);
7331
+ await closeEnrollmentServer();
7332
+ setState("closed");
7333
+ }
7334
+ };
7335
+ } catch (error) {
7336
+ options.signal?.removeEventListener("abort", abort);
7337
+ await stopProcess(metro);
7338
+ await closeEnrollmentServer();
7339
+ setState("failed");
7340
+ throw error;
7341
+ }
7342
+ };
6905
7343
  // src/mobile/ciWorkflow.ts
6906
7344
  import { existsSync as existsSync3 } from "fs";
6907
- import { access as access10, mkdir as mkdir12, readFile as readFile16, writeFile as writeFile13 } from "fs/promises";
7345
+ import { access as access11, mkdir as mkdir12, readFile as readFile16, writeFile as writeFile13 } from "fs/promises";
6908
7346
  import { dirname as dirname11, extname as extname4, relative as relative11, resolve as resolve14, sep as sep6 } from "path";
6909
7347
  var ABSOLUTE_MOBILE_CI_WORKFLOW_FORMAT = 1;
6910
7348
  var SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]*$/u;
@@ -6926,7 +7364,7 @@ var RESERVED_SECRET_NAMES = new Set([
6926
7364
  ]);
6927
7365
  var exists4 = async (path) => {
6928
7366
  try {
6929
- await access10(path);
7367
+ await access11(path);
6930
7368
  return true;
6931
7369
  } catch {
6932
7370
  return false;
@@ -6969,7 +7407,7 @@ var normalizeSecretEnvironment = (values = []) => {
6969
7407
  };
6970
7408
  var customSecretEnvironment = (names, indentation = CI_ENV_INDENTATION) => names.map((name) => `${" ".repeat(indentation)}${name}: \${{ secrets.${name} }}`).join(`
6971
7409
  `);
6972
- var commandEnvironment = (options) => ` ABSOLUTE_CONFIG_PATH: ${yamlString(options.configPath ?? "")}
7410
+ var commandEnvironment2 = (options) => ` ABSOLUTE_CONFIG_PATH: ${yamlString(options.configPath ?? "")}
6973
7411
  ABSOLUTE_REGISTRY_MODULE: ${yamlString(options.registryModule)}
6974
7412
  ABSOLUTE_SERVER_ENTRY: ${yamlString(options.serverEntry)}`;
6975
7413
  var appendConfigArgument = `if [[ -n "$ABSOLUTE_CONFIG_PATH" ]]; then
@@ -7128,7 +7566,7 @@ var androidJob = (options) => {
7128
7566
  ABSOLUTE_ANDROID_KEY_PASSWORD: \${{ secrets.ABSOLUTE_ANDROID_KEY_PASSWORD }}
7129
7567
  ABSOLUTE_ANDROID_KEYSTORE_PATH: \${{ runner.temp }}/absolute-release.jks${publishEnvironment}${custom ? `
7130
7568
  ${custom}` : ""}
7131
- ${commandEnvironment({ configPath: undefined, registryModule: "", serverEntry: "" })}
7569
+ ${commandEnvironment2({ configPath: undefined, registryModule: "", serverEntry: "" })}
7132
7570
  steps:
7133
7571
  ${installSteps}
7134
7572
  - name: Provision Android signing
@@ -7227,7 +7665,7 @@ var iosJob = (options) => {
7227
7665
  ABSOLUTE_IOS_KEYCHAIN_PASSWORD: \${{ secrets.ABSOLUTE_IOS_KEYCHAIN_PASSWORD }}${publishEnvironment}${custom ? `
7228
7666
  ${custom}` : ""}
7229
7667
  ABSOLUTE_IOS_DEVELOPMENT_TEAM: \${{ secrets.ABSOLUTE_IOS_DEVELOPMENT_TEAM }}
7230
- ${commandEnvironment({ configPath: undefined, registryModule: "", serverEntry: "" })}
7668
+ ${commandEnvironment2({ configPath: undefined, registryModule: "", serverEntry: "" })}
7231
7669
  steps:
7232
7670
  ${installSteps}
7233
7671
  - name: Provision iOS signing
@@ -7321,7 +7759,7 @@ var createAbsoluteMobileGithubWorkflow = (options) => {
7321
7759
  const serverEntry = projectPath(options.projectRoot, options.serverEntry ?? "server.ts", "mobile ci github server entry");
7322
7760
  const configPath = options.configPath ? projectPath(options.projectRoot, options.configPath, "mobile ci github --config") : undefined;
7323
7761
  const registryModule = projectPath(options.projectRoot, options.registryModule ?? "mobile.release.ts", "mobile ci github --registry", { allowMissing: !includePublishing });
7324
- const environment = commandEnvironment({
7762
+ const environment = commandEnvironment2({
7325
7763
  configPath,
7326
7764
  registryModule,
7327
7765
  serverEntry
@@ -7613,7 +8051,7 @@ var createAbsoluteMobilePreviewPlugin = (mobile) => {
7613
8051
  };
7614
8052
  // src/mobile/nativeDeepLinks.ts
7615
8053
  import { readFile as readFile17, rename as rename12, writeFile as writeFile14 } from "fs/promises";
7616
- import { join as join18 } from "path";
8054
+ import { join as join19 } from "path";
7617
8055
  var START_MARKER = "<!-- absolutejs:deep-links:start -->";
7618
8056
  var END_MARKER = "<!-- absolutejs:deep-links:end -->";
7619
8057
  var IOS_ENTITLEMENTS = "App/AbsoluteJS.entitlements";
@@ -7670,7 +8108,7 @@ ${hosts}
7670
8108
  `;
7671
8109
  };
7672
8110
  var configureAndroid = async (config) => {
7673
- const path = join18(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
8111
+ const path = join19(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
7674
8112
  const source = await readFile17(path, "utf8");
7675
8113
  const mainActivity = source.indexOf('android:name=".MainActivity"');
7676
8114
  if (mainActivity === NOT_FOUND) {
@@ -7696,7 +8134,7 @@ var iosSchemeRegion = (scheme) => ` ${START_MARKER}
7696
8134
  ${END_MARKER}
7697
8135
  `;
7698
8136
  var configureIosInfo = async (config) => {
7699
- const path = join18(config.nativeProjectDirectory, "ios/App/App/Info.plist");
8137
+ const path = join19(config.nativeProjectDirectory, "ios/App/App/Info.plist");
7700
8138
  const source = await readFile17(path, "utf8");
7701
8139
  const region = config.deepLinkScheme ? iosSchemeRegion(config.deepLinkScheme) : ` ${START_MARKER}
7702
8140
  ${END_MARKER}
@@ -7720,7 +8158,7 @@ ${domains}
7720
8158
  `;
7721
8159
  };
7722
8160
  var configureIosEntitlements = async (config) => {
7723
- const path = join18(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
8161
+ const path = join19(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
7724
8162
  let current = "";
7725
8163
  try {
7726
8164
  current = await readFile17(path, "utf8");
@@ -7738,7 +8176,7 @@ var configureIosEntitlements = async (config) => {
7738
8176
  return true;
7739
8177
  };
7740
8178
  var configureIosProject = async (config) => {
7741
- const path = join18(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
8179
+ const path = join19(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
7742
8180
  const source = await readFile17(path, "utf8");
7743
8181
  const declarations = [
7744
8182
  ...source.matchAll(/CODE_SIGN_ENTITLEMENTS = ([^;]+);/g)
@@ -7779,7 +8217,7 @@ var applyAbsoluteNativeDeepLinks = async (config, platforms = config.platforms)
7779
8217
  // src/mobile/nativeDeviceCapabilities.ts
7780
8218
  init_deviceCapabilities();
7781
8219
  import { readFile as readFile18, rename as rename13, writeFile as writeFile15 } from "fs/promises";
7782
- import { join as join19 } from "path";
8220
+ import { join as join20 } from "path";
7783
8221
  var START_MARKER2 = "<!-- absolutejs:device-capabilities:start -->";
7784
8222
  var END_MARKER2 = "<!-- absolutejs:device-capabilities:end -->";
7785
8223
  var NOT_FOUND2 = -1;
@@ -7929,7 +8367,7 @@ var writeIosPrivacyManifest = async (path, current, source) => {
7929
8367
  var configureIosPrivacyProject = async (config, requirements) => {
7930
8368
  if (requirements.iosPrivacyAccessedApis.length === 0)
7931
8369
  return false;
7932
- const projectPath2 = join19(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
8370
+ const projectPath2 = join20(config.nativeProjectDirectory, "ios/App/App.xcodeproj/project.pbxproj");
7933
8371
  const project = await readFile18(projectPath2, "utf8");
7934
8372
  return writeChangedFile2(projectPath2, addIosPrivacyProjectReference(project));
7935
8373
  };
@@ -7982,7 +8420,7 @@ ${next.slice(index)}`;
7982
8420
  return next;
7983
8421
  };
7984
8422
  var configureIos2 = async (config, plan) => {
7985
- const path = join19(config.nativeProjectDirectory, "ios/App/App/Info.plist");
8423
+ const path = join20(config.nativeProjectDirectory, "ios/App/App/Info.plist");
7986
8424
  const source = await readFile18(path, "utf8");
7987
8425
  const requirements = absoluteDeviceNativeRequirements(plan);
7988
8426
  const existingSystemBars = source.match(/<key>UIViewControllerBasedStatusBarAppearance<\/key>\s*<(true|false)\s*\/>/u);
@@ -8003,7 +8441,7 @@ ${content}
8003
8441
  ${END_MARKER2}
8004
8442
  ` : "";
8005
8443
  const infoChanged = await writeChangedFile2(path, managed(source, region, source.lastIndexOf("</dict>")));
8006
- const privacyPath = join19(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
8444
+ const privacyPath = join20(config.nativeProjectDirectory, "ios/App/App/PrivacyInfo.xcprivacy");
8007
8445
  const privacyCurrent = await optionalSource(privacyPath);
8008
8446
  const privacySource = privacyManifestSource(privacyCurrent, requirements);
8009
8447
  const [privacyChanged, projectChanged, pushChanged] = await Promise.all([
@@ -8014,7 +8452,7 @@ ${content}
8014
8452
  return infoChanged || privacyChanged || projectChanged || pushChanged;
8015
8453
  };
8016
8454
  var configureIosPushNotifications = async (config, enabled) => {
8017
- const entitlementsPath = join19(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
8455
+ const entitlementsPath = join20(config.nativeProjectDirectory, "ios/App/AbsoluteJS.entitlements");
8018
8456
  const entitlements = await optionalSource(entitlementsPath);
8019
8457
  if (entitlements === null && !enabled)
8020
8458
  return false;
@@ -8026,7 +8464,7 @@ var configureIosPushNotifications = async (config, enabled) => {
8026
8464
  <!-- ${PUSH_END_MARKER} -->
8027
8465
  ` : "";
8028
8466
  const nextEntitlements = replacePushRegion(entitlements, entitlementRegion, entitlements.lastIndexOf("</dict>"));
8029
- const delegatePath = join19(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
8467
+ const delegatePath = join20(config.nativeProjectDirectory, "ios/App/App/AppDelegate.swift");
8030
8468
  const delegate = await optionalSource(delegatePath);
8031
8469
  if (delegate === null && !enabled)
8032
8470
  return false;
@@ -8075,7 +8513,7 @@ var replacePushRegion = (source, region, insertion) => {
8075
8513
  return `${source.slice(0, insertion)}${region}${source.slice(insertion)}`;
8076
8514
  };
8077
8515
  var configureAndroid2 = async (config, plan) => {
8078
- const path = join19(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
8516
+ const path = join20(config.nativeProjectDirectory, "android/app/src/main/AndroidManifest.xml");
8079
8517
  const source = await readFile18(path, "utf8");
8080
8518
  const permissions = absoluteDeviceNativeRequirements(plan).androidPermissions;
8081
8519
  const content = permissions.map((permission) => ` <uses-permission android:name="${escapeXml2(permission)}" />`).join(`
@@ -8111,7 +8549,7 @@ ${content}
8111
8549
  throw new TypeError(`Android google-services.json does not contain package ${config.appId}.`);
8112
8550
  const [manifestChanged, firebaseChanged] = await Promise.all([
8113
8551
  writeChangedFile2(path, nextManifest),
8114
- writeOptionalChangedFile(join19(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
8552
+ writeOptionalChangedFile(join20(config.nativeProjectDirectory, "android/app/google-services.json"), firebaseSource)
8115
8553
  ]);
8116
8554
  return manifestChanged || firebaseChanged;
8117
8555
  };
@@ -8125,7 +8563,7 @@ var applyAbsoluteNativeDeviceCapabilities = async (projectRoot, config, platform
8125
8563
  };
8126
8564
  };
8127
8565
  // src/mobile/releasePublisher.ts
8128
- import { access as access11 } from "fs/promises";
8566
+ import { access as access12 } from "fs/promises";
8129
8567
  import { isAbsolute as isAbsolute6, relative as relative12, resolve as resolve15, sep as sep7 } from "path";
8130
8568
  import { pathToFileURL as pathToFileURL3 } from "url";
8131
8569
  var prepareAbsoluteIosRelease = async (publisher, options) => {
@@ -8162,7 +8600,7 @@ var publisherModulePath = (projectRoot, requested) => {
8162
8600
  };
8163
8601
  var loadAbsoluteNativeReleasePublisher = async (projectRoot, requestedModulePath) => {
8164
8602
  const modulePath = publisherModulePath(projectRoot, requestedModulePath);
8165
- await access11(modulePath).catch(() => {
8603
+ await access12(modulePath).catch(() => {
8166
8604
  throw new TypeError(`Native release registry module does not exist: ${modulePath}`);
8167
8605
  });
8168
8606
  const loaded = await import(pathToFileURL3(modulePath).href);
@@ -8972,6 +9410,7 @@ export {
8972
9410
  startAbsoluteIosTcpRelay,
8973
9411
  startAbsoluteIosDevSession,
8974
9412
  startAbsoluteIosCaEnrollmentServer,
9413
+ startAbsoluteExpoDevSession,
8975
9414
  serializeAbsoluteMobileAuthEnvironment,
8976
9415
  runWithAbsoluteMobileProducer,
8977
9416
  runAbsoluteAndroidUpgradeConformance,
@@ -8994,6 +9433,7 @@ export {
8994
9433
  prepareAbsoluteIosRelease,
8995
9434
  prepareAbsoluteIosDevProject,
8996
9435
  prepareAbsoluteAndroidRelease,
9436
+ planAbsoluteExpoDevSession,
8997
9437
  parseIosSimulators,
8998
9438
  parseIosRuntimes,
8999
9439
  parseIosDeviceTypes,
@@ -9075,6 +9515,7 @@ export {
9075
9515
  absoluteRemoteMacSshBase,
9076
9516
  absoluteMobilePreviewDocument,
9077
9517
  absoluteIosDeviceAcceptanceCommands,
9518
+ absoluteExpoExecutable,
9078
9519
  absoluteDeviceNativeRequirements,
9079
9520
  MOBILE_PAGE_REQUEST_HEADERS,
9080
9521
  AbsoluteMobilePageProtocolError,
@@ -9104,5 +9545,5 @@ export {
9104
9545
  ABSOLUTE_ANDROID_RELEASE_FORMAT
9105
9546
  };
9106
9547
 
9107
- //# debugId=81AD994206FFE2B464756E2164756E21
9548
+ //# debugId=711828E2D236C77164756E2164756E21
9108
9549
  //# sourceMappingURL=index.js.map