@expo/build-tools 21.4.0 → 21.5.1

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.
Files changed (32) hide show
  1. package/dist/android/gradleProfile.js +14 -2
  2. package/dist/builders/custom.js +20 -12
  3. package/dist/common/git.d.ts +4 -0
  4. package/dist/common/git.js +35 -2
  5. package/dist/common/jobHooks.js +10 -0
  6. package/dist/generic.js +15 -6
  7. package/dist/index.d.ts +2 -1
  8. package/dist/index.js +3 -1
  9. package/dist/logging/HttpLogStream.d.ts +28 -0
  10. package/dist/logging/HttpLogStream.js +137 -0
  11. package/dist/steps/compositeFunctions.d.ts +11 -0
  12. package/dist/steps/compositeFunctions.js +62 -0
  13. package/dist/steps/easFunctions.js +4 -0
  14. package/dist/steps/functions/checkout.js +26 -1
  15. package/dist/steps/functions/collectServeSimMetrics.d.ts +3 -0
  16. package/dist/steps/functions/collectServeSimMetrics.js +41 -0
  17. package/dist/steps/functions/startAgentDeviceRemoteSession.d.ts +1 -0
  18. package/dist/steps/functions/startAgentDeviceRemoteSession.js +56 -48
  19. package/dist/steps/functions/startArgentRemoteSession.js +17 -2
  20. package/dist/steps/functions/startServeSimMetrics.d.ts +2 -0
  21. package/dist/steps/functions/startServeSimMetrics.js +17 -0
  22. package/dist/steps/functions/startServeSimRemoteSession.js +19 -14
  23. package/dist/steps/utils/agentDeviceArtifacts.js +1 -1
  24. package/dist/steps/utils/argentArtifacts.js +1 -1
  25. package/dist/steps/utils/deviceRunSessionEvents.js +68 -10
  26. package/dist/steps/utils/remoteDeviceRunSession.d.ts +43 -5
  27. package/dist/steps/utils/remoteDeviceRunSession.js +249 -37
  28. package/dist/steps/utils/serveSimMetricsArtifacts.d.ts +9 -0
  29. package/dist/steps/utils/serveSimMetricsArtifacts.js +49 -0
  30. package/dist/steps/utils/serveSimMetricsRecorder.d.ts +30 -0
  31. package/dist/steps/utils/serveSimMetricsRecorder.js +232 -0
  32. package/package.json +4 -4
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DEFAULT_AGENT_DEVICE_VERSION = void 0;
6
7
  exports.createStartAgentDeviceRemoteSessionBuildFunction = createStartAgentDeviceRemoteSessionBuildFunction;
7
8
  exports.stopAgentDeviceEventCollectionSafelyAsync = stopAgentDeviceEventCollectionSafelyAsync;
8
9
  const eas_build_job_1 = require("@expo/eas-build-job");
@@ -16,6 +17,7 @@ const agentDeviceArtifacts_1 = require("../utils/agentDeviceArtifacts");
16
17
  const agentDeviceEvents_1 = require("../utils/agentDeviceEvents");
17
18
  const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
18
19
  const AGENT_DEVICE_PACKAGE_NAME = 'agent-device';
20
+ exports.DEFAULT_AGENT_DEVICE_VERSION = '0.20.3';
19
21
  const AGENT_DEVICE_REPO_URL = 'https://github.com/callstack/agent-device.git';
20
22
  const SRC_DIR = '/tmp/agent-device-src';
21
23
  const AGENT_DEVICE_STATE_DIR = node_path_1.default.join(node_os_1.default.homedir(), '.agent-device');
@@ -46,9 +48,9 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
46
48
  const deviceRunSessionId = (0, remoteDeviceRunSession_1.getDeviceRunSessionIdOrThrow)(env);
47
49
  const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
48
50
  const ngrokAuthtoken = (0, remoteDeviceRunSession_1.getNgrokAuthtokenOrThrow)(env);
49
- const packageVersion = inputs.package_version.value;
51
+ const packageVersion = inputs.package_version.value ?? exports.DEFAULT_AGENT_DEVICE_VERSION;
50
52
  const { runtimePlatform } = global;
51
- logger.info(`Starting agent-device remote session (version: ${packageVersion ?? 'latest'}, runtime: ${runtimePlatform}).`);
53
+ logger.info(`Starting agent-device remote session (version: ${packageVersion}, runtime: ${runtimePlatform}).`);
52
54
  if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
53
55
  await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
54
56
  }
@@ -59,50 +61,51 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
59
61
  daemonProcess,
60
62
  });
61
63
  logger.info(`Daemon is listening on port ${daemonPort}; loaded auth token.`);
62
- const agentDeviceRemoteSessionUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
64
+ const agentDeviceTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
63
65
  port: daemonPort,
64
66
  subdomainPrefix: 'agent-device',
65
67
  baseDomain: ngrokTunnelDomain,
66
68
  authtoken: ngrokAuthtoken,
67
69
  logger,
68
70
  });
71
+ const agentDeviceRemoteSessionUrl = agentDeviceTunnel.url;
69
72
  logger.info(`Tunnel is ready at ${agentDeviceRemoteSessionUrl}.`);
70
- // serve-sim is iOS-only — only launch it (and report a webPreviewUrl)
71
- // on Darwin. Android sessions go without a preview URL.
72
- let webPreviewUrl;
73
- if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
74
- const { previewUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
75
- baseDomain: ngrokTunnelDomain,
76
- env,
73
+ let serveSim;
74
+ let eventCollection;
75
+ try {
76
+ // serve-sim is iOS-only — only launch it (and report a webPreviewUrl)
77
+ // on Darwin. Android sessions go without a preview URL.
78
+ if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
79
+ serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
80
+ baseDomain: ngrokTunnelDomain,
81
+ env,
82
+ logger,
83
+ timeoutMs: STARTUP_TIMEOUT_MS,
84
+ });
85
+ logger.info(`Web preview URL: ${serveSim.previewUrl}`);
86
+ }
87
+ await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
88
+ ctx,
89
+ deviceRunSessionId,
90
+ remoteConfig: {
91
+ agentDeviceRemoteSessionUrl,
92
+ agentDeviceRemoteSessionToken: daemonToken,
93
+ ...(serveSim ? { webPreviewUrl: serveSim.previewUrl } : {}),
94
+ },
95
+ logger,
96
+ });
97
+ void (0, agentDeviceArtifacts_1.pollAgentDeviceArtifactsForUploadAsync)(ctx, {
98
+ deviceRunSessionId,
99
+ daemonUrl: `http://127.0.0.1:${daemonPort}`,
100
+ daemonToken,
101
+ logger,
102
+ });
103
+ eventCollection = await (0, agentDeviceEvents_1.startAgentDeviceEventCollectionAsync)({
104
+ ctx,
105
+ deviceRunSessionId,
106
+ stateDir: AGENT_DEVICE_STATE_DIR,
77
107
  logger,
78
- timeoutMs: STARTUP_TIMEOUT_MS,
79
108
  });
80
- webPreviewUrl = previewUrl;
81
- logger.info(`Web preview URL: ${webPreviewUrl}`);
82
- }
83
- await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
84
- ctx,
85
- deviceRunSessionId,
86
- remoteConfig: {
87
- agentDeviceRemoteSessionUrl,
88
- agentDeviceRemoteSessionToken: daemonToken,
89
- ...(webPreviewUrl ? { webPreviewUrl } : {}),
90
- },
91
- logger,
92
- });
93
- void (0, agentDeviceArtifacts_1.pollAgentDeviceArtifactsForUploadAsync)(ctx, {
94
- deviceRunSessionId,
95
- daemonUrl: `http://127.0.0.1:${daemonPort}`,
96
- daemonToken,
97
- logger,
98
- });
99
- const eventCollection = await (0, agentDeviceEvents_1.startAgentDeviceEventCollectionAsync)({
100
- ctx,
101
- deviceRunSessionId,
102
- stateDir: AGENT_DEVICE_STATE_DIR,
103
- logger,
104
- });
105
- try {
106
109
  await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
107
110
  ctx,
108
111
  deviceRunSessionId,
@@ -111,11 +114,18 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
111
114
  });
112
115
  }
113
116
  finally {
114
- await stopAgentDeviceEventCollectionSafelyAsync({
115
- eventCollection,
116
- deviceRunSessionId,
117
- logger,
118
- });
117
+ if (serveSim) {
118
+ await serveSim.stopAsync();
119
+ }
120
+ await agentDeviceTunnel.stopAsync();
121
+ if (eventCollection) {
122
+ await stopAgentDeviceEventCollectionSafelyAsync({
123
+ eventCollection,
124
+ deviceRunSessionId,
125
+ logger,
126
+ });
127
+ }
128
+ await daemonProcess.stopAsync();
119
129
  }
120
130
  },
121
131
  });
@@ -164,7 +174,7 @@ async function startAgentDeviceDaemonAsync({ packageVersion, env, logger, }) {
164
174
  },
165
175
  extras: {
166
176
  packageSpec,
167
- packageVersion: packageVersion ?? 'latest',
177
+ packageVersion,
168
178
  bunVersion,
169
179
  bunInstallConfigured: Boolean(env.BUN_INSTALL?.trim()),
170
180
  },
@@ -174,9 +184,7 @@ async function startAgentDeviceDaemonAsync({ packageVersion, env, logger, }) {
174
184
  }
175
185
  }
176
186
  async function startAgentDeviceDaemonFromGitAsync({ packageVersion, env, logger, }) {
177
- logger.info(packageVersion
178
- ? `Cloning agent-device @ v${packageVersion} into ${SRC_DIR}.`
179
- : `Cloning agent-device (latest) into ${SRC_DIR}.`);
187
+ logger.info(`Cloning agent-device @ v${packageVersion} into ${SRC_DIR}.`);
180
188
  await cloneAgentDeviceAsync({ packageVersion, env, logger });
181
189
  logger.info('Installing agent-device dependencies.');
182
190
  await (0, turtle_spawn_1.default)('bun', ['install', '--production'], {
@@ -193,7 +201,7 @@ async function startAgentDeviceDaemonFromGitAsync({ packageVersion, env, logger,
193
201
  });
194
202
  }
195
203
  async function cloneAgentDeviceAsync({ packageVersion, env, logger, }) {
196
- const branchArgs = packageVersion ? ['--branch', `v${packageVersion}`] : [];
204
+ const branchArgs = ['--branch', `v${packageVersion}`];
197
205
  await (0, turtle_spawn_1.default)('git', ['clone', '--depth', '1', ...branchArgs, AGENT_DEVICE_REPO_URL, SRC_DIR], {
198
206
  env,
199
207
  logger,
@@ -225,7 +233,7 @@ async function getBunVersionForDiagnosticsAsync(env) {
225
233
  }
226
234
  }
227
235
  function createAgentDevicePackageSpec(packageVersion) {
228
- const versionSpec = packageVersion ? packageVersion.replace(/^v(?=\d)/, '') : 'latest';
236
+ const versionSpec = packageVersion.replace(/^v(?=\d)/, '');
229
237
  return `${AGENT_DEVICE_PACKAGE_NAME}@${versionSpec}`;
230
238
  }
231
239
  function getGlobalAgentDeviceDaemonPath(env) {
@@ -69,6 +69,11 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
69
69
  if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
70
70
  await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
71
71
  }
72
+ // Argent shells out to ffmpeg to record the screen. Installing it pulls a
73
+ // large Homebrew dependency tree, so do not block session readiness on it:
74
+ // the session comes up at its usual speed and only a recording started in
75
+ // the first moments misses ffmpeg. Never rejects, so `void` is safe.
76
+ void (0, remoteDeviceRunSession_1.ensureFfmpegInstalledAsync)({ runtimePlatform, env, logger });
72
77
  logger.info('Enabling the Argent artifacts list endpoint flag.');
73
78
  await (0, turtle_spawn_1.default)('bunx', [`${ARGENT_PACKAGE_NAME}@${versionSpec}`, 'enable', ARGENT_ARTIFACTS_LIST_ENDPOINT_FLAG], { env, logger });
74
79
  logger.info('Enabling the Argent tool-server event log flag.');
@@ -127,8 +132,10 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
127
132
  eventLogPath: ARGENT_EVENT_LOG_PATH,
128
133
  logger,
129
134
  });
135
+ let toolsTunnel;
136
+ let serveSim;
130
137
  try {
131
- const publicToolsUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
138
+ toolsTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
132
139
  port: toolServerPort,
133
140
  subdomainPrefix: 'argent',
134
141
  baseDomain: ngrokTunnelDomain,
@@ -136,11 +143,12 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
136
143
  rewriteHostHeader: true,
137
144
  logger,
138
145
  });
146
+ const publicToolsUrl = toolsTunnel.url;
139
147
  logger.info(`Tunnel is ready at ${publicToolsUrl}.`);
140
148
  // serve-sim is iOS-only — Android sessions go without a preview URL.
141
149
  let webPreviewUrl;
142
150
  if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
143
- const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
151
+ serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
144
152
  baseDomain: ngrokTunnelDomain,
145
153
  env,
146
154
  logger,
@@ -167,6 +175,12 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
167
175
  });
168
176
  }
169
177
  finally {
178
+ if (serveSim) {
179
+ await serveSim.stopAsync();
180
+ }
181
+ if (toolsTunnel) {
182
+ await toolsTunnel.stopAsync();
183
+ }
170
184
  await stopArgentEventCollectionSafelyAsync({ eventCollection, deviceRunSessionId, logger });
171
185
  artifactPollAbortController.abort();
172
186
  try {
@@ -177,6 +191,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
177
191
  sentry_1.Sentry.capture('Could not finish Argent remote session artifact polling', error);
178
192
  logger.warn({ err: error }, 'Could not finish Argent remote session artifact polling.');
179
193
  }
194
+ await argentServer.stopAsync();
180
195
  }
181
196
  },
182
197
  });
@@ -0,0 +1,2 @@
1
+ import { BuildFunction } from '@expo/steps';
2
+ export declare function createStartServeSimMetricsBuildFunction(): BuildFunction;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStartServeSimMetricsBuildFunction = createStartServeSimMetricsBuildFunction;
4
+ const steps_1 = require("@expo/steps");
5
+ const serveSimMetricsRecorder_1 = require("../utils/serveSimMetricsRecorder");
6
+ function createStartServeSimMetricsBuildFunction() {
7
+ return new steps_1.BuildFunction({
8
+ namespace: 'eas',
9
+ id: 'start_serve_sim_metrics',
10
+ name: 'Start serve-sim metrics',
11
+ __metricsId: 'eas/start_serve_sim_metrics',
12
+ supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
13
+ fn: async ({ logger }) => {
14
+ await serveSimMetricsRecorder_1.ServeSimMetricsRecorder.startAsync({ logger });
15
+ },
16
+ });
17
+ }
@@ -16,25 +16,30 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
16
16
  const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
17
17
  logger.info('Starting serve-sim remote session.');
18
18
  await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
19
- const { previewUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
19
+ const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
20
20
  baseDomain: ngrokTunnelDomain,
21
21
  env,
22
22
  logger,
23
23
  timeoutMs: STARTUP_TIMEOUT_MS,
24
24
  });
25
- logger.info(`Preview URL: ${previewUrl}`);
26
- await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
27
- ctx,
28
- deviceRunSessionId,
29
- remoteConfig: { previewUrl },
30
- logger,
31
- });
32
- await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
33
- ctx,
34
- deviceRunSessionId,
35
- logger,
36
- signal,
37
- });
25
+ logger.info(`Preview URL: ${serveSim.previewUrl}`);
26
+ try {
27
+ await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
28
+ ctx,
29
+ deviceRunSessionId,
30
+ remoteConfig: { previewUrl: serveSim.previewUrl },
31
+ logger,
32
+ });
33
+ await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
34
+ ctx,
35
+ deviceRunSessionId,
36
+ logger,
37
+ signal,
38
+ });
39
+ }
40
+ finally {
41
+ await serveSim.stopAsync();
42
+ }
38
43
  },
39
44
  });
40
45
  }
@@ -68,7 +68,7 @@ async function pollAgentDeviceArtifactsForUploadAsync(ctx, { deviceRunSessionId,
68
68
  }
69
69
  const error = err instanceof Error ? err : new Error(String(err));
70
70
  listArtifactsErrorCount += 1;
71
- if (listArtifactsErrorCount === 1 || listArtifactsErrorCount % 5 === 0) {
71
+ if (listArtifactsErrorCount % 5 === 0) {
72
72
  sentry_1.Sentry.capture('Could not list agent-device remote session artifacts', error);
73
73
  logger.warn({ err: error, failedArtifactListCount: listArtifactsErrorCount }, 'Could not list agent-device remote session artifacts.');
74
74
  }
@@ -68,7 +68,7 @@ async function pollArgentArtifactsForUploadAsync(ctx, { deviceRunSessionId, tool
68
68
  }
69
69
  const error = err instanceof Error ? err : new Error(String(err));
70
70
  listArtifactsErrorCount += 1;
71
- if (listArtifactsErrorCount === 1 || listArtifactsErrorCount % 5 === 0) {
71
+ if (listArtifactsErrorCount % 5 === 0) {
72
72
  sentry_1.Sentry.capture('Could not list Argent remote session artifacts', error);
73
73
  logger.warn({ err: error, failedArtifactListCount: listArtifactsErrorCount }, 'Could not list Argent remote session artifacts.');
74
74
  }
@@ -9,10 +9,13 @@ const gql_tada_1 = require("gql.tada");
9
9
  const node_fs_1 = __importDefault(require("node:fs"));
10
10
  const node_string_decoder_1 = require("node:string_decoder");
11
11
  const promises_1 = require("node:timers/promises");
12
+ const HttpLogStream_1 = __importDefault(require("../../logging/HttpLogStream"));
12
13
  const RemoteLoggerStream_1 = __importDefault(require("../../logging/RemoteLoggerStream"));
13
14
  const sentry_1 = require("../../sentry");
14
15
  const POLL_INTERVAL_MS = 1_000;
15
16
  const UPLOAD_INTERVAL_MS = 5_000;
17
+ const EAS_LOGS_THREAD = 'session-events';
18
+ const EAS_LOGS_BUFFER_RETENTION_MS = 30_000;
16
19
  const CREATE_DEVICE_RUN_SESSION_EVENT_LOG_UPLOAD_SESSION_MUTATION = (0, gql_tada_1.graphql)(`
17
20
  mutation CreateDeviceRunSessionEventLogUploadSession($deviceRunSessionId: ID!) {
18
21
  deviceRunSession {
@@ -53,10 +56,31 @@ async function startDeviceRunSessionEventCollectionAsync({ ctx, deviceRunSession
53
56
  }
54
57
  catch (err) {
55
58
  const error = err instanceof Error ? err : new Error(String(err));
56
- logger.warn({ err: error }, 'Could not start device run session event collection.');
59
+ logger.warn({ err: error }, 'Could not persist device run session events to the artifact.');
57
60
  reportEventLogFailure(error, 'setup');
58
61
  return { stopAsync: async () => { } };
59
62
  }
63
+ let didReportRealtimeLogFailure = false;
64
+ const reportRealtimeLogFailure = (error, operation) => {
65
+ if (didReportRealtimeLogFailure) {
66
+ return;
67
+ }
68
+ didReportRealtimeLogFailure = true;
69
+ sentry_1.Sentry.capture('Could not publish device run session events in real time', error, {
70
+ level: 'warning',
71
+ tags: { phase: 'device-run-session-event-collection', operation, producer },
72
+ extras: { deviceRunSessionId },
73
+ });
74
+ };
75
+ let realtimeLogStream;
76
+ try {
77
+ realtimeLogStream = createRealtimeLogStream(ctx, logger);
78
+ }
79
+ catch (err) {
80
+ const error = err instanceof Error ? err : new Error(String(err));
81
+ logger.warn({ err: error }, 'Could not start publishing device run session events in real time.');
82
+ reportRealtimeLogFailure(error, 'setup');
83
+ }
60
84
  const states = new Map();
61
85
  const controller = new AbortController();
62
86
  let parseFailureCount = 0;
@@ -81,7 +105,10 @@ async function startDeviceRunSessionEventCollectionAsync({ ctx, deviceRunSession
81
105
  state,
82
106
  source,
83
107
  deviceRunSessionId,
84
- writeEvent: event => eventLogStream.write(event),
108
+ writeEvent: event => {
109
+ eventLogStream.write(event);
110
+ realtimeLogStream?.write({ ...event, logId: event.eventId });
111
+ },
85
112
  onParseFailure: ({ failure, lineNumber }) => {
86
113
  parseFailureCount += 1;
87
114
  parseFailureCounts[failure] += 1;
@@ -155,16 +182,47 @@ async function startDeviceRunSessionEventCollectionAsync({ ctx, deviceRunSession
155
182
  extras: { deviceRunSessionId, parseFailureCount, parseFailureCounts },
156
183
  });
157
184
  }
158
- try {
159
- await eventLogStream.cleanUp();
160
- }
161
- catch (err) {
162
- const error = err instanceof Error ? err : new Error(String(err));
163
- logger.warn({ err: error }, 'Could not finish device run session event collection.');
164
- reportEventLogFailure(error, 'cleanup');
165
- }
185
+ await Promise.all([cleanUpEventLogStreamAsync(), cleanUpRealtimeLogStreamAsync()]);
166
186
  },
167
187
  };
188
+ async function cleanUpEventLogStreamAsync() {
189
+ try {
190
+ await eventLogStream.cleanUp();
191
+ }
192
+ catch (err) {
193
+ const error = err instanceof Error ? err : new Error(String(err));
194
+ logger.warn({ err: error }, 'Could not finish persisting device run session events.');
195
+ reportEventLogFailure(error, 'cleanup');
196
+ }
197
+ }
198
+ async function cleanUpRealtimeLogStreamAsync() {
199
+ try {
200
+ await realtimeLogStream?.cleanUp();
201
+ }
202
+ catch (err) {
203
+ const error = err instanceof Error ? err : new Error(String(err));
204
+ logger.warn({ err: error }, 'Could not finish publishing device run session events in real time.');
205
+ reportRealtimeLogFailure(error, 'cleanup');
206
+ }
207
+ }
208
+ }
209
+ function createRealtimeLogStream(ctx, logger) {
210
+ const baseUrl = ctx.env.EXPO_LOCAL
211
+ ? 'http://localhost:4999/logs/'
212
+ : ctx.env.EXPO_STAGING
213
+ ? 'https://staging-logs.expo.dev/logs/'
214
+ : undefined;
215
+ const jobRunId = ctx.env.EAS_BUILD_ID;
216
+ const robotAccessToken = ctx.job.secrets?.robotAccessToken;
217
+ if (!baseUrl || !jobRunId || !robotAccessToken) {
218
+ return undefined;
219
+ }
220
+ return new HttpLogStream_1.default({
221
+ url: new URL(`${jobRunId}/${EAS_LOGS_THREAD}`, baseUrl).toString(),
222
+ headers: { Authorization: `Bearer ${robotAccessToken}` },
223
+ logger,
224
+ bufferRetentionMs: EAS_LOGS_BUFFER_RETENTION_MS,
225
+ });
168
226
  }
169
227
  async function createEventLogUploadSessionAsync(ctx, deviceRunSessionId) {
170
228
  const result = await ctx.graphqlClient
@@ -1,5 +1,5 @@
1
1
  import { bunyan } from '@expo/logger';
2
- import { BuildStepEnv } from '@expo/steps';
2
+ import { BuildRuntimePlatform, BuildStepEnv } from '@expo/steps';
3
3
  import { z } from 'zod';
4
4
  import { CustomBuildContext } from '../../customBuildContext';
5
5
  export declare function getDeviceRunSessionIdOrThrow(env: BuildStepEnv): string;
@@ -21,6 +21,26 @@ export declare function waitForDeviceRunSessionStoppedAsync({ ctx, deviceRunSess
21
21
  logger: bunyan;
22
22
  signal?: AbortSignal;
23
23
  }): Promise<void>;
24
+ /**
25
+ * Install ffmpeg when the runtime does not already provide it, so argent's
26
+ * `screen-recording-start` tool can encode a video. The worker images do not
27
+ * ship ffmpeg yet, so without this the tool fails with "`ffmpeg` was not found
28
+ * on PATH" — on macOS (iOS simulators) and Linux (Android emulators) alike.
29
+ *
30
+ * Best-effort by design: screen recording is one optional argent tool, so a
31
+ * failure here is logged and the session continues without it.
32
+ *
33
+ * The whole body is wrapped because the caller runs this in the background with
34
+ * `void`. There is no unhandledRejection handler in the worker, so a rejection
35
+ * escaping here would crash the process and take the live session with it.
36
+ * `spawn` is not an async function and can throw synchronously, which
37
+ * `asyncResult` cannot catch — it only wraps an already-created promise.
38
+ */
39
+ export declare function ensureFfmpegInstalledAsync({ runtimePlatform, env, logger, }: {
40
+ runtimePlatform: BuildRuntimePlatform;
41
+ env: BuildStepEnv;
42
+ logger: bunyan;
43
+ }): Promise<void>;
24
44
  /**
25
45
  * Translate Cloudflare ICE servers into serve-sim CLI flags: `--stun-url` (the
26
46
  * credential-less entries) and `--turn-url`/`--turn-username`/`--turn-credential`
@@ -51,6 +71,7 @@ export type DetachedProcessHandle = {
51
71
  /** PID of the directly spawned process, if the OS assigned one. */
52
72
  pid: number | undefined;
53
73
  getOutput: () => string;
74
+ stopAsync: () => Promise<void>;
54
75
  };
55
76
  export declare function spawnDetached({ command, args, cwd, env, }: {
56
77
  command: string;
@@ -58,14 +79,31 @@ export declare function spawnDetached({ command, args, cwd, env, }: {
58
79
  cwd?: string;
59
80
  env: BuildStepEnv;
60
81
  }): DetachedProcessHandle;
82
+ export declare function metricsCorsOriginToServeSimArgs(env: BuildStepEnv): string[];
83
+ export declare function createServeSimArgs({ port, turnArgs, metricsCorsArgs, }: {
84
+ port: number;
85
+ turnArgs?: string[];
86
+ metricsCorsArgs?: string[];
87
+ }): string[];
88
+ export declare function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }: {
89
+ serveSim: Pick<DetachedProcessHandle, 'pid' | 'getOutput'>;
90
+ port: number;
91
+ timeoutMs: number;
92
+ }): Promise<void>;
93
+ export type ServeSimPreviewHandle = {
94
+ previewUrl: string;
95
+ stopAsync: () => Promise<void>;
96
+ };
61
97
  export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, }: {
62
98
  baseDomain: string;
63
99
  env: BuildStepEnv;
64
100
  logger: bunyan;
65
101
  timeoutMs: number;
66
- }): Promise<{
67
- previewUrl: string;
68
- }>;
102
+ }): Promise<ServeSimPreviewHandle>;
103
+ export type NgrokTunnelHandle = {
104
+ url: string;
105
+ stopAsync: () => Promise<void>;
106
+ };
69
107
  export declare function startNgrokTunnelAsync({ port, subdomainPrefix, baseDomain, authtoken, rewriteHostHeader, logger, }: {
70
108
  port: number;
71
109
  subdomainPrefix: string;
@@ -73,7 +111,7 @@ export declare function startNgrokTunnelAsync({ port, subdomainPrefix, baseDomai
73
111
  authtoken: string;
74
112
  rewriteHostHeader?: boolean;
75
113
  logger: bunyan;
76
- }): Promise<string>;
114
+ }): Promise<NgrokTunnelHandle>;
77
115
  export declare function waitForFileAsync<T>({ filePath, timeoutMs, description, parse, }: {
78
116
  filePath: string;
79
117
  timeoutMs: number;