@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.
- package/dist/android/gradleProfile.js +14 -2
- package/dist/builders/custom.js +20 -12
- package/dist/common/git.d.ts +4 -0
- package/dist/common/git.js +35 -2
- package/dist/common/jobHooks.js +10 -0
- package/dist/generic.js +15 -6
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/logging/HttpLogStream.d.ts +28 -0
- package/dist/logging/HttpLogStream.js +137 -0
- package/dist/steps/compositeFunctions.d.ts +11 -0
- package/dist/steps/compositeFunctions.js +62 -0
- package/dist/steps/easFunctions.js +4 -0
- package/dist/steps/functions/checkout.js +26 -1
- package/dist/steps/functions/collectServeSimMetrics.d.ts +3 -0
- package/dist/steps/functions/collectServeSimMetrics.js +41 -0
- package/dist/steps/functions/startAgentDeviceRemoteSession.d.ts +1 -0
- package/dist/steps/functions/startAgentDeviceRemoteSession.js +56 -48
- package/dist/steps/functions/startArgentRemoteSession.js +17 -2
- package/dist/steps/functions/startServeSimMetrics.d.ts +2 -0
- package/dist/steps/functions/startServeSimMetrics.js +17 -0
- package/dist/steps/functions/startServeSimRemoteSession.js +19 -14
- package/dist/steps/utils/agentDeviceArtifacts.js +1 -1
- package/dist/steps/utils/argentArtifacts.js +1 -1
- package/dist/steps/utils/deviceRunSessionEvents.js +68 -10
- package/dist/steps/utils/remoteDeviceRunSession.d.ts +43 -5
- package/dist/steps/utils/remoteDeviceRunSession.js +249 -37
- package/dist/steps/utils/serveSimMetricsArtifacts.d.ts +9 -0
- package/dist/steps/utils/serveSimMetricsArtifacts.js +49 -0
- package/dist/steps/utils/serveSimMetricsRecorder.d.ts +30 -0
- package/dist/steps/utils/serveSimMetricsRecorder.js +232 -0
- 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
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,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
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
|
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
|
|
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
|
|
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 =>
|
|
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
|
-
|
|
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
|
-
|
|
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<
|
|
114
|
+
}): Promise<NgrokTunnelHandle>;
|
|
77
115
|
export declare function waitForFileAsync<T>({ filePath, timeoutMs, description, parse, }: {
|
|
78
116
|
filePath: string;
|
|
79
117
|
timeoutMs: number;
|