@expo/build-tools 20.1.0 → 20.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/dist/builders/custom.js +23 -0
  2. package/dist/common/installDependencies.d.ts +10 -1
  3. package/dist/common/installDependencies.js +95 -1
  4. package/dist/common/prebuild.js +2 -3
  5. package/dist/{ios → common}/xcpretty.d.ts +1 -0
  6. package/dist/{ios → common}/xcpretty.js +18 -3
  7. package/dist/datadog.js +7 -3
  8. package/dist/index.d.ts +1 -0
  9. package/dist/index.js +1 -0
  10. package/dist/ios/fastlane.js +1 -1
  11. package/dist/ios/pod.js +1 -1
  12. package/dist/runtimeSettings.d.ts +12 -0
  13. package/dist/runtimeSettings.js +120 -0
  14. package/dist/steps/easFunctions.js +1 -1
  15. package/dist/steps/functions/downloadBuild.d.ts +5 -3
  16. package/dist/steps/functions/downloadBuild.js +34 -4
  17. package/dist/steps/functions/findAndUploadBuildArtifacts.js +2 -2
  18. package/dist/steps/functions/installMaestro.js +13 -2
  19. package/dist/steps/functions/maestroResultParser.d.ts +18 -0
  20. package/dist/steps/functions/maestroResultParser.js +132 -3
  21. package/dist/steps/functions/maestroTests.js +26 -13
  22. package/dist/steps/functions/repack.d.ts +3 -1
  23. package/dist/steps/functions/repack.js +15 -1
  24. package/dist/steps/functions/reportMaestroTestResults.js +39 -20
  25. package/dist/steps/functions/restoreBuildCache.js +9 -6
  26. package/dist/steps/functions/startAgentDeviceRemoteSession.d.ts +1 -1
  27. package/dist/steps/functions/startAgentDeviceRemoteSession.js +101 -22
  28. package/dist/steps/functions/startArgentRemoteSession.d.ts +5 -0
  29. package/dist/steps/functions/startArgentRemoteSession.js +60 -19
  30. package/dist/steps/functions/startServeSimRemoteSession.js +1 -1
  31. package/dist/steps/functions/uploadArtifact.js +1 -1
  32. package/dist/steps/utils/ios/fastlane.js +1 -1
  33. package/dist/steps/utils/remoteDeviceRunSession.d.ts +31 -2
  34. package/dist/steps/utils/remoteDeviceRunSession.js +82 -3
  35. package/package.json +3 -3
  36. package/dist/steps/utils/ios/xcpretty.d.ts +0 -15
  37. package/dist/steps/utils/ios/xcpretty.js +0 -92
@@ -4,19 +4,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.createStartArgentRemoteSessionBuildFunction = createStartArgentRemoteSessionBuildFunction;
7
+ exports.warnIfArgentPackageVersionCannotBeVerified = warnIfArgentPackageVersionCannotBeVerified;
7
8
  const eas_build_job_1 = require("@expo/eas-build-job");
8
9
  const steps_1 = require("@expo/steps");
9
10
  const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
10
11
  const node_fs_1 = __importDefault(require("node:fs"));
11
12
  const node_os_1 = __importDefault(require("node:os"));
12
13
  const node_path_1 = __importDefault(require("node:path"));
14
+ const semver_1 = __importDefault(require("semver"));
13
15
  const zod_1 = require("zod");
14
16
  const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
15
17
  const ARGENT_PACKAGE_NAME = '@swmansion/argent';
18
+ const MIN_ARGENT_REMOTE_SESSION_VERSION = '0.11.0';
16
19
  const ARGENT_STATE_FILE = node_path_1.default.join(node_os_1.default.homedir(), '.argent', 'tool-server.json');
17
20
  const XCODE_DEVELOPER_DIR = '/Applications/Xcode.app/Contents/Developer';
18
21
  const STARTUP_TIMEOUT_MS = 60_000;
19
- const ArgentToolServerStateSchema = zod_1.z.object({ port: zod_1.z.number() });
22
+ const ArgentToolServerStateSchema = zod_1.z.object({
23
+ port: zod_1.z.number(),
24
+ token: zod_1.z.string().optional(),
25
+ });
20
26
  function createStartArgentRemoteSessionBuildFunction(ctx) {
21
27
  return new steps_1.BuildFunction({
22
28
  namespace: 'eas',
@@ -39,6 +45,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
39
45
  const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
40
46
  const ngrokAuthtoken = (0, remoteDeviceRunSession_1.getNgrokAuthtokenOrThrow)(env);
41
47
  const packageVersion = inputs.package_version.value;
48
+ warnIfArgentPackageVersionCannotBeVerified({ packageVersion, logger });
42
49
  const versionSpec = packageVersion ?? 'latest';
43
50
  const { runtimePlatform } = global;
44
51
  logger.info(`Starting argent remote session (version: ${versionSpec}, runtime: ${runtimePlatform}).`);
@@ -48,36 +55,52 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
48
55
  }
49
56
  // Stale state from a previous run would mask the new server's port.
50
57
  await node_fs_1.default.promises.rm(ARGENT_STATE_FILE, { force: true });
51
- logger.info(`Launching ${ARGENT_PACKAGE_NAME}@${versionSpec} via bunx.`);
52
- // `argent mcp` is the public entry that triggers @argent/tools-client
53
- // to spawn the tool-server detached + unref'd, so the tool-server
54
- // outlives this MCP process. ARGENT_IDLE_TIMEOUT_MINUTES=0 disables the
55
- // 30-min idle shutdown that would otherwise tear the tunnel down.
56
- (0, remoteDeviceRunSession_1.spawnDetached)({
58
+ logger.info(`Launching ${ARGENT_PACKAGE_NAME}@${versionSpec} tool-server via bunx.`);
59
+ const argentServer = (0, remoteDeviceRunSession_1.spawnDetached)({
57
60
  command: 'bunx',
58
- args: [`${ARGENT_PACKAGE_NAME}@${versionSpec}`, 'mcp'],
59
- env: { ...env, ARGENT_IDLE_TIMEOUT_MINUTES: '0' },
61
+ args: [
62
+ `${ARGENT_PACKAGE_NAME}@${versionSpec}`,
63
+ 'server',
64
+ 'start',
65
+ '--port',
66
+ '0',
67
+ '--idle-timeout',
68
+ '0',
69
+ '--detach',
70
+ ],
71
+ env,
60
72
  });
61
73
  logger.info(`Waiting for argent tool-server state at ${ARGENT_STATE_FILE}.`);
62
- const { port: toolServerPort } = await (0, remoteDeviceRunSession_1.waitForFileAsync)({
63
- filePath: ARGENT_STATE_FILE,
64
- timeoutMs: STARTUP_TIMEOUT_MS,
65
- description: 'argent tool-server state',
66
- parse: parseArgentToolServerState,
67
- });
74
+ let toolServerPort;
75
+ let toolServerToken;
76
+ try {
77
+ const toolServerState = await (0, remoteDeviceRunSession_1.waitForFileAsync)({
78
+ filePath: ARGENT_STATE_FILE,
79
+ timeoutMs: STARTUP_TIMEOUT_MS,
80
+ description: 'argent tool-server state',
81
+ parse: parseArgentToolServerState,
82
+ });
83
+ toolServerPort = toolServerState.port;
84
+ toolServerToken = toolServerState.token;
85
+ }
86
+ catch (err) {
87
+ const output = argentServer.getOutput();
88
+ throw new eas_build_job_1.SystemError(`${err instanceof Error ? err.message : `Timed out waiting for argent tool-server state.`}${output ? `\nArgent tool-server output:\n${output}` : ''}`);
89
+ }
68
90
  logger.info(`Argent tool-server is listening on port ${toolServerPort}.`);
69
- const toolsUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
91
+ const publicToolsUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
70
92
  port: toolServerPort,
71
93
  subdomainPrefix: 'argent',
72
94
  baseDomain: ngrokTunnelDomain,
73
95
  authtoken: ngrokAuthtoken,
96
+ rewriteHostHeader: true,
74
97
  logger,
75
98
  });
76
- logger.info(`Tunnel is ready at ${toolsUrl}.`);
99
+ logger.info(`Tunnel is ready at ${publicToolsUrl}.`);
77
100
  // serve-sim is iOS-only — Android sessions go without a preview URL.
78
101
  let webPreviewUrl;
79
102
  if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
80
- const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)({
103
+ const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
81
104
  baseDomain: ngrokTunnelDomain,
82
105
  env,
83
106
  logger,
@@ -90,7 +113,8 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
90
113
  ctx,
91
114
  deviceRunSessionId,
92
115
  remoteConfig: {
93
- toolsUrl,
116
+ toolsUrl: publicToolsUrl,
117
+ ...(toolServerToken ? { toolsAuthToken: toolServerToken } : {}),
94
118
  ...(webPreviewUrl ? { webPreviewUrl } : {}),
95
119
  },
96
120
  logger,
@@ -102,6 +126,23 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
102
126
  },
103
127
  });
104
128
  }
129
+ function warnIfArgentPackageVersionCannotBeVerified({ packageVersion, logger, }) {
130
+ if (!packageVersion || packageVersion === 'latest') {
131
+ return;
132
+ }
133
+ const validVersion = semver_1.default.valid(packageVersion);
134
+ if (!validVersion) {
135
+ logger.warn(`Argent remote simulator sessions require ${ARGENT_PACKAGE_NAME}@${MIN_ARGENT_REMOTE_SESSION_VERSION} or newer, ` +
136
+ `but package_version "${packageVersion}" is not an exact semver version that EAS can verify. ` +
137
+ `Continuing and letting bunx resolve it.`);
138
+ return;
139
+ }
140
+ if (semver_1.default.lt(validVersion, MIN_ARGENT_REMOTE_SESSION_VERSION)) {
141
+ throw new eas_build_job_1.SystemError(`Argent remote simulator sessions require ${ARGENT_PACKAGE_NAME}@${MIN_ARGENT_REMOTE_SESSION_VERSION} or newer. ` +
142
+ `The requested package_version "${packageVersion}" is too old for the EAS remote-session API. ` +
143
+ `Use "latest" or pass an exact version >= ${MIN_ARGENT_REMOTE_SESSION_VERSION}.`);
144
+ }
145
+ }
105
146
  function parseArgentToolServerState(raw) {
106
147
  const result = ArgentToolServerStateSchema.safeParse(JSON.parse(raw));
107
148
  if (!result.success) {
@@ -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.`, 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;
@@ -28,11 +55,12 @@ export declare function startServeSimWithTunnelAsync({ baseDomain, env, logger,
28
55
  previewUrl: string;
29
56
  streamUrl: string;
30
57
  }>;
31
- export declare function startNgrokTunnelAsync({ port, subdomainPrefix, baseDomain, authtoken, logger, }: {
58
+ export declare function startNgrokTunnelAsync({ port, subdomainPrefix, baseDomain, authtoken, rewriteHostHeader, logger, }: {
32
59
  port: number;
33
60
  subdomainPrefix: string;
34
61
  baseDomain: string;
35
62
  authtoken: string;
63
+ rewriteHostHeader?: boolean;
36
64
  logger: bunyan;
37
65
  }): Promise<string>;
38
66
  export declare function waitForFileAsync<T>({ filePath, timeoutMs, description, parse, }: {
@@ -41,3 +69,4 @@ export declare function waitForFileAsync<T>({ filePath, timeoutMs, description,
41
69
  description: string;
42
70
  parse: (raw: string) => T;
43
71
  }): Promise<T>;
72
+ 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: [
@@ -136,6 +209,7 @@ async function startServeSimWithTunnelAsync({ baseDomain, env, logger, timeoutMs
136
209
  '0.55',
137
210
  '--codec',
138
211
  'webrtc',
212
+ ...turnArgs,
139
213
  ],
140
214
  env,
141
215
  });
@@ -157,12 +231,17 @@ function matchLabeledUrl({ output, label, baseDomain, }) {
157
231
  const match = labelPattern.exec(output);
158
232
  return match ? match[1] : null;
159
233
  }
160
- async function startNgrokTunnelAsync({ port, subdomainPrefix, baseDomain, authtoken, logger, }) {
234
+ async function startNgrokTunnelAsync({ port, subdomainPrefix, baseDomain, authtoken, rewriteHostHeader, logger, }) {
161
235
  const domain = `${subdomainPrefix}-${(0, node_crypto_1.randomBytes)(8).toString('hex')}.${baseDomain}`;
162
236
  logger.info(`Starting ngrok tunnel ${domain} -> http://localhost:${port}.`);
163
237
  // Run the ngrok agent in-process via the SDK; it keeps the session alive until
164
238
  // the process exits, and the step blocks forever to hold it open.
165
- const listener = await ngrok.forward({ addr: port, authtoken, domain });
239
+ const listener = await ngrok.forward({
240
+ addr: port,
241
+ authtoken,
242
+ domain,
243
+ ...(rewriteHostHeader ? { request_header_add: [`Host:localhost:${port}`] } : {}),
244
+ });
166
245
  const url = listener.url();
167
246
  if (!url) {
168
247
  throw new eas_build_job_1.SystemError(`ngrok tunnel for ${domain} did not return a public URL.`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expo/build-tools",
3
- "version": "20.1.0",
3
+ "version": "20.3.0",
4
4
  "bugs": "https://github.com/expo/eas-cli/issues",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Expo <support@expo.io>",
@@ -77,7 +77,7 @@
77
77
  "zod": "^4.3.5"
78
78
  },
79
79
  "devDependencies": {
80
- "@expo/repack-app": "~0.4.1",
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": "33225d2b73a34db5cbdfeaa537aedb6c6b4a84af"
103
+ "gitHead": "490457976c996d06447e6442fbd1aec1ace09f1b"
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
- }
@@ -1,92 +0,0 @@
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.XcodeBuildLogger = void 0;
7
- const spawn_async_1 = __importDefault(require("@expo/spawn-async"));
8
- const xcpretty_1 = require("@expo/xcpretty");
9
- const assert_1 = __importDefault(require("assert"));
10
- const fast_glob_1 = __importDefault(require("fast-glob"));
11
- const fs_extra_1 = __importDefault(require("fs-extra"));
12
- const path_1 = __importDefault(require("path"));
13
- const CHECK_FILE_INTERVAL_MS = 1000;
14
- class XcodeBuildLogger {
15
- logger;
16
- projectRoot;
17
- loggerError;
18
- flushing = false;
19
- logReaderPromise;
20
- logsPath;
21
- constructor(logger, projectRoot) {
22
- this.logger = logger;
23
- this.projectRoot = projectRoot;
24
- }
25
- async watchLogFiles(logsDirectory) {
26
- while (!this.flushing) {
27
- const logsFilename = await this.getBuildLogFilename(logsDirectory);
28
- if (logsFilename) {
29
- this.logsPath = path_1.default.join(logsDirectory, logsFilename);
30
- void this.startBuildLogger(this.logsPath);
31
- return;
32
- }
33
- await new Promise(res => setTimeout(res, CHECK_FILE_INTERVAL_MS));
34
- }
35
- }
36
- async flush() {
37
- this.flushing = true;
38
- if (this.loggerError) {
39
- throw this.loggerError;
40
- }
41
- if (this.logReaderPromise) {
42
- this.logReaderPromise.child.kill('SIGINT');
43
- try {
44
- await this.logReaderPromise;
45
- }
46
- catch { }
47
- }
48
- if (this.logsPath) {
49
- await this.findBundlerErrors(this.logsPath);
50
- }
51
- }
52
- async getBuildLogFilename(logsDirectory) {
53
- const paths = await (0, fast_glob_1.default)('*.log', { cwd: logsDirectory });
54
- return paths.length >= 1 ? paths[0] : undefined;
55
- }
56
- async startBuildLogger(logsPath) {
57
- try {
58
- const formatter = xcpretty_1.ExpoRunFormatter.create(this.projectRoot, {
59
- // TODO: Can provide xcode project name for better parsing
60
- isDebug: false,
61
- });
62
- this.logReaderPromise = (0, spawn_async_1.default)('tail', ['-n', '+0', '-f', logsPath], { stdio: 'pipe' });
63
- (0, assert_1.default)(this.logReaderPromise.child.stdout, 'stdout is not available');
64
- this.logReaderPromise.child.stdout.on('data', (data) => {
65
- const lines = formatter.pipe(data.toString());
66
- for (const line of lines) {
67
- this.logger.info(line);
68
- }
69
- });
70
- await this.logReaderPromise;
71
- this.logger.info(formatter.getBuildSummary());
72
- }
73
- catch (err) {
74
- if (!this.flushing) {
75
- this.loggerError = err;
76
- }
77
- }
78
- }
79
- async findBundlerErrors(logsPath) {
80
- try {
81
- const logFile = await fs_extra_1.default.readFile(logsPath, 'utf-8');
82
- const match = logFile.match(/Welcome to Metro!\s* Fast - Scalable - Integrated\s*([\s\S]*)Run CLI with --verbose flag for more details.\nCommand PhaseScriptExecution failed with a nonzero exit code/);
83
- if (match) {
84
- this.logger.info(match[1]);
85
- }
86
- }
87
- catch (err) {
88
- this.logger.error({ err }, 'Failed to read Xcode logs');
89
- }
90
- }
91
- }
92
- exports.XcodeBuildLogger = XcodeBuildLogger;