@expo/build-tools 23.2.0 → 24.1.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/projectSources.js +4 -2
- package/dist/context.d.ts +2 -0
- package/dist/context.js +2 -0
- package/dist/customBuildContext.d.ts +1 -0
- package/dist/customBuildContext.js +2 -0
- package/dist/generic.js +11 -1
- package/dist/steps/easFunctions.js +2 -0
- package/dist/steps/functions/startAgentDeviceRemoteSession.js +7 -3
- package/dist/steps/functions/startAndroidEmulator.js +21 -0
- package/dist/steps/functions/startAppiumRemoteSession.js +7 -3
- package/dist/steps/functions/startArgentRemoteSession.js +26 -15
- package/dist/steps/functions/startLocalEgress.d.ts +10 -0
- package/dist/steps/functions/startLocalEgress.js +156 -0
- package/dist/steps/functions/startWebPreviewRemoteSession.js +10 -4
- package/dist/steps/functions/uploadToAsc.js +11 -5
- package/dist/steps/utils/appiumCommandSummary.js +3 -0
- package/dist/steps/utils/appiumCommands.generated.d.ts +2 -2
- package/dist/steps/utils/localEgress.d.ts +152 -0
- package/dist/steps/utils/localEgress.js +550 -0
- package/dist/steps/utils/localEgressSession.d.ts +9 -0
- package/dist/steps/utils/localEgressSession.js +40 -0
- package/dist/steps/utils/remoteDeviceRunSession.d.ts +8 -18
- package/dist/steps/utils/remoteDeviceRunSession.js +66 -21
- package/dist/steps/utils/serveSimMetricsRecorder.d.ts +4 -1
- package/dist/steps/utils/serveSimMetricsRecorder.js +14 -6
- package/dist/utils/AndroidEmulatorUtils.d.ts +4 -1
- package/dist/utils/AndroidEmulatorUtils.js +5 -1
- package/dist/utils/logPhase.d.ts +2 -0
- package/dist/utils/logPhase.js +22 -0
- package/package.json +8 -8
|
@@ -26,9 +26,11 @@ async function prepareProjectSourcesAsync(ctx, destinationDirectory) {
|
|
|
26
26
|
else {
|
|
27
27
|
const projectArchiveResult = await (0, results_1.asyncResult)(fetchProjectArchiveSourceAsync(ctx));
|
|
28
28
|
if (!projectArchiveResult.ok) {
|
|
29
|
-
|
|
29
|
+
throw new eas_build_job_1.SystemError('Failed to fetch project sources. Re-run the job.', {
|
|
30
|
+
cause: projectArchiveResult.reason,
|
|
31
|
+
});
|
|
30
32
|
}
|
|
31
|
-
projectArchive = projectArchiveResult.value
|
|
33
|
+
projectArchive = projectArchiveResult.value;
|
|
32
34
|
}
|
|
33
35
|
switch (projectArchive.type) {
|
|
34
36
|
case eas_build_job_1.ArchiveSourceType.R2:
|
package/dist/context.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export interface BuildContextOptions {
|
|
|
48
48
|
skipNativeBuild?: boolean;
|
|
49
49
|
metadata?: Metadata;
|
|
50
50
|
expoApiV2BaseUrl?: string;
|
|
51
|
+
mcpServerUrl?: string;
|
|
51
52
|
}
|
|
52
53
|
export declare class SkipNativeBuildError extends Error {
|
|
53
54
|
}
|
|
@@ -62,6 +63,7 @@ export declare class BuildContext<TJob extends Job = Job> {
|
|
|
62
63
|
}) => void;
|
|
63
64
|
readonly skipNativeBuild?: boolean;
|
|
64
65
|
readonly expoApiV2BaseUrl?: string;
|
|
66
|
+
readonly mcpServerUrl?: string;
|
|
65
67
|
artifacts: Artifacts;
|
|
66
68
|
private readonly _isLocal;
|
|
67
69
|
private _env;
|
package/dist/context.js
CHANGED
|
@@ -25,6 +25,7 @@ class BuildContext {
|
|
|
25
25
|
reportError;
|
|
26
26
|
skipNativeBuild;
|
|
27
27
|
expoApiV2BaseUrl;
|
|
28
|
+
mcpServerUrl;
|
|
28
29
|
artifacts = {};
|
|
29
30
|
_isLocal;
|
|
30
31
|
_env;
|
|
@@ -49,6 +50,7 @@ class BuildContext {
|
|
|
49
50
|
this._metadata = options.metadata;
|
|
50
51
|
this.skipNativeBuild = options.skipNativeBuild;
|
|
51
52
|
this.expoApiV2BaseUrl = options.expoApiV2BaseUrl;
|
|
53
|
+
this.mcpServerUrl = options.mcpServerUrl;
|
|
52
54
|
const environmentSecrets = this.getEnvironmentSecrets(job);
|
|
53
55
|
this._env = {
|
|
54
56
|
...options.env,
|
|
@@ -23,6 +23,7 @@ export declare class CustomBuildContext<TJob extends Job = Job> implements Exter
|
|
|
23
23
|
readonly logger: bunyan;
|
|
24
24
|
readonly graphqlClient: Client;
|
|
25
25
|
readonly runtimeApi: BuilderRuntimeApi;
|
|
26
|
+
readonly mcpServerUrl?: string;
|
|
26
27
|
job: TJob;
|
|
27
28
|
metadata?: Metadata;
|
|
28
29
|
private _env;
|
|
@@ -38,6 +38,7 @@ class CustomBuildContext {
|
|
|
38
38
|
logger;
|
|
39
39
|
graphqlClient;
|
|
40
40
|
runtimeApi;
|
|
41
|
+
mcpServerUrl;
|
|
41
42
|
job;
|
|
42
43
|
metadata;
|
|
43
44
|
_env;
|
|
@@ -56,6 +57,7 @@ class CustomBuildContext {
|
|
|
56
57
|
this.runtimeApi = {
|
|
57
58
|
uploadArtifact: (...args) => buildCtx['uploadArtifact'](...args),
|
|
58
59
|
};
|
|
60
|
+
this.mcpServerUrl = buildCtx.mcpServerUrl;
|
|
59
61
|
this.expoApiV2BaseUrl = buildCtx.expoApiV2BaseUrl;
|
|
60
62
|
this.startTime = new Date();
|
|
61
63
|
}
|
package/dist/generic.js
CHANGED
|
@@ -13,6 +13,7 @@ const projectSources_1 = require("./common/projectSources");
|
|
|
13
13
|
const customBuildContext_1 = require("./customBuildContext");
|
|
14
14
|
const easFunctionGroups_1 = require("./steps/easFunctionGroups");
|
|
15
15
|
const easFunctions_1 = require("./steps/easFunctions");
|
|
16
|
+
const localEgress_1 = require("./steps/utils/localEgress");
|
|
16
17
|
const outputs_1 = require("./utils/outputs");
|
|
17
18
|
const retry_1 = require("./utils/retry");
|
|
18
19
|
async function runGenericJobAsync(ctx) {
|
|
@@ -63,7 +64,16 @@ async function runGenericJobAsync(ctx) {
|
|
|
63
64
|
throw parseError;
|
|
64
65
|
}
|
|
65
66
|
});
|
|
66
|
-
const runResult = await (0, results_1.asyncResult)(
|
|
67
|
+
const runResult = await (0, results_1.asyncResult)((async () => {
|
|
68
|
+
try {
|
|
69
|
+
await workflow.executeAsync();
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
// The session-owning step may never run if simulator boot/setup fails,
|
|
73
|
+
// or may time out while resource acquisition is still in flight.
|
|
74
|
+
await (0, localEgress_1.stopLocalEgressResourcesAsync)(ctx.logger);
|
|
75
|
+
}
|
|
76
|
+
})());
|
|
67
77
|
await ctx.runBuildPhase(eas_build_job_1.BuildPhase.COMPLETE_JOB, async () => {
|
|
68
78
|
const results = await Promise.allSettled([
|
|
69
79
|
(0, outputs_1.uploadJobOutputsToWwwAsync)(globalContext, {
|
|
@@ -49,6 +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 startLocalEgress_1 = require("./functions/startLocalEgress");
|
|
52
53
|
const startWebPreviewRemoteSession_1 = require("./functions/startWebPreviewRemoteSession");
|
|
53
54
|
const startServeSimMetrics_1 = require("./functions/startServeSimMetrics");
|
|
54
55
|
const collectServeSimMetrics_1 = require("./functions/collectServeSimMetrics");
|
|
@@ -99,6 +100,7 @@ function getEasFunctions(ctx) {
|
|
|
99
100
|
(0, startAndroidEmulator_1.createStartAndroidEmulatorBuildFunction)(),
|
|
100
101
|
(0, startCuttlefishDevice_1.createStartCuttlefishDeviceBuildFunction)(),
|
|
101
102
|
(0, startIosSimulator_1.createStartIosSimulatorBuildFunction)(),
|
|
103
|
+
(0, startLocalEgress_1.createStartLocalEgressBuildFunction)(),
|
|
102
104
|
(0, startIosSimulatorRecordings_1.createStartIosSimulatorRecordingsBuildFunction)(),
|
|
103
105
|
(0, finishIosSimulatorRecordings_1.createFinishIosSimulatorRecordingsBuildFunction)(),
|
|
104
106
|
(0, uploadDeviceRunSessionScreenRecordings_1.createUploadDeviceRunSessionScreenRecordingsBuildFunction)(ctx),
|
|
@@ -11,6 +11,7 @@ const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
|
|
|
11
11
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
12
|
const node_os_1 = __importDefault(require("node:os"));
|
|
13
13
|
const node_path_1 = __importDefault(require("node:path"));
|
|
14
|
+
const localEgressSession_1 = require("../utils/localEgressSession");
|
|
14
15
|
const sentry_1 = require("../../sentry");
|
|
15
16
|
const agentDeviceArtifacts_1 = require("../utils/agentDeviceArtifacts");
|
|
16
17
|
const agentDeviceEvents_1 = require("../utils/agentDeviceEvents");
|
|
@@ -48,7 +49,7 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
|
|
|
48
49
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
49
50
|
}),
|
|
50
51
|
],
|
|
51
|
-
fn: async ({ logger, global }, { inputs, env, signal }) => {
|
|
52
|
+
fn: (0, localEgressSession_1.withLocalEgressSession)(async ({ logger, global }, { inputs, env, signal }) => {
|
|
52
53
|
// Fail fast before any expensive setup if the injected env
|
|
53
54
|
// vars are missing: DEVICE_RUN_SESSION_ID (to report the remote config
|
|
54
55
|
// back to the API server), EAS_SIMULATOR_NGROK_TUNNEL_DOMAIN (base domain
|
|
@@ -92,13 +93,16 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
|
|
|
92
93
|
timeoutMs: STARTUP_TIMEOUT_MS,
|
|
93
94
|
});
|
|
94
95
|
logger.info(`Web preview URL: ${webPreview.previewUrl}`);
|
|
95
|
-
await (0,
|
|
96
|
+
await (0, localEgressSession_1.uploadRemoteSessionConfigWithLocalEgressAsync)({
|
|
97
|
+
env,
|
|
98
|
+
signal,
|
|
96
99
|
ctx,
|
|
97
100
|
deviceRunSessionId,
|
|
98
101
|
remoteConfig: {
|
|
99
102
|
agentDeviceRemoteSessionUrl,
|
|
100
103
|
agentDeviceRemoteSessionToken: daemonToken,
|
|
101
104
|
webPreviewUrl: webPreview.previewUrl,
|
|
105
|
+
...(webPreview.previewToken ? { webPreviewToken: webPreview.previewToken } : {}),
|
|
102
106
|
},
|
|
103
107
|
logger,
|
|
104
108
|
});
|
|
@@ -142,7 +146,7 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
|
|
|
142
146
|
}
|
|
143
147
|
await daemonProcess.stopAsync();
|
|
144
148
|
}
|
|
145
|
-
},
|
|
149
|
+
}),
|
|
146
150
|
});
|
|
147
151
|
}
|
|
148
152
|
async function stopAgentDeviceEventCollectionSafelyAsync({ eventCollection, deviceRunSessionId, logger, }) {
|
|
@@ -39,6 +39,21 @@ function createStartAndroidEmulatorBuildFunction() {
|
|
|
39
39
|
defaultValue: AndroidEmulatorUtils_1.AndroidEmulatorUtils.defaultSystemImagePackage,
|
|
40
40
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
|
|
41
41
|
}),
|
|
42
|
+
steps_1.BuildStepInput.createProvider({
|
|
43
|
+
id: 'lcd_width',
|
|
44
|
+
required: false,
|
|
45
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
46
|
+
}),
|
|
47
|
+
steps_1.BuildStepInput.createProvider({
|
|
48
|
+
id: 'lcd_height',
|
|
49
|
+
required: false,
|
|
50
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
51
|
+
}),
|
|
52
|
+
steps_1.BuildStepInput.createProvider({
|
|
53
|
+
id: 'lcd_density',
|
|
54
|
+
required: false,
|
|
55
|
+
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
56
|
+
}),
|
|
42
57
|
steps_1.BuildStepInput.createProvider({
|
|
43
58
|
id: 'count',
|
|
44
59
|
required: false,
|
|
@@ -75,6 +90,9 @@ function createStartAndroidEmulatorBuildFunction() {
|
|
|
75
90
|
const systemImagePackage = `${inputs.system_image_package.value}`;
|
|
76
91
|
// We can cast because allowedValueTypeName validated this is a string.
|
|
77
92
|
const deviceIdentifier = inputs.device_identifier.value;
|
|
93
|
+
const lcdWidth = inputs.lcd_width.value;
|
|
94
|
+
const lcdHeight = inputs.lcd_height.value;
|
|
95
|
+
const lcdDensity = inputs.lcd_density.value;
|
|
78
96
|
const shouldAdjustAnimationScale = env.ANDROID_EMULATOR_ADJUST_ANIMATION_SCALE !== 'false' &&
|
|
79
97
|
env.ANDROID_EMULATOR_ADJUST_ANIMATION_SCALE !== '0';
|
|
80
98
|
if (!shouldAdjustAnimationScale) {
|
|
@@ -107,6 +125,9 @@ function createStartAndroidEmulatorBuildFunction() {
|
|
|
107
125
|
deviceName,
|
|
108
126
|
systemImagePackage,
|
|
109
127
|
deviceIdentifier: deviceIdentifier ?? null,
|
|
128
|
+
lcdWidth: lcdWidth ?? null,
|
|
129
|
+
lcdHeight: lcdHeight ?? null,
|
|
130
|
+
lcdDensity: lcdDensity ?? null,
|
|
110
131
|
env,
|
|
111
132
|
logger,
|
|
112
133
|
});
|
|
@@ -14,6 +14,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
14
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
15
|
const semver_1 = __importDefault(require("semver"));
|
|
16
16
|
const zod_1 = require("zod");
|
|
17
|
+
const localEgressSession_1 = require("../utils/localEgressSession");
|
|
17
18
|
const AndroidEmulatorUtils_1 = require("../../utils/AndroidEmulatorUtils");
|
|
18
19
|
const IosSimulatorUtils_1 = require("../../utils/IosSimulatorUtils");
|
|
19
20
|
const retry_1 = require("../../utils/retry");
|
|
@@ -43,7 +44,7 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
|
|
|
43
44
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
44
45
|
}),
|
|
45
46
|
],
|
|
46
|
-
fn: async ({ logger, global }, { inputs, env, signal }) => {
|
|
47
|
+
fn: (0, localEgressSession_1.withLocalEgressSession)(async ({ logger, global }, { inputs, env, signal }) => {
|
|
47
48
|
const deviceRunSessionId = (0, remoteDeviceRunSession_1.getDeviceRunSessionIdOrThrow)(env);
|
|
48
49
|
const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
|
|
49
50
|
const ngrokAuthtoken = (0, remoteDeviceRunSession_1.getNgrokAuthtokenOrThrow)(env);
|
|
@@ -113,7 +114,9 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
|
|
|
113
114
|
logger,
|
|
114
115
|
timeoutMs: APPIUM_STARTUP_TIMEOUT_MS,
|
|
115
116
|
});
|
|
116
|
-
await (0,
|
|
117
|
+
await (0, localEgressSession_1.uploadRemoteSessionConfigWithLocalEgressAsync)({
|
|
118
|
+
env,
|
|
119
|
+
signal,
|
|
117
120
|
ctx,
|
|
118
121
|
deviceRunSessionId,
|
|
119
122
|
remoteConfig: {
|
|
@@ -124,6 +127,7 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
|
|
|
124
127
|
'appium:udid': device.udid,
|
|
125
128
|
},
|
|
126
129
|
webPreviewUrl: webPreview.previewUrl,
|
|
130
|
+
...(webPreview.previewToken ? { webPreviewToken: webPreview.previewToken } : {}),
|
|
127
131
|
},
|
|
128
132
|
logger,
|
|
129
133
|
});
|
|
@@ -151,7 +155,7 @@ function createStartAppiumRemoteSessionBuildFunction(ctx) {
|
|
|
151
155
|
await appiumProcess.stopAsync();
|
|
152
156
|
await node_fs_1.default.promises.rm(appiumHome, { recursive: true, force: true });
|
|
153
157
|
}
|
|
154
|
-
},
|
|
158
|
+
}),
|
|
155
159
|
});
|
|
156
160
|
}
|
|
157
161
|
function resolveAppium3VersionSpec(packageVersion) {
|
|
@@ -16,6 +16,7 @@ const node_os_1 = __importDefault(require("node:os"));
|
|
|
16
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
17
17
|
const semver_1 = __importDefault(require("semver"));
|
|
18
18
|
const zod_1 = require("zod");
|
|
19
|
+
const localEgressSession_1 = require("../utils/localEgressSession");
|
|
19
20
|
const sentry_1 = require("../../sentry");
|
|
20
21
|
const processes_1 = require("../../utils/processes");
|
|
21
22
|
const retry_1 = require("../../utils/retry");
|
|
@@ -63,7 +64,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
|
|
|
63
64
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
64
65
|
}),
|
|
65
66
|
],
|
|
66
|
-
fn: async ({ logger, global }, { inputs, env, signal }) => {
|
|
67
|
+
fn: (0, localEgressSession_1.withLocalEgressSession)(async ({ logger, global }, { inputs, env, signal }) => {
|
|
67
68
|
// Fail fast before any expensive setup if the injected env
|
|
68
69
|
// vars are missing: DEVICE_RUN_SESSION_ID (to report the remote config
|
|
69
70
|
// back to the API server), EAS_SIMULATOR_NGROK_TUNNEL_DOMAIN (base domain
|
|
@@ -82,21 +83,28 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
|
|
|
82
83
|
if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
|
|
83
84
|
await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
|
|
84
85
|
}
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
// the first moments
|
|
89
|
-
|
|
86
|
+
// Start the potentially slow installation while Argent is being prepared.
|
|
87
|
+
// On Linux expo-device-hub calls this again and awaits the same in-flight
|
|
88
|
+
// setup before launching. On macOS this remains non-blocking, so only a
|
|
89
|
+
// recording started in the first moments may miss ffmpeg.
|
|
90
|
+
// Never rejects, so `void` is safe.
|
|
91
|
+
void (0, remoteDeviceRunSession_1.ensureFfmpegInstalledOnceAsync)({ runtimePlatform, env, logger });
|
|
90
92
|
logger.info('Enabling the Argent artifacts list endpoint flag.');
|
|
91
|
-
await (0, turtle_spawn_1.default)('
|
|
93
|
+
await (0, turtle_spawn_1.default)('bun', [
|
|
94
|
+
'x',
|
|
95
|
+
`${ARGENT_PACKAGE_NAME}@${versionSpec}`,
|
|
96
|
+
'enable',
|
|
97
|
+
ARGENT_ARTIFACTS_LIST_ENDPOINT_FLAG,
|
|
98
|
+
], { env, logger });
|
|
92
99
|
logger.info('Enabling the Argent tool-server event log flag.');
|
|
93
|
-
await (0, turtle_spawn_1.default)('
|
|
94
|
-
logger.info(`Launching ${ARGENT_PACKAGE_NAME}@${versionSpec} tool-server via
|
|
95
|
-
// Keep Argent itself in foreground mode under the detached
|
|
96
|
-
// the
|
|
100
|
+
await (0, turtle_spawn_1.default)('bun', ['x', `${ARGENT_PACKAGE_NAME}@${versionSpec}`, 'enable', ARGENT_EVENT_LOG_FLAG], { env, logger });
|
|
101
|
+
logger.info(`Launching ${ARGENT_PACKAGE_NAME}@${versionSpec} tool-server via bun x.`);
|
|
102
|
+
// Keep Argent itself in foreground mode under the detached bun process. This preserves
|
|
103
|
+
// the bun -> Argent CLI -> tool-server ancestry used to identify the matching state file.
|
|
97
104
|
const argentServer = (0, remoteDeviceRunSession_1.spawnDetached)({
|
|
98
|
-
command: '
|
|
105
|
+
command: 'bun',
|
|
99
106
|
args: [
|
|
107
|
+
'x',
|
|
100
108
|
`${ARGENT_PACKAGE_NAME}@${versionSpec}`,
|
|
101
109
|
'server',
|
|
102
110
|
'start',
|
|
@@ -166,13 +174,16 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
|
|
|
166
174
|
timeoutMs: STARTUP_TIMEOUT_MS,
|
|
167
175
|
});
|
|
168
176
|
logger.info(`Web preview URL: ${webPreview.previewUrl}`);
|
|
169
|
-
await (0,
|
|
177
|
+
await (0, localEgressSession_1.uploadRemoteSessionConfigWithLocalEgressAsync)({
|
|
178
|
+
env,
|
|
179
|
+
signal,
|
|
170
180
|
ctx,
|
|
171
181
|
deviceRunSessionId,
|
|
172
182
|
remoteConfig: {
|
|
173
183
|
toolsUrl: publicToolsUrl,
|
|
174
184
|
...(toolServerToken ? { toolsAuthToken: toolServerToken } : {}),
|
|
175
185
|
webPreviewUrl: webPreview.previewUrl,
|
|
186
|
+
...(webPreview.previewToken ? { webPreviewToken: webPreview.previewToken } : {}),
|
|
176
187
|
},
|
|
177
188
|
logger,
|
|
178
189
|
});
|
|
@@ -209,7 +220,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
|
|
|
209
220
|
}
|
|
210
221
|
await argentServer.stopAsync();
|
|
211
222
|
}
|
|
212
|
-
},
|
|
223
|
+
}),
|
|
213
224
|
});
|
|
214
225
|
}
|
|
215
226
|
async function stopArgentEventCollectionSafelyAsync({ eventCollection, deviceRunSessionId, logger, }) {
|
|
@@ -234,7 +245,7 @@ function warnIfArgentPackageVersionCannotBeVerified({ packageVersion, logger, })
|
|
|
234
245
|
if (!validVersion) {
|
|
235
246
|
logger.warn(`Argent remote simulator sessions require ${ARGENT_PACKAGE_NAME}@${exports.MIN_ARGENT_REMOTE_SESSION_VERSION} or newer, ` +
|
|
236
247
|
`but package_version "${packageVersion}" is not an exact semver version that EAS can verify. ` +
|
|
237
|
-
`Continuing and letting
|
|
248
|
+
`Continuing and letting bun x resolve it.`);
|
|
238
249
|
return;
|
|
239
250
|
}
|
|
240
251
|
if (semver_1.default.lt(validVersion, exports.MIN_ARGENT_REMOTE_SESSION_VERSION)) {
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BuildFunction } from '@expo/steps';
|
|
2
|
+
/**
|
|
3
|
+
* Points the device host's system HTTP(S) proxy at the EAS CLI's egress client.
|
|
4
|
+
* Must run before `eas/start_ios_simulator`: the simulator reads the system proxy
|
|
5
|
+
* at boot. Nothing else on the host changes, so requests from libraries that
|
|
6
|
+
* bypass the system proxy are not covered; the shared session monitor reports
|
|
7
|
+
* them. The shared session cleanup releases the resources started here when
|
|
8
|
+
* the session ends, with a job finalizer as a fallback.
|
|
9
|
+
*/
|
|
10
|
+
export declare function createStartLocalEgressBuildFunction(): BuildFunction;
|
|
@@ -0,0 +1,156 @@
|
|
|
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.createStartLocalEgressBuildFunction = createStartLocalEgressBuildFunction;
|
|
7
|
+
const steps_1 = require("@expo/steps");
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const localEgress_1 = require("../utils/localEgress");
|
|
12
|
+
const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
|
|
13
|
+
/**
|
|
14
|
+
* The downloader and ngrok SDK do not accept an AbortSignal. Stop awaiting them
|
|
15
|
+
* on cancellation, while retaining handlers that dispose any eventual result.
|
|
16
|
+
*/
|
|
17
|
+
function awaitLocalEgressAcquisitionAsync(pending, signal, disposeAbandonedAsync, logger) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
let abandoned = false;
|
|
20
|
+
const abort = () => {
|
|
21
|
+
signal.removeEventListener('abort', abort);
|
|
22
|
+
abandoned = true;
|
|
23
|
+
reject(signal.reason);
|
|
24
|
+
};
|
|
25
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
26
|
+
if (signal.aborted) {
|
|
27
|
+
abort();
|
|
28
|
+
}
|
|
29
|
+
void pending
|
|
30
|
+
.then(async (result) => {
|
|
31
|
+
signal.removeEventListener('abort', abort);
|
|
32
|
+
if (abandoned) {
|
|
33
|
+
await disposeAbandonedAsync(result);
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
resolve(result);
|
|
37
|
+
}
|
|
38
|
+
}, async (error) => {
|
|
39
|
+
signal.removeEventListener('abort', abort);
|
|
40
|
+
if (abandoned) {
|
|
41
|
+
await disposeAbandonedAsync();
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
reject(error);
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
.catch(err => logger.warn({ err }, 'Could not release a cancelled local egress acquisition.'));
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Points the device host's system HTTP(S) proxy at the EAS CLI's egress client.
|
|
52
|
+
* Must run before `eas/start_ios_simulator`: the simulator reads the system proxy
|
|
53
|
+
* at boot. Nothing else on the host changes, so requests from libraries that
|
|
54
|
+
* bypass the system proxy are not covered; the shared session monitor reports
|
|
55
|
+
* them. The shared session cleanup releases the resources started here when
|
|
56
|
+
* the session ends, with a job finalizer as a fallback.
|
|
57
|
+
*/
|
|
58
|
+
function createStartLocalEgressBuildFunction() {
|
|
59
|
+
return new steps_1.BuildFunction({
|
|
60
|
+
namespace: 'eas',
|
|
61
|
+
id: 'start_local_egress',
|
|
62
|
+
name: 'Start local egress',
|
|
63
|
+
__metricsId: 'eas/start_local_egress',
|
|
64
|
+
supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
|
|
65
|
+
fn: async ({ logger }, { env, signal }) => {
|
|
66
|
+
const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
|
|
67
|
+
const ngrokAuthtoken = (0, remoteDeviceRunSession_1.getNgrokAuthtokenOrThrow)(env);
|
|
68
|
+
let workDir;
|
|
69
|
+
let server;
|
|
70
|
+
let tunnel;
|
|
71
|
+
let finishSetup;
|
|
72
|
+
const setupFinished = new Promise(resolve => {
|
|
73
|
+
finishSetup = resolve;
|
|
74
|
+
});
|
|
75
|
+
// Register before acquisition. Cancellation ends setup promptly; pending
|
|
76
|
+
// SDK calls retain their own late-result disposal handlers.
|
|
77
|
+
const lifetimeSignal = (0, localEgress_1.registerLocalEgressResources)(async () => {
|
|
78
|
+
await setupFinished;
|
|
79
|
+
const results = await Promise.allSettled([
|
|
80
|
+
Promise.resolve().then(() => tunnel?.stopAsync()),
|
|
81
|
+
Promise.resolve().then(() => server?.stopAsync()),
|
|
82
|
+
]);
|
|
83
|
+
for (const result of results) {
|
|
84
|
+
if (result.status === 'rejected') {
|
|
85
|
+
logger.warn({ err: result.reason }, 'Could not stop a local egress resource.');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await Promise.all([
|
|
89
|
+
node_fs_1.default.promises.rm(localEgress_1.LOCAL_EGRESS_HANDOFF_PATH, { force: true }),
|
|
90
|
+
workDir ? node_fs_1.default.promises.rm(workDir, { recursive: true, force: true }) : undefined,
|
|
91
|
+
]);
|
|
92
|
+
});
|
|
93
|
+
const startupSignal = signal ? AbortSignal.any([signal, lifetimeSignal]) : lifetimeSignal;
|
|
94
|
+
try {
|
|
95
|
+
startupSignal.throwIfAborted();
|
|
96
|
+
workDir = await node_fs_1.default.promises.mkdtemp(node_path_1.default.join(node_os_1.default.tmpdir(), 'eas-local-egress-'));
|
|
97
|
+
startupSignal.throwIfAborted();
|
|
98
|
+
logger.info(`Downloading the reverse tunnel server (chisel ${localEgress_1.CHISEL_VERSION}).`);
|
|
99
|
+
const downloadDir = workDir;
|
|
100
|
+
const chiselPath = await awaitLocalEgressAcquisitionAsync((0, localEgress_1.downloadChiselAsync)({ destinationDir: downloadDir, logger }), startupSignal, async () => await node_fs_1.default.promises.rm(downloadDir, { recursive: true, force: true }), logger);
|
|
101
|
+
startupSignal.throwIfAborted();
|
|
102
|
+
const credentials = (0, localEgress_1.generateEgressCredentials)();
|
|
103
|
+
const authfilePath = node_path_1.default.join(workDir, 'authfile.json');
|
|
104
|
+
await node_fs_1.default.promises.writeFile(authfilePath, (0, localEgress_1.createChiselAuthfileContents)({ ...credentials, port: localEgress_1.LOCAL_EGRESS_PROXY_PORT }), { encoding: 'utf8', mode: 0o600 });
|
|
105
|
+
startupSignal.throwIfAborted();
|
|
106
|
+
const controlPort = await (0, remoteDeviceRunSession_1.findAvailablePortAsync)();
|
|
107
|
+
startupSignal.throwIfAborted();
|
|
108
|
+
logger.info(`Starting the reverse tunnel server on 127.0.0.1:${controlPort}.`);
|
|
109
|
+
const started = await (0, localEgress_1.startChiselServerAsync)({
|
|
110
|
+
chiselPath,
|
|
111
|
+
controlPort,
|
|
112
|
+
authfilePath,
|
|
113
|
+
env,
|
|
114
|
+
signal: startupSignal,
|
|
115
|
+
});
|
|
116
|
+
server = started.process;
|
|
117
|
+
startupSignal.throwIfAborted();
|
|
118
|
+
tunnel = await awaitLocalEgressAcquisitionAsync((0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
|
|
119
|
+
port: controlPort,
|
|
120
|
+
subdomainPrefix: 'egress',
|
|
121
|
+
baseDomain: ngrokTunnelDomain,
|
|
122
|
+
authtoken: ngrokAuthtoken,
|
|
123
|
+
logger,
|
|
124
|
+
}), startupSignal, async (listener) => await listener?.stopAsync(), logger);
|
|
125
|
+
startupSignal.throwIfAborted();
|
|
126
|
+
const { service } = await (0, localEgress_1.configureSystemProxyAsync)({
|
|
127
|
+
env,
|
|
128
|
+
logger,
|
|
129
|
+
port: localEgress_1.LOCAL_EGRESS_PROXY_PORT,
|
|
130
|
+
signal: startupSignal,
|
|
131
|
+
});
|
|
132
|
+
startupSignal.throwIfAborted();
|
|
133
|
+
await (0, localEgress_1.writeLocalEgressHandoffAsync)({
|
|
134
|
+
url: tunnel.url,
|
|
135
|
+
token: credentials.password,
|
|
136
|
+
fingerprint: started.fingerprint,
|
|
137
|
+
port: localEgress_1.LOCAL_EGRESS_PROXY_PORT,
|
|
138
|
+
});
|
|
139
|
+
startupSignal.throwIfAborted();
|
|
140
|
+
logger.info(`Local egress is configured on network service "${service}". HTTP(S) and WebSocket ` +
|
|
141
|
+
'requests that honor the system proxy (WebKit, URLSession and other CFNetwork clients) ' +
|
|
142
|
+
'fail until the EAS CLI egress client connects, then exit from that machine. Requests ' +
|
|
143
|
+
'from libraries that bypass the system proxy are not covered and exit from this worker. ' +
|
|
144
|
+
'Connection sampling may report these bypasses, but can miss short connections and unconnected UDP traffic.');
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
finishSetup();
|
|
148
|
+
await (0, localEgress_1.stopLocalEgressResourcesAsync)(logger);
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
finishSetup();
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.createStartWebPreviewRemoteSessionBuildFunction = createStartWebPreviewRemoteSessionBuildFunction;
|
|
4
4
|
const steps_1 = require("@expo/steps");
|
|
5
|
+
const localEgressSession_1 = require("../utils/localEgressSession");
|
|
5
6
|
const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
|
|
6
7
|
const STARTUP_TIMEOUT_MS = 60_000;
|
|
7
8
|
function createStartWebPreviewRemoteSessionBuildFunction(ctx) {
|
|
@@ -22,7 +23,7 @@ function createStartWebPreviewRemoteSessionBuildFunction(ctx) {
|
|
|
22
23
|
allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
|
|
23
24
|
}),
|
|
24
25
|
],
|
|
25
|
-
fn: async ({ logger, global }, { inputs, env, signal }) => {
|
|
26
|
+
fn: (0, localEgressSession_1.withLocalEgressSession)(async ({ logger, global }, { inputs, env, signal }) => {
|
|
26
27
|
const deviceRunSessionId = (0, remoteDeviceRunSession_1.getDeviceRunSessionIdOrThrow)(env);
|
|
27
28
|
const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
|
|
28
29
|
const maxDurationSeconds = inputs.max_duration_seconds?.value;
|
|
@@ -42,10 +43,15 @@ function createStartWebPreviewRemoteSessionBuildFunction(ctx) {
|
|
|
42
43
|
});
|
|
43
44
|
logger.info(`Preview URL: ${webPreview.previewUrl}`);
|
|
44
45
|
try {
|
|
45
|
-
await (0,
|
|
46
|
+
await (0, localEgressSession_1.uploadRemoteSessionConfigWithLocalEgressAsync)({
|
|
47
|
+
env,
|
|
48
|
+
signal,
|
|
46
49
|
ctx,
|
|
47
50
|
deviceRunSessionId,
|
|
48
|
-
remoteConfig: {
|
|
51
|
+
remoteConfig: {
|
|
52
|
+
previewUrl: webPreview.previewUrl,
|
|
53
|
+
...(webPreview.previewToken ? { previewToken: webPreview.previewToken } : {}),
|
|
54
|
+
},
|
|
49
55
|
logger,
|
|
50
56
|
});
|
|
51
57
|
await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
|
|
@@ -59,6 +65,6 @@ function createStartWebPreviewRemoteSessionBuildFunction(ctx) {
|
|
|
59
65
|
finally {
|
|
60
66
|
await webPreview.stopAsync();
|
|
61
67
|
}
|
|
62
|
-
},
|
|
68
|
+
}),
|
|
63
69
|
});
|
|
64
70
|
}
|
|
@@ -124,18 +124,24 @@ function createUploadToAscBuildFunction() {
|
|
|
124
124
|
const ascApiKeyJson = await fs_extra_1.default.readJson(ascApiKeyPath);
|
|
125
125
|
const ascApiKey = zod_1.z
|
|
126
126
|
.object({
|
|
127
|
-
issuer_id: zod_1.z.string(),
|
|
127
|
+
issuer_id: zod_1.z.string().nullish(),
|
|
128
128
|
key_id: zod_1.z.string(),
|
|
129
129
|
key: zod_1.z.string(),
|
|
130
130
|
})
|
|
131
131
|
.parse(ascApiKeyJson);
|
|
132
132
|
const privateKey = await jose.importPKCS8(ascApiKey.key, 'ES256');
|
|
133
|
-
const
|
|
133
|
+
const jwt = new jose.SignJWT({})
|
|
134
134
|
.setProtectedHeader({ alg: 'ES256', kid: ascApiKey.key_id })
|
|
135
|
-
.setIssuer(ascApiKey.issuer_id)
|
|
136
135
|
.setAudience('appstoreconnect-v1')
|
|
137
|
-
.setExpirationTime('20m')
|
|
138
|
-
|
|
136
|
+
.setExpirationTime('20m');
|
|
137
|
+
if (ascApiKey.issuer_id) {
|
|
138
|
+
jwt.setIssuer(ascApiKey.issuer_id);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
// Nullish issuer_id means an individual API key
|
|
142
|
+
jwt.setSubject('user');
|
|
143
|
+
}
|
|
144
|
+
const token = await jwt.sign(privateKey);
|
|
139
145
|
const client = new AscApiClient_1.AscApiClient({ token, logger: stepsCtx.logger });
|
|
140
146
|
stepsCtx.logger.info(`Reading App information for Apple app identifier: ${appleAppIdentifier}...`);
|
|
141
147
|
const appResponse = await AscApiUtils_1.AscApiUtils.getAppInfoAsync({ client, appleAppIdentifier });
|
|
@@ -350,6 +350,7 @@ exports.APPIUM_COMMAND_SUMMARIES = {
|
|
|
350
350
|
mobileSimctl: 'Ran a simctl command',
|
|
351
351
|
mobileSiriCommand: 'Sent a Siri command',
|
|
352
352
|
mobileStartActivity: 'Started an activity',
|
|
353
|
+
mobileStartAutomationSession: 'Started an automation session',
|
|
353
354
|
mobileStartLogsBroadcast: 'Started log streaming',
|
|
354
355
|
mobileStartMediaProjectionRecording: 'Started media projection recording',
|
|
355
356
|
mobileStartNetworkMonitor: 'Started network monitoring',
|
|
@@ -359,6 +360,7 @@ exports.APPIUM_COMMAND_SUMMARIES = {
|
|
|
359
360
|
mobileStartService: 'Started a service',
|
|
360
361
|
mobileStartSystemMonitor: 'Started system monitoring',
|
|
361
362
|
mobileStartXctestScreenRecording: 'Started XCTest screen recording',
|
|
363
|
+
mobileStopAutomationSession: 'Stopped an automation session',
|
|
362
364
|
mobileStopLogsBroadcast: 'Stopped log streaming',
|
|
363
365
|
mobileStopMediaProjectionRecording: 'Stopped media projection recording',
|
|
364
366
|
mobileStopNetworkMonitor: 'Stopped network monitoring',
|
|
@@ -380,6 +382,7 @@ exports.APPIUM_COMMAND_SUMMARIES = {
|
|
|
380
382
|
mobileUnscheduleAction: 'Canceled a scheduled action',
|
|
381
383
|
mobileUpdateSafariPreferences: 'Updated Safari preferences',
|
|
382
384
|
mobileViewPortRect: 'Read the viewport size',
|
|
385
|
+
mobileViewportElementRect: 'Read an element position and size in the viewport',
|
|
383
386
|
mobileViewportScreenshot: 'Took a viewport screenshot',
|
|
384
387
|
mobileVoiceOverCurrentSpeech: 'Read current VoiceOver speech',
|
|
385
388
|
mobileVoiceOverMove: 'Moved the VoiceOver cursor',
|