@expo/build-tools 21.4.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 (31) 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 +48 -40
  18. package/dist/steps/functions/startArgentRemoteSession.js +17 -2
  19. package/dist/steps/functions/startServeSimMetrics.d.ts +2 -0
  20. package/dist/steps/functions/startServeSimMetrics.js +17 -0
  21. package/dist/steps/functions/startServeSimRemoteSession.js +19 -14
  22. package/dist/steps/utils/agentDeviceArtifacts.js +1 -1
  23. package/dist/steps/utils/argentArtifacts.js +1 -1
  24. package/dist/steps/utils/deviceRunSessionEvents.js +68 -10
  25. package/dist/steps/utils/remoteDeviceRunSession.d.ts +43 -5
  26. package/dist/steps/utils/remoteDeviceRunSession.js +249 -37
  27. package/dist/steps/utils/serveSimMetricsArtifacts.d.ts +9 -0
  28. package/dist/steps/utils/serveSimMetricsArtifacts.js +49 -0
  29. package/dist/steps/utils/serveSimMetricsRecorder.d.ts +30 -0
  30. package/dist/steps/utils/serveSimMetricsRecorder.js +232 -0
  31. package/package.json +4 -4
@@ -59,50 +59,51 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
59
59
  daemonProcess,
60
60
  });
61
61
  logger.info(`Daemon is listening on port ${daemonPort}; loaded auth token.`);
62
- const agentDeviceRemoteSessionUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
62
+ const agentDeviceTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
63
63
  port: daemonPort,
64
64
  subdomainPrefix: 'agent-device',
65
65
  baseDomain: ngrokTunnelDomain,
66
66
  authtoken: ngrokAuthtoken,
67
67
  logger,
68
68
  });
69
+ const agentDeviceRemoteSessionUrl = agentDeviceTunnel.url;
69
70
  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,
71
+ let serveSim;
72
+ let eventCollection;
73
+ try {
74
+ // serve-sim is iOS-only — only launch it (and report a webPreviewUrl)
75
+ // on Darwin. Android sessions go without a preview URL.
76
+ if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
77
+ serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
78
+ baseDomain: ngrokTunnelDomain,
79
+ env,
80
+ logger,
81
+ timeoutMs: STARTUP_TIMEOUT_MS,
82
+ });
83
+ logger.info(`Web preview URL: ${serveSim.previewUrl}`);
84
+ }
85
+ await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
86
+ ctx,
87
+ deviceRunSessionId,
88
+ remoteConfig: {
89
+ agentDeviceRemoteSessionUrl,
90
+ agentDeviceRemoteSessionToken: daemonToken,
91
+ ...(serveSim ? { webPreviewUrl: serveSim.previewUrl } : {}),
92
+ },
93
+ logger,
94
+ });
95
+ void (0, agentDeviceArtifacts_1.pollAgentDeviceArtifactsForUploadAsync)(ctx, {
96
+ deviceRunSessionId,
97
+ daemonUrl: `http://127.0.0.1:${daemonPort}`,
98
+ daemonToken,
99
+ logger,
100
+ });
101
+ eventCollection = await (0, agentDeviceEvents_1.startAgentDeviceEventCollectionAsync)({
102
+ ctx,
103
+ deviceRunSessionId,
104
+ stateDir: AGENT_DEVICE_STATE_DIR,
77
105
  logger,
78
- timeoutMs: STARTUP_TIMEOUT_MS,
79
106
  });
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
107
  await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
107
108
  ctx,
108
109
  deviceRunSessionId,
@@ -111,11 +112,18 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
111
112
  });
112
113
  }
113
114
  finally {
114
- await stopAgentDeviceEventCollectionSafelyAsync({
115
- eventCollection,
116
- deviceRunSessionId,
117
- logger,
118
- });
115
+ if (serveSim) {
116
+ await serveSim.stopAsync();
117
+ }
118
+ await agentDeviceTunnel.stopAsync();
119
+ if (eventCollection) {
120
+ await stopAgentDeviceEventCollectionSafelyAsync({
121
+ eventCollection,
122
+ deviceRunSessionId,
123
+ logger,
124
+ });
125
+ }
126
+ await daemonProcess.stopAsync();
119
127
  }
120
128
  },
121
129
  });
@@ -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;