@expo/build-tools 23.0.0 → 23.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/common/installDependencies.js +5 -1
- package/dist/common/projectSources.d.ts +6 -0
- package/dist/common/projectSources.js +3 -0
- package/dist/steps/easFunctions.js +2 -2
- package/dist/steps/functions/startAgentDeviceRemoteSession.js +12 -15
- package/dist/steps/functions/startAppiumRemoteSession.js +13 -16
- package/dist/steps/functions/startArgentRemoteSession.js +12 -16
- package/dist/steps/functions/startWebPreviewRemoteSession.d.ts +3 -0
- package/dist/steps/functions/{startServeSimRemoteSession.js → startWebPreviewRemoteSession.js} +14 -11
- package/dist/steps/utils/remoteDeviceRunSession.d.ts +35 -12
- package/dist/steps/utils/remoteDeviceRunSession.js +105 -37
- package/package.json +6 -7
- package/dist/steps/functions/startServeSimRemoteSession.d.ts +0 -3
|
@@ -21,7 +21,11 @@ async function installDependenciesAsync({ packageManager, env, logger, infoCallb
|
|
|
21
21
|
break;
|
|
22
22
|
}
|
|
23
23
|
case packageManager_1.PackageManager.PNPM: {
|
|
24
|
-
args = [
|
|
24
|
+
args = [
|
|
25
|
+
'install',
|
|
26
|
+
...(env['EAS_PNPM_FILTER_WORKSPACE'] ? ['--filter', env['EAS_PNPM_FILTER_WORKSPACE']] : []),
|
|
27
|
+
useFrozenLockfile ? '--frozen-lockfile' : '--no-frozen-lockfile',
|
|
28
|
+
];
|
|
25
29
|
break;
|
|
26
30
|
}
|
|
27
31
|
case packageManager_1.PackageManager.YARN: {
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { Job } from '@expo/eas-build-job';
|
|
2
|
+
import { bunyan } from '@expo/logger';
|
|
2
3
|
import { BuildContext } from '../context';
|
|
3
4
|
export declare function prepareProjectSourcesAsync<TJob extends Job>(ctx: BuildContext<TJob>, destinationDirectory: string): Promise<{
|
|
4
5
|
handled: boolean;
|
|
5
6
|
}>;
|
|
6
7
|
export declare function downloadAndUnpackProjectFromTarGzAsync<TJob extends Job>(ctx: BuildContext<TJob>, projectArchiveUrl: string, destinationDirectory: string): Promise<void>;
|
|
8
|
+
export declare function unpackTarGzAsync({ logger, source, destination, }: {
|
|
9
|
+
logger: bunyan;
|
|
10
|
+
source: string;
|
|
11
|
+
destination: string;
|
|
12
|
+
}): Promise<void>;
|
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.prepareProjectSourcesAsync = prepareProjectSourcesAsync;
|
|
7
7
|
exports.downloadAndUnpackProjectFromTarGzAsync = downloadAndUnpackProjectFromTarGzAsync;
|
|
8
|
+
exports.unpackTarGzAsync = unpackTarGzAsync;
|
|
8
9
|
const downloader_1 = __importDefault(require("@expo/downloader"));
|
|
9
10
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
10
11
|
const results_1 = require("@expo/results");
|
|
@@ -88,6 +89,8 @@ async function unpackTarGzAsync({ logger, source, destination, }) {
|
|
|
88
89
|
await (0, turtle_spawn_1.default)('tar', ['-C', destination, '--strip-components', '1', '-zxf', source], {
|
|
89
90
|
logger,
|
|
90
91
|
});
|
|
92
|
+
logger.info('Normalizing project source permissions');
|
|
93
|
+
await (0, turtle_spawn_1.default)('chmod', ['-R', 'u+rwX', destination], { logger });
|
|
91
94
|
}
|
|
92
95
|
function uploadProjectMetadataAsFireAndForget(ctx, { projectDirectory }) {
|
|
93
96
|
void (async () => {
|
|
@@ -49,7 +49,7 @@ const startAppiumRemoteSession_1 = require("./functions/startAppiumRemoteSession
|
|
|
49
49
|
const startCuttlefishDevice_1 = require("./functions/startCuttlefishDevice");
|
|
50
50
|
const startIosSimulator_1 = require("./functions/startIosSimulator");
|
|
51
51
|
const startIosSimulatorRecordings_1 = require("./functions/startIosSimulatorRecordings");
|
|
52
|
-
const
|
|
52
|
+
const startWebPreviewRemoteSession_1 = require("./functions/startWebPreviewRemoteSession");
|
|
53
53
|
const startServeSimMetrics_1 = require("./functions/startServeSimMetrics");
|
|
54
54
|
const collectServeSimMetrics_1 = require("./functions/collectServeSimMetrics");
|
|
55
55
|
const uploadArtifact_1 = require("./functions/uploadArtifact");
|
|
@@ -102,7 +102,7 @@ function getEasFunctions(ctx) {
|
|
|
102
102
|
(0, startIosSimulatorRecordings_1.createStartIosSimulatorRecordingsBuildFunction)(),
|
|
103
103
|
(0, finishIosSimulatorRecordings_1.createFinishIosSimulatorRecordingsBuildFunction)(),
|
|
104
104
|
(0, uploadDeviceRunSessionScreenRecordings_1.createUploadDeviceRunSessionScreenRecordingsBuildFunction)(ctx),
|
|
105
|
-
(0,
|
|
105
|
+
(0, startWebPreviewRemoteSession_1.createStartWebPreviewRemoteSessionBuildFunction)(ctx),
|
|
106
106
|
(0, startServeSimMetrics_1.createStartServeSimMetricsBuildFunction)(),
|
|
107
107
|
(0, collectServeSimMetrics_1.createCollectServeSimMetricsBuildFunction)(ctx),
|
|
108
108
|
(0, installMaestro_1.createInstallMaestroBuildFunction)(),
|
|
@@ -81,27 +81,24 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
|
|
|
81
81
|
});
|
|
82
82
|
const agentDeviceRemoteSessionUrl = agentDeviceTunnel.url;
|
|
83
83
|
logger.info(`Tunnel is ready at ${agentDeviceRemoteSessionUrl}.`);
|
|
84
|
-
let
|
|
84
|
+
let webPreview;
|
|
85
85
|
let eventCollection;
|
|
86
86
|
try {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
});
|
|
96
|
-
logger.info(`Web preview URL: ${serveSim.previewUrl}`);
|
|
97
|
-
}
|
|
87
|
+
webPreview = await (0, remoteDeviceRunSession_1.startDeviceWebPreviewWithTunnelAsync)(ctx, {
|
|
88
|
+
runtimePlatform,
|
|
89
|
+
baseDomain: ngrokTunnelDomain,
|
|
90
|
+
env,
|
|
91
|
+
logger,
|
|
92
|
+
timeoutMs: STARTUP_TIMEOUT_MS,
|
|
93
|
+
});
|
|
94
|
+
logger.info(`Web preview URL: ${webPreview.previewUrl}`);
|
|
98
95
|
await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
|
|
99
96
|
ctx,
|
|
100
97
|
deviceRunSessionId,
|
|
101
98
|
remoteConfig: {
|
|
102
99
|
agentDeviceRemoteSessionUrl,
|
|
103
100
|
agentDeviceRemoteSessionToken: daemonToken,
|
|
104
|
-
|
|
101
|
+
webPreviewUrl: webPreview.previewUrl,
|
|
105
102
|
},
|
|
106
103
|
logger,
|
|
107
104
|
});
|
|
@@ -132,8 +129,8 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
|
|
|
132
129
|
});
|
|
133
130
|
}
|
|
134
131
|
finally {
|
|
135
|
-
if (
|
|
136
|
-
await
|
|
132
|
+
if (webPreview) {
|
|
133
|
+
await webPreview.stopAsync();
|
|
137
134
|
}
|
|
138
135
|
await agentDeviceTunnel.stopAsync();
|
|
139
136
|
if (eventCollection) {
|
|
@@ -95,7 +95,7 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
|
|
|
95
95
|
logger,
|
|
96
96
|
});
|
|
97
97
|
let appiumTunnel;
|
|
98
|
-
let
|
|
98
|
+
let webPreview;
|
|
99
99
|
try {
|
|
100
100
|
appiumTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
|
|
101
101
|
port: APPIUM_PORT,
|
|
@@ -104,18 +104,15 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
|
|
|
104
104
|
authtoken: ngrokAuthtoken,
|
|
105
105
|
logger,
|
|
106
106
|
});
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
case steps_1.BuildRuntimePlatform.LINUX:
|
|
117
|
-
break;
|
|
118
|
-
}
|
|
107
|
+
// expo-device-hub has no serial-selection flag. Device run session workflows must expose
|
|
108
|
+
// a single booted Android emulator so the Hub and Appium resolve the same device.
|
|
109
|
+
webPreview = await (0, remoteDeviceRunSession_1.startDeviceWebPreviewWithTunnelAsync)(ctx, {
|
|
110
|
+
runtimePlatform,
|
|
111
|
+
baseDomain: ngrokTunnelDomain,
|
|
112
|
+
env,
|
|
113
|
+
logger,
|
|
114
|
+
timeoutMs: APPIUM_STARTUP_TIMEOUT_MS,
|
|
115
|
+
});
|
|
119
116
|
await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
|
|
120
117
|
ctx,
|
|
121
118
|
deviceRunSessionId,
|
|
@@ -126,7 +123,7 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
|
|
|
126
123
|
'appium:automationName': device.automationName,
|
|
127
124
|
'appium:udid': device.udid,
|
|
128
125
|
},
|
|
129
|
-
|
|
126
|
+
webPreviewUrl: webPreview.previewUrl,
|
|
130
127
|
},
|
|
131
128
|
logger,
|
|
132
129
|
});
|
|
@@ -144,8 +141,8 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
|
|
|
144
141
|
});
|
|
145
142
|
}
|
|
146
143
|
finally {
|
|
147
|
-
if (
|
|
148
|
-
await
|
|
144
|
+
if (webPreview) {
|
|
145
|
+
await webPreview.stopAsync();
|
|
149
146
|
}
|
|
150
147
|
if (appiumTunnel) {
|
|
151
148
|
await appiumTunnel.stopAsync();
|
|
@@ -146,7 +146,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
|
|
|
146
146
|
logger,
|
|
147
147
|
});
|
|
148
148
|
let toolsTunnel;
|
|
149
|
-
let
|
|
149
|
+
let webPreview;
|
|
150
150
|
try {
|
|
151
151
|
toolsTunnel = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
|
|
152
152
|
port: toolServerPort,
|
|
@@ -158,25 +158,21 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
|
|
|
158
158
|
});
|
|
159
159
|
const publicToolsUrl = toolsTunnel.url;
|
|
160
160
|
logger.info(`Tunnel is ready at ${publicToolsUrl}.`);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
});
|
|
170
|
-
webPreviewUrl = serveSim.previewUrl;
|
|
171
|
-
logger.info(`Web preview URL: ${webPreviewUrl}`);
|
|
172
|
-
}
|
|
161
|
+
webPreview = await (0, remoteDeviceRunSession_1.startDeviceWebPreviewWithTunnelAsync)(ctx, {
|
|
162
|
+
runtimePlatform,
|
|
163
|
+
baseDomain: ngrokTunnelDomain,
|
|
164
|
+
env,
|
|
165
|
+
logger,
|
|
166
|
+
timeoutMs: STARTUP_TIMEOUT_MS,
|
|
167
|
+
});
|
|
168
|
+
logger.info(`Web preview URL: ${webPreview.previewUrl}`);
|
|
173
169
|
await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
|
|
174
170
|
ctx,
|
|
175
171
|
deviceRunSessionId,
|
|
176
172
|
remoteConfig: {
|
|
177
173
|
toolsUrl: publicToolsUrl,
|
|
178
174
|
...(toolServerToken ? { toolsAuthToken: toolServerToken } : {}),
|
|
179
|
-
|
|
175
|
+
webPreviewUrl: webPreview.previewUrl,
|
|
180
176
|
},
|
|
181
177
|
logger,
|
|
182
178
|
});
|
|
@@ -195,8 +191,8 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
|
|
|
195
191
|
});
|
|
196
192
|
}
|
|
197
193
|
finally {
|
|
198
|
-
if (
|
|
199
|
-
await
|
|
194
|
+
if (webPreview) {
|
|
195
|
+
await webPreview.stopAsync();
|
|
200
196
|
}
|
|
201
197
|
if (toolsTunnel) {
|
|
202
198
|
await toolsTunnel.stopAsync();
|
package/dist/steps/functions/{startServeSimRemoteSession.js → startWebPreviewRemoteSession.js}
RENAMED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.createStartWebPreviewRemoteSessionBuildFunction = createStartWebPreviewRemoteSessionBuildFunction;
|
|
4
4
|
const steps_1 = require("@expo/steps");
|
|
5
5
|
const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
|
|
6
6
|
const STARTUP_TIMEOUT_MS = 60_000;
|
|
7
|
-
function
|
|
7
|
+
function createStartWebPreviewRemoteSessionBuildFunction(ctx) {
|
|
8
8
|
return new steps_1.BuildFunction({
|
|
9
9
|
namespace: 'eas',
|
|
10
10
|
id: 'start_serve_sim_remote_session',
|
|
11
|
-
name: 'Start
|
|
11
|
+
name: 'Start web preview remote session',
|
|
12
12
|
__metricsId: 'eas/start_serve_sim_remote_session',
|
|
13
|
-
supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
|
|
14
13
|
inputProviders: [
|
|
15
14
|
steps_1.BuildStepInput.createProvider({
|
|
16
15
|
id: 'package_version',
|
|
@@ -23,26 +22,30 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
|
|
|
23
22
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
24
23
|
}),
|
|
25
24
|
],
|
|
26
|
-
fn: async ({ logger }, { inputs, env, signal }) => {
|
|
25
|
+
fn: async ({ logger, global }, { inputs, env, signal }) => {
|
|
27
26
|
const deviceRunSessionId = (0, remoteDeviceRunSession_1.getDeviceRunSessionIdOrThrow)(env);
|
|
28
27
|
const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
|
|
29
28
|
const maxDurationSeconds = inputs.max_duration_seconds?.value;
|
|
30
29
|
const packageVersion = inputs.package_version?.value;
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const { runtimePlatform } = global;
|
|
31
|
+
logger.info(`Starting web preview remote session (runtime: ${runtimePlatform}).`);
|
|
32
|
+
if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
|
|
33
|
+
await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
|
|
34
|
+
}
|
|
35
|
+
const webPreview = await (0, remoteDeviceRunSession_1.startDeviceWebPreviewWithTunnelAsync)(ctx, {
|
|
36
|
+
runtimePlatform,
|
|
34
37
|
baseDomain: ngrokTunnelDomain,
|
|
35
38
|
env,
|
|
36
39
|
logger,
|
|
37
40
|
timeoutMs: STARTUP_TIMEOUT_MS,
|
|
38
41
|
packageVersion,
|
|
39
42
|
});
|
|
40
|
-
logger.info(`Preview URL: ${
|
|
43
|
+
logger.info(`Preview URL: ${webPreview.previewUrl}`);
|
|
41
44
|
try {
|
|
42
45
|
await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
|
|
43
46
|
ctx,
|
|
44
47
|
deviceRunSessionId,
|
|
45
|
-
remoteConfig: { previewUrl:
|
|
48
|
+
remoteConfig: { previewUrl: webPreview.previewUrl },
|
|
46
49
|
logger,
|
|
47
50
|
});
|
|
48
51
|
await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
|
|
@@ -54,7 +57,7 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
|
|
|
54
57
|
});
|
|
55
58
|
}
|
|
56
59
|
finally {
|
|
57
|
-
await
|
|
60
|
+
await webPreview.stopAsync();
|
|
58
61
|
}
|
|
59
62
|
},
|
|
60
63
|
});
|
|
@@ -54,22 +54,23 @@ export declare function ensureFfmpegInstalledAsync({ runtimePlatform, env, logge
|
|
|
54
54
|
logger: bunyan;
|
|
55
55
|
}): Promise<void>;
|
|
56
56
|
/**
|
|
57
|
-
* Translate Cloudflare ICE servers into
|
|
57
|
+
* Translate Cloudflare ICE servers into web preview CLI flags: `--stun-url` (the
|
|
58
58
|
* credential-less entries) and `--turn-url`/`--turn-username`/`--turn-credential`
|
|
59
|
-
* (the entry carrying the short-lived credentials).
|
|
59
|
+
* (the entry carrying the short-lived credentials). serve-sim and expo-device-hub
|
|
60
|
+
* intentionally expose the same ICE flag contract.
|
|
60
61
|
*/
|
|
61
|
-
export declare function
|
|
62
|
+
export declare function turnIceServersToWebPreviewArgs(iceServers: TurnIceServers): string[];
|
|
62
63
|
/**
|
|
63
64
|
* Fetch short-lived Cloudflare TURN ICE servers for this job run from www
|
|
64
65
|
* (minted on demand, mirroring how the worker fetches project clone URLs) and
|
|
65
|
-
* translate them into
|
|
66
|
+
* translate them into web preview CLI flags.
|
|
66
67
|
*
|
|
67
|
-
* Best-effort: on any failure we log and return [] so
|
|
68
|
-
* its built-in P2P/STUN behavior. The credential is passed
|
|
69
|
-
*
|
|
70
|
-
*
|
|
68
|
+
* Best-effort: on any failure we log and return [] so the preview server falls
|
|
69
|
+
* back to its built-in P2P/STUN behavior. The credential is passed as a process
|
|
70
|
+
* arg and deliberately not logged (turtle-spawn never logs argv and the worker
|
|
71
|
+
* is single-tenant).
|
|
71
72
|
*/
|
|
72
|
-
export declare function
|
|
73
|
+
export declare function fetchWebPreviewTurnArgsAsync(ctx: CustomBuildContext, { env, logger }: {
|
|
73
74
|
env: BuildStepEnv;
|
|
74
75
|
logger: bunyan;
|
|
75
76
|
}): Promise<string[]>;
|
|
@@ -98,15 +99,22 @@ export declare function createServeSimArgs({ port, turnArgs, metricsCorsArgs, pa
|
|
|
98
99
|
metricsCorsArgs?: string[];
|
|
99
100
|
packageVersion?: string;
|
|
100
101
|
}): string[];
|
|
101
|
-
export declare function
|
|
102
|
-
|
|
102
|
+
export declare function createExpoDeviceHubArgs({ port, turnArgs, packageVersion, }: {
|
|
103
|
+
port: number;
|
|
104
|
+
turnArgs?: string[];
|
|
105
|
+
packageVersion?: string;
|
|
106
|
+
}): string[];
|
|
107
|
+
export declare function waitForWebPreviewReadyAsync({ previewServer, serverName, port, timeoutMs, }: {
|
|
108
|
+
previewServer: Pick<DetachedProcessHandle, 'pid' | 'getOutput'>;
|
|
109
|
+
serverName: string;
|
|
103
110
|
port: number;
|
|
104
111
|
timeoutMs: number;
|
|
105
112
|
}): Promise<void>;
|
|
106
|
-
export type
|
|
113
|
+
export type DeviceWebPreviewHandle = {
|
|
107
114
|
previewUrl: string;
|
|
108
115
|
stopAsync: () => Promise<void>;
|
|
109
116
|
};
|
|
117
|
+
export type ServeSimPreviewHandle = DeviceWebPreviewHandle;
|
|
110
118
|
export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, packageVersion, }: {
|
|
111
119
|
baseDomain: string;
|
|
112
120
|
env: BuildStepEnv;
|
|
@@ -114,6 +122,21 @@ export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, {
|
|
|
114
122
|
timeoutMs: number;
|
|
115
123
|
packageVersion?: string;
|
|
116
124
|
}): Promise<ServeSimPreviewHandle>;
|
|
125
|
+
export declare function startExpoDeviceHubWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, packageVersion, }: {
|
|
126
|
+
baseDomain: string;
|
|
127
|
+
env: BuildStepEnv;
|
|
128
|
+
logger: bunyan;
|
|
129
|
+
timeoutMs: number;
|
|
130
|
+
packageVersion?: string;
|
|
131
|
+
}): Promise<DeviceWebPreviewHandle>;
|
|
132
|
+
export declare function startDeviceWebPreviewWithTunnelAsync(ctx: CustomBuildContext, { runtimePlatform, ...options }: {
|
|
133
|
+
runtimePlatform: BuildRuntimePlatform;
|
|
134
|
+
baseDomain: string;
|
|
135
|
+
env: BuildStepEnv;
|
|
136
|
+
logger: bunyan;
|
|
137
|
+
timeoutMs: number;
|
|
138
|
+
packageVersion?: string;
|
|
139
|
+
}): Promise<DeviceWebPreviewHandle>;
|
|
117
140
|
export type NgrokTunnelHandle = {
|
|
118
141
|
url: string;
|
|
119
142
|
stopAsync: () => Promise<void>;
|
|
@@ -42,14 +42,17 @@ exports.getNgrokAuthtokenOrThrow = getNgrokAuthtokenOrThrow;
|
|
|
42
42
|
exports.selectXcodeDeveloperDirectoryAsync = selectXcodeDeveloperDirectoryAsync;
|
|
43
43
|
exports.waitForDeviceRunSessionStoppedAsync = waitForDeviceRunSessionStoppedAsync;
|
|
44
44
|
exports.ensureFfmpegInstalledAsync = ensureFfmpegInstalledAsync;
|
|
45
|
-
exports.
|
|
46
|
-
exports.
|
|
45
|
+
exports.turnIceServersToWebPreviewArgs = turnIceServersToWebPreviewArgs;
|
|
46
|
+
exports.fetchWebPreviewTurnArgsAsync = fetchWebPreviewTurnArgsAsync;
|
|
47
47
|
exports.uploadRemoteSessionConfigAsync = uploadRemoteSessionConfigAsync;
|
|
48
48
|
exports.spawnDetached = spawnDetached;
|
|
49
49
|
exports.metricsCorsOriginToServeSimArgs = metricsCorsOriginToServeSimArgs;
|
|
50
50
|
exports.createServeSimArgs = createServeSimArgs;
|
|
51
|
-
exports.
|
|
51
|
+
exports.createExpoDeviceHubArgs = createExpoDeviceHubArgs;
|
|
52
|
+
exports.waitForWebPreviewReadyAsync = waitForWebPreviewReadyAsync;
|
|
52
53
|
exports.startServeSimWithTunnelAsync = startServeSimWithTunnelAsync;
|
|
54
|
+
exports.startExpoDeviceHubWithTunnelAsync = startExpoDeviceHubWithTunnelAsync;
|
|
55
|
+
exports.startDeviceWebPreviewWithTunnelAsync = startDeviceWebPreviewWithTunnelAsync;
|
|
53
56
|
exports.startNgrokTunnelAsync = startNgrokTunnelAsync;
|
|
54
57
|
exports.waitForFileAsync = waitForFileAsync;
|
|
55
58
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
@@ -69,12 +72,16 @@ const sentry_1 = require("../../sentry");
|
|
|
69
72
|
const retry_1 = require("../../utils/retry");
|
|
70
73
|
const turtleFetch_1 = require("../../utils/turtleFetch");
|
|
71
74
|
const XCODE_DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer';
|
|
75
|
+
const WEB_PREVIEW_HOST = '127.0.0.1';
|
|
72
76
|
const SERVE_SIM_PACKAGE_NAME = '@expo/serve-sim';
|
|
73
|
-
const SERVE_SIM_HOST = '127.0.0.1';
|
|
74
77
|
const SERVE_SIM_MAX_DIMENSION = '960';
|
|
75
78
|
const SERVE_SIM_MJPEG_QUALITY = '0.55';
|
|
76
79
|
const SERVE_SIM_VIDEO_BITRATE = '6000000';
|
|
77
80
|
const SERVE_SIM_VIDEO_FPS = '60';
|
|
81
|
+
const EXPO_DEVICE_HUB_PACKAGE_NAME = 'expo-device-hub';
|
|
82
|
+
const EXPO_DEVICE_HUB_MAX_DIMENSION = '1280';
|
|
83
|
+
const EXPO_DEVICE_HUB_VIDEO_BITRATE = '6000000';
|
|
84
|
+
const EXPO_DEVICE_HUB_VIDEO_FPS = '60';
|
|
78
85
|
const START_DEVICE_RUN_SESSION_MUTATION = (0, gql_tada_1.graphql)(`
|
|
79
86
|
mutation StartDeviceRunSession($deviceRunSessionId: ID!, $remoteConfig: JSONObject!) {
|
|
80
87
|
deviceRunSession {
|
|
@@ -328,11 +335,12 @@ const TurnIceServersResponseSchema = zod_1.z.object({
|
|
|
328
335
|
}),
|
|
329
336
|
});
|
|
330
337
|
/**
|
|
331
|
-
* Translate Cloudflare ICE servers into
|
|
338
|
+
* Translate Cloudflare ICE servers into web preview CLI flags: `--stun-url` (the
|
|
332
339
|
* credential-less entries) and `--turn-url`/`--turn-username`/`--turn-credential`
|
|
333
|
-
* (the entry carrying the short-lived credentials).
|
|
340
|
+
* (the entry carrying the short-lived credentials). serve-sim and expo-device-hub
|
|
341
|
+
* intentionally expose the same ICE flag contract.
|
|
334
342
|
*/
|
|
335
|
-
function
|
|
343
|
+
function turnIceServersToWebPreviewArgs(iceServers) {
|
|
336
344
|
const stunUrls = iceServers
|
|
337
345
|
.filter(server => !server.username && !server.credential)
|
|
338
346
|
.flatMap(server => server.urls);
|
|
@@ -349,14 +357,14 @@ function turnIceServersToServeSimArgs(iceServers) {
|
|
|
349
357
|
/**
|
|
350
358
|
* Fetch short-lived Cloudflare TURN ICE servers for this job run from www
|
|
351
359
|
* (minted on demand, mirroring how the worker fetches project clone URLs) and
|
|
352
|
-
* translate them into
|
|
360
|
+
* translate them into web preview CLI flags.
|
|
353
361
|
*
|
|
354
|
-
* Best-effort: on any failure we log and return [] so
|
|
355
|
-
* its built-in P2P/STUN behavior. The credential is passed
|
|
356
|
-
*
|
|
357
|
-
*
|
|
362
|
+
* Best-effort: on any failure we log and return [] so the preview server falls
|
|
363
|
+
* back to its built-in P2P/STUN behavior. The credential is passed as a process
|
|
364
|
+
* arg and deliberately not logged (turtle-spawn never logs argv and the worker
|
|
365
|
+
* is single-tenant).
|
|
358
366
|
*/
|
|
359
|
-
async function
|
|
367
|
+
async function fetchWebPreviewTurnArgsAsync(ctx, { env, logger }) {
|
|
360
368
|
try {
|
|
361
369
|
const deviceRunSessionId = getDeviceRunSessionIdOrThrow(env);
|
|
362
370
|
const expoApiServerUrl = (0, nullthrows_1.default)(ctx.env.__API_SERVER_URL, '__API_SERVER_URL is not set');
|
|
@@ -370,16 +378,16 @@ async function fetchServeSimTurnArgsAsync(ctx, { env, logger }) {
|
|
|
370
378
|
logger,
|
|
371
379
|
});
|
|
372
380
|
const { data } = TurnIceServersResponseSchema.parse(await response.json());
|
|
373
|
-
const args =
|
|
381
|
+
const args = turnIceServersToWebPreviewArgs(data.iceServers);
|
|
374
382
|
if (args.length > 0) {
|
|
375
|
-
logger.info('Configured
|
|
383
|
+
logger.info('Configured the web preview with Cloudflare TURN ICE servers.');
|
|
376
384
|
}
|
|
377
385
|
return args;
|
|
378
386
|
}
|
|
379
387
|
catch (err) {
|
|
380
388
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
381
389
|
sentry_1.Sentry.capture('Could not fetch Cloudflare TURN ICE servers', error, { level: 'warning' });
|
|
382
|
-
logger.warn({ err: error }, 'Could not fetch Cloudflare TURN ICE servers;
|
|
390
|
+
logger.warn({ err: error }, 'Could not fetch Cloudflare TURN ICE servers; the web preview will fall back to P2P/STUN.');
|
|
383
391
|
return [];
|
|
384
392
|
}
|
|
385
393
|
}
|
|
@@ -476,6 +484,9 @@ function metricsCorsOriginToServeSimArgs(env) {
|
|
|
476
484
|
function createServeSimPackageSpec(packageVersion) {
|
|
477
485
|
return `${SERVE_SIM_PACKAGE_NAME}@${packageVersion ?? 'latest'}`;
|
|
478
486
|
}
|
|
487
|
+
function createExpoDeviceHubPackageSpec(packageVersion) {
|
|
488
|
+
return `${EXPO_DEVICE_HUB_PACKAGE_NAME}@${packageVersion ?? 'latest'}`;
|
|
489
|
+
}
|
|
479
490
|
function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], packageVersion, }) {
|
|
480
491
|
return [
|
|
481
492
|
'--yes',
|
|
@@ -483,7 +494,7 @@ function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], package
|
|
|
483
494
|
'--port',
|
|
484
495
|
String(port),
|
|
485
496
|
'--host',
|
|
486
|
-
|
|
497
|
+
WEB_PREVIEW_HOST,
|
|
487
498
|
'--transport',
|
|
488
499
|
'webrtc',
|
|
489
500
|
'--webrtc-codec',
|
|
@@ -500,40 +511,67 @@ function createServeSimArgs({ port, turnArgs = [], metricsCorsArgs = [], package
|
|
|
500
511
|
...metricsCorsArgs,
|
|
501
512
|
];
|
|
502
513
|
}
|
|
514
|
+
function createExpoDeviceHubArgs({ port, turnArgs = [], packageVersion, }) {
|
|
515
|
+
return [
|
|
516
|
+
'--yes',
|
|
517
|
+
createExpoDeviceHubPackageSpec(packageVersion),
|
|
518
|
+
'--port',
|
|
519
|
+
String(port),
|
|
520
|
+
'--host',
|
|
521
|
+
WEB_PREVIEW_HOST,
|
|
522
|
+
'--platform',
|
|
523
|
+
'android',
|
|
524
|
+
'--transport',
|
|
525
|
+
'webrtc',
|
|
526
|
+
'--webrtc-codec',
|
|
527
|
+
'h264',
|
|
528
|
+
'--webrtc-ice-policy',
|
|
529
|
+
'all',
|
|
530
|
+
'--max-dimension',
|
|
531
|
+
EXPO_DEVICE_HUB_MAX_DIMENSION,
|
|
532
|
+
'--video-bitrate',
|
|
533
|
+
EXPO_DEVICE_HUB_VIDEO_BITRATE,
|
|
534
|
+
'--video-fps',
|
|
535
|
+
EXPO_DEVICE_HUB_VIDEO_FPS,
|
|
536
|
+
'--hide-sidebar',
|
|
537
|
+
'--hide-boot-device',
|
|
538
|
+
...turnArgs,
|
|
539
|
+
];
|
|
540
|
+
}
|
|
503
541
|
async function findAvailablePortAsync() {
|
|
504
542
|
const server = (0, node_net_1.createServer)();
|
|
505
543
|
server.unref();
|
|
506
544
|
await new Promise((resolve, reject) => {
|
|
507
545
|
server.once('error', reject);
|
|
508
|
-
server.listen(0,
|
|
546
|
+
server.listen(0, WEB_PREVIEW_HOST, () => resolve());
|
|
509
547
|
});
|
|
510
548
|
const address = server.address();
|
|
511
549
|
await new Promise((resolve, reject) => {
|
|
512
550
|
server.close(err => (err ? reject(err) : resolve()));
|
|
513
551
|
});
|
|
514
552
|
if (!address || typeof address === 'string') {
|
|
515
|
-
throw new eas_build_job_1.SystemError('Could not allocate a local port for
|
|
553
|
+
throw new eas_build_job_1.SystemError('Could not allocate a local port for the web preview.');
|
|
516
554
|
}
|
|
517
555
|
return address.port;
|
|
518
556
|
}
|
|
519
|
-
const
|
|
557
|
+
const WebPreviewReadyResponseSchema = zod_1.z.object({
|
|
520
558
|
status: zod_1.z.literal('ready'),
|
|
521
559
|
device: zod_1.z.string(),
|
|
522
560
|
});
|
|
523
|
-
async function
|
|
524
|
-
const readyUrl = `http://${
|
|
561
|
+
async function waitForWebPreviewReadyAsync({ previewServer, serverName, port, timeoutMs, }) {
|
|
562
|
+
const readyUrl = `http://${WEB_PREVIEW_HOST}:${port}/readyz`;
|
|
525
563
|
const deadline = Date.now() + timeoutMs;
|
|
526
564
|
let lastError;
|
|
527
565
|
while (Date.now() < deadline) {
|
|
528
|
-
if (
|
|
529
|
-
throw new eas_build_job_1.SystemError(
|
|
566
|
+
if (previewServer.pid !== undefined && !isProcessRunning(previewServer.pid)) {
|
|
567
|
+
throw new eas_build_job_1.SystemError(`${serverName} exited before becoming ready. Last output:\n${previewServer.getOutput() || '<empty>'}`);
|
|
530
568
|
}
|
|
531
569
|
try {
|
|
532
570
|
const response = await (0, turtleFetch_1.turtleFetch)(readyUrl, 'GET', {
|
|
533
571
|
retries: 0,
|
|
534
572
|
timeout: 2_000,
|
|
535
573
|
});
|
|
536
|
-
|
|
574
|
+
WebPreviewReadyResponseSchema.parse(await response.json());
|
|
537
575
|
return;
|
|
538
576
|
}
|
|
539
577
|
catch (error) {
|
|
@@ -541,21 +579,20 @@ async function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }) {
|
|
|
541
579
|
}
|
|
542
580
|
await (0, retry_1.sleepAsync)(1_000);
|
|
543
581
|
}
|
|
544
|
-
throw new eas_build_job_1.SystemError(`Timed out waiting for
|
|
582
|
+
throw new eas_build_job_1.SystemError(`Timed out waiting for ${serverName} readiness at ${readyUrl}${lastError instanceof Error ? `: ${lastError.message}` : ''}. Last output:\n${previewServer.getOutput() || '<empty>'}`);
|
|
545
583
|
}
|
|
546
|
-
async function
|
|
584
|
+
async function startWebPreviewWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, serverName, packageSpec, createArgs, }) {
|
|
547
585
|
const port = await findAvailablePortAsync();
|
|
548
|
-
logger.info(`Launching ${
|
|
549
|
-
const turnArgs = await
|
|
550
|
-
const
|
|
551
|
-
const serveSim = spawnDetached({
|
|
586
|
+
logger.info(`Launching ${packageSpec} on ${WEB_PREVIEW_HOST}:${port}.`);
|
|
587
|
+
const turnArgs = await fetchWebPreviewTurnArgsAsync(ctx, { env, logger });
|
|
588
|
+
const previewServer = spawnDetached({
|
|
552
589
|
command: 'npx',
|
|
553
|
-
args:
|
|
590
|
+
args: createArgs(port, turnArgs),
|
|
554
591
|
env,
|
|
555
592
|
});
|
|
556
593
|
try {
|
|
557
|
-
logger.info(
|
|
558
|
-
await
|
|
594
|
+
logger.info(`Waiting for ${serverName} to become ready.`);
|
|
595
|
+
await waitForWebPreviewReadyAsync({ previewServer, serverName, port, timeoutMs });
|
|
559
596
|
const tunnel = await startNgrokTunnelAsync({
|
|
560
597
|
port,
|
|
561
598
|
subdomainPrefix: 'web-preview',
|
|
@@ -566,20 +603,51 @@ async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, time
|
|
|
566
603
|
return {
|
|
567
604
|
previewUrl: tunnel.url,
|
|
568
605
|
stopAsync: async () => {
|
|
569
|
-
const results = await Promise.allSettled([tunnel.stopAsync(),
|
|
606
|
+
const results = await Promise.allSettled([tunnel.stopAsync(), previewServer.stopAsync()]);
|
|
570
607
|
for (const result of results) {
|
|
571
608
|
if (result.status === 'rejected') {
|
|
572
|
-
logger.warn({ err: result.reason },
|
|
609
|
+
logger.warn({ err: result.reason }, `Could not stop a ${serverName} preview resource.`);
|
|
573
610
|
}
|
|
574
611
|
}
|
|
575
612
|
},
|
|
576
613
|
};
|
|
577
614
|
}
|
|
578
615
|
catch (error) {
|
|
579
|
-
await
|
|
616
|
+
await previewServer.stopAsync();
|
|
580
617
|
throw error;
|
|
581
618
|
}
|
|
582
619
|
}
|
|
620
|
+
async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, packageVersion, }) {
|
|
621
|
+
const metricsCorsArgs = metricsCorsOriginToServeSimArgs(env);
|
|
622
|
+
return await startWebPreviewWithTunnelAsync(ctx, {
|
|
623
|
+
baseDomain,
|
|
624
|
+
env,
|
|
625
|
+
logger,
|
|
626
|
+
timeoutMs,
|
|
627
|
+
serverName: 'serve-sim',
|
|
628
|
+
packageSpec: createServeSimPackageSpec(packageVersion),
|
|
629
|
+
createArgs: (port, turnArgs) => createServeSimArgs({ port, turnArgs, metricsCorsArgs, packageVersion }),
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
async function startExpoDeviceHubWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, packageVersion, }) {
|
|
633
|
+
return await startWebPreviewWithTunnelAsync(ctx, {
|
|
634
|
+
baseDomain,
|
|
635
|
+
env,
|
|
636
|
+
logger,
|
|
637
|
+
timeoutMs,
|
|
638
|
+
serverName: 'expo-device-hub',
|
|
639
|
+
packageSpec: createExpoDeviceHubPackageSpec(packageVersion),
|
|
640
|
+
createArgs: (port, turnArgs) => createExpoDeviceHubArgs({ port, turnArgs, packageVersion }),
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
async function startDeviceWebPreviewWithTunnelAsync(ctx, { runtimePlatform, ...options }) {
|
|
644
|
+
switch (runtimePlatform) {
|
|
645
|
+
case steps_1.BuildRuntimePlatform.DARWIN:
|
|
646
|
+
return await startServeSimWithTunnelAsync(ctx, options);
|
|
647
|
+
case steps_1.BuildRuntimePlatform.LINUX:
|
|
648
|
+
return await startExpoDeviceHubWithTunnelAsync(ctx, options);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
583
651
|
async function startNgrokTunnelAsync({ port, subdomainPrefix, baseDomain, authtoken, rewriteHostHeader, logger, }) {
|
|
584
652
|
const domain = `${subdomainPrefix}-${(0, node_crypto_1.randomBytes)(16).toString('hex')}.${baseDomain}`;
|
|
585
653
|
logger.info(`Starting ngrok tunnel ${domain} -> http://localhost:${port}.`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/build-tools",
|
|
3
|
-
"version": "23.
|
|
3
|
+
"version": "23.2.0",
|
|
4
4
|
"bugs": "https://github.com/expo/eas-cli/issues",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Expo <support@expo.io>",
|
|
@@ -40,16 +40,16 @@
|
|
|
40
40
|
"@expo/config": "55.0.10",
|
|
41
41
|
"@expo/config-plugins": "55.0.7",
|
|
42
42
|
"@expo/downloader": "23.0.0",
|
|
43
|
-
"@expo/eas-build-job": "23.
|
|
43
|
+
"@expo/eas-build-job": "23.1.0",
|
|
44
44
|
"@expo/env": "^0.4.0",
|
|
45
|
-
"@expo/logger": "23.
|
|
45
|
+
"@expo/logger": "23.1.0",
|
|
46
46
|
"@expo/package-manager": "1.9.10",
|
|
47
47
|
"@expo/plist": "^0.3.5",
|
|
48
48
|
"@expo/results": "^1.0.0",
|
|
49
49
|
"@expo/spawn-async": "1.7.2",
|
|
50
|
-
"@expo/steps": "23.
|
|
50
|
+
"@expo/steps": "23.1.0",
|
|
51
51
|
"@expo/template-file": "23.0.0",
|
|
52
|
-
"@expo/turtle-spawn": "23.
|
|
52
|
+
"@expo/turtle-spawn": "23.1.0",
|
|
53
53
|
"@expo/xcpretty": "^4.3.1",
|
|
54
54
|
"@ngrok/ngrok": "1.7.0",
|
|
55
55
|
"@sentry/node": "7.77.0",
|
|
@@ -92,7 +92,6 @@
|
|
|
92
92
|
"@types/retry": "^0.12.5",
|
|
93
93
|
"@types/semver": "^7.5.8",
|
|
94
94
|
"@types/uuid": "^9.0.8",
|
|
95
|
-
"bunyan": "^1.8.15",
|
|
96
95
|
"jest": "^29.7.0",
|
|
97
96
|
"memfs": "^4.17.1",
|
|
98
97
|
"nock": "13.4.0",
|
|
@@ -103,5 +102,5 @@
|
|
|
103
102
|
"typescript": "^5.5.4",
|
|
104
103
|
"uuid": "^9.0.1"
|
|
105
104
|
},
|
|
106
|
-
"gitHead": "
|
|
105
|
+
"gitHead": "74292d2ace404759ab8936d5aa1369e15d4b4f9d"
|
|
107
106
|
}
|