@expo/build-tools 21.3.0 → 21.5.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.
Files changed (37) 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.js +49 -41
  18. package/dist/steps/functions/startArgentRemoteSession.d.ts +8 -1
  19. package/dist/steps/functions/startArgentRemoteSession.js +52 -4
  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/agentDeviceEvents.d.ts +11 -0
  25. package/dist/steps/utils/agentDeviceEvents.js +116 -0
  26. package/dist/steps/utils/argentArtifacts.js +1 -1
  27. package/dist/steps/utils/argentEvents.d.ts +12 -0
  28. package/dist/steps/utils/argentEvents.js +131 -0
  29. package/dist/steps/utils/deviceRunSessionEvents.d.ts +43 -2
  30. package/dist/steps/utils/deviceRunSessionEvents.js +95 -115
  31. package/dist/steps/utils/remoteDeviceRunSession.d.ts +43 -5
  32. package/dist/steps/utils/remoteDeviceRunSession.js +249 -37
  33. package/dist/steps/utils/serveSimMetricsArtifacts.d.ts +9 -0
  34. package/dist/steps/utils/serveSimMetricsArtifacts.js +49 -0
  35. package/dist/steps/utils/serveSimMetricsRecorder.d.ts +30 -0
  36. package/dist/steps/utils/serveSimMetricsRecorder.js +232 -0
  37. package/package.json +4 -4
@@ -1,5 +1,11 @@
1
1
  import { type bunyan } from '@expo/logger';
2
2
  import { type CustomBuildContext } from '../../customBuildContext';
3
+ /**
4
+ * The normalized, producer-agnostic event shape uploaded to the API server.
5
+ * Every remote-session producer (agent-device, argent, ...) tails its own
6
+ * event log and maps its records onto this common contract so consumers render
7
+ * a single unified session timeline.
8
+ */
3
9
  export type DeviceRunSessionEvent = {
4
10
  v: 1;
5
11
  eventId: string;
@@ -12,10 +18,45 @@ export type DeviceRunSessionEvent = {
12
18
  summary: string;
13
19
  data?: Record<string, unknown>;
14
20
  };
15
- export declare function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, stateDir, logger, pollIntervalMs, }: {
21
+ export type DeviceRunSessionEventParseFailure = 'invalid-json' | 'invalid-event';
22
+ export type DeviceRunSessionEventParseResult = {
23
+ event?: DeviceRunSessionEvent;
24
+ failure?: DeviceRunSessionEventParseFailure;
25
+ };
26
+ /**
27
+ * Producer-specific adapter plugged into the generic collection engine. It only
28
+ * has to say where its event files live and how to turn one raw NDJSON line
29
+ * into a {@link DeviceRunSessionEvent}; the engine owns tailing, upload, polling
30
+ * and failure reporting.
31
+ */
32
+ export type DeviceRunSessionEventSource = {
33
+ /**
34
+ * Stable producer identifier, e.g. `agent-device` or `argent`. Also used
35
+ * verbatim in diagnostic messages, which are phrased so it never needs
36
+ * recasing.
37
+ */
38
+ producer: string;
39
+ /** Discover the NDJSON event files to tail (absolute paths). */
40
+ findEventFilesAsync: () => Promise<string[]>;
41
+ /** Namespace component of the event ID derived from a tailed file path. */
42
+ sourceKeyForFile: (eventFile: string) => string;
43
+ /**
44
+ * Parse one raw NDJSON line. `sequenceNumber` is monotonic per file (kept
45
+ * stable across truncations) and `sourceKey` namespaces the event ID so an ID
46
+ * is never reused during collection. Blank lines should return an empty
47
+ * result so they neither emit an event nor count as a parse failure.
48
+ */
49
+ parseLine: (args: {
50
+ line: string;
51
+ sourceKey: string;
52
+ sequenceNumber: number;
53
+ deviceRunSessionId: string;
54
+ }) => DeviceRunSessionEventParseResult;
55
+ };
56
+ export declare function startDeviceRunSessionEventCollectionAsync({ ctx, deviceRunSessionId, source, logger, pollIntervalMs, }: {
16
57
  ctx: CustomBuildContext;
17
58
  deviceRunSessionId: string;
18
- stateDir: string;
59
+ source: DeviceRunSessionEventSource;
19
60
  logger: bunyan;
20
61
  pollIntervalMs?: number;
21
62
  }): Promise<{
@@ -3,36 +3,19 @@ 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.startAgentDeviceEventCollectionAsync = startAgentDeviceEventCollectionAsync;
6
+ exports.startDeviceRunSessionEventCollectionAsync = startDeviceRunSessionEventCollectionAsync;
7
7
  const eas_build_job_1 = require("@expo/eas-build-job");
8
8
  const gql_tada_1 = require("gql.tada");
9
9
  const node_fs_1 = __importDefault(require("node:fs"));
10
- const node_path_1 = __importDefault(require("node:path"));
11
10
  const node_string_decoder_1 = require("node:string_decoder");
12
11
  const promises_1 = require("node:timers/promises");
13
- const zod_1 = require("zod");
12
+ const HttpLogStream_1 = __importDefault(require("../../logging/HttpLogStream"));
14
13
  const RemoteLoggerStream_1 = __importDefault(require("../../logging/RemoteLoggerStream"));
15
14
  const sentry_1 = require("../../sentry");
16
15
  const POLL_INTERVAL_MS = 1_000;
17
16
  const UPLOAD_INTERVAL_MS = 5_000;
18
- const AGENT_DEVICE_EVENT_KIND_TO_TYPE = {
19
- 'request.started': 'operation.started',
20
- 'request.finished': 'operation.completed',
21
- 'action.recorded': 'interaction.recorded',
22
- };
23
- const AgentDeviceEventSchema = zod_1.z
24
- .object({
25
- version: zod_1.z.number(),
26
- ts: zod_1.z.string(),
27
- session: zod_1.z.string(),
28
- kind: zod_1.z.string(),
29
- requestId: zod_1.z.string().optional().catch(undefined),
30
- command: zod_1.z.string().optional().catch(undefined),
31
- status: zod_1.z.string().optional().catch(undefined),
32
- summary: zod_1.z.string().optional().catch(undefined),
33
- details: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional().catch(undefined),
34
- })
35
- .passthrough();
17
+ const EAS_LOGS_THREAD = 'session-events';
18
+ const EAS_LOGS_BUFFER_RETENTION_MS = 30_000;
36
19
  const CREATE_DEVICE_RUN_SESSION_EVENT_LOG_UPLOAD_SESSION_MUTATION = (0, gql_tada_1.graphql)(`
37
20
  mutation CreateDeviceRunSessionEventLogUploadSession($deviceRunSessionId: ID!) {
38
21
  deviceRunSession {
@@ -45,7 +28,8 @@ const CREATE_DEVICE_RUN_SESSION_EVENT_LOG_UPLOAD_SESSION_MUTATION = (0, gql_tada
45
28
  }
46
29
  }
47
30
  `);
48
- async function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, stateDir, logger, pollIntervalMs = POLL_INTERVAL_MS, }) {
31
+ async function startDeviceRunSessionEventCollectionAsync({ ctx, deviceRunSessionId, source, logger, pollIntervalMs = POLL_INTERVAL_MS, }) {
32
+ const { producer } = source;
49
33
  let didReportEventLogFailure = false;
50
34
  const reportEventLogFailure = (error, operation) => {
51
35
  if (didReportEventLogFailure) {
@@ -54,7 +38,7 @@ async function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, s
54
38
  didReportEventLogFailure = true;
55
39
  sentry_1.Sentry.capture('Could not persist device run session events', error, {
56
40
  level: 'warning',
57
- tags: { phase: 'device-run-session-event-collection', operation },
41
+ tags: { phase: 'device-run-session-event-collection', operation, producer },
58
42
  extras: { deviceRunSessionId },
59
43
  });
60
44
  };
@@ -72,10 +56,31 @@ async function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, s
72
56
  }
73
57
  catch (err) {
74
58
  const error = err instanceof Error ? err : new Error(String(err));
75
- 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.');
76
60
  reportEventLogFailure(error, 'setup');
77
61
  return { stopAsync: async () => { } };
78
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
+ }
79
84
  const states = new Map();
80
85
  const controller = new AbortController();
81
86
  let parseFailureCount = 0;
@@ -85,7 +90,7 @@ async function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, s
85
90
  };
86
91
  let didReportCollectionFailure = false;
87
92
  const collectAsync = async () => {
88
- const eventFiles = await findAgentDeviceEventFilesAsync(stateDir);
93
+ const eventFiles = await source.findEventFilesAsync();
89
94
  await Promise.all(eventFiles.map(async (eventFile) => {
90
95
  const state = states.get(eventFile) ?? {
91
96
  offset: 0,
@@ -98,18 +103,22 @@ async function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, s
98
103
  await collectEventFileAsync({
99
104
  eventFile,
100
105
  state,
106
+ source,
101
107
  deviceRunSessionId,
102
- writeEvent: event => eventLogStream.write(event),
108
+ writeEvent: event => {
109
+ eventLogStream.write(event);
110
+ realtimeLogStream?.write({ ...event, logId: event.eventId });
111
+ },
103
112
  onParseFailure: ({ failure, lineNumber }) => {
104
113
  parseFailureCount += 1;
105
114
  parseFailureCounts[failure] += 1;
106
115
  if (parseFailureCount !== 1) {
107
116
  return;
108
117
  }
109
- logger.warn({ agentDeviceEventParseFailure: failure, lineNumber }, 'Could not parse an agent-device event log record.');
110
- sentry_1.Sentry.capture('Could not parse an agent-device event log record', {
118
+ logger.warn({ producer, eventParseFailure: failure, lineNumber }, `Could not parse an ${producer} event log record.`);
119
+ sentry_1.Sentry.capture(`Could not parse an ${producer} event log record`, {
111
120
  level: 'warning',
112
- tags: { phase: 'agent-device-event-collection', reason: failure },
121
+ tags: { phase: `${producer}-event-collection`, reason: failure },
113
122
  extras: { deviceRunSessionId, lineNumber },
114
123
  });
115
124
  },
@@ -127,10 +136,10 @@ async function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, s
127
136
  }
128
137
  didReportCollectionFailure = true;
129
138
  const error = err instanceof Error ? err : new Error(String(err));
130
- logger.warn({ err: error }, 'Could not collect agent-device events.');
131
- sentry_1.Sentry.capture('Could not collect agent-device events', error, {
139
+ logger.warn({ err: error }, `Could not collect ${producer} events.`);
140
+ sentry_1.Sentry.capture(`Could not collect ${producer} events`, error, {
132
141
  level: 'warning',
133
- tags: { phase: 'agent-device-event-collection' },
142
+ tags: { phase: `${producer}-event-collection` },
134
143
  extras: { deviceRunSessionId },
135
144
  });
136
145
  }
@@ -150,10 +159,10 @@ async function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, s
150
159
  })()
151
160
  .catch(err => {
152
161
  const error = err instanceof Error ? err : new Error(String(err));
153
- logger.warn({ err: error }, 'Agent-device event collection poller failed.');
154
- sentry_1.Sentry.capture('Agent-device event collection poller failed', error, {
162
+ logger.warn({ err: error }, `Event collection poller for ${producer} failed.`);
163
+ sentry_1.Sentry.capture(`Event collection poller for ${producer} failed`, error, {
155
164
  level: 'warning',
156
- tags: { phase: 'agent-device-event-collection', operation: 'poll' },
165
+ tags: { phase: `${producer}-event-collection`, operation: 'poll' },
157
166
  extras: { deviceRunSessionId },
158
167
  });
159
168
  })
@@ -166,23 +175,54 @@ async function startAgentDeviceEventCollectionAsync({ ctx, deviceRunSessionId, s
166
175
  await pollingPromise;
167
176
  await collectSafelyAsync();
168
177
  if (parseFailureCount > 1) {
169
- logger.warn({ agentDeviceEventParseFailures: parseFailureCounts, parseFailureCount }, `Could not parse ${parseFailureCount} agent-device event log records.`);
170
- sentry_1.Sentry.capture('Could not parse multiple agent-device event log records', {
178
+ logger.warn({ producer, eventParseFailures: parseFailureCounts, parseFailureCount }, `Could not parse ${parseFailureCount} ${producer} event log records.`);
179
+ sentry_1.Sentry.capture(`Could not parse multiple ${producer} event log records`, {
171
180
  level: 'warning',
172
- tags: { phase: 'agent-device-event-collection' },
181
+ tags: { phase: `${producer}-event-collection` },
173
182
  extras: { deviceRunSessionId, parseFailureCount, parseFailureCounts },
174
183
  });
175
184
  }
176
- try {
177
- await eventLogStream.cleanUp();
178
- }
179
- catch (err) {
180
- const error = err instanceof Error ? err : new Error(String(err));
181
- logger.warn({ err: error }, 'Could not finish device run session event collection.');
182
- reportEventLogFailure(error, 'cleanup');
183
- }
185
+ await Promise.all([cleanUpEventLogStreamAsync(), cleanUpRealtimeLogStreamAsync()]);
184
186
  },
185
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
+ });
186
226
  }
187
227
  async function createEventLogUploadSessionAsync(ctx, deviceRunSessionId) {
188
228
  const result = await ctx.graphqlClient
@@ -199,23 +239,7 @@ async function createEventLogUploadSessionAsync(ctx, deviceRunSessionId) {
199
239
  headers: uploadSession.headers,
200
240
  };
201
241
  }
202
- async function findAgentDeviceEventFilesAsync(stateDir) {
203
- const sessionsDir = node_path_1.default.join(stateDir, 'sessions');
204
- let entries;
205
- try {
206
- entries = await node_fs_1.default.promises.readdir(sessionsDir, { withFileTypes: true });
207
- }
208
- catch (err) {
209
- if (err.code === 'ENOENT') {
210
- return [];
211
- }
212
- throw err;
213
- }
214
- return entries
215
- .filter(entry => entry.isDirectory())
216
- .map(entry => node_path_1.default.join(sessionsDir, entry.name, 'events.ndjson'));
217
- }
218
- async function collectEventFileAsync({ eventFile, state, deviceRunSessionId, writeEvent, onParseFailure, }) {
242
+ async function collectEventFileAsync({ eventFile, state, source, deviceRunSessionId, writeEvent, onParseFailure, }) {
219
243
  let fileSize;
220
244
  try {
221
245
  fileSize = (await node_fs_1.default.promises.stat(eventFile)).size;
@@ -234,6 +258,7 @@ async function collectEventFileAsync({ eventFile, state, deviceRunSessionId, wri
234
258
  if (fileSize === state.offset) {
235
259
  return;
236
260
  }
261
+ const sourceKey = source.sourceKeyForFile(eventFile);
237
262
  const handle = await node_fs_1.default.promises.open(eventFile, 'r');
238
263
  try {
239
264
  const buffer = new Uint8Array(new ArrayBuffer(fileSize - state.offset));
@@ -245,67 +270,22 @@ async function collectEventFileAsync({ eventFile, state, deviceRunSessionId, wri
245
270
  for (const line of lines) {
246
271
  const lineNumber = state.nextLineNumber++;
247
272
  const sequenceNumber = state.nextSequenceNumber++;
248
- const { event, failure } = parseAgentDeviceEvent(line);
273
+ const { event, failure } = source.parseLine({
274
+ line,
275
+ sourceKey,
276
+ sequenceNumber,
277
+ deviceRunSessionId,
278
+ });
249
279
  if (failure) {
250
280
  onParseFailure({ failure, lineNumber });
251
281
  }
252
282
  if (!event) {
253
283
  continue;
254
284
  }
255
- const deviceRunSessionEvent = normalizeAgentDeviceEvent({
256
- event,
257
- sequenceNumber,
258
- sourceSessionDirectory: node_path_1.default.basename(node_path_1.default.dirname(eventFile)),
259
- deviceRunSessionId,
260
- });
261
- writeEvent(deviceRunSessionEvent);
285
+ writeEvent(event);
262
286
  }
263
287
  }
264
288
  finally {
265
289
  await handle.close();
266
290
  }
267
291
  }
268
- function parseAgentDeviceEvent(line) {
269
- if (!line.trim()) {
270
- return {};
271
- }
272
- try {
273
- const result = AgentDeviceEventSchema.safeParse(JSON.parse(line));
274
- return result.success ? { event: result.data } : { failure: 'invalid-event' };
275
- }
276
- catch {
277
- return { failure: 'invalid-json' };
278
- }
279
- }
280
- function normalizeAgentDeviceEvent({ event, sequenceNumber, sourceSessionDirectory, deviceRunSessionId, }) {
281
- const type = AGENT_DEVICE_EVENT_KIND_TO_TYPE[event.kind] ?? event.kind;
282
- const durationMs = event.details?.durationMs;
283
- const outcome = event.status === 'ok' ? 'success' : event.status === 'error' ? 'failure' : undefined;
284
- const summary = event.summary ??
285
- (event.kind === 'request.started'
286
- ? `Started ${event.command ?? 'activity'}`
287
- : event.kind === 'request.finished'
288
- ? `Finished ${event.command ?? 'activity'}`
289
- : event.kind === 'action.recorded'
290
- ? `Recorded ${event.command ?? 'activity'}`
291
- : `${event.kind}${event.command ? `: ${event.command}` : ''}`);
292
- return {
293
- v: 1,
294
- // Consumers use eventId to deduplicate events across polls. Keep the per-file sequence
295
- // monotonic across source-file truncations so an ID is never reused during collection.
296
- eventId: `agent-device:${deviceRunSessionId}:${sourceSessionDirectory}:${sequenceNumber}`,
297
- ts: event.ts,
298
- producer: 'agent-device',
299
- type,
300
- ...(event.requestId ? { operationId: event.requestId } : {}),
301
- ...(outcome ? { outcome } : {}),
302
- ...(typeof durationMs === 'number' ? { durationMs } : {}),
303
- summary,
304
- data: {
305
- ...event.details,
306
- session: event.session,
307
- sourceVersion: event.version,
308
- ...(event.status ? { sourceStatus: event.status } : {}),
309
- },
310
- };
311
- }
@@ -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;