@expo/build-tools 20.0.0 → 20.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/builders/android.js +6 -0
- package/dist/builders/custom.js +23 -0
- package/dist/builders/ios.js +6 -0
- package/dist/common/installDependencies.d.ts +10 -1
- package/dist/common/installDependencies.js +95 -1
- package/dist/common/prebuild.js +2 -3
- package/dist/{ios → common}/xcpretty.d.ts +1 -0
- package/dist/{ios → common}/xcpretty.js +18 -3
- package/dist/datadog.d.ts +8 -1
- package/dist/datadog.js +23 -13
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/ios/fastlane.js +1 -1
- package/dist/ios/pod.js +1 -1
- package/dist/runtimeSettings.d.ts +12 -0
- package/dist/runtimeSettings.js +120 -0
- package/dist/steps/easFunctions.js +1 -1
- package/dist/steps/functions/downloadBuild.d.ts +5 -3
- package/dist/steps/functions/downloadBuild.js +34 -4
- package/dist/steps/functions/findAndUploadBuildArtifacts.js +2 -2
- package/dist/steps/functions/installMaestro.js +13 -2
- package/dist/steps/functions/maestroResultParser.d.ts +18 -0
- package/dist/steps/functions/maestroResultParser.js +132 -3
- package/dist/steps/functions/maestroTests.js +26 -13
- package/dist/steps/functions/repack.d.ts +3 -1
- package/dist/steps/functions/repack.js +15 -1
- package/dist/steps/functions/reportMaestroTestResults.js +39 -20
- package/dist/steps/functions/restoreBuildCache.js +9 -6
- package/dist/steps/functions/startAgentDeviceRemoteSession.d.ts +1 -1
- package/dist/steps/functions/startAgentDeviceRemoteSession.js +101 -22
- package/dist/steps/functions/startArgentRemoteSession.js +1 -1
- package/dist/steps/functions/startIosSimulator.js +12 -0
- package/dist/steps/functions/startServeSimRemoteSession.js +1 -1
- package/dist/steps/functions/uploadArtifact.js +1 -1
- package/dist/steps/utils/ios/fastlane.js +1 -1
- package/dist/steps/utils/remoteDeviceRunSession.d.ts +29 -1
- package/dist/steps/utils/remoteDeviceRunSession.js +76 -2
- package/dist/utils/IosSimulatorUtils.d.ts +4 -0
- package/dist/utils/IosSimulatorUtils.js +5 -0
- package/dist/utils/expoUpdatesEmbedded.d.ts +3 -0
- package/dist/utils/expoUpdatesEmbedded.js +109 -0
- package/package.json +5 -5
- package/dist/steps/utils/ios/xcpretty.d.ts +0 -15
- package/dist/steps/utils/ios/xcpretty.js +0 -92
|
@@ -7,9 +7,12 @@ exports.createStartAgentDeviceRemoteSessionBuildFunction = createStartAgentDevic
|
|
|
7
7
|
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
8
8
|
const steps_1 = require("@expo/steps");
|
|
9
9
|
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
11
|
const node_os_1 = __importDefault(require("node:os"));
|
|
11
12
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
|
+
const sentry_1 = require("../../sentry");
|
|
12
14
|
const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
|
|
15
|
+
const AGENT_DEVICE_PACKAGE_NAME = 'agent-device';
|
|
13
16
|
const AGENT_DEVICE_REPO_URL = 'https://github.com/callstackincubator/agent-device.git';
|
|
14
17
|
const SRC_DIR = '/tmp/agent-device-src';
|
|
15
18
|
const DAEMON_JSON_PATH = node_path_1.default.join(node_os_1.default.homedir(), '.agent-device', 'daemon.json');
|
|
@@ -43,29 +46,11 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
|
|
|
43
46
|
logger.info(`Selecting Xcode developer directory: ${XCODE_DEVELOPER_DIR}.`);
|
|
44
47
|
await (0, turtle_spawn_1.default)('sudo', ['xcode-select', '-s', XCODE_DEVELOPER_DIR], { env, logger });
|
|
45
48
|
}
|
|
46
|
-
logger.info(packageVersion
|
|
47
|
-
? `Cloning agent-device @ v${packageVersion} into ${SRC_DIR}.`
|
|
48
|
-
: `Cloning agent-device (latest) into ${SRC_DIR}.`);
|
|
49
|
-
await cloneAgentDeviceAsync({ packageVersion, env, logger });
|
|
50
|
-
logger.info('Installing agent-device dependencies.');
|
|
51
|
-
await (0, turtle_spawn_1.default)('bun', ['install', '--production'], {
|
|
52
|
-
cwd: SRC_DIR,
|
|
53
|
-
env,
|
|
54
|
-
logger,
|
|
55
|
-
});
|
|
56
49
|
logger.info('Launching agent-device daemon.');
|
|
57
|
-
(
|
|
58
|
-
command: 'bun',
|
|
59
|
-
args: ['run', 'src/daemon.ts'],
|
|
60
|
-
cwd: SRC_DIR,
|
|
61
|
-
env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' },
|
|
62
|
-
});
|
|
50
|
+
const daemonProcess = await startAgentDeviceDaemonAsync({ packageVersion, env, logger });
|
|
63
51
|
logger.info(`Waiting for daemon credentials at ${DAEMON_JSON_PATH}.`);
|
|
64
|
-
const { port: daemonPort, token: daemonToken } = await (
|
|
65
|
-
|
|
66
|
-
timeoutMs: STARTUP_TIMEOUT_MS,
|
|
67
|
-
description: 'agent-device daemon credentials',
|
|
68
|
-
parse: parseDaemonInfo,
|
|
52
|
+
const { port: daemonPort, token: daemonToken } = await waitForDaemonInfoAsync({
|
|
53
|
+
daemonProcess,
|
|
69
54
|
});
|
|
70
55
|
logger.info(`Daemon is listening on port ${daemonPort}; loaded auth token.`);
|
|
71
56
|
const agentDeviceRemoteSessionUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
|
|
@@ -80,7 +65,7 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
|
|
|
80
65
|
// on Darwin. Android sessions go without a preview URL.
|
|
81
66
|
let webPreviewUrl;
|
|
82
67
|
if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
|
|
83
|
-
const { previewUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)({
|
|
68
|
+
const { previewUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
|
|
84
69
|
baseDomain: ngrokTunnelDomain,
|
|
85
70
|
env,
|
|
86
71
|
logger,
|
|
@@ -106,6 +91,64 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
|
|
|
106
91
|
},
|
|
107
92
|
});
|
|
108
93
|
}
|
|
94
|
+
async function startAgentDeviceDaemonAsync({ packageVersion, env, logger, }) {
|
|
95
|
+
const packageSpec = createAgentDevicePackageSpec(packageVersion);
|
|
96
|
+
try {
|
|
97
|
+
logger.info(`Installing ${packageSpec} globally with Bun.`);
|
|
98
|
+
await (0, turtle_spawn_1.default)('bun', ['add', '--global', packageSpec], {
|
|
99
|
+
env,
|
|
100
|
+
logger,
|
|
101
|
+
});
|
|
102
|
+
const daemonPath = getGlobalAgentDeviceDaemonPath(env);
|
|
103
|
+
if (!node_fs_1.default.existsSync(daemonPath)) {
|
|
104
|
+
throw new eas_build_job_1.SystemError(`Expected agent-device daemon entry at ${daemonPath}.`);
|
|
105
|
+
}
|
|
106
|
+
logger.info(`Launching daemon from ${daemonPath}.`);
|
|
107
|
+
return (0, remoteDeviceRunSession_1.spawnDetached)({
|
|
108
|
+
command: 'node',
|
|
109
|
+
args: [daemonPath],
|
|
110
|
+
env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' },
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
115
|
+
const bunVersion = await getBunVersionForDiagnosticsAsync(env);
|
|
116
|
+
sentry_1.Sentry.capture('Failed to start agent-device daemon from global Bun package; falling back to git clone', error, {
|
|
117
|
+
level: 'warning',
|
|
118
|
+
tags: {
|
|
119
|
+
phase: 'agent-device-daemon-start',
|
|
120
|
+
fallback: 'git-clone',
|
|
121
|
+
},
|
|
122
|
+
extras: {
|
|
123
|
+
packageSpec,
|
|
124
|
+
packageVersion: packageVersion ?? 'latest',
|
|
125
|
+
bunVersion,
|
|
126
|
+
bunInstallConfigured: Boolean(env.BUN_INSTALL?.trim()),
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
logger.warn(`Failed to start daemon from global ${packageSpec}; falling back to git clone: ${error.message}`);
|
|
130
|
+
return await startAgentDeviceDaemonFromGitAsync({ packageVersion, env, logger });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
async function startAgentDeviceDaemonFromGitAsync({ packageVersion, env, logger, }) {
|
|
134
|
+
logger.info(packageVersion
|
|
135
|
+
? `Cloning agent-device @ v${packageVersion} into ${SRC_DIR}.`
|
|
136
|
+
: `Cloning agent-device (latest) into ${SRC_DIR}.`);
|
|
137
|
+
await cloneAgentDeviceAsync({ packageVersion, env, logger });
|
|
138
|
+
logger.info('Installing agent-device dependencies.');
|
|
139
|
+
await (0, turtle_spawn_1.default)('bun', ['install', '--production'], {
|
|
140
|
+
cwd: SRC_DIR,
|
|
141
|
+
env,
|
|
142
|
+
logger,
|
|
143
|
+
});
|
|
144
|
+
logger.info('Launching daemon from cloned agent-device source.');
|
|
145
|
+
return (0, remoteDeviceRunSession_1.spawnDetached)({
|
|
146
|
+
command: 'bun',
|
|
147
|
+
args: ['run', 'src/daemon.ts'],
|
|
148
|
+
cwd: SRC_DIR,
|
|
149
|
+
env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' },
|
|
150
|
+
});
|
|
151
|
+
}
|
|
109
152
|
async function cloneAgentDeviceAsync({ packageVersion, env, logger, }) {
|
|
110
153
|
const branchArgs = packageVersion ? ['--branch', `v${packageVersion}`] : [];
|
|
111
154
|
await (0, turtle_spawn_1.default)('git', ['clone', '--depth', '1', ...branchArgs, AGENT_DEVICE_REPO_URL, SRC_DIR], {
|
|
@@ -113,6 +156,42 @@ async function cloneAgentDeviceAsync({ packageVersion, env, logger, }) {
|
|
|
113
156
|
logger,
|
|
114
157
|
});
|
|
115
158
|
}
|
|
159
|
+
async function waitForDaemonInfoAsync({ daemonProcess, }) {
|
|
160
|
+
try {
|
|
161
|
+
return await (0, remoteDeviceRunSession_1.waitForFileAsync)({
|
|
162
|
+
filePath: DAEMON_JSON_PATH,
|
|
163
|
+
timeoutMs: STARTUP_TIMEOUT_MS,
|
|
164
|
+
description: 'agent-device daemon credentials',
|
|
165
|
+
parse: parseDaemonInfo,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
const output = daemonProcess.getOutput();
|
|
170
|
+
throw new eas_build_job_1.SystemError(`${err instanceof Error
|
|
171
|
+
? err.message
|
|
172
|
+
: `Timed out waiting for agent-device daemon credentials.`}${output ? `\nagent-device daemon output:\n${output}` : ''}`);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async function getBunVersionForDiagnosticsAsync(env) {
|
|
176
|
+
try {
|
|
177
|
+
const result = await (0, turtle_spawn_1.default)('bun', ['--version'], { stdio: 'pipe', env, cwd: node_os_1.default.tmpdir() });
|
|
178
|
+
return result.stdout.trim() || 'unknown';
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
return 'unknown';
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function createAgentDevicePackageSpec(packageVersion) {
|
|
185
|
+
const versionSpec = packageVersion ? packageVersion.replace(/^v(?=\d)/, '') : 'latest';
|
|
186
|
+
return `${AGENT_DEVICE_PACKAGE_NAME}@${versionSpec}`;
|
|
187
|
+
}
|
|
188
|
+
function getGlobalAgentDeviceDaemonPath(env) {
|
|
189
|
+
return node_path_1.default.join(getBunInstallDirectory(env), 'install', 'global', 'node_modules', AGENT_DEVICE_PACKAGE_NAME, 'dist', 'src', 'internal', 'daemon.js');
|
|
190
|
+
}
|
|
191
|
+
function getBunInstallDirectory(env) {
|
|
192
|
+
const bunInstall = env.BUN_INSTALL?.trim();
|
|
193
|
+
return bunInstall ? bunInstall : node_path_1.default.join(node_os_1.default.homedir(), '.bun');
|
|
194
|
+
}
|
|
116
195
|
function parseDaemonInfo(raw) {
|
|
117
196
|
const parsed = JSON.parse(raw);
|
|
118
197
|
if (!parsed ||
|
|
@@ -77,7 +77,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
|
|
|
77
77
|
// serve-sim is iOS-only — Android sessions go without a preview URL.
|
|
78
78
|
let webPreviewUrl;
|
|
79
79
|
if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
|
|
80
|
-
const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)({
|
|
80
|
+
const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
|
|
81
81
|
baseDomain: ngrokTunnelDomain,
|
|
82
82
|
env,
|
|
83
83
|
logger,
|
|
@@ -52,6 +52,12 @@ function createStartIosSimulatorBuildFunction() {
|
|
|
52
52
|
deviceIdentifier: originalDeviceIdentifier,
|
|
53
53
|
env,
|
|
54
54
|
});
|
|
55
|
+
try {
|
|
56
|
+
await IosSimulatorUtils_1.IosSimulatorUtils.disableApsdAsync({ udid, env });
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
logger.warn({ err }, 'Failed to disable apsd in the Simulator.');
|
|
60
|
+
}
|
|
55
61
|
await IosSimulatorUtils_1.IosSimulatorUtils.waitForReadyAsync({ udid, env });
|
|
56
62
|
logger.info('');
|
|
57
63
|
const device = await IosSimulatorUtils_1.IosSimulatorUtils.getDeviceAsync({ udid, env });
|
|
@@ -76,6 +82,12 @@ function createStartIosSimulatorBuildFunction() {
|
|
|
76
82
|
deviceIdentifier: cloneDeviceName,
|
|
77
83
|
env,
|
|
78
84
|
});
|
|
85
|
+
try {
|
|
86
|
+
await IosSimulatorUtils_1.IosSimulatorUtils.disableApsdAsync({ udid: cloneUdid, env });
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
logger.warn({ err }, 'Failed to disable apsd in the Simulator.');
|
|
90
|
+
}
|
|
79
91
|
await IosSimulatorUtils_1.IosSimulatorUtils.waitForReadyAsync({
|
|
80
92
|
udid: cloneUdid,
|
|
81
93
|
env,
|
|
@@ -22,7 +22,7 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
|
|
|
22
22
|
logger.info('Starting serve-sim remote session.');
|
|
23
23
|
logger.info(`Selecting Xcode developer directory: ${XCODE_DEVELOPER_DIR}.`);
|
|
24
24
|
await (0, turtle_spawn_1.default)('sudo', ['xcode-select', '-s', XCODE_DEVELOPER_DIR], { env, logger });
|
|
25
|
-
const { previewUrl, streamUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)({
|
|
25
|
+
const { previewUrl, streamUrl } = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
|
|
26
26
|
baseDomain: ngrokTunnelDomain,
|
|
27
27
|
env,
|
|
28
28
|
logger,
|
|
@@ -112,7 +112,7 @@ function createUploadArtifactBuildFunction(ctx) {
|
|
|
112
112
|
}
|
|
113
113
|
catch (error) {
|
|
114
114
|
if (inputs.ignore_error.value) {
|
|
115
|
-
logger.error(`Failed to upload ${artifact.type}. Ignoring error
|
|
115
|
+
logger.error({ err: error }, `Failed to upload ${artifact.type}. Ignoring error.`);
|
|
116
116
|
// Ignoring error.
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
@@ -7,8 +7,8 @@ exports.runFastlaneGym = runFastlaneGym;
|
|
|
7
7
|
exports.runFastlane = runFastlane;
|
|
8
8
|
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const xcpretty_1 = require("./xcpretty");
|
|
11
10
|
const fastlane_1 = require("../../../common/fastlane");
|
|
11
|
+
const xcpretty_1 = require("../../../common/xcpretty");
|
|
12
12
|
async function runFastlaneGym({ workingDir, logger, buildLogsDirectory, env, extraEnv, }) {
|
|
13
13
|
const buildLogger = new xcpretty_1.XcodeBuildLogger(logger, workingDir);
|
|
14
14
|
void buildLogger.watchLogFiles(buildLogsDirectory);
|
|
@@ -1,9 +1,36 @@
|
|
|
1
1
|
import { bunyan } from '@expo/logger';
|
|
2
2
|
import { BuildStepEnv } from '@expo/steps';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
import { CustomBuildContext } from '../../customBuildContext';
|
|
4
5
|
export declare function getDeviceRunSessionIdOrThrow(env: BuildStepEnv): string;
|
|
5
6
|
export declare function getNgrokTunnelDomainOrThrow(env: BuildStepEnv): string;
|
|
6
7
|
export declare function getNgrokAuthtokenOrThrow(env: BuildStepEnv): string;
|
|
8
|
+
declare const TurnIceServersSchema: z.ZodArray<z.ZodObject<{
|
|
9
|
+
urls: z.ZodArray<z.ZodString>;
|
|
10
|
+
username: z.ZodOptional<z.ZodString>;
|
|
11
|
+
credential: z.ZodOptional<z.ZodString>;
|
|
12
|
+
}, z.core.$strip>>;
|
|
13
|
+
export type TurnIceServers = z.infer<typeof TurnIceServersSchema>;
|
|
14
|
+
/**
|
|
15
|
+
* Translate Cloudflare ICE servers into serve-sim CLI flags: `--stun-url` (the
|
|
16
|
+
* credential-less entries) and `--turn-url`/`--turn-username`/`--turn-credential`
|
|
17
|
+
* (the entry carrying the short-lived credentials).
|
|
18
|
+
*/
|
|
19
|
+
export declare function turnIceServersToServeSimArgs(iceServers: TurnIceServers): string[];
|
|
20
|
+
/**
|
|
21
|
+
* Fetch short-lived Cloudflare TURN ICE servers for this job run from www
|
|
22
|
+
* (minted on demand, mirroring how the worker fetches project clone URLs) and
|
|
23
|
+
* translate them into serve-sim CLI flags.
|
|
24
|
+
*
|
|
25
|
+
* Best-effort: on any failure we log and return [] so serve-sim falls back to
|
|
26
|
+
* its built-in P2P/STUN behavior. The credential is passed to serve-sim as a
|
|
27
|
+
* process arg and deliberately not logged (turtle-spawn never logs argv and the
|
|
28
|
+
* worker is single-tenant).
|
|
29
|
+
*/
|
|
30
|
+
export declare function fetchServeSimTurnArgsAsync(ctx: CustomBuildContext, { env, logger }: {
|
|
31
|
+
env: BuildStepEnv;
|
|
32
|
+
logger: bunyan;
|
|
33
|
+
}): Promise<string[]>;
|
|
7
34
|
export declare function uploadRemoteSessionConfigAsync({ ctx, deviceRunSessionId, remoteConfig, logger, }: {
|
|
8
35
|
ctx: CustomBuildContext;
|
|
9
36
|
deviceRunSessionId: string;
|
|
@@ -19,7 +46,7 @@ export declare function spawnDetached({ command, args, cwd, env, }: {
|
|
|
19
46
|
cwd?: string;
|
|
20
47
|
env: BuildStepEnv;
|
|
21
48
|
}): DetachedProcessHandle;
|
|
22
|
-
export declare function startServeSimWithTunnelAsync({ baseDomain, env, logger, timeoutMs, }: {
|
|
49
|
+
export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, }: {
|
|
23
50
|
baseDomain: string;
|
|
24
51
|
env: BuildStepEnv;
|
|
25
52
|
logger: bunyan;
|
|
@@ -41,3 +68,4 @@ export declare function waitForFileAsync<T>({ filePath, timeoutMs, description,
|
|
|
41
68
|
description: string;
|
|
42
69
|
parse: (raw: string) => T;
|
|
43
70
|
}): Promise<T>;
|
|
71
|
+
export {};
|
|
@@ -39,6 +39,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.getDeviceRunSessionIdOrThrow = getDeviceRunSessionIdOrThrow;
|
|
40
40
|
exports.getNgrokTunnelDomainOrThrow = getNgrokTunnelDomainOrThrow;
|
|
41
41
|
exports.getNgrokAuthtokenOrThrow = getNgrokAuthtokenOrThrow;
|
|
42
|
+
exports.turnIceServersToServeSimArgs = turnIceServersToServeSimArgs;
|
|
43
|
+
exports.fetchServeSimTurnArgsAsync = fetchServeSimTurnArgsAsync;
|
|
42
44
|
exports.uploadRemoteSessionConfigAsync = uploadRemoteSessionConfigAsync;
|
|
43
45
|
exports.spawnDetached = spawnDetached;
|
|
44
46
|
exports.startServeSimWithTunnelAsync = startServeSimWithTunnelAsync;
|
|
@@ -48,9 +50,13 @@ const eas_build_job_1 = require("@expo/eas-build-job");
|
|
|
48
50
|
const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
49
51
|
const ngrok = __importStar(require("@ngrok/ngrok"));
|
|
50
52
|
const gql_tada_1 = require("gql.tada");
|
|
53
|
+
const nullthrows_1 = __importDefault(require("nullthrows"));
|
|
54
|
+
const zod_1 = require("zod");
|
|
51
55
|
const node_crypto_1 = require("node:crypto");
|
|
52
56
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
57
|
+
const sentry_1 = require("../../sentry");
|
|
53
58
|
const retry_1 = require("../../utils/retry");
|
|
59
|
+
const turtleFetch_1 = require("../../utils/turtleFetch");
|
|
54
60
|
const START_DEVICE_RUN_SESSION_MUTATION = (0, gql_tada_1.graphql)(`
|
|
55
61
|
mutation StartDeviceRunSession($deviceRunSessionId: ID!, $remoteConfig: JSONObject!) {
|
|
56
62
|
deviceRunSession {
|
|
@@ -91,6 +97,72 @@ function getNgrokAuthtokenOrThrow(env) {
|
|
|
91
97
|
}
|
|
92
98
|
return authtoken;
|
|
93
99
|
}
|
|
100
|
+
const TurnIceServersSchema = zod_1.z.array(zod_1.z.object({
|
|
101
|
+
urls: zod_1.z.array(zod_1.z.string()),
|
|
102
|
+
username: zod_1.z.string().optional(),
|
|
103
|
+
credential: zod_1.z.string().optional(),
|
|
104
|
+
}));
|
|
105
|
+
const TurnIceServersResponseSchema = zod_1.z.object({
|
|
106
|
+
data: zod_1.z.object({
|
|
107
|
+
iceServers: TurnIceServersSchema,
|
|
108
|
+
}),
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* Translate Cloudflare ICE servers into serve-sim CLI flags: `--stun-url` (the
|
|
112
|
+
* credential-less entries) and `--turn-url`/`--turn-username`/`--turn-credential`
|
|
113
|
+
* (the entry carrying the short-lived credentials).
|
|
114
|
+
*/
|
|
115
|
+
function turnIceServersToServeSimArgs(iceServers) {
|
|
116
|
+
const stunUrls = iceServers
|
|
117
|
+
.filter(server => !server.username && !server.credential)
|
|
118
|
+
.flatMap(server => server.urls);
|
|
119
|
+
const turnServer = iceServers.find(server => server.username && server.credential);
|
|
120
|
+
const args = [];
|
|
121
|
+
if (stunUrls.length > 0) {
|
|
122
|
+
args.push('--stun-url', stunUrls.join(','));
|
|
123
|
+
}
|
|
124
|
+
if (turnServer?.username && turnServer.credential && turnServer.urls.length > 0) {
|
|
125
|
+
args.push('--turn-url', turnServer.urls.join(','), '--turn-username', turnServer.username, '--turn-credential', turnServer.credential);
|
|
126
|
+
}
|
|
127
|
+
return args;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Fetch short-lived Cloudflare TURN ICE servers for this job run from www
|
|
131
|
+
* (minted on demand, mirroring how the worker fetches project clone URLs) and
|
|
132
|
+
* translate them into serve-sim CLI flags.
|
|
133
|
+
*
|
|
134
|
+
* Best-effort: on any failure we log and return [] so serve-sim falls back to
|
|
135
|
+
* its built-in P2P/STUN behavior. The credential is passed to serve-sim as a
|
|
136
|
+
* process arg and deliberately not logged (turtle-spawn never logs argv and the
|
|
137
|
+
* worker is single-tenant).
|
|
138
|
+
*/
|
|
139
|
+
async function fetchServeSimTurnArgsAsync(ctx, { env, logger }) {
|
|
140
|
+
try {
|
|
141
|
+
const deviceRunSessionId = getDeviceRunSessionIdOrThrow(env);
|
|
142
|
+
const expoApiServerUrl = (0, nullthrows_1.default)(ctx.env.__API_SERVER_URL, '__API_SERVER_URL is not set');
|
|
143
|
+
const robotAccessToken = (0, nullthrows_1.default)(ctx.job.secrets?.robotAccessToken, 'robot access token is not set');
|
|
144
|
+
const response = await (0, turtleFetch_1.turtleFetch)(new URL(`/v2/device-run-sessions/${deviceRunSessionId}/turn-ice-servers`, expoApiServerUrl).toString(), 'POST', {
|
|
145
|
+
headers: {
|
|
146
|
+
Authorization: `Bearer ${robotAccessToken}`,
|
|
147
|
+
},
|
|
148
|
+
timeout: 5000,
|
|
149
|
+
retries: 1,
|
|
150
|
+
logger,
|
|
151
|
+
});
|
|
152
|
+
const { data } = TurnIceServersResponseSchema.parse(await response.json());
|
|
153
|
+
const args = turnIceServersToServeSimArgs(data.iceServers);
|
|
154
|
+
if (args.length > 0) {
|
|
155
|
+
logger.info('Configured serve-sim with Cloudflare TURN ICE servers.');
|
|
156
|
+
}
|
|
157
|
+
return args;
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
161
|
+
sentry_1.Sentry.capture('Could not fetch Cloudflare TURN ICE servers', error, { level: 'warning' });
|
|
162
|
+
logger.warn({ err: error }, 'Could not fetch Cloudflare TURN ICE servers; serve-sim will fall back to P2P/STUN.');
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
94
166
|
async function uploadRemoteSessionConfigAsync({ ctx, deviceRunSessionId, remoteConfig, logger, }) {
|
|
95
167
|
logger.info(`Reporting remote config to the API server (device run session: ${deviceRunSessionId}).`);
|
|
96
168
|
const result = await ctx.graphqlClient
|
|
@@ -119,8 +191,9 @@ function spawnDetached({ command, args, cwd, env, }) {
|
|
|
119
191
|
promise.child.stderr?.on('data', appendChunk);
|
|
120
192
|
return { getOutput: () => output };
|
|
121
193
|
}
|
|
122
|
-
async function startServeSimWithTunnelAsync({ baseDomain, env, logger, timeoutMs, }) {
|
|
194
|
+
async function startServeSimWithTunnelAsync(ctx, { baseDomain, env, logger, timeoutMs, }) {
|
|
123
195
|
logger.info('Launching serve-sim with tunnel.');
|
|
196
|
+
const turnArgs = await fetchServeSimTurnArgsAsync(ctx, { env, logger });
|
|
124
197
|
const serveSim = spawnDetached({
|
|
125
198
|
command: 'npx',
|
|
126
199
|
args: [
|
|
@@ -135,7 +208,8 @@ async function startServeSimWithTunnelAsync({ baseDomain, env, logger, timeoutMs
|
|
|
135
208
|
'--stream-quality',
|
|
136
209
|
'0.55',
|
|
137
210
|
'--codec',
|
|
138
|
-
'
|
|
211
|
+
'webrtc',
|
|
212
|
+
...turnArgs,
|
|
139
213
|
],
|
|
140
214
|
env,
|
|
141
215
|
});
|
|
@@ -49,6 +49,10 @@ export declare namespace IosSimulatorUtils {
|
|
|
49
49
|
udid: IosSimulatorUuid;
|
|
50
50
|
env: NodeJS.ProcessEnv;
|
|
51
51
|
}): Promise<void>;
|
|
52
|
+
export function disableApsdAsync({ udid, env, }: {
|
|
53
|
+
udid: IosSimulatorUuid;
|
|
54
|
+
env: NodeJS.ProcessEnv;
|
|
55
|
+
}): Promise<void>;
|
|
52
56
|
export function collectLogsAsync({ deviceIdentifier, env, }: {
|
|
53
57
|
deviceIdentifier: IosSimulatorName | IosSimulatorUuid;
|
|
54
58
|
env: NodeJS.ProcessEnv;
|
|
@@ -77,6 +77,11 @@ var IosSimulatorUtils;
|
|
|
77
77
|
});
|
|
78
78
|
}
|
|
79
79
|
IosSimulatorUtils.waitForReadyAsync = waitForReadyAsync;
|
|
80
|
+
async function disableApsdAsync({ udid, env, }) {
|
|
81
|
+
await (0, turtle_spawn_1.default)('xcrun', ['simctl', 'spawn', udid, 'launchctl', 'disable', 'system/com.apple.apsd'], { env });
|
|
82
|
+
await (0, turtle_spawn_1.default)('xcrun', ['simctl', 'spawn', udid, 'launchctl', 'bootout', 'system/com.apple.apsd'], { env });
|
|
83
|
+
}
|
|
84
|
+
IosSimulatorUtils.disableApsdAsync = disableApsdAsync;
|
|
80
85
|
async function collectLogsAsync({ deviceIdentifier, env, }) {
|
|
81
86
|
const outputDir = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'ios-simulator-logs-'));
|
|
82
87
|
const outputPath = node_path_1.default.join(outputDir, `${deviceIdentifier}.logarchive`);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uploadEmbeddedBundleAsync = uploadEmbeddedBundleAsync;
|
|
7
|
+
const eas_build_job_1 = require("@expo/eas-build-job");
|
|
8
|
+
const logger_1 = require("@expo/logger");
|
|
9
|
+
const results_1 = require("@expo/results");
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
11
|
+
const os_1 = __importDefault(require("os"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const node_stream_zip_1 = __importDefault(require("node-stream-zip"));
|
|
14
|
+
const artifacts_1 = require("./artifacts");
|
|
15
|
+
const easCli_1 = require("./easCli");
|
|
16
|
+
const resolve_1 = require("../ios/resolve");
|
|
17
|
+
const expoUpdates_1 = require("./expoUpdates");
|
|
18
|
+
async function uploadEmbeddedBundleAsync(ctx) {
|
|
19
|
+
if (!(await (0, expoUpdates_1.isEASUpdateConfigured)(ctx))) {
|
|
20
|
+
ctx.markBuildPhaseSkipped();
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { platform } = ctx.job;
|
|
24
|
+
if (platform === eas_build_job_1.Platform.IOS && ctx.job.simulator) {
|
|
25
|
+
ctx.markBuildPhaseSkipped();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const channel = ctx.job.updates?.channel;
|
|
29
|
+
if (!channel) {
|
|
30
|
+
ctx.logger.warn('Skipping embedded bundle upload: no channel configured for this build profile.');
|
|
31
|
+
ctx.markBuildPhaseHasWarnings();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const projectDir = ctx.getReactNativeProjectDirectory();
|
|
35
|
+
let archivePattern;
|
|
36
|
+
if (platform === eas_build_job_1.Platform.IOS) {
|
|
37
|
+
archivePattern = (0, resolve_1.resolveArtifactPath)(ctx);
|
|
38
|
+
}
|
|
39
|
+
else if (platform === eas_build_job_1.Platform.ANDROID) {
|
|
40
|
+
archivePattern =
|
|
41
|
+
ctx.job.applicationArchivePath ??
|
|
42
|
+
'android/app/build/outputs/**/*.{apk,aab}';
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
throw new Error(`Uploading embedded updates is not supported for the ${platform} platform.`);
|
|
46
|
+
}
|
|
47
|
+
const [archivePath] = await (0, artifacts_1.findArtifacts)({
|
|
48
|
+
rootDir: projectDir,
|
|
49
|
+
patternOrPath: archivePattern,
|
|
50
|
+
logger: null,
|
|
51
|
+
}).catch(() => []);
|
|
52
|
+
if (!archivePath) {
|
|
53
|
+
ctx.logger.warn('Skipping embedded bundle upload: build archive not found.');
|
|
54
|
+
ctx.markBuildPhaseHasWarnings();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const tmpDir = await fs_extra_1.default.mkdtemp(path_1.default.join(os_1.default.tmpdir(), 'eas-embedded-bundle-'));
|
|
58
|
+
const bundleName = platform === eas_build_job_1.Platform.IOS ? 'main.jsbundle' : 'index.android.bundle';
|
|
59
|
+
const bundlePath = path_1.default.join(tmpDir, bundleName);
|
|
60
|
+
const manifestPath = path_1.default.join(tmpDir, 'app.manifest');
|
|
61
|
+
const zip = new node_stream_zip_1.default.async({ file: archivePath });
|
|
62
|
+
try {
|
|
63
|
+
const entries = Object.values(await zip.entries());
|
|
64
|
+
const bundleEntry = entries.find(e => platform === eas_build_job_1.Platform.IOS
|
|
65
|
+
? e.name.endsWith('/main.jsbundle')
|
|
66
|
+
: e.name.endsWith('assets/index.android.bundle'));
|
|
67
|
+
const manifestEntry = entries.find(e => platform === eas_build_job_1.Platform.IOS
|
|
68
|
+
? e.name.includes('EXUpdates.bundle/app.manifest')
|
|
69
|
+
: e.name.endsWith('assets/app.manifest'));
|
|
70
|
+
if (!bundleEntry || !manifestEntry) {
|
|
71
|
+
ctx.logger.warn('Skipping embedded bundle upload: bundle or manifest not found in archive.');
|
|
72
|
+
ctx.markBuildPhaseHasWarnings();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
await zip.extract(bundleEntry.name, bundlePath);
|
|
76
|
+
await zip.extract(manifestEntry.name, manifestPath);
|
|
77
|
+
const args = [
|
|
78
|
+
'update:embedded:upload',
|
|
79
|
+
'--platform',
|
|
80
|
+
platform,
|
|
81
|
+
'--bundle',
|
|
82
|
+
bundlePath,
|
|
83
|
+
'--manifest',
|
|
84
|
+
manifestPath,
|
|
85
|
+
'--channel',
|
|
86
|
+
channel,
|
|
87
|
+
'--non-interactive',
|
|
88
|
+
];
|
|
89
|
+
if (ctx.env.EAS_BUILD_ID) {
|
|
90
|
+
args.push('--build-id', ctx.env.EAS_BUILD_ID);
|
|
91
|
+
}
|
|
92
|
+
await (0, easCli_1.runEasCliCommand)({
|
|
93
|
+
args,
|
|
94
|
+
options: {
|
|
95
|
+
cwd: projectDir,
|
|
96
|
+
env: ctx.env,
|
|
97
|
+
logger: ctx.logger,
|
|
98
|
+
mode: logger_1.PipeMode.STDERR_ONLY_AS_STDOUT,
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
ctx.logger.warn({ err }, 'Failed to upload embedded bundle.');
|
|
104
|
+
ctx.markBuildPhaseHasWarnings();
|
|
105
|
+
}
|
|
106
|
+
finally {
|
|
107
|
+
await (0, results_1.asyncResult)(zip.close());
|
|
108
|
+
}
|
|
109
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/build-tools",
|
|
3
|
-
"version": "20.
|
|
3
|
+
"version": "20.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>",
|
|
@@ -38,14 +38,14 @@
|
|
|
38
38
|
"@expo/config": "55.0.10",
|
|
39
39
|
"@expo/config-plugins": "55.0.7",
|
|
40
40
|
"@expo/downloader": "20.0.0",
|
|
41
|
-
"@expo/eas-build-job": "20.
|
|
41
|
+
"@expo/eas-build-job": "20.1.0",
|
|
42
42
|
"@expo/env": "^0.4.0",
|
|
43
43
|
"@expo/logger": "20.0.0",
|
|
44
44
|
"@expo/package-manager": "1.9.10",
|
|
45
45
|
"@expo/plist": "^0.2.0",
|
|
46
46
|
"@expo/results": "^1.0.0",
|
|
47
47
|
"@expo/spawn-async": "1.7.2",
|
|
48
|
-
"@expo/steps": "20.
|
|
48
|
+
"@expo/steps": "20.1.0",
|
|
49
49
|
"@expo/template-file": "20.0.0",
|
|
50
50
|
"@expo/turtle-spawn": "20.0.0",
|
|
51
51
|
"@expo/xcpretty": "^4.3.1",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"zod": "^4.3.5"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
|
-
"@expo/repack-app": "~0.
|
|
80
|
+
"@expo/repack-app": "~0.6.1",
|
|
81
81
|
"@types/fs-extra": "^11.0.4",
|
|
82
82
|
"@types/jest": "^29.5.12",
|
|
83
83
|
"@types/lodash": "^4.17.4",
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"typescript": "^5.5.4",
|
|
101
101
|
"uuid": "^9.0.1"
|
|
102
102
|
},
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "203e9997eebafcbddc600285f6a809afb4a93a74"
|
|
104
104
|
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { bunyan } from '@expo/logger';
|
|
2
|
-
export declare class XcodeBuildLogger {
|
|
3
|
-
private readonly logger;
|
|
4
|
-
private readonly projectRoot;
|
|
5
|
-
private loggerError?;
|
|
6
|
-
private flushing;
|
|
7
|
-
private logReaderPromise?;
|
|
8
|
-
private logsPath?;
|
|
9
|
-
constructor(logger: bunyan, projectRoot: string);
|
|
10
|
-
watchLogFiles(logsDirectory: string): Promise<void>;
|
|
11
|
-
flush(): Promise<void>;
|
|
12
|
-
private getBuildLogFilename;
|
|
13
|
-
private startBuildLogger;
|
|
14
|
-
private findBundlerErrors;
|
|
15
|
-
}
|