@expo/build-tools 23.1.0 → 23.2.0

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.
@@ -49,7 +49,7 @@ const startAppiumRemoteSession_1 = require("./functions/startAppiumRemoteSession
49
49
  const startCuttlefishDevice_1 = require("./functions/startCuttlefishDevice");
50
50
  const startIosSimulator_1 = require("./functions/startIosSimulator");
51
51
  const startIosSimulatorRecordings_1 = require("./functions/startIosSimulatorRecordings");
52
- const startServeSimRemoteSession_1 = require("./functions/startServeSimRemoteSession");
52
+ const startWebPreviewRemoteSession_1 = require("./functions/startWebPreviewRemoteSession");
53
53
  const startServeSimMetrics_1 = require("./functions/startServeSimMetrics");
54
54
  const collectServeSimMetrics_1 = require("./functions/collectServeSimMetrics");
55
55
  const uploadArtifact_1 = require("./functions/uploadArtifact");
@@ -102,7 +102,7 @@ function getEasFunctions(ctx) {
102
102
  (0, startIosSimulatorRecordings_1.createStartIosSimulatorRecordingsBuildFunction)(),
103
103
  (0, finishIosSimulatorRecordings_1.createFinishIosSimulatorRecordingsBuildFunction)(),
104
104
  (0, uploadDeviceRunSessionScreenRecordings_1.createUploadDeviceRunSessionScreenRecordingsBuildFunction)(ctx),
105
- (0, startServeSimRemoteSession_1.createStartServeSimRemoteSessionBuildFunction)(ctx),
105
+ (0, startWebPreviewRemoteSession_1.createStartWebPreviewRemoteSessionBuildFunction)(ctx),
106
106
  (0, startServeSimMetrics_1.createStartServeSimMetricsBuildFunction)(),
107
107
  (0, collectServeSimMetrics_1.createCollectServeSimMetricsBuildFunction)(ctx),
108
108
  (0, installMaestro_1.createInstallMaestroBuildFunction)(),
@@ -81,27 +81,24 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
81
81
  });
82
82
  const agentDeviceRemoteSessionUrl = agentDeviceTunnel.url;
83
83
  logger.info(`Tunnel is ready at ${agentDeviceRemoteSessionUrl}.`);
84
- let serveSim;
84
+ let webPreview;
85
85
  let eventCollection;
86
86
  try {
87
- // serve-sim is iOS-only — only launch it (and report a webPreviewUrl)
88
- // on Darwin. Android sessions go without a preview URL.
89
- if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
90
- serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
91
- baseDomain: ngrokTunnelDomain,
92
- env,
93
- logger,
94
- timeoutMs: STARTUP_TIMEOUT_MS,
95
- });
96
- logger.info(`Web preview URL: ${serveSim.previewUrl}`);
97
- }
87
+ webPreview = await (0, remoteDeviceRunSession_1.startDeviceWebPreviewWithTunnelAsync)(ctx, {
88
+ runtimePlatform,
89
+ baseDomain: ngrokTunnelDomain,
90
+ env,
91
+ logger,
92
+ timeoutMs: STARTUP_TIMEOUT_MS,
93
+ });
94
+ logger.info(`Web preview URL: ${webPreview.previewUrl}`);
98
95
  await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
99
96
  ctx,
100
97
  deviceRunSessionId,
101
98
  remoteConfig: {
102
99
  agentDeviceRemoteSessionUrl,
103
100
  agentDeviceRemoteSessionToken: daemonToken,
104
- ...(serveSim ? { webPreviewUrl: serveSim.previewUrl } : {}),
101
+ webPreviewUrl: webPreview.previewUrl,
105
102
  },
106
103
  logger,
107
104
  });
@@ -132,8 +129,8 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
132
129
  });
133
130
  }
134
131
  finally {
135
- if (serveSim) {
136
- await serveSim.stopAsync();
132
+ if (webPreview) {
133
+ await webPreview.stopAsync();
137
134
  }
138
135
  await agentDeviceTunnel.stopAsync();
139
136
  if (eventCollection) {
@@ -95,7 +95,7 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
95
95
  logger,
96
96
  });
97
97
  let appiumTunnel;
98
- let serveSim;
98
+ let webPreview;
99
99
  try {
100
100
  appiumTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
101
101
  port: APPIUM_PORT,
@@ -104,18 +104,15 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
104
104
  authtoken: ngrokAuthtoken,
105
105
  logger,
106
106
  });
107
- switch (runtimePlatform) {
108
- case steps_1.BuildRuntimePlatform.DARWIN:
109
- serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
110
- baseDomain: ngrokTunnelDomain,
111
- env,
112
- logger,
113
- timeoutMs: APPIUM_STARTUP_TIMEOUT_MS,
114
- });
115
- break;
116
- case steps_1.BuildRuntimePlatform.LINUX:
117
- break;
118
- }
107
+ // expo-device-hub has no serial-selection flag. Device run session workflows must expose
108
+ // a single booted Android emulator so the Hub and Appium resolve the same device.
109
+ webPreview = await (0, remoteDeviceRunSession_1.startDeviceWebPreviewWithTunnelAsync)(ctx, {
110
+ runtimePlatform,
111
+ baseDomain: ngrokTunnelDomain,
112
+ env,
113
+ logger,
114
+ timeoutMs: APPIUM_STARTUP_TIMEOUT_MS,
115
+ });
119
116
  await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
120
117
  ctx,
121
118
  deviceRunSessionId,
@@ -126,7 +123,7 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
126
123
  'appium:automationName': device.automationName,
127
124
  'appium:udid': device.udid,
128
125
  },
129
- ...(serveSim ? { webPreviewUrl: serveSim.previewUrl } : {}),
126
+ webPreviewUrl: webPreview.previewUrl,
130
127
  },
131
128
  logger,
132
129
  });
@@ -144,8 +141,8 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
144
141
  });
145
142
  }
146
143
  finally {
147
- if (serveSim) {
148
- await serveSim.stopAsync();
144
+ if (webPreview) {
145
+ await webPreview.stopAsync();
149
146
  }
150
147
  if (appiumTunnel) {
151
148
  await appiumTunnel.stopAsync();
@@ -146,7 +146,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
146
146
  logger,
147
147
  });
148
148
  let toolsTunnel;
149
- let serveSim;
149
+ let webPreview;
150
150
  try {
151
151
  toolsTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
152
152
  port: toolServerPort,
@@ -158,25 +158,21 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
158
158
  });
159
159
  const publicToolsUrl = toolsTunnel.url;
160
160
  logger.info(`Tunnel is ready at ${publicToolsUrl}.`);
161
- // serve-sim is iOS-only Android sessions go without a preview URL.
162
- let webPreviewUrl;
163
- if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
164
- serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
165
- baseDomain: ngrokTunnelDomain,
166
- env,
167
- logger,
168
- timeoutMs: STARTUP_TIMEOUT_MS,
169
- });
170
- webPreviewUrl = serveSim.previewUrl;
171
- logger.info(`Web preview URL: ${webPreviewUrl}`);
172
- }
161
+ webPreview = await (0, remoteDeviceRunSession_1.startDeviceWebPreviewWithTunnelAsync)(ctx, {
162
+ runtimePlatform,
163
+ baseDomain: ngrokTunnelDomain,
164
+ env,
165
+ logger,
166
+ timeoutMs: STARTUP_TIMEOUT_MS,
167
+ });
168
+ logger.info(`Web preview URL: ${webPreview.previewUrl}`);
173
169
  await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
174
170
  ctx,
175
171
  deviceRunSessionId,
176
172
  remoteConfig: {
177
173
  toolsUrl: publicToolsUrl,
178
174
  ...(toolServerToken ? { toolsAuthToken: toolServerToken } : {}),
179
- ...(webPreviewUrl ? { webPreviewUrl } : {}),
175
+ webPreviewUrl: webPreview.previewUrl,
180
176
  },
181
177
  logger,
182
178
  });
@@ -195,8 +191,8 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
195
191
  });
196
192
  }
197
193
  finally {
198
- if (serveSim) {
199
- await serveSim.stopAsync();
194
+ if (webPreview) {
195
+ await webPreview.stopAsync();
200
196
  }
201
197
  if (toolsTunnel) {
202
198
  await toolsTunnel.stopAsync();
@@ -0,0 +1,3 @@
1
+ import { BuildFunction } from '@expo/steps';
2
+ import { CustomBuildContext } from '../../customBuildContext';
3
+ export declare function createStartWebPreviewRemoteSessionBuildFunction(ctx: CustomBuildContext): BuildFunction;
@@ -1,16 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.createStartServeSimRemoteSessionBuildFunction = createStartServeSimRemoteSessionBuildFunction;
3
+ exports.createStartWebPreviewRemoteSessionBuildFunction = createStartWebPreviewRemoteSessionBuildFunction;
4
4
  const steps_1 = require("@expo/steps");
5
5
  const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
6
6
  const STARTUP_TIMEOUT_MS = 60_000;
7
- function createStartServeSimRemoteSessionBuildFunction(ctx) {
7
+ function createStartWebPreviewRemoteSessionBuildFunction(ctx) {
8
8
  return new steps_1.BuildFunction({
9
9
  namespace: 'eas',
10
10
  id: 'start_serve_sim_remote_session',
11
- name: 'Start serve-sim remote session',
11
+ name: 'Start web preview remote session',
12
12
  __metricsId: 'eas/start_serve_sim_remote_session',
13
- supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
14
13
  inputProviders: [
15
14
  steps_1.BuildStepInput.createProvider({
16
15
  id: 'package_version',
@@ -23,26 +22,30 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
23
22
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
24
23
  }),
25
24
  ],
26
- fn: async ({ logger }, { inputs, env, signal }) => {
25
+ fn: async ({ logger, global }, { inputs, env, signal }) => {
27
26
  const deviceRunSessionId = (0, remoteDeviceRunSession_1.getDeviceRunSessionIdOrThrow)(env);
28
27
  const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
29
28
  const maxDurationSeconds = inputs.max_duration_seconds?.value;
30
29
  const packageVersion = inputs.package_version?.value;
31
- logger.info('Starting serve-sim remote session.');
32
- await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
33
- const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
30
+ const { runtimePlatform } = global;
31
+ logger.info(`Starting web preview remote session (runtime: ${runtimePlatform}).`);
32
+ if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
33
+ await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
34
+ }
35
+ const webPreview = await (0, remoteDeviceRunSession_1.startDeviceWebPreviewWithTunnelAsync)(ctx, {
36
+ runtimePlatform,
34
37
  baseDomain: ngrokTunnelDomain,
35
38
  env,
36
39
  logger,
37
40
  timeoutMs: STARTUP_TIMEOUT_MS,
38
41
  packageVersion,
39
42
  });
40
- logger.info(`Preview URL: ${serveSim.previewUrl}`);
43
+ logger.info(`Preview URL: ${webPreview.previewUrl}`);
41
44
  try {
42
45
  await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
43
46
  ctx,
44
47
  deviceRunSessionId,
45
- remoteConfig: { previewUrl: serveSim.previewUrl },
48
+ remoteConfig: { previewUrl: webPreview.previewUrl },
46
49
  logger,
47
50
  });
48
51
  await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
@@ -54,7 +57,7 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
54
57
  });
55
58
  }
56
59
  finally {
57
- await serveSim.stopAsync();
60
+ await webPreview.stopAsync();
58
61
  }
59
62
  },
60
63
  });
@@ -54,22 +54,23 @@ export declare function ensureFfmpegInstalledAsync({ runtimePlatform, env, logge
54
54
  logger: bunyan;
55
55
  }): Promise<void>;
56
56
  /**
57
- * Translate Cloudflare ICE servers into serve-sim CLI flags: `--stun-url` (the
57
+ * Translate Cloudflare ICE servers into web preview CLI flags: `--stun-url` (the
58
58
  * credential-less entries) and `--turn-url`/`--turn-username`/`--turn-credential`
59
- * (the entry carrying the short-lived credentials).
59
+ * (the entry carrying the short-lived credentials). serve-sim and expo-device-hub
60
+ * intentionally expose the same ICE flag contract.
60
61
  */
61
- export declare function turnIceServersToServeSimArgs(iceServers: TurnIceServers): string[];
62
+ export declare function turnIceServersToWebPreviewArgs(iceServers: TurnIceServers): string[];
62
63
  /**
63
64
  * Fetch short-lived Cloudflare TURN ICE servers for this job run from www
64
65
  * (minted on demand, mirroring how the worker fetches project clone URLs) and
65
- * translate them into serve-sim CLI flags.
66
+ * translate them into web preview CLI flags.
66
67
  *
67
- * Best-effort: on any failure we log and return [] so serve-sim falls back to
68
- * its built-in P2P/STUN behavior. The credential is passed to serve-sim as a
69
- * process arg and deliberately not logged (turtle-spawn never logs argv and the
70
- * worker is single-tenant).
68
+ * Best-effort: on any failure we log and return [] so the preview server falls
69
+ * back to its built-in P2P/STUN behavior. The credential is passed as a process
70
+ * arg and deliberately not logged (turtle-spawn never logs argv and the worker
71
+ * is single-tenant).
71
72
  */
72
- export declare function fetchServeSimTurnArgsAsync(ctx: CustomBuildContext, { env, logger }: {
73
+ export declare function fetchWebPreviewTurnArgsAsync(ctx: CustomBuildContext, { env, logger }: {
73
74
  env: BuildStepEnv;
74
75
  logger: bunyan;
75
76
  }): Promise<string[]>;
@@ -98,15 +99,22 @@ export declare function createServeSimArgs({ port, turnArgs, metricsCorsArgs, pa
98
99
  metricsCorsArgs?: string[];
99
100
  packageVersion?: string;
100
101
  }): string[];
101
- export declare function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }: {
102
- serveSim: Pick<DetachedProcessHandle, 'pid' | 'getOutput'>;
102
+ export declare function createExpoDeviceHubArgs({ port, turnArgs, packageVersion, }: {
103
+ port: number;
104
+ turnArgs?: string[];
105
+ packageVersion?: string;
106
+ }): string[];
107
+ export declare function waitForWebPreviewReadyAsync({ previewServer, serverName, port, timeoutMs, }: {
108
+ previewServer: Pick<DetachedProcessHandle, 'pid' | 'getOutput'>;
109
+ serverName: string;
103
110
  port: number;
104
111
  timeoutMs: number;
105
112
  }): Promise<void>;
106
- export type ServeSimPreviewHandle = {
113
+ export type DeviceWebPreviewHandle = {
107
114
  previewUrl: string;
108
115
  stopAsync: () => Promise<void>;
109
116
  };
117
+ export type ServeSimPreviewHandle = DeviceWebPreviewHandle;
110
118
  export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, packageVersion, }: {
111
119
  baseDomain: string;
112
120
  env: BuildStepEnv;
@@ -114,6 +122,21 @@ export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, {
114
122
  timeoutMs: number;
115
123
  packageVersion?: string;
116
124
  }): Promise<ServeSimPreviewHandle>;
125
+ export declare function startExpoDeviceHubWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, packageVersion, }: {
126
+ baseDomain: string;
127
+ env: BuildStepEnv;
128
+ logger: bunyan;
129
+ timeoutMs: number;
130
+ packageVersion?: string;
131
+ }): Promise<DeviceWebPreviewHandle>;
132
+ export declare function startDeviceWebPreviewWithTunnelAsync(ctx: CustomBuildContext, { runtimePlatform, ...options }: {
133
+ runtimePlatform: BuildRuntimePlatform;
134
+ baseDomain: string;
135
+ env: BuildStepEnv;
136
+ logger: bunyan;
137
+ timeoutMs: number;
138
+ packageVersion?: string;
139
+ }): Promise<DeviceWebPreviewHandle>;
117
140
  export type NgrokTunnelHandle = {
118
141
  url: string;
119
142
  stopAsync: () => Promise<void>;
@@ -42,14 +42,17 @@ exports.getNgrokAuthtokenOrThrow = getNgrokAuthtokenOrThrow;
42
42
  exports.selectXcodeDeveloperDirectoryAsync = selectXcodeDeveloperDirectoryAsync;
43
43
  exports.waitForDeviceRunSessionStoppedAsync = waitForDeviceRunSessionStoppedAsync;
44
44
  exports.ensureFfmpegInstalledAsync = ensureFfmpegInstalledAsync;
45
- exports.turnIceServersToServeSimArgs = turnIceServersToServeSimArgs;
46
- exports.fetchServeSimTurnArgsAsync = fetchServeSimTurnArgsAsync;
45
+ exports.turnIceServersToWebPreviewArgs = turnIceServersToWebPreviewArgs;
46
+ exports.fetchWebPreviewTurnArgsAsync = fetchWebPreviewTurnArgsAsync;
47
47
  exports.uploadRemoteSessionConfigAsync = uploadRemoteSessionConfigAsync;
48
48
  exports.spawnDetached = spawnDetached;
49
49
  exports.metricsCorsOriginToServeSimArgs = metricsCorsOriginToServeSimArgs;
50
50
  exports.createServeSimArgs = createServeSimArgs;
51
- exports.waitForServeSimReadyAsync = waitForServeSimReadyAsync;
51
+ exports.createExpoDeviceHubArgs = createExpoDeviceHubArgs;
52
+ exports.waitForWebPreviewReadyAsync = waitForWebPreviewReadyAsync;
52
53
  exports.startServeSimWithTunnelAsync = startServeSimWithTunnelAsync;
54
+ exports.startExpoDeviceHubWithTunnelAsync = startExpoDeviceHubWithTunnelAsync;
55
+ exports.startDeviceWebPreviewWithTunnelAsync = startDeviceWebPreviewWithTunnelAsync;
53
56
  exports.startNgrokTunnelAsync = startNgrokTunnelAsync;
54
57
  exports.waitForFileAsync = waitForFileAsync;
55
58
  const eas_build_job_1 = require("@expo/eas-build-job");
@@ -69,12 +72,16 @@ const sentry_1 = require("../../sentry");
69
72
  const retry_1 = require("../../utils/retry");
70
73
  const turtleFetch_1 = require("../../utils/turtleFetch");
71
74
  const XCODE_DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer';
75
+ const WEB_PREVIEW_HOST = '127.0.0.1';
72
76
  const SERVE_SIM_PACKAGE_NAME = '@expo/serve-sim';
73
- const SERVE_SIM_HOST = '127.0.0.1';
74
77
  const SERVE_SIM_MAX_DIMENSION = '960';
75
78
  const SERVE_SIM_MJPEG_QUALITY = '0.55';
76
79
  const SERVE_SIM_VIDEO_BITRATE = '6000000';
77
80
  const SERVE_SIM_VIDEO_FPS = '60';
81
+ const EXPO_DEVICE_HUB_PACKAGE_NAME = 'expo-device-hub';
82
+ const EXPO_DEVICE_HUB_MAX_DIMENSION = '1280';
83
+ const EXPO_DEVICE_HUB_VIDEO_BITRATE = '6000000';
84
+ const EXPO_DEVICE_HUB_VIDEO_FPS = '60';
78
85
  const START_DEVICE_RUN_SESSION_MUTATION = (0, gql_tada_1.graphql)(`
79
86
  mutation StartDeviceRunSession($deviceRunSessionId: ID!, $remoteConfig: JSONObject!) {
80
87
  deviceRunSession {
@@ -328,11 +335,12 @@ const TurnIceServersResponseSchema = zod_1.z.object({
328
335
  }),
329
336
  });
330
337
  /**
331
- * Translate Cloudflare ICE servers into serve-sim CLI flags: `--stun-url` (the
338
+ * Translate Cloudflare ICE servers into web preview CLI flags: `--stun-url` (the
332
339
  * credential-less entries) and `--turn-url`/`--turn-username`/`--turn-credential`
333
- * (the entry carrying the short-lived credentials).
340
+ * (the entry carrying the short-lived credentials). serve-sim and expo-device-hub
341
+ * intentionally expose the same ICE flag contract.
334
342
  */
335
- function turnIceServersToServeSimArgs(iceServers) {
343
+ function turnIceServersToWebPreviewArgs(iceServers) {
336
344
  const stunUrls = iceServers
337
345
  .filter(server => !server.username && !server.credential)
338
346
  .flatMap(server => server.urls);
@@ -349,14 +357,14 @@ function turnIceServersToServeSimArgs(iceServers) {
349
357
  /**
350
358
  * Fetch short-lived Cloudflare TURN ICE servers for this job run from www
351
359
  * (minted on demand, mirroring how the worker fetches project clone URLs) and
352
- * translate them into serve-sim CLI flags.
360
+ * translate them into web preview CLI flags.
353
361
  *
354
- * Best-effort: on any failure we log and return [] so serve-sim falls back to
355
- * its built-in P2P/STUN behavior. The credential is passed to serve-sim as a
356
- * process arg and deliberately not logged (turtle-spawn never logs argv and the
357
- * worker is single-tenant).
362
+ * Best-effort: on any failure we log and return [] so the preview server falls
363
+ * back to its built-in P2P/STUN behavior. The credential is passed as a process
364
+ * arg and deliberately not logged (turtle-spawn never logs argv and the worker
365
+ * is single-tenant).
358
366
  */
359
- async function fetchServeSimTurnArgsAsync(ctx, { env, logger }) {
367
+ async function fetchWebPreviewTurnArgsAsync(ctx, { env, logger }) {
360
368
  try {
361
369
  const deviceRunSessionId = getDeviceRunSessionIdOrThrow(env);
362
370
  const expoApiServerUrl = (0, nullthrows_1.default)(ctx.env.__API_SERVER_URL, '__API_SERVER_URL is not set');
@@ -370,16 +378,16 @@ async function fetchServeSimTurnArgsAsync(ctx, { env, logger }) {
370
378
  logger,
371
379
  });
372
380
  const { data } = TurnIceServersResponseSchema.parse(await response.json());
373
- const args = turnIceServersToServeSimArgs(data.iceServers);
381
+ const args = turnIceServersToWebPreviewArgs(data.iceServers);
374
382
  if (args.length > 0) {
375
- logger.info('Configured serve-sim with Cloudflare TURN ICE servers.');
383
+ logger.info('Configured the web preview with Cloudflare TURN ICE servers.');
376
384
  }
377
385
  return args;
378
386
  }
379
387
  catch (err) {
380
388
  const error = err instanceof Error ? err : new Error(String(err));
381
389
  sentry_1.Sentry.capture('Could not fetch Cloudflare TURN ICE servers', error, { level: 'warning' });
382
- logger.warn({ err: error }, 'Could not fetch Cloudflare TURN ICE servers; serve-sim will fall back to P2P/STUN.');
390
+ logger.warn({ err: error }, 'Could not fetch Cloudflare TURN ICE servers; the web preview will fall back to P2P/STUN.');
383
391
  return [];
384
392
  }
385
393
  }
@@ -476,6 +484,9 @@ function metricsCorsOriginToServeSimArgs(env) {
476
484
  function createServeSimPackageSpec(packageVersion) {
477
485
  return `${SERVE_SIM_PACKAGE_NAME}@${packageVersion ?? 'latest'}`;
478
486
  }
487
+ function createExpoDeviceHubPackageSpec(packageVersion) {
488
+ return `${EXPO_DEVICE_HUB_PACKAGE_NAME}@${packageVersion ?? 'latest'}`;
489
+ }
479
490
  function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], packageVersion, }) {
480
491
  return [
481
492
  '--yes',
@@ -483,7 +494,7 @@ function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], package
483
494
  '--port',
484
495
  String(port),
485
496
  '--host',
486
- SERVE_SIM_HOST,
497
+ WEB_PREVIEW_HOST,
487
498
  '--transport',
488
499
  'webrtc',
489
500
  '--webrtc-codec',
@@ -500,40 +511,67 @@ function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], package
500
511
  ...metricsCorsArgs,
501
512
  ];
502
513
  }
514
+ function createExpoDeviceHubArgs({ port, turnArgs = [], packageVersion, }) {
515
+ return [
516
+ '--yes',
517
+ createExpoDeviceHubPackageSpec(packageVersion),
518
+ '--port',
519
+ String(port),
520
+ '--host',
521
+ WEB_PREVIEW_HOST,
522
+ '--platform',
523
+ 'android',
524
+ '--transport',
525
+ 'webrtc',
526
+ '--webrtc-codec',
527
+ 'h264',
528
+ '--webrtc-ice-policy',
529
+ 'all',
530
+ '--max-dimension',
531
+ EXPO_DEVICE_HUB_MAX_DIMENSION,
532
+ '--video-bitrate',
533
+ EXPO_DEVICE_HUB_VIDEO_BITRATE,
534
+ '--video-fps',
535
+ EXPO_DEVICE_HUB_VIDEO_FPS,
536
+ '--hide-sidebar',
537
+ '--hide-boot-device',
538
+ ...turnArgs,
539
+ ];
540
+ }
503
541
  async function findAvailablePortAsync() {
504
542
  const server = (0, node_net_1.createServer)();
505
543
  server.unref();
506
544
  await new Promise((resolve, reject) => {
507
545
  server.once('error', reject);
508
- server.listen(0, SERVE_SIM_HOST, () => resolve());
546
+ server.listen(0, WEB_PREVIEW_HOST, () => resolve());
509
547
  });
510
548
  const address = server.address();
511
549
  await new Promise((resolve, reject) => {
512
550
  server.close(err => (err ? reject(err) : resolve()));
513
551
  });
514
552
  if (!address || typeof address === 'string') {
515
- throw new eas_build_job_1.SystemError('Could not allocate a local port for serve-sim.');
553
+ throw new eas_build_job_1.SystemError('Could not allocate a local port for the web preview.');
516
554
  }
517
555
  return address.port;
518
556
  }
519
- const ServeSimReadyResponseSchema = zod_1.z.object({
557
+ const WebPreviewReadyResponseSchema = zod_1.z.object({
520
558
  status: zod_1.z.literal('ready'),
521
559
  device: zod_1.z.string(),
522
560
  });
523
- async function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }) {
524
- const readyUrl = `http://${SERVE_SIM_HOST}:${port}/readyz`;
561
+ async function waitForWebPreviewReadyAsync({ previewServer, serverName, port, timeoutMs, }) {
562
+ const readyUrl = `http://${WEB_PREVIEW_HOST}:${port}/readyz`;
525
563
  const deadline = Date.now() + timeoutMs;
526
564
  let lastError;
527
565
  while (Date.now() < deadline) {
528
- if (serveSim.pid !== undefined && !isProcessRunning(serveSim.pid)) {
529
- throw new eas_build_job_1.SystemError(`serve-sim exited before becoming ready. Last output:\n${serveSim.getOutput() || '<empty>'}`);
566
+ if (previewServer.pid !== undefined && !isProcessRunning(previewServer.pid)) {
567
+ throw new eas_build_job_1.SystemError(`${serverName} exited before becoming ready. Last output:\n${previewServer.getOutput() || '<empty>'}`);
530
568
  }
531
569
  try {
532
570
  const response = await (0, turtleFetch_1.turtleFetch)(readyUrl, 'GET', {
533
571
  retries: 0,
534
572
  timeout: 2_000,
535
573
  });
536
- ServeSimReadyResponseSchema.parse(await response.json());
574
+ WebPreviewReadyResponseSchema.parse(await response.json());
537
575
  return;
538
576
  }
539
577
  catch (error) {
@@ -541,21 +579,20 @@ async function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }) {
541
579
  }
542
580
  await (0, retry_1.sleepAsync)(1_000);
543
581
  }
544
- throw new eas_build_job_1.SystemError(`Timed out waiting for serve-sim readiness at ${readyUrl}${lastError instanceof Error ? `: ${lastError.message}` : ''}. Last output:\n${serveSim.getOutput() || '<empty>'}`);
582
+ throw new eas_build_job_1.SystemError(`Timed out waiting for ${serverName} readiness at ${readyUrl}${lastError instanceof Error ? `: ${lastError.message}` : ''}. Last output:\n${previewServer.getOutput() || '<empty>'}`);
545
583
  }
546
- async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, packageVersion, }) {
584
+ async function startWebPreviewWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, serverName, packageSpec, createArgs, }) {
547
585
  const port = await findAvailablePortAsync();
548
- logger.info(`Launching ${createServeSimPackageSpec(packageVersion)} on ${SERVE_SIM_HOST}:${port}.`);
549
- const turnArgs = await fetchServeSimTurnArgsAsync(ctx, { env, logger });
550
- const metricsCorsArgs = metricsCorsOriginToServeSimArgs(env);
551
- const serveSim = spawnDetached({
586
+ logger.info(`Launching ${packageSpec} on ${WEB_PREVIEW_HOST}:${port}.`);
587
+ const turnArgs = await fetchWebPreviewTurnArgsAsync(ctx, { env, logger });
588
+ const previewServer = spawnDetached({
552
589
  command: 'npx',
553
- args: createServeSimArgs({ port, turnArgs, metricsCorsArgs, packageVersion }),
590
+ args: createArgs(port, turnArgs),
554
591
  env,
555
592
  });
556
593
  try {
557
- logger.info('Waiting for serve-sim to become ready.');
558
- await waitForServeSimReadyAsync({ serveSim, port, timeoutMs });
594
+ logger.info(`Waiting for ${serverName} to become ready.`);
595
+ await waitForWebPreviewReadyAsync({ previewServer, serverName, port, timeoutMs });
559
596
  const tunnel = await startNgrokTunnelAsync({
560
597
  port,
561
598
  subdomainPrefix: 'web-preview',
@@ -566,20 +603,51 @@ async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, time
566
603
  return {
567
604
  previewUrl: tunnel.url,
568
605
  stopAsync: async () => {
569
- const results = await Promise.allSettled([tunnel.stopAsync(), serveSim.stopAsync()]);
606
+ const results = await Promise.allSettled([tunnel.stopAsync(), previewServer.stopAsync()]);
570
607
  for (const result of results) {
571
608
  if (result.status === 'rejected') {
572
- logger.warn({ err: result.reason }, 'Could not stop a serve-sim preview resource.');
609
+ logger.warn({ err: result.reason }, `Could not stop a ${serverName} preview resource.`);
573
610
  }
574
611
  }
575
612
  },
576
613
  };
577
614
  }
578
615
  catch (error) {
579
- await serveSim.stopAsync();
616
+ await previewServer.stopAsync();
580
617
  throw error;
581
618
  }
582
619
  }
620
+ async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, packageVersion, }) {
621
+ const metricsCorsArgs = metricsCorsOriginToServeSimArgs(env);
622
+ return await startWebPreviewWithTunnelAsync(ctx, {
623
+ baseDomain,
624
+ env,
625
+ logger,
626
+ timeoutMs,
627
+ serverName: 'serve-sim',
628
+ packageSpec: createServeSimPackageSpec(packageVersion),
629
+ createArgs: (port, turnArgs) => createServeSimArgs({ port, turnArgs, metricsCorsArgs, packageVersion }),
630
+ });
631
+ }
632
+ async function startExpoDeviceHubWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, packageVersion, }) {
633
+ return await startWebPreviewWithTunnelAsync(ctx, {
634
+ baseDomain,
635
+ env,
636
+ logger,
637
+ timeoutMs,
638
+ serverName: 'expo-device-hub',
639
+ packageSpec: createExpoDeviceHubPackageSpec(packageVersion),
640
+ createArgs: (port, turnArgs) => createExpoDeviceHubArgs({ port, turnArgs, packageVersion }),
641
+ });
642
+ }
643
+ async function startDeviceWebPreviewWithTunnelAsync(ctx, { runtimePlatform, ...options }) {
644
+ switch (runtimePlatform) {
645
+ case steps_1.BuildRuntimePlatform.DARWIN:
646
+ return await startServeSimWithTunnelAsync(ctx, options);
647
+ case steps_1.BuildRuntimePlatform.LINUX:
648
+ return await startExpoDeviceHubWithTunnelAsync(ctx, options);
649
+ }
650
+ }
583
651
  async function startNgrokTunnelAsync({ port, subdomainPrefix, baseDomain, authtoken, rewriteHostHeader, logger, }) {
584
652
  const domain = `${subdomainPrefix}-${(0, node_crypto_1.randomBytes)(16).toString('hex')}.${baseDomain}`;
585
653
  logger.info(`Starting ngrok tunnel ${domain} -> http://localhost:${port}.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/build-tools",
3
- "version": "23.1.0",
3
+ "version": "23.2.0",
4
4
  "bugs": "https://github.com/expo/eas-cli/issues",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Expo <support@expo.io>",
@@ -102,5 +102,5 @@
102
102
  "typescript": "^5.5.4",
103
103
  "uuid": "^9.0.1"
104
104
  },
105
- "gitHead": "0658d58efdbf614f6920e3b4142223ac198bc003"
105
+ "gitHead": "74292d2ace404759ab8936d5aa1369e15d4b4f9d"
106
106
  }
@@ -1,3 +0,0 @@
1
- import { BuildFunction } from '@expo/steps';
2
- import { CustomBuildContext } from '../../customBuildContext';
3
- export declare function createStartServeSimRemoteSessionBuildFunction(ctx: CustomBuildContext): BuildFunction;