@expo/build-tools 23.1.0 → 24.0.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.
package/dist/context.d.ts CHANGED
@@ -48,6 +48,7 @@ export interface BuildContextOptions {
48
48
  skipNativeBuild?: boolean;
49
49
  metadata?: Metadata;
50
50
  expoApiV2BaseUrl?: string;
51
+ mcpServerUrl?: string;
51
52
  }
52
53
  export declare class SkipNativeBuildError extends Error {
53
54
  }
@@ -62,6 +63,7 @@ export declare class BuildContext<TJob extends Job = Job> {
62
63
  }) => void;
63
64
  readonly skipNativeBuild?: boolean;
64
65
  readonly expoApiV2BaseUrl?: string;
66
+ readonly mcpServerUrl?: string;
65
67
  artifacts: Artifacts;
66
68
  private readonly _isLocal;
67
69
  private _env;
package/dist/context.js CHANGED
@@ -25,6 +25,7 @@ class BuildContext {
25
25
  reportError;
26
26
  skipNativeBuild;
27
27
  expoApiV2BaseUrl;
28
+ mcpServerUrl;
28
29
  artifacts = {};
29
30
  _isLocal;
30
31
  _env;
@@ -49,6 +50,7 @@ class BuildContext {
49
50
  this._metadata = options.metadata;
50
51
  this.skipNativeBuild = options.skipNativeBuild;
51
52
  this.expoApiV2BaseUrl = options.expoApiV2BaseUrl;
53
+ this.mcpServerUrl = options.mcpServerUrl;
52
54
  const environmentSecrets = this.getEnvironmentSecrets(job);
53
55
  this._env = {
54
56
  ...options.env,
@@ -23,6 +23,7 @@ export declare class CustomBuildContext<TJob extends Job = Job> implements Exter
23
23
  readonly logger: bunyan;
24
24
  readonly graphqlClient: Client;
25
25
  readonly runtimeApi: BuilderRuntimeApi;
26
+ readonly mcpServerUrl?: string;
26
27
  job: TJob;
27
28
  metadata?: Metadata;
28
29
  private _env;
@@ -38,6 +38,7 @@ class CustomBuildContext {
38
38
  logger;
39
39
  graphqlClient;
40
40
  runtimeApi;
41
+ mcpServerUrl;
41
42
  job;
42
43
  metadata;
43
44
  _env;
@@ -56,6 +57,7 @@ class CustomBuildContext {
56
57
  this.runtimeApi = {
57
58
  uploadArtifact: (...args) => buildCtx['uploadArtifact'](...args),
58
59
  };
60
+ this.mcpServerUrl = buildCtx.mcpServerUrl;
59
61
  this.expoApiV2BaseUrl = buildCtx.expoApiV2BaseUrl;
60
62
  this.startTime = new Date();
61
63
  }
@@ -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) {
@@ -39,6 +39,21 @@ function createStartAndroidEmulatorBuildFunction() {
39
39
  defaultValue: AndroidEmulatorUtils_1.AndroidEmulatorUtils.defaultSystemImagePackage,
40
40
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
41
41
  }),
42
+ steps_1.BuildStepInput.createProvider({
43
+ id: 'lcd_width',
44
+ required: false,
45
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
46
+ }),
47
+ steps_1.BuildStepInput.createProvider({
48
+ id: 'lcd_height',
49
+ required: false,
50
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
51
+ }),
52
+ steps_1.BuildStepInput.createProvider({
53
+ id: 'lcd_density',
54
+ required: false,
55
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
56
+ }),
42
57
  steps_1.BuildStepInput.createProvider({
43
58
  id: 'count',
44
59
  required: false,
@@ -75,6 +90,9 @@ function createStartAndroidEmulatorBuildFunction() {
75
90
  const systemImagePackage = `${inputs.system_image_package.value}`;
76
91
  // We can cast because allowedValueTypeName validated this is a string.
77
92
  const deviceIdentifier = inputs.device_identifier.value;
93
+ const lcdWidth = inputs.lcd_width.value;
94
+ const lcdHeight = inputs.lcd_height.value;
95
+ const lcdDensity = inputs.lcd_density.value;
78
96
  const shouldAdjustAnimationScale = env.ANDROID_EMULATOR_ADJUST_ANIMATION_SCALE !== 'false' &&
79
97
  env.ANDROID_EMULATOR_ADJUST_ANIMATION_SCALE !== '0';
80
98
  if (!shouldAdjustAnimationScale) {
@@ -107,6 +125,9 @@ function createStartAndroidEmulatorBuildFunction() {
107
125
  deviceName,
108
126
  systemImagePackage,
109
127
  deviceIdentifier: deviceIdentifier ?? null,
128
+ lcdWidth: lcdWidth ?? null,
129
+ lcdHeight: lcdHeight ?? null,
130
+ lcdDensity: lcdDensity ?? null,
110
131
  env,
111
132
  logger,
112
133
  });
@@ -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();
@@ -82,21 +82,28 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
82
82
  if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
83
83
  await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
84
84
  }
85
- // Argent shells out to ffmpeg to record the screen. Installing it pulls a
86
- // large Homebrew dependency tree, so do not block session readiness on it:
87
- // the session comes up at its usual speed and only a recording started in
88
- // the first moments misses ffmpeg. Never rejects, so `void` is safe.
89
- void (0, remoteDeviceRunSession_1.ensureFfmpegInstalledAsync)({ runtimePlatform, env, logger });
85
+ // Start the potentially slow installation while Argent is being prepared.
86
+ // On Linux expo-device-hub calls this again and awaits the same in-flight
87
+ // setup before launching. On macOS this remains non-blocking, so only a
88
+ // recording started in the first moments may miss ffmpeg.
89
+ // Never rejects, so `void` is safe.
90
+ void (0, remoteDeviceRunSession_1.ensureFfmpegInstalledOnceAsync)({ runtimePlatform, env, logger });
90
91
  logger.info('Enabling the Argent artifacts list endpoint flag.');
91
- await (0, turtle_spawn_1.default)('bunx', [`${ARGENT_PACKAGE_NAME}@${versionSpec}`, 'enable', ARGENT_ARTIFACTS_LIST_ENDPOINT_FLAG], { env, logger });
92
+ await (0, turtle_spawn_1.default)('bun', [
93
+ 'x',
94
+ `${ARGENT_PACKAGE_NAME}@${versionSpec}`,
95
+ 'enable',
96
+ ARGENT_ARTIFACTS_LIST_ENDPOINT_FLAG,
97
+ ], { env, logger });
92
98
  logger.info('Enabling the Argent tool-server event log flag.');
93
- await (0, turtle_spawn_1.default)('bunx', [`${ARGENT_PACKAGE_NAME}@${versionSpec}`, 'enable', ARGENT_EVENT_LOG_FLAG], { env, logger });
94
- logger.info(`Launching ${ARGENT_PACKAGE_NAME}@${versionSpec} tool-server via bunx.`);
95
- // Keep Argent itself in foreground mode under the detached bunx process. This preserves
96
- // the bunx -> Argent CLI -> tool-server ancestry used to identify the matching state file.
99
+ await (0, turtle_spawn_1.default)('bun', ['x', `${ARGENT_PACKAGE_NAME}@${versionSpec}`, 'enable', ARGENT_EVENT_LOG_FLAG], { env, logger });
100
+ logger.info(`Launching ${ARGENT_PACKAGE_NAME}@${versionSpec} tool-server via bun x.`);
101
+ // Keep Argent itself in foreground mode under the detached bun process. This preserves
102
+ // the bun -> Argent CLI -> tool-server ancestry used to identify the matching state file.
97
103
  const argentServer = (0, remoteDeviceRunSession_1.spawnDetached)({
98
- command: 'bunx',
104
+ command: 'bun',
99
105
  args: [
106
+ 'x',
100
107
  `${ARGENT_PACKAGE_NAME}@${versionSpec}`,
101
108
  'server',
102
109
  'start',
@@ -146,7 +153,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
146
153
  logger,
147
154
  });
148
155
  let toolsTunnel;
149
- let serveSim;
156
+ let webPreview;
150
157
  try {
151
158
  toolsTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
152
159
  port: toolServerPort,
@@ -158,25 +165,21 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
158
165
  });
159
166
  const publicToolsUrl = toolsTunnel.url;
160
167
  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
- }
168
+ webPreview = await (0, remoteDeviceRunSession_1.startDeviceWebPreviewWithTunnelAsync)(ctx, {
169
+ runtimePlatform,
170
+ baseDomain: ngrokTunnelDomain,
171
+ env,
172
+ logger,
173
+ timeoutMs: STARTUP_TIMEOUT_MS,
174
+ });
175
+ logger.info(`Web preview URL: ${webPreview.previewUrl}`);
173
176
  await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
174
177
  ctx,
175
178
  deviceRunSessionId,
176
179
  remoteConfig: {
177
180
  toolsUrl: publicToolsUrl,
178
181
  ...(toolServerToken ? { toolsAuthToken: toolServerToken } : {}),
179
- ...(webPreviewUrl ? { webPreviewUrl } : {}),
182
+ webPreviewUrl: webPreview.previewUrl,
180
183
  },
181
184
  logger,
182
185
  });
@@ -195,8 +198,8 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
195
198
  });
196
199
  }
197
200
  finally {
198
- if (serveSim) {
199
- await serveSim.stopAsync();
201
+ if (webPreview) {
202
+ await webPreview.stopAsync();
200
203
  }
201
204
  if (toolsTunnel) {
202
205
  await toolsTunnel.stopAsync();
@@ -238,7 +241,7 @@ function warnIfArgentPackageVersionCannotBeVerified({ packageVersion, logger, })
238
241
  if (!validVersion) {
239
242
  logger.warn(`Argent remote simulator sessions require ${ARGENT_PACKAGE_NAME}@${exports.MIN_ARGENT_REMOTE_SESSION_VERSION} or newer, ` +
240
243
  `but package_version "${packageVersion}" is not an exact semver version that EAS can verify. ` +
241
- `Continuing and letting bunx resolve it.`);
244
+ `Continuing and letting bun x resolve it.`);
242
245
  return;
243
246
  }
244
247
  if (semver_1.default.lt(validVersion, exports.MIN_ARGENT_REMOTE_SESSION_VERSION)) {
@@ -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
  });
@@ -124,18 +124,24 @@ function createUploadToAscBuildFunction() {
124
124
  const ascApiKeyJson = await fs_extra_1.default.readJson(ascApiKeyPath);
125
125
  const ascApiKey = zod_1.z
126
126
  .object({
127
- issuer_id: zod_1.z.string(),
127
+ issuer_id: zod_1.z.string().nullish(),
128
128
  key_id: zod_1.z.string(),
129
129
  key: zod_1.z.string(),
130
130
  })
131
131
  .parse(ascApiKeyJson);
132
132
  const privateKey = await jose.importPKCS8(ascApiKey.key, 'ES256');
133
- const token = await new jose.SignJWT({})
133
+ const jwt = new jose.SignJWT({})
134
134
  .setProtectedHeader({ alg: 'ES256', kid: ascApiKey.key_id })
135
- .setIssuer(ascApiKey.issuer_id)
136
135
  .setAudience('appstoreconnect-v1')
137
- .setExpirationTime('20m')
138
- .sign(privateKey);
136
+ .setExpirationTime('20m');
137
+ if (ascApiKey.issuer_id) {
138
+ jwt.setIssuer(ascApiKey.issuer_id);
139
+ }
140
+ else {
141
+ // Nullish issuer_id means an individual API key
142
+ jwt.setSubject('user');
143
+ }
144
+ const token = await jwt.sign(privateKey);
139
145
  const client = new AscApiClient_1.AscApiClient({ token, logger: stepsCtx.logger });
140
146
  stepsCtx.logger.info(`Reading App information for Apple app identifier: ${appleAppIdentifier}...`);
141
147
  const appResponse = await AscApiUtils_1.AscApiUtils.getAppInfoAsync({ client, appleAppIdentifier });
@@ -350,6 +350,7 @@ exports.APPIUM_COMMAND_SUMMARIES = {
350
350
  mobileSimctl: 'Ran a simctl command',
351
351
  mobileSiriCommand: 'Sent a Siri command',
352
352
  mobileStartActivity: 'Started an activity',
353
+ mobileStartAutomationSession: 'Started an automation session',
353
354
  mobileStartLogsBroadcast: 'Started log streaming',
354
355
  mobileStartMediaProjectionRecording: 'Started media projection recording',
355
356
  mobileStartNetworkMonitor: 'Started network monitoring',
@@ -359,6 +360,7 @@ exports.APPIUM_COMMAND_SUMMARIES = {
359
360
  mobileStartService: 'Started a service',
360
361
  mobileStartSystemMonitor: 'Started system monitoring',
361
362
  mobileStartXctestScreenRecording: 'Started XCTest screen recording',
363
+ mobileStopAutomationSession: 'Stopped an automation session',
362
364
  mobileStopLogsBroadcast: 'Stopped log streaming',
363
365
  mobileStopMediaProjectionRecording: 'Stopped media projection recording',
364
366
  mobileStopNetworkMonitor: 'Stopped network monitoring',
@@ -380,6 +382,7 @@ exports.APPIUM_COMMAND_SUMMARIES = {
380
382
  mobileUnscheduleAction: 'Canceled a scheduled action',
381
383
  mobileUpdateSafariPreferences: 'Updated Safari preferences',
382
384
  mobileViewPortRect: 'Read the viewport size',
385
+ mobileViewportElementRect: 'Read an element position and size in the viewport',
383
386
  mobileViewportScreenshot: 'Took a viewport screenshot',
384
387
  mobileVoiceOverCurrentSpeech: 'Read current VoiceOver speech',
385
388
  mobileVoiceOverMove: 'Moved the VoiceOver cursor',
@@ -1,4 +1,4 @@
1
1
  export type BaseDriverAppiumCommand = 'activateApp' | 'activateIMEEngine' | 'active' | 'addAuthCredential' | 'addVirtualAuthenticator' | 'availableIMEEngines' | 'back' | 'clear' | 'clearDevicePosture' | 'click' | 'closeWindow' | 'createNewWindow' | 'createSession' | 'createVirtualPressureSource' | 'createVirtualSensor' | 'deactivateIMEEngine' | 'deleteCookie' | 'deleteCookies' | 'deleteSession' | 'deleteVirtualPressureSource' | 'deleteVirtualSensor' | 'elementDisplayed' | 'elementEnabled' | 'elementSelected' | 'elementShadowRoot' | 'execute' | 'executeAsync' | 'executeCdp' | 'fedCMCancelDialog' | 'fedCMClickDialogButton' | 'fedCMGetAccounts' | 'fedCMGetDialogType' | 'fedCMGetTitle' | 'fedCMResetCooldown' | 'fedCMSelectAccount' | 'fedCMSetDelayEnabled' | 'findElement' | 'findElementFromElement' | 'findElementFromShadowRoot' | 'findElements' | 'findElementsFromElement' | 'findElementsFromShadowRoot' | 'forward' | 'fullScreenWindow' | 'generateTestReport' | 'getActiveIMEEngine' | 'getAlertText' | 'getAppiumSessionCapabilities' | 'getAppiumSessions' | 'getAttribute' | 'getAuthCredential' | 'getComputedLabel' | 'getComputedRole' | 'getContexts' | 'getCookie' | 'getCookies' | 'getCssProperty' | 'getCurrentContext' | 'getDeviceTime' | 'getElementRect' | 'getElementScreenshot' | 'getGeoLocation' | 'getGlobalPrivacyControl' | 'getLog' | 'getLogEvents' | 'getLogTypes' | 'getName' | 'getNetworkConnection' | 'getOrientation' | 'getPageSource' | 'getProperty' | 'getRotation' | 'getScreenshot' | 'getSession' | 'getSettings' | 'getStatus' | 'getText' | 'getTimeouts' | 'getUrl' | 'getVirtualSensorInfo' | 'getWindowHandle' | 'getWindowHandles' | 'getWindowRect' | 'hideKeyboard' | 'installApp' | 'isAppInstalled' | 'isIMEActivated' | 'isKeyboardShown' | 'listCommands' | 'listExtensions' | 'logCustomEvent' | 'maximizeWindow' | 'minimizeWindow' | 'performActions' | 'postAcceptAlert' | 'postDismissAlert' | 'printPage' | 'pullFile' | 'pullFolder' | 'pushFile' | 'queryAppState' | 'receiveAsyncResponse' | 'refresh' | 'releaseActions' | 'removeAllAuthCredentials' | 'removeApp' | 'removeAuthCredential' | 'removeVirtualAuthenticator' | 'setAlertText' | 'setContext' | 'setCookie' | 'setDevicePosture' | 'setFrame' | 'setGeoLocation' | 'setGlobalPrivacyControl' | 'setNetworkConnection' | 'setOrientation' | 'setPermissions' | 'setRPHRegistrationMode' | 'setRotation' | 'setSPCTransactionMode' | 'setStorageAccess' | 'setUrl' | 'setUserAuthVerified' | 'setValue' | 'setWindow' | 'setWindowRect' | 'switchToParentFrame' | 'terminateApp' | 'timeouts' | 'title' | 'updateSettings' | 'updateVirtualPressureSource' | 'updateVirtualSensorReading';
2
- export type XCUITestAppiumCommand = 'asyncScriptTimeout' | 'background' | 'closeApp' | 'disableConditionInducer' | 'enableConditionInducer' | 'getClipboard' | 'getLocation' | 'getLocationInView' | 'getScreenInfo' | 'getSize' | 'getStrings' | 'getViewportRect' | 'getViewportScreenshot' | 'getWindowSize' | 'implicitWait' | 'isLocked' | 'keys' | 'launchApp' | 'listConditionInducers' | 'lock' | 'mobileActivateApp' | 'mobileCalibrateWebToRealCoordinatesTranslation' | 'mobileClearApp' | 'mobileClearKeychains' | 'mobileConfigureLocalization' | 'mobileDeepLink' | 'mobileDeleteFile' | 'mobileDeleteFolder' | 'mobileDisableVoiceOver' | 'mobileDoubleTap' | 'mobileDragFromToForDuration' | 'mobileDragFromToWithVelocity' | 'mobileEnableVoiceOver' | 'mobileEnrollBiometric' | 'mobileExpectNotification' | 'mobileForcePress' | 'mobileGetActiveAppInfo' | 'mobileGetAppearance' | 'mobileGetBatteryInfo' | 'mobileGetContentSize' | 'mobileGetContexts' | 'mobileGetDeviceInfo' | 'mobileGetDeviceTime' | 'mobileGetIncreaseContrast' | 'mobileGetPasteboard' | 'mobileGetPermission' | 'mobileGetSimulatedLocation' | 'mobileGetSource' | 'mobileGetXctestScreenRecordingInfo' | 'mobileHandleAlert' | 'mobileHideKeyboard' | 'mobileInstallApp' | 'mobileInstallCertificate' | 'mobileInstallXCTestBundle' | 'mobileIsAppInstalled' | 'mobileIsBiometricEnrolled' | 'mobileIsVoiceOverEnabled' | 'mobileKeys' | 'mobileKillApp' | 'mobileLaunchApp' | 'mobileListApps' | 'mobileListCertificates' | 'mobileListXCTestBundles' | 'mobilePerformAccessibilityAudit' | 'mobilePerformHandGesture' | 'mobilePerformIndigoHidEvent' | 'mobilePerformIoHidEvent' | 'mobilePinch' | 'mobilePressButton' | 'mobilePullFile' | 'mobilePullFolder' | 'mobilePushFile' | 'mobilePushNotification' | 'mobileQueryAppState' | 'mobileRemoveApp' | 'mobileRemoveCertificate' | 'mobileResetLocationService' | 'mobileResetPermission' | 'mobileResetSimulatedLocation' | 'mobileRotateDigitalCrown' | 'mobileRotateElement' | 'mobileRunXCTest' | 'mobileScroll' | 'mobileScrollToElement' | 'mobileSelectPickerWheelValue' | 'mobileSendBiometricMatch' | 'mobileSendMemoryWarning' | 'mobileSetAppearance' | 'mobileSetContentSize' | 'mobileSetIncreaseContrast' | 'mobileSetPasteboard' | 'mobileSetPermissions' | 'mobileSetSimulatedLocation' | 'mobileShake' | 'mobileSimctl' | 'mobileSiriCommand' | 'mobileStartLogsBroadcast' | 'mobileStartNetworkMonitor' | 'mobileStartPerfRecord' | 'mobileStartScreenRecording' | 'mobileStartSystemMonitor' | 'mobileStartXctestScreenRecording' | 'mobileStopLogsBroadcast' | 'mobileStopNetworkMonitor' | 'mobileStopPerfRecord' | 'mobileStopScreenRecording' | 'mobileStopSystemMonitor' | 'mobileStopXctestScreenRecording' | 'mobileSwipe' | 'mobileTap' | 'mobileTapWithNumberOfTaps' | 'mobileTerminateApp' | 'mobileTouchAndHold' | 'mobileTwoFingerTap' | 'mobileUpdateSafariPreferences' | 'mobileVoiceOverCurrentSpeech' | 'mobileVoiceOverMove' | 'reset' | 'setClipboard' | 'setValueImmediate' | 'startAudioRecording' | 'startRecordingScreen' | 'stopAudioRecording' | 'stopRecordingScreen' | 'submit' | 'toggleEnrollTouchId' | 'touchId' | 'unlock';
3
- export type UiAutomator2AppiumCommand = 'background' | 'fingerprint' | 'getClipboard' | 'getCurrentActivity' | 'getCurrentPackage' | 'getDisplayDensity' | 'getLocation' | 'getLocationInView' | 'getPerformanceData' | 'getPerformanceDataTypes' | 'getSize' | 'getStrings' | 'getSystemBars' | 'getWindowSize' | 'gsmCall' | 'gsmSignal' | 'gsmVoice' | 'implicitWait' | 'isLocationServicesEnabled' | 'isLocked' | 'keyevent' | 'keys' | 'lock' | 'longPressKeyCode' | 'mobileAcceptAlert' | 'mobileBackgroundApp' | 'mobileBluetooth' | 'mobileBroadcast' | 'mobileChangePermissions' | 'mobileClearApp' | 'mobileClickGesture' | 'mobileDeepLink' | 'mobileDeleteFile' | 'mobileDeviceidle' | 'mobileDismissAlert' | 'mobileDoubleClickGesture' | 'mobileDragGesture' | 'mobileExecEmuConsoleCommand' | 'mobileFingerprint' | 'mobileFlingGesture' | 'mobileGetActionHistory' | 'mobileGetBatteryInfo' | 'mobileGetChromeCapabilities' | 'mobileGetConnectivity' | 'mobileGetContexts' | 'mobileGetDeclaredOrientation' | 'mobileGetDeviceInfo' | 'mobileGetDeviceTime' | 'mobileGetGeolocation' | 'mobileGetNotifications' | 'mobileGetPerformanceData' | 'mobileGetPermissions' | 'mobileGetUiMode' | 'mobileGsmCall' | 'mobileGsmSignal' | 'mobileGsmVoice' | 'mobileInjectEmulatorCameraImage' | 'mobileInstallApp' | 'mobileInstallMultipleApks' | 'mobileIsAppInstalled' | 'mobileIsMediaProjectionRecordingRunning' | 'mobileListApps' | 'mobileListDisplays' | 'mobileListSms' | 'mobileListWindows' | 'mobileLongClickGesture' | 'mobileNetworkSpeed' | 'mobileNfc' | 'mobilePerformEditorAction' | 'mobilePerformStatusBarCommand' | 'mobilePinchCloseGesture' | 'mobilePinchOpenGesture' | 'mobilePowerAc' | 'mobilePowerCapacity' | 'mobilePressKey' | 'mobileRefreshGpsCache' | 'mobileRemoveApp' | 'mobileReplaceElementValue' | 'mobileResetAccessibilityCache' | 'mobileResetGeolocation' | 'mobileScheduleAction' | 'mobileScreenshots' | 'mobileScroll' | 'mobileScrollBackTo' | 'mobileScrollGesture' | 'mobileSendSms' | 'mobileSendTrimMemory' | 'mobileSetConnectivity' | 'mobileSetGeolocation' | 'mobileSetUiMode' | 'mobileShell' | 'mobileStartActivity' | 'mobileStartLogsBroadcast' | 'mobileStartMediaProjectionRecording' | 'mobileStartScreenStreaming' | 'mobileStartService' | 'mobileStopLogsBroadcast' | 'mobileStopMediaProjectionRecording' | 'mobileStopScreenStreaming' | 'mobileStopService' | 'mobileSwipeGesture' | 'mobileTerminateApp' | 'mobileType' | 'mobileUnlock' | 'mobileUnscheduleAction' | 'mobileViewPortRect' | 'mobileViewportScreenshot' | 'networkSpeed' | 'openNotifications' | 'powerAC' | 'powerCapacity' | 'pressKeyCode' | 'replaceValue' | 'sendSMS' | 'sensorSet' | 'setClipboard' | 'setStylusHandwriting' | 'setValueImmediate' | 'startActivity' | 'startRecordingScreen' | 'stopRecordingScreen' | 'toggleData' | 'toggleFlightMode' | 'toggleLocationServices' | 'toggleWiFi' | 'unlock';
2
+ export type XCUITestAppiumCommand = 'asyncScriptTimeout' | 'background' | 'closeApp' | 'disableConditionInducer' | 'enableConditionInducer' | 'getClipboard' | 'getLocation' | 'getLocationInView' | 'getScreenInfo' | 'getSize' | 'getStrings' | 'getViewportRect' | 'getViewportScreenshot' | 'getWindowSize' | 'implicitWait' | 'isLocked' | 'keys' | 'launchApp' | 'listConditionInducers' | 'lock' | 'mobileActivateApp' | 'mobileCalibrateWebToRealCoordinatesTranslation' | 'mobileClearApp' | 'mobileClearKeychains' | 'mobileConfigureLocalization' | 'mobileDeepLink' | 'mobileDeleteFile' | 'mobileDeleteFolder' | 'mobileDisableVoiceOver' | 'mobileDoubleTap' | 'mobileDragFromToForDuration' | 'mobileDragFromToWithVelocity' | 'mobileEnableVoiceOver' | 'mobileEnrollBiometric' | 'mobileExpectNotification' | 'mobileForcePress' | 'mobileGetActiveAppInfo' | 'mobileGetAppearance' | 'mobileGetBatteryInfo' | 'mobileGetContentSize' | 'mobileGetContexts' | 'mobileGetDeviceInfo' | 'mobileGetDeviceTime' | 'mobileGetIncreaseContrast' | 'mobileGetPasteboard' | 'mobileGetPermission' | 'mobileGetSimulatedLocation' | 'mobileGetSource' | 'mobileGetXctestScreenRecordingInfo' | 'mobileHandleAlert' | 'mobileHideKeyboard' | 'mobileInstallApp' | 'mobileInstallCertificate' | 'mobileInstallXCTestBundle' | 'mobileIsAppInstalled' | 'mobileIsBiometricEnrolled' | 'mobileIsVoiceOverEnabled' | 'mobileKeys' | 'mobileKillApp' | 'mobileLaunchApp' | 'mobileListApps' | 'mobileListCertificates' | 'mobileListXCTestBundles' | 'mobilePerformAccessibilityAudit' | 'mobilePerformHandGesture' | 'mobilePerformIndigoHidEvent' | 'mobilePerformIoHidEvent' | 'mobilePinch' | 'mobilePressButton' | 'mobilePullFile' | 'mobilePullFolder' | 'mobilePushFile' | 'mobilePushNotification' | 'mobileQueryAppState' | 'mobileRemoveApp' | 'mobileRemoveCertificate' | 'mobileResetLocationService' | 'mobileResetPermission' | 'mobileResetSimulatedLocation' | 'mobileRotateDigitalCrown' | 'mobileRotateElement' | 'mobileRunXCTest' | 'mobileScroll' | 'mobileScrollToElement' | 'mobileSelectPickerWheelValue' | 'mobileSendBiometricMatch' | 'mobileSendMemoryWarning' | 'mobileSetAppearance' | 'mobileSetContentSize' | 'mobileSetIncreaseContrast' | 'mobileSetPasteboard' | 'mobileSetPermissions' | 'mobileSetSimulatedLocation' | 'mobileShake' | 'mobileSimctl' | 'mobileSiriCommand' | 'mobileStartAutomationSession' | 'mobileStartLogsBroadcast' | 'mobileStartNetworkMonitor' | 'mobileStartPerfRecord' | 'mobileStartScreenRecording' | 'mobileStartSystemMonitor' | 'mobileStartXctestScreenRecording' | 'mobileStopAutomationSession' | 'mobileStopLogsBroadcast' | 'mobileStopNetworkMonitor' | 'mobileStopPerfRecord' | 'mobileStopScreenRecording' | 'mobileStopSystemMonitor' | 'mobileStopXctestScreenRecording' | 'mobileSwipe' | 'mobileTap' | 'mobileTapWithNumberOfTaps' | 'mobileTerminateApp' | 'mobileTouchAndHold' | 'mobileTwoFingerTap' | 'mobileUpdateSafariPreferences' | 'mobileVoiceOverCurrentSpeech' | 'mobileVoiceOverMove' | 'reset' | 'setClipboard' | 'setValueImmediate' | 'startAudioRecording' | 'startRecordingScreen' | 'stopAudioRecording' | 'stopRecordingScreen' | 'submit' | 'toggleEnrollTouchId' | 'touchId' | 'unlock';
3
+ export type UiAutomator2AppiumCommand = 'background' | 'fingerprint' | 'getClipboard' | 'getCurrentActivity' | 'getCurrentPackage' | 'getDisplayDensity' | 'getLocation' | 'getLocationInView' | 'getPerformanceData' | 'getPerformanceDataTypes' | 'getSize' | 'getStrings' | 'getSystemBars' | 'getWindowSize' | 'gsmCall' | 'gsmSignal' | 'gsmVoice' | 'implicitWait' | 'isLocationServicesEnabled' | 'isLocked' | 'keyevent' | 'keys' | 'lock' | 'longPressKeyCode' | 'mobileAcceptAlert' | 'mobileBackgroundApp' | 'mobileBluetooth' | 'mobileBroadcast' | 'mobileChangePermissions' | 'mobileClearApp' | 'mobileClickGesture' | 'mobileDeepLink' | 'mobileDeleteFile' | 'mobileDeviceidle' | 'mobileDismissAlert' | 'mobileDoubleClickGesture' | 'mobileDragGesture' | 'mobileExecEmuConsoleCommand' | 'mobileFingerprint' | 'mobileFlingGesture' | 'mobileGetActionHistory' | 'mobileGetBatteryInfo' | 'mobileGetChromeCapabilities' | 'mobileGetConnectivity' | 'mobileGetContexts' | 'mobileGetDeclaredOrientation' | 'mobileGetDeviceInfo' | 'mobileGetDeviceTime' | 'mobileGetGeolocation' | 'mobileGetNotifications' | 'mobileGetPerformanceData' | 'mobileGetPermissions' | 'mobileGetUiMode' | 'mobileGsmCall' | 'mobileGsmSignal' | 'mobileGsmVoice' | 'mobileInjectEmulatorCameraImage' | 'mobileInstallApp' | 'mobileInstallMultipleApks' | 'mobileIsAppInstalled' | 'mobileIsMediaProjectionRecordingRunning' | 'mobileListApps' | 'mobileListDisplays' | 'mobileListSms' | 'mobileListWindows' | 'mobileLongClickGesture' | 'mobileNetworkSpeed' | 'mobileNfc' | 'mobilePerformEditorAction' | 'mobilePerformStatusBarCommand' | 'mobilePinchCloseGesture' | 'mobilePinchOpenGesture' | 'mobilePowerAc' | 'mobilePowerCapacity' | 'mobilePressKey' | 'mobileRefreshGpsCache' | 'mobileRemoveApp' | 'mobileReplaceElementValue' | 'mobileResetAccessibilityCache' | 'mobileResetGeolocation' | 'mobileScheduleAction' | 'mobileScreenshots' | 'mobileScroll' | 'mobileScrollBackTo' | 'mobileScrollGesture' | 'mobileSendSms' | 'mobileSendTrimMemory' | 'mobileSetConnectivity' | 'mobileSetGeolocation' | 'mobileSetUiMode' | 'mobileShell' | 'mobileStartActivity' | 'mobileStartLogsBroadcast' | 'mobileStartMediaProjectionRecording' | 'mobileStartScreenStreaming' | 'mobileStartService' | 'mobileStopLogsBroadcast' | 'mobileStopMediaProjectionRecording' | 'mobileStopScreenStreaming' | 'mobileStopService' | 'mobileSwipeGesture' | 'mobileTerminateApp' | 'mobileType' | 'mobileUnlock' | 'mobileUnscheduleAction' | 'mobileViewPortRect' | 'mobileViewportElementRect' | 'mobileViewportScreenshot' | 'networkSpeed' | 'openNotifications' | 'powerAC' | 'powerCapacity' | 'pressKeyCode' | 'replaceValue' | 'sendSMS' | 'sensorSet' | 'setClipboard' | 'setStylusHandwriting' | 'setValueImmediate' | 'startActivity' | 'startRecordingScreen' | 'stopRecordingScreen' | 'toggleData' | 'toggleFlightMode' | 'toggleLocationServices' | 'toggleWiFi' | 'unlock';
4
4
  export type AppiumCommand = BaseDriverAppiumCommand | XCUITestAppiumCommand | UiAutomator2AppiumCommand;
@@ -33,43 +33,29 @@ export declare function waitForDeviceRunSessionStoppedAsync({ ctx, deviceRunSess
33
33
  signal?: AbortSignal;
34
34
  idleTimeout?: DeviceRunSessionIdleTimeout;
35
35
  }): Promise<void>;
36
- /**
37
- * Install ffmpeg when the runtime does not already provide it, so argent's
38
- * `screen-recording-start` tool can encode a video. The worker images do not
39
- * ship ffmpeg yet, so without this the tool fails with "`ffmpeg` was not found
40
- * on PATH" — on macOS (iOS simulators) and Linux (Android emulators) alike.
41
- *
42
- * Best-effort by design: screen recording is one optional argent tool, so a
43
- * failure here is logged and the session continues without it.
44
- *
45
- * The whole body is wrapped because the caller runs this in the background with
46
- * `void`. There is no unhandledRejection handler in the worker, so a rejection
47
- * escaping here would crash the process and take the live session with it.
48
- * `spawn` is not an async function and can throw synchronously, which
49
- * `asyncResult` cannot catch — it only wraps an already-created promise.
50
- */
51
- export declare function ensureFfmpegInstalledAsync({ runtimePlatform, env, logger, }: {
36
+ export declare function ensureFfmpegInstalledOnceAsync({ runtimePlatform, env, logger, }: {
52
37
  runtimePlatform: BuildRuntimePlatform;
53
38
  env: BuildStepEnv;
54
39
  logger: bunyan;
55
40
  }): Promise<void>;
56
41
  /**
57
- * Translate Cloudflare ICE servers into serve-sim CLI flags: `--stun-url` (the
42
+ * Translate Cloudflare ICE servers into web preview CLI flags: `--stun-url` (the
58
43
  * credential-less entries) and `--turn-url`/`--turn-username`/`--turn-credential`
59
- * (the entry carrying the short-lived credentials).
44
+ * (the entry carrying the short-lived credentials). serve-sim and expo-device-hub
45
+ * intentionally expose the same ICE flag contract.
60
46
  */
61
- export declare function turnIceServersToServeSimArgs(iceServers: TurnIceServers): string[];
47
+ export declare function turnIceServersToWebPreviewArgs(iceServers: TurnIceServers): string[];
62
48
  /**
63
49
  * Fetch short-lived Cloudflare TURN ICE servers for this job run from www
64
50
  * (minted on demand, mirroring how the worker fetches project clone URLs) and
65
- * translate them into serve-sim CLI flags.
51
+ * translate them into web preview CLI flags.
66
52
  *
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).
53
+ * Best-effort: on any failure we log and return [] so the preview server falls
54
+ * back to its built-in P2P/STUN behavior. The credential is passed as a process
55
+ * arg and deliberately not logged (turtle-spawn never logs argv and the worker
56
+ * is single-tenant).
71
57
  */
72
- export declare function fetchServeSimTurnArgsAsync(ctx: CustomBuildContext, { env, logger }: {
58
+ export declare function fetchWebPreviewTurnArgsAsync(ctx: CustomBuildContext, { env, logger }: {
73
59
  env: BuildStepEnv;
74
60
  logger: bunyan;
75
61
  }): Promise<string[]>;
@@ -98,15 +84,22 @@ export declare function createServeSimArgs({ port, turnArgs, metricsCorsArgs, pa
98
84
  metricsCorsArgs?: string[];
99
85
  packageVersion?: string;
100
86
  }): string[];
101
- export declare function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }: {
102
- serveSim: Pick<DetachedProcessHandle, 'pid' | 'getOutput'>;
87
+ export declare function createExpoDeviceHubArgs({ port, turnArgs, packageVersion, }: {
88
+ port: number;
89
+ turnArgs?: string[];
90
+ packageVersion?: string;
91
+ }): string[];
92
+ export declare function waitForWebPreviewReadyAsync({ previewServer, serverName, port, timeoutMs, }: {
93
+ previewServer: Pick<DetachedProcessHandle, 'pid' | 'getOutput'>;
94
+ serverName: string;
103
95
  port: number;
104
96
  timeoutMs: number;
105
97
  }): Promise<void>;
106
- export type ServeSimPreviewHandle = {
98
+ export type DeviceWebPreviewHandle = {
107
99
  previewUrl: string;
108
100
  stopAsync: () => Promise<void>;
109
101
  };
102
+ export type ServeSimPreviewHandle = DeviceWebPreviewHandle;
110
103
  export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, packageVersion, }: {
111
104
  baseDomain: string;
112
105
  env: BuildStepEnv;
@@ -114,6 +107,22 @@ export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, {
114
107
  timeoutMs: number;
115
108
  packageVersion?: string;
116
109
  }): Promise<ServeSimPreviewHandle>;
110
+ export declare function startExpoDeviceHubWithTunnelAsync(ctx: CustomBuildContext, { runtimePlatform, baseDomain, env, logger, timeoutMs, packageVersion, }: {
111
+ runtimePlatform: BuildRuntimePlatform;
112
+ baseDomain: string;
113
+ env: BuildStepEnv;
114
+ logger: bunyan;
115
+ timeoutMs: number;
116
+ packageVersion?: string;
117
+ }): Promise<DeviceWebPreviewHandle>;
118
+ export declare function startDeviceWebPreviewWithTunnelAsync(ctx: CustomBuildContext, { runtimePlatform, ...options }: {
119
+ runtimePlatform: BuildRuntimePlatform;
120
+ baseDomain: string;
121
+ env: BuildStepEnv;
122
+ logger: bunyan;
123
+ timeoutMs: number;
124
+ packageVersion?: string;
125
+ }): Promise<DeviceWebPreviewHandle>;
117
126
  export type NgrokTunnelHandle = {
118
127
  url: string;
119
128
  stopAsync: () => Promise<void>;
@@ -41,15 +41,18 @@ exports.getNgrokTunnelDomainOrThrow = getNgrokTunnelDomainOrThrow;
41
41
  exports.getNgrokAuthtokenOrThrow = getNgrokAuthtokenOrThrow;
42
42
  exports.selectXcodeDeveloperDirectoryAsync = selectXcodeDeveloperDirectoryAsync;
43
43
  exports.waitForDeviceRunSessionStoppedAsync = waitForDeviceRunSessionStoppedAsync;
44
- exports.ensureFfmpegInstalledAsync = ensureFfmpegInstalledAsync;
45
- exports.turnIceServersToServeSimArgs = turnIceServersToServeSimArgs;
46
- exports.fetchServeSimTurnArgsAsync = fetchServeSimTurnArgsAsync;
44
+ exports.ensureFfmpegInstalledOnceAsync = ensureFfmpegInstalledOnceAsync;
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 = '960';
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 {
@@ -262,10 +269,9 @@ async function sleepUntilAbortedAsync(timeoutMs, signal) {
262
269
  }
263
270
  }
264
271
  }
265
- // Argent encodes screen recordings by piping simulator frames into `ffmpeg`,
266
- // which it resolves from PATH. The tool-server inherits this step's env, so
267
- // spawning ffmpeg resolves against the same PATH argent will search: it rejects
268
- // with ENOENT when the binary is absent, and running it also proves it works.
272
+ // Device-session tools resolve `ffmpeg` from PATH. Spawning it with the step's
273
+ // environment rejects with ENOENT when the binary is absent, and running it also
274
+ // proves that the installed binary works.
269
275
  async function isFfmpegAvailableAsync(env) {
270
276
  return (await (0, results_1.asyncResult)((0, turtle_spawn_1.default)('ffmpeg', ['-version'], { env }))).ok;
271
277
  }
@@ -283,14 +289,14 @@ async function installFfmpegWithAptAsync({ env, logger, }) {
283
289
  await (0, results_1.asyncResult)((0, turtle_spawn_1.default)('sudo', ['apt-get', 'update'], { env: aptEnv, logger }));
284
290
  await (0, turtle_spawn_1.default)('sudo', ['apt-get', 'install', '-y', 'ffmpeg'], { env: aptEnv, logger });
285
291
  }
292
+ let ffmpegSetupPromise;
286
293
  /**
287
- * Install ffmpeg when the runtime does not already provide it, so argent's
288
- * `screen-recording-start` tool can encode a video. The worker images do not
289
- * ship ffmpeg yet, so without this the tool fails with "`ffmpeg` was not found
290
- * on PATH" — on macOS (iOS simulators) and Linux (Android emulators) alike.
294
+ * Install ffmpeg when the runtime does not already provide it. Device-session
295
+ * tools use it for video encoding on macOS (iOS simulators) and Linux (Android
296
+ * emulators) alike, but the worker images do not ship it yet.
291
297
  *
292
- * Best-effort by design: screen recording is one optional argent tool, so a
293
- * failure here is logged and the session continues without it.
298
+ * Best-effort by design: a failure here is logged and the session continues
299
+ * without FFmpeg-dependent features.
294
300
  *
295
301
  * The whole body is wrapped because the caller runs this in the background with
296
302
  * `void`. There is no unhandledRejection handler in the worker, so a rejection
@@ -305,7 +311,7 @@ async function ensureFfmpegInstalledAsync({ runtimePlatform, env, logger, }) {
305
311
  return;
306
312
  }
307
313
  const isDarwin = runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN;
308
- logger.info(`ffmpeg is not installed, installing it with ${isDarwin ? 'Homebrew' : 'apt'} for argent screen recording.`);
314
+ logger.info(`ffmpeg is not installed, installing it with ${isDarwin ? 'Homebrew' : 'apt'} for the device session.`);
309
315
  if (isDarwin) {
310
316
  await installFfmpegWithHomebrewAsync({ env, logger });
311
317
  }
@@ -316,10 +322,26 @@ async function ensureFfmpegInstalledAsync({ runtimePlatform, env, logger, }) {
316
322
  }
317
323
  catch (err) {
318
324
  const error = err instanceof Error ? err : new Error(String(err));
319
- sentry_1.Sentry.capture('Could not install ffmpeg for argent screen recording', error, {
325
+ sentry_1.Sentry.capture('Could not install ffmpeg for the device session', error, {
320
326
  level: 'warning',
321
327
  });
322
- logger.warn({ err: error }, 'Could not install ffmpeg. Argent screen recording will not work in this session.');
328
+ logger.warn({ err: error }, 'Could not install ffmpeg. FFmpeg-dependent features may not work in this session.');
329
+ }
330
+ }
331
+ async function ensureFfmpegInstalledOnceAsync({ runtimePlatform, env, logger, }) {
332
+ if (ffmpegSetupPromise) {
333
+ await ffmpegSetupPromise;
334
+ return;
335
+ }
336
+ const setupPromise = ensureFfmpegInstalledAsync({ runtimePlatform, env, logger });
337
+ ffmpegSetupPromise = setupPromise;
338
+ try {
339
+ await setupPromise;
340
+ }
341
+ finally {
342
+ if (ffmpegSetupPromise === setupPromise) {
343
+ ffmpegSetupPromise = undefined;
344
+ }
323
345
  }
324
346
  }
325
347
  const TurnIceServersResponseSchema = zod_1.z.object({
@@ -328,11 +350,12 @@ const TurnIceServersResponseSchema = zod_1.z.object({
328
350
  }),
329
351
  });
330
352
  /**
331
- * Translate Cloudflare ICE servers into serve-sim CLI flags: `--stun-url` (the
353
+ * Translate Cloudflare ICE servers into web preview CLI flags: `--stun-url` (the
332
354
  * credential-less entries) and `--turn-url`/`--turn-username`/`--turn-credential`
333
- * (the entry carrying the short-lived credentials).
355
+ * (the entry carrying the short-lived credentials). serve-sim and expo-device-hub
356
+ * intentionally expose the same ICE flag contract.
334
357
  */
335
- function turnIceServersToServeSimArgs(iceServers) {
358
+ function turnIceServersToWebPreviewArgs(iceServers) {
336
359
  const stunUrls = iceServers
337
360
  .filter(server => !server.username && !server.credential)
338
361
  .flatMap(server => server.urls);
@@ -349,14 +372,14 @@ function turnIceServersToServeSimArgs(iceServers) {
349
372
  /**
350
373
  * Fetch short-lived Cloudflare TURN ICE servers for this job run from www
351
374
  * (minted on demand, mirroring how the worker fetches project clone URLs) and
352
- * translate them into serve-sim CLI flags.
375
+ * translate them into web preview CLI flags.
353
376
  *
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).
377
+ * Best-effort: on any failure we log and return [] so the preview server falls
378
+ * back to its built-in P2P/STUN behavior. The credential is passed as a process
379
+ * arg and deliberately not logged (turtle-spawn never logs argv and the worker
380
+ * is single-tenant).
358
381
  */
359
- async function fetchServeSimTurnArgsAsync(ctx, { env, logger }) {
382
+ async function fetchWebPreviewTurnArgsAsync(ctx, { env, logger }) {
360
383
  try {
361
384
  const deviceRunSessionId = getDeviceRunSessionIdOrThrow(env);
362
385
  const expoApiServerUrl = (0, nullthrows_1.default)(ctx.env.__API_SERVER_URL, '__API_SERVER_URL is not set');
@@ -370,16 +393,16 @@ async function fetchServeSimTurnArgsAsync(ctx, { env, logger }) {
370
393
  logger,
371
394
  });
372
395
  const { data } = TurnIceServersResponseSchema.parse(await response.json());
373
- const args = turnIceServersToServeSimArgs(data.iceServers);
396
+ const args = turnIceServersToWebPreviewArgs(data.iceServers);
374
397
  if (args.length > 0) {
375
- logger.info('Configured serve-sim with Cloudflare TURN ICE servers.');
398
+ logger.info('Configured the web preview with Cloudflare TURN ICE servers.');
376
399
  }
377
400
  return args;
378
401
  }
379
402
  catch (err) {
380
403
  const error = err instanceof Error ? err : new Error(String(err));
381
404
  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.');
405
+ logger.warn({ err: error }, 'Could not fetch Cloudflare TURN ICE servers; the web preview will fall back to P2P/STUN.');
383
406
  return [];
384
407
  }
385
408
  }
@@ -476,6 +499,9 @@ function metricsCorsOriginToServeSimArgs(env) {
476
499
  function createServeSimPackageSpec(packageVersion) {
477
500
  return `${SERVE_SIM_PACKAGE_NAME}@${packageVersion ?? 'latest'}`;
478
501
  }
502
+ function createExpoDeviceHubPackageSpec(packageVersion) {
503
+ return `${EXPO_DEVICE_HUB_PACKAGE_NAME}@${packageVersion ?? 'latest'}`;
504
+ }
479
505
  function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], packageVersion, }) {
480
506
  return [
481
507
  '--yes',
@@ -483,7 +509,7 @@ function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], package
483
509
  '--port',
484
510
  String(port),
485
511
  '--host',
486
- SERVE_SIM_HOST,
512
+ WEB_PREVIEW_HOST,
487
513
  '--transport',
488
514
  'webrtc',
489
515
  '--webrtc-codec',
@@ -500,40 +526,67 @@ function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], package
500
526
  ...metricsCorsArgs,
501
527
  ];
502
528
  }
529
+ function createExpoDeviceHubArgs({ port, turnArgs = [], packageVersion, }) {
530
+ return [
531
+ '--yes',
532
+ createExpoDeviceHubPackageSpec(packageVersion),
533
+ '--port',
534
+ String(port),
535
+ '--host',
536
+ WEB_PREVIEW_HOST,
537
+ '--platform',
538
+ 'android',
539
+ '--transport',
540
+ 'webrtc',
541
+ '--webrtc-codec',
542
+ 'h264',
543
+ '--webrtc-ice-policy',
544
+ 'all',
545
+ '--max-dimension',
546
+ EXPO_DEVICE_HUB_MAX_DIMENSION,
547
+ '--video-bitrate',
548
+ EXPO_DEVICE_HUB_VIDEO_BITRATE,
549
+ '--video-fps',
550
+ EXPO_DEVICE_HUB_VIDEO_FPS,
551
+ '--hide-sidebar',
552
+ '--hide-boot-device',
553
+ ...turnArgs,
554
+ ];
555
+ }
503
556
  async function findAvailablePortAsync() {
504
557
  const server = (0, node_net_1.createServer)();
505
558
  server.unref();
506
559
  await new Promise((resolve, reject) => {
507
560
  server.once('error', reject);
508
- server.listen(0, SERVE_SIM_HOST, () => resolve());
561
+ server.listen(0, WEB_PREVIEW_HOST, () => resolve());
509
562
  });
510
563
  const address = server.address();
511
564
  await new Promise((resolve, reject) => {
512
565
  server.close(err => (err ? reject(err) : resolve()));
513
566
  });
514
567
  if (!address || typeof address === 'string') {
515
- throw new eas_build_job_1.SystemError('Could not allocate a local port for serve-sim.');
568
+ throw new eas_build_job_1.SystemError('Could not allocate a local port for the web preview.');
516
569
  }
517
570
  return address.port;
518
571
  }
519
- const ServeSimReadyResponseSchema = zod_1.z.object({
572
+ const WebPreviewReadyResponseSchema = zod_1.z.object({
520
573
  status: zod_1.z.literal('ready'),
521
574
  device: zod_1.z.string(),
522
575
  });
523
- async function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }) {
524
- const readyUrl = `http://${SERVE_SIM_HOST}:${port}/readyz`;
576
+ async function waitForWebPreviewReadyAsync({ previewServer, serverName, port, timeoutMs, }) {
577
+ const readyUrl = `http://${WEB_PREVIEW_HOST}:${port}/readyz`;
525
578
  const deadline = Date.now() + timeoutMs;
526
579
  let lastError;
527
580
  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>'}`);
581
+ if (previewServer.pid !== undefined && !isProcessRunning(previewServer.pid)) {
582
+ throw new eas_build_job_1.SystemError(`${serverName} exited before becoming ready. Last output:\n${previewServer.getOutput() || '<empty>'}`);
530
583
  }
531
584
  try {
532
585
  const response = await (0, turtleFetch_1.turtleFetch)(readyUrl, 'GET', {
533
586
  retries: 0,
534
587
  timeout: 2_000,
535
588
  });
536
- ServeSimReadyResponseSchema.parse(await response.json());
589
+ WebPreviewReadyResponseSchema.parse(await response.json());
537
590
  return;
538
591
  }
539
592
  catch (error) {
@@ -541,21 +594,20 @@ async function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }) {
541
594
  }
542
595
  await (0, retry_1.sleepAsync)(1_000);
543
596
  }
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>'}`);
597
+ 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
598
  }
546
- async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, packageVersion, }) {
599
+ async function startWebPreviewWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, serverName, packageSpec, createArgs, }) {
547
600
  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({
601
+ logger.info(`Launching ${packageSpec} on ${WEB_PREVIEW_HOST}:${port}.`);
602
+ const turnArgs = await fetchWebPreviewTurnArgsAsync(ctx, { env, logger });
603
+ const previewServer = spawnDetached({
552
604
  command: 'npx',
553
- args: createServeSimArgs({ port, turnArgs, metricsCorsArgs, packageVersion }),
605
+ args: createArgs(port, turnArgs),
554
606
  env,
555
607
  });
556
608
  try {
557
- logger.info('Waiting for serve-sim to become ready.');
558
- await waitForServeSimReadyAsync({ serveSim, port, timeoutMs });
609
+ logger.info(`Waiting for ${serverName} to become ready.`);
610
+ await waitForWebPreviewReadyAsync({ previewServer, serverName, port, timeoutMs });
559
611
  const tunnel = await startNgrokTunnelAsync({
560
612
  port,
561
613
  subdomainPrefix: 'web-preview',
@@ -566,20 +618,54 @@ async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, time
566
618
  return {
567
619
  previewUrl: tunnel.url,
568
620
  stopAsync: async () => {
569
- const results = await Promise.allSettled([tunnel.stopAsync(), serveSim.stopAsync()]);
621
+ const results = await Promise.allSettled([tunnel.stopAsync(), previewServer.stopAsync()]);
570
622
  for (const result of results) {
571
623
  if (result.status === 'rejected') {
572
- logger.warn({ err: result.reason }, 'Could not stop a serve-sim preview resource.');
624
+ logger.warn({ err: result.reason }, `Could not stop a ${serverName} preview resource.`);
573
625
  }
574
626
  }
575
627
  },
576
628
  };
577
629
  }
578
630
  catch (error) {
579
- await serveSim.stopAsync();
631
+ await previewServer.stopAsync();
580
632
  throw error;
581
633
  }
582
634
  }
635
+ async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, packageVersion, }) {
636
+ const metricsCorsArgs = metricsCorsOriginToServeSimArgs(env);
637
+ return await startWebPreviewWithTunnelAsync(ctx, {
638
+ baseDomain,
639
+ env,
640
+ logger,
641
+ timeoutMs,
642
+ serverName: 'serve-sim',
643
+ packageSpec: createServeSimPackageSpec(packageVersion),
644
+ createArgs: (port, turnArgs) => createServeSimArgs({ port, turnArgs, metricsCorsArgs, packageVersion }),
645
+ });
646
+ }
647
+ async function startExpoDeviceHubWithTunnelAsync(ctx, { runtimePlatform, baseDomain, env, logger, timeoutMs, packageVersion, }) {
648
+ if (runtimePlatform === steps_1.BuildRuntimePlatform.LINUX) {
649
+ await ensureFfmpegInstalledOnceAsync({ runtimePlatform, env, logger });
650
+ }
651
+ return await startWebPreviewWithTunnelAsync(ctx, {
652
+ baseDomain,
653
+ env,
654
+ logger,
655
+ timeoutMs,
656
+ serverName: 'expo-device-hub',
657
+ packageSpec: createExpoDeviceHubPackageSpec(packageVersion),
658
+ createArgs: (port, turnArgs) => createExpoDeviceHubArgs({ port, turnArgs, packageVersion }),
659
+ });
660
+ }
661
+ async function startDeviceWebPreviewWithTunnelAsync(ctx, { runtimePlatform, ...options }) {
662
+ switch (runtimePlatform) {
663
+ case steps_1.BuildRuntimePlatform.DARWIN:
664
+ return await startServeSimWithTunnelAsync(ctx, options);
665
+ case steps_1.BuildRuntimePlatform.LINUX:
666
+ return await startExpoDeviceHubWithTunnelAsync(ctx, { ...options, runtimePlatform });
667
+ }
668
+ }
583
669
  async function startNgrokTunnelAsync({ port, subdomainPrefix, baseDomain, authtoken, rewriteHostHeader, logger, }) {
584
670
  const domain = `${subdomainPrefix}-${(0, node_crypto_1.randomBytes)(16).toString('hex')}.${baseDomain}`;
585
671
  logger.info(`Starting ngrok tunnel ${domain} -> http://localhost:${port}.`);
@@ -21,10 +21,13 @@ export declare namespace AndroidEmulatorUtils {
21
21
  deviceName: AndroidVirtualDeviceName;
22
22
  env: NodeJS.ProcessEnv;
23
23
  }): Promise<AndroidDeviceSerialId | null>;
24
- function createAsync({ deviceName, systemImagePackage, deviceIdentifier, env, logger, }: {
24
+ function createAsync({ deviceName, systemImagePackage, deviceIdentifier, lcdWidth, lcdHeight, lcdDensity, env, logger, }: {
25
25
  deviceName: AndroidVirtualDeviceName;
26
26
  systemImagePackage: string;
27
27
  deviceIdentifier: AndroidDeviceName | null;
28
+ lcdWidth: number | null;
29
+ lcdHeight: number | null;
30
+ lcdDensity: number | null;
28
31
  env: NodeJS.ProcessEnv;
29
32
  logger: bunyan;
30
33
  }): Promise<void>;
@@ -68,7 +68,7 @@ var AndroidEmulatorUtils;
68
68
  return null;
69
69
  }
70
70
  AndroidEmulatorUtils.getSerialIdAsync = getSerialIdAsync;
71
- async function createAsync({ deviceName, systemImagePackage, deviceIdentifier, env, logger, }) {
71
+ async function createAsync({ deviceName, systemImagePackage, deviceIdentifier, lcdWidth, lcdHeight, lcdDensity, env, logger, }) {
72
72
  const avdManager = (0, turtle_spawn_1.default)('avdmanager', [
73
73
  'create',
74
74
  'avd',
@@ -121,6 +121,10 @@ var AndroidEmulatorUtils;
121
121
  }
122
122
  }
123
123
  }
124
+ if (lcdWidth !== null && lcdHeight !== null && lcdDensity !== null) {
125
+ logger.info(`Setting screen resolution to ${lcdWidth}x${lcdHeight} and density to ${lcdDensity} ppi.`);
126
+ configIniFileContent = `${configIniFileContent}\nhw.lcd.height=${lcdHeight}\nhw.lcd.width=${lcdWidth}\nhw.lcd.density=${lcdDensity}\n`;
127
+ }
124
128
  const shouldAdjustHeapSize = env.ANDROID_EMULATOR_ADJUST_HEAP_SIZE !== 'false' &&
125
129
  env.ANDROID_EMULATOR_ADJUST_HEAP_SIZE !== '0';
126
130
  if (shouldAdjustHeapSize) {
@@ -0,0 +1,2 @@
1
+ import { type bunyan } from '@expo/logger';
2
+ export declare function withLogPhaseAsync<T>(logger: bunyan, name: string, fn: (logger: bunyan) => Promise<T>): Promise<T>;
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.withLogPhaseAsync = withLogPhaseAsync;
4
+ const eas_build_job_1 = require("@expo/eas-build-job");
5
+ const steps_1 = require("@expo/steps");
6
+ async function withLogPhaseAsync(logger, name, fn) {
7
+ const phaseLogger = logger.child({
8
+ phase: eas_build_job_1.BuildPhase.CUSTOM,
9
+ buildStepId: steps_1.BuildStep.getNewId(),
10
+ buildStepDisplayName: name,
11
+ });
12
+ phaseLogger.info({ marker: eas_build_job_1.LogMarker.START_PHASE }, `Start phase: ${name}`);
13
+ try {
14
+ const result = await fn(phaseLogger);
15
+ phaseLogger.info({ marker: eas_build_job_1.LogMarker.END_PHASE, result: eas_build_job_1.BuildPhaseResult.SUCCESS }, `End phase: ${name}`);
16
+ return result;
17
+ }
18
+ catch (error) {
19
+ phaseLogger.info({ marker: eas_build_job_1.LogMarker.END_PHASE, result: eas_build_job_1.BuildPhaseResult.FAIL }, `End phase: ${name}`);
20
+ throw error;
21
+ }
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/build-tools",
3
- "version": "23.1.0",
3
+ "version": "24.0.0",
4
4
  "bugs": "https://github.com/expo/eas-cli/issues",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Expo <support@expo.io>",
@@ -39,17 +39,17 @@
39
39
  "dependencies": {
40
40
  "@expo/config": "55.0.10",
41
41
  "@expo/config-plugins": "55.0.7",
42
- "@expo/downloader": "23.0.0",
43
- "@expo/eas-build-job": "23.1.0",
42
+ "@expo/downloader": "24.0.0",
43
+ "@expo/eas-build-job": "24.0.0",
44
44
  "@expo/env": "^0.4.0",
45
- "@expo/logger": "23.1.0",
45
+ "@expo/logger": "24.0.0",
46
46
  "@expo/package-manager": "1.9.10",
47
47
  "@expo/plist": "^0.3.5",
48
48
  "@expo/results": "^1.0.0",
49
49
  "@expo/spawn-async": "1.7.2",
50
- "@expo/steps": "23.1.0",
51
- "@expo/template-file": "23.0.0",
52
- "@expo/turtle-spawn": "23.1.0",
50
+ "@expo/steps": "24.0.0",
51
+ "@expo/template-file": "24.0.0",
52
+ "@expo/turtle-spawn": "24.0.0",
53
53
  "@expo/xcpretty": "^4.3.1",
54
54
  "@ngrok/ngrok": "1.7.0",
55
55
  "@sentry/node": "7.77.0",
@@ -102,5 +102,5 @@
102
102
  "typescript": "^5.5.4",
103
103
  "uuid": "^9.0.1"
104
104
  },
105
- "gitHead": "0658d58efdbf614f6920e3b4142223ac198bc003"
105
+ "gitHead": "5409c03d58c50b80e4ec9d5ca40f2fbdfaba9aa0"
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;