@expo/build-tools 20.4.0 → 21.0.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 (69) hide show
  1. package/dist/android/gradle.d.ts +5 -0
  2. package/dist/android/gradle.js +13 -33
  3. package/dist/common/installDependencies.js +21 -18
  4. package/dist/common/projectSources.js +1 -1
  5. package/dist/context.d.ts +2 -2
  6. package/dist/context.js +31 -1
  7. package/dist/ios/fastlane.d.ts +1 -1
  8. package/dist/steps/easFunctions.js +18 -0
  9. package/dist/steps/functions/capturePosthogEvent.d.ts +2 -0
  10. package/dist/steps/functions/capturePosthogEvent.js +79 -0
  11. package/dist/steps/functions/createPosthogAnnotation.d.ts +2 -0
  12. package/dist/steps/functions/createPosthogAnnotation.js +75 -0
  13. package/dist/steps/functions/createSubmissionEntity.js +25 -1
  14. package/dist/steps/functions/downloadArtifact.js +13 -3
  15. package/dist/steps/functions/finishIosSimulatorRecordings.d.ts +2 -0
  16. package/dist/steps/functions/finishIosSimulatorRecordings.js +24 -0
  17. package/dist/steps/functions/repack.js +12 -7
  18. package/dist/steps/functions/restoreCache.js +2 -1
  19. package/dist/steps/functions/rolloutPosthogFlag.d.ts +2 -0
  20. package/dist/steps/functions/rolloutPosthogFlag.js +208 -0
  21. package/dist/steps/functions/saveCache.js +3 -2
  22. package/dist/steps/functions/startAgentDeviceRemoteSession.js +21 -8
  23. package/dist/steps/functions/startArgentRemoteSession.d.ts +14 -0
  24. package/dist/steps/functions/startArgentRemoteSession.js +105 -44
  25. package/dist/steps/functions/startIosSimulator.js +19 -0
  26. package/dist/steps/functions/startIosSimulatorRecordings.d.ts +2 -0
  27. package/dist/steps/functions/startIosSimulatorRecordings.js +20 -0
  28. package/dist/steps/functions/startServeSimRemoteSession.js +7 -5
  29. package/dist/steps/functions/uploadDeviceRunSessionScreenRecordings.d.ts +3 -0
  30. package/dist/steps/functions/uploadDeviceRunSessionScreenRecordings.js +91 -0
  31. package/dist/steps/functions/uploadPosthogSourcemaps.d.ts +2 -0
  32. package/dist/steps/functions/uploadPosthogSourcemaps.js +83 -0
  33. package/dist/steps/functions/waitForPosthogMetric.d.ts +2 -0
  34. package/dist/steps/functions/waitForPosthogMetric.js +135 -0
  35. package/dist/steps/functions/waitForPosthogQuery.d.ts +2 -0
  36. package/dist/steps/functions/waitForPosthogQuery.js +95 -0
  37. package/dist/steps/utils/IosSimulatorRecordingUtils.d.ts +17 -0
  38. package/dist/steps/utils/IosSimulatorRecordingUtils.js +218 -0
  39. package/dist/steps/utils/PosthogClient.d.ts +46 -0
  40. package/dist/steps/utils/PosthogClient.js +156 -0
  41. package/dist/steps/utils/PosthogUtils.d.ts +41 -0
  42. package/dist/steps/utils/PosthogUtils.js +65 -0
  43. package/dist/steps/utils/agentDeviceArtifacts.d.ts +27 -0
  44. package/dist/steps/utils/agentDeviceArtifacts.js +133 -0
  45. package/dist/steps/utils/android/gradle.d.ts +5 -0
  46. package/dist/steps/utils/android/gradle.js +13 -62
  47. package/dist/steps/utils/argentArtifacts.d.ts +5 -3
  48. package/dist/steps/utils/argentArtifacts.js +95 -23
  49. package/dist/steps/utils/deviceRunSessionArtifacts.d.ts +3 -1
  50. package/dist/steps/utils/deviceRunSessionArtifacts.js +6 -2
  51. package/dist/steps/utils/remoteDeviceRunSession.d.ts +8 -0
  52. package/dist/steps/utils/remoteDeviceRunSession.js +62 -1
  53. package/dist/utils/AndroidEmulatorUtils.js +3 -0
  54. package/dist/utils/IosSimulatorUtils.d.ts +5 -0
  55. package/dist/utils/IosSimulatorUtils.js +81 -2
  56. package/dist/utils/expoUpdates.js +6 -1
  57. package/dist/utils/expoUpdatesEmbedded.js +4 -0
  58. package/dist/utils/processes.d.ts +1 -0
  59. package/dist/utils/processes.js +23 -0
  60. package/package.json +9 -8
  61. package/resources/record-sim/Package.swift +28 -0
  62. package/resources/record-sim/README.md +79 -0
  63. package/resources/record-sim/Sources/RecordSim/FramebufferDisplaySource.swift +192 -0
  64. package/resources/record-sim/Sources/RecordSim/PrivateSimulatorServices.swift +96 -0
  65. package/resources/record-sim/Sources/RecordSim/RecordingModels.swift +70 -0
  66. package/resources/record-sim/Sources/RecordSim/RecordingOutputWriter.swift +136 -0
  67. package/resources/record-sim/Sources/RecordSim/SimulatorRecorder.swift +632 -0
  68. package/resources/record-sim/Sources/RecordSim/Utilities.swift +60 -0
  69. package/resources/record-sim/Sources/record-sim/main.swift +143 -0
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createRolloutPosthogFlagFunction = createRolloutPosthogFlagFunction;
4
+ const eas_build_job_1 = require("@expo/eas-build-job");
5
+ const steps_1 = require("@expo/steps");
6
+ const zod_1 = require("zod");
7
+ const PosthogClient_1 = require("../utils/PosthogClient");
8
+ const PosthogUtils_1 = require("../utils/PosthogUtils");
9
+ const SEARCH_LIMIT = 200;
10
+ const FeatureFlagGroupSchema = zod_1.z.object({ properties: zod_1.z.unknown().optional() }).passthrough();
11
+ const FeatureFlagSchema = zod_1.z
12
+ .object({
13
+ id: zod_1.z.number(),
14
+ key: zod_1.z.string(),
15
+ filters: zod_1.z
16
+ .object({
17
+ groups: zod_1.z.array(FeatureFlagGroupSchema).optional(),
18
+ payloads: zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()).optional(),
19
+ })
20
+ .passthrough()
21
+ .optional(),
22
+ })
23
+ .passthrough();
24
+ const FlagSearchResponseSchema = zod_1.z.object({ results: zod_1.z.array(zod_1.z.unknown()) });
25
+ function isCatchAllGroup(group) {
26
+ const { properties } = group;
27
+ return (properties === undefined ||
28
+ properties === null ||
29
+ (Array.isArray(properties) && properties.length === 0));
30
+ }
31
+ function createRolloutPosthogFlagFunction() {
32
+ return new steps_1.BuildFunction({
33
+ namespace: 'eas',
34
+ id: 'posthog_flag_rollout',
35
+ name: 'Roll out a PostHog feature flag',
36
+ __metricsId: 'eas/posthog_flag_rollout',
37
+ inputProviders: [
38
+ steps_1.BuildStepInput.createProvider({
39
+ id: 'flag',
40
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
41
+ required: true,
42
+ }),
43
+ steps_1.BuildStepInput.createProvider({
44
+ id: 'active',
45
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
46
+ required: false,
47
+ }),
48
+ steps_1.BuildStepInput.createProvider({
49
+ id: 'rollout_percentage',
50
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
51
+ required: false,
52
+ }),
53
+ steps_1.BuildStepInput.createProvider({
54
+ id: 'payload',
55
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.JSON,
56
+ required: false,
57
+ }),
58
+ steps_1.BuildStepInput.createProvider({
59
+ id: 'variant',
60
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
61
+ required: false,
62
+ }),
63
+ steps_1.BuildStepInput.createProvider({
64
+ id: 'api_key',
65
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
66
+ required: false,
67
+ }),
68
+ steps_1.BuildStepInput.createProvider({
69
+ id: 'project_id',
70
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
71
+ required: false,
72
+ }),
73
+ steps_1.BuildStepInput.createProvider({
74
+ id: 'ignore_error',
75
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
76
+ required: false,
77
+ }),
78
+ ],
79
+ fn: async (stepCtx, { inputs, env, signal }) => {
80
+ const { logger } = stepCtx;
81
+ const ignoreError = Boolean(inputs.ignore_error.value);
82
+ const flagKey = inputs.flag.value;
83
+ const active = inputs.active.value;
84
+ const rolloutPercentage = inputs.rollout_percentage.value;
85
+ const payload = inputs.payload.value;
86
+ const variant = inputs.variant.value;
87
+ if (active === undefined && rolloutPercentage === undefined && payload === undefined) {
88
+ throw new eas_build_job_1.UserError('EAS_POSTHOG_FLAG_NO_CHANGE', 'You need to provide "active", "rollout_percentage", or "payload" to change the feature flag.');
89
+ }
90
+ if (rolloutPercentage !== undefined &&
91
+ (!Number.isInteger(rolloutPercentage) || rolloutPercentage < 0 || rolloutPercentage > 100)) {
92
+ throw new eas_build_job_1.UserError('EAS_POSTHOG_FLAG_INVALID_ROLLOUT', `"rollout_percentage" must be an integer between 0 and 100, got ${rolloutPercentage}.`);
93
+ }
94
+ const result = PosthogClient_1.PosthogClient.fromEnv({
95
+ apiKeyOverride: inputs.api_key.value,
96
+ projectIdOverride: inputs.project_id.value,
97
+ env,
98
+ });
99
+ if (!result.client) {
100
+ PosthogUtils_1.PosthogUtils.failOrLogError({
101
+ logger,
102
+ ignoreError,
103
+ error: PosthogUtils_1.PosthogUtils.missingCredentialsError(result.missing),
104
+ });
105
+ return;
106
+ }
107
+ const client = result.client;
108
+ await rolloutPosthogFlagAsync({
109
+ logger,
110
+ client,
111
+ flagKey,
112
+ active,
113
+ rolloutPercentage,
114
+ payload,
115
+ variant,
116
+ ignoreError,
117
+ signal,
118
+ });
119
+ },
120
+ });
121
+ }
122
+ async function rolloutPosthogFlagAsync({ logger, client, flagKey, active, rolloutPercentage, payload, variant, ignoreError, signal, }) {
123
+ let searchResponse;
124
+ try {
125
+ searchResponse = await client.requestAsync('GET', `/feature_flags/?limit=${SEARCH_LIMIT}&search=${encodeURIComponent(flagKey)}`, {
126
+ action: `Looking up PostHog flag "${flagKey}"`,
127
+ forbiddenScope: 'feature_flag:read',
128
+ signal,
129
+ });
130
+ }
131
+ catch (error) {
132
+ PosthogUtils_1.PosthogUtils.failOrLogError({ logger, ignoreError, error });
133
+ return;
134
+ }
135
+ let results;
136
+ try {
137
+ results = FlagSearchResponseSchema.parse(await searchResponse.json()).results;
138
+ }
139
+ catch (error) {
140
+ PosthogUtils_1.PosthogUtils.failOrLogError({
141
+ logger,
142
+ ignoreError,
143
+ error: new eas_build_job_1.UserError('EAS_POSTHOG_FLAG_UNEXPECTED_RESPONSE', `PostHog flag lookup for "${flagKey}" returned an unexpected response.`, { cause: error }),
144
+ });
145
+ return;
146
+ }
147
+ const flag = results
148
+ .flatMap(result => {
149
+ const parsed = FeatureFlagSchema.safeParse(result);
150
+ return parsed.success ? [parsed.data] : [];
151
+ })
152
+ .find(candidate => candidate.key === flagKey);
153
+ if (!flag) {
154
+ PosthogUtils_1.PosthogUtils.failOrLogError({
155
+ logger,
156
+ ignoreError,
157
+ error: new eas_build_job_1.UserError('EAS_POSTHOG_FLAG_NOT_FOUND', `No PostHog feature flag with key "${flagKey}" was found. Check the "flag" input.`),
158
+ });
159
+ return;
160
+ }
161
+ const patch = {};
162
+ if (active !== undefined) {
163
+ patch.active = active;
164
+ }
165
+ let filters = flag.filters;
166
+ if (rolloutPercentage !== undefined) {
167
+ filters = buildRolloutFilters({ logger, flagKey, filters, rolloutPercentage });
168
+ }
169
+ if (payload !== undefined) {
170
+ filters = {
171
+ ...filters,
172
+ payloads: { ...filters?.payloads, [variant ?? 'true']: JSON.stringify(payload) },
173
+ };
174
+ }
175
+ if (rolloutPercentage !== undefined || payload !== undefined) {
176
+ patch.filters = filters;
177
+ }
178
+ try {
179
+ await client.requestAsync('PATCH', `/feature_flags/${flag.id}/`, {
180
+ action: `Updating PostHog flag "${flagKey}"`,
181
+ forbiddenScope: 'feature_flag:write',
182
+ body: patch,
183
+ signal,
184
+ });
185
+ }
186
+ catch (error) {
187
+ PosthogUtils_1.PosthogUtils.failOrLogError({ logger, ignoreError, error });
188
+ return;
189
+ }
190
+ logger.info(`Updated PostHog feature flag "${flagKey}".`);
191
+ }
192
+ function buildRolloutFilters({ logger, flagKey, filters, rolloutPercentage, }) {
193
+ const groups = filters?.groups ?? [];
194
+ if (groups.length === 0) {
195
+ return { ...filters, groups: [{ properties: [], rollout_percentage: rolloutPercentage }] };
196
+ }
197
+ const catchAllIndex = groups.findIndex(isCatchAllGroup);
198
+ const hasCatchAll = catchAllIndex >= 0;
199
+ const targetIndex = hasCatchAll ? catchAllIndex : 0;
200
+ if (!hasCatchAll) {
201
+ logger.warn(`Flag "${flagKey}" has no catch-all release-condition group; setting rollout_percentage on the first group, which is condition-scoped.`);
202
+ }
203
+ else if (groups.length > 1) {
204
+ logger.warn(`Flag "${flagKey}" has ${groups.length} release-condition groups; setting rollout_percentage only on the catch-all group to preserve the others.`);
205
+ }
206
+ const groupsWithRollout = groups.map((group, index) => index === targetIndex ? { ...group, rollout_percentage: rolloutPercentage } : group);
207
+ return { ...filters, groups: groupsWithRollout };
208
+ }
@@ -80,6 +80,7 @@ function createSaveCacheFunction() {
80
80
  .filter(path => path.length > 0);
81
81
  const key = zod_1.default.string().parse(inputs.key.value);
82
82
  const jobId = (0, nullthrows_1.default)(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');
83
+ const expoApiServerURL = (0, nullthrows_1.default)(stepsCtx.global.staticContext.expoApiServerURL, 'expoApiServerURL is not set');
83
84
  const robotAccessToken = (0, nullthrows_1.default)(stepsCtx.global.staticContext.job.secrets?.robotAccessToken, 'robotAccessToken is not set');
84
85
  const { archivePath } = await compressCacheAsync({
85
86
  paths,
@@ -92,7 +93,7 @@ function createSaveCacheFunction() {
92
93
  await uploadPublicCacheAsync({
93
94
  logger,
94
95
  jobId,
95
- expoApiServerURL: stepsCtx.global.staticContext.expoApiServerURL,
96
+ expoApiServerURL,
96
97
  robotAccessToken,
97
98
  archivePath,
98
99
  key,
@@ -105,7 +106,7 @@ function createSaveCacheFunction() {
105
106
  await uploadCacheAsync({
106
107
  logger,
107
108
  jobId,
108
- expoApiServerURL: stepsCtx.global.staticContext.expoApiServerURL,
109
+ expoApiServerURL,
109
110
  robotAccessToken,
110
111
  archivePath,
111
112
  key,
@@ -11,12 +11,17 @@ const node_fs_1 = __importDefault(require("node:fs"));
11
11
  const node_os_1 = __importDefault(require("node:os"));
12
12
  const node_path_1 = __importDefault(require("node:path"));
13
13
  const sentry_1 = require("../../sentry");
14
+ const agentDeviceArtifacts_1 = require("../utils/agentDeviceArtifacts");
14
15
  const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
15
16
  const AGENT_DEVICE_PACKAGE_NAME = 'agent-device';
16
- const AGENT_DEVICE_REPO_URL = 'https://github.com/callstackincubator/agent-device.git';
17
+ const AGENT_DEVICE_REPO_URL = 'https://github.com/callstack/agent-device.git';
17
18
  const SRC_DIR = '/tmp/agent-device-src';
18
19
  const DAEMON_JSON_PATH = node_path_1.default.join(node_os_1.default.homedir(), '.agent-device', 'daemon.json');
19
20
  const STARTUP_TIMEOUT_MS = 60_000;
21
+ const AGENT_DEVICE_DAEMON_ENV = {
22
+ AGENT_DEVICE_DAEMON_SERVER_MODE: 'http',
23
+ AGENT_DEVICE_RETAIN_ARTIFACTS: '1',
24
+ };
20
25
  function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
21
26
  return new steps_1.BuildFunction({
22
27
  namespace: 'eas',
@@ -30,7 +35,7 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
30
35
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
31
36
  }),
32
37
  ],
33
- fn: async ({ logger, global }, { inputs, env }) => {
38
+ fn: async ({ logger, global }, { inputs, env, signal }) => {
34
39
  // Fail fast before any expensive setup if the injected env
35
40
  // vars are missing: DEVICE_RUN_SESSION_ID (to report the remote config
36
41
  // back to the API server), EAS_SIMULATOR_NGROK_TUNNEL_DOMAIN (base domain
@@ -82,10 +87,18 @@ function createStartAgentDeviceRemoteSessionBuildFunction(ctx) {
82
87
  },
83
88
  logger,
84
89
  });
85
- logger.info('Remote session is live. Keeping the job alive until the session is stopped.');
86
- // Keep the turtle job alive so the daemon and tunnel stay reachable
87
- // until stopDeviceRunSession cancels the run.
88
- await new Promise(() => { });
90
+ void (0, agentDeviceArtifacts_1.pollAgentDeviceArtifactsForUploadAsync)(ctx, {
91
+ deviceRunSessionId,
92
+ daemonUrl: `http://127.0.0.1:${daemonPort}`,
93
+ daemonToken,
94
+ logger,
95
+ });
96
+ await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
97
+ ctx,
98
+ deviceRunSessionId,
99
+ logger,
100
+ signal,
101
+ });
89
102
  },
90
103
  });
91
104
  }
@@ -105,7 +118,7 @@ async function startAgentDeviceDaemonAsync({ packageVersion, env, logger, }) {
105
118
  return (0, remoteDeviceRunSession_1.spawnDetached)({
106
119
  command: 'node',
107
120
  args: [daemonPath],
108
- env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' },
121
+ env: { ...env, ...AGENT_DEVICE_DAEMON_ENV },
109
122
  });
110
123
  }
111
124
  catch (err) {
@@ -144,7 +157,7 @@ async function startAgentDeviceDaemonFromGitAsync({ packageVersion, env, logger,
144
157
  command: 'bun',
145
158
  args: ['run', 'src/daemon.ts'],
146
159
  cwd: SRC_DIR,
147
- env: { ...env, AGENT_DEVICE_DAEMON_SERVER_MODE: 'http' },
160
+ env: { ...env, ...AGENT_DEVICE_DAEMON_ENV },
148
161
  });
149
162
  }
150
163
  async function cloneAgentDeviceAsync({ packageVersion, env, logger, }) {
@@ -1,9 +1,23 @@
1
1
  import { type bunyan } from '@expo/logger';
2
2
  import { BuildFunction } from '@expo/steps';
3
+ import { z } from 'zod';
3
4
  import { CustomBuildContext } from '../../customBuildContext';
4
5
  export declare const MIN_ARGENT_REMOTE_SESSION_VERSION = "0.12.0";
6
+ declare const ArgentToolServerStateSchema: z.ZodObject<{
7
+ port: z.ZodNumber;
8
+ pid: z.ZodNumber;
9
+ token: z.ZodOptional<z.ZodString>;
10
+ }, z.core.$strip>;
11
+ type ArgentToolServerState = z.infer<typeof ArgentToolServerStateSchema>;
5
12
  export declare function createStartArgentRemoteSessionBuildFunction(ctx: CustomBuildContext): BuildFunction;
6
13
  export declare function warnIfArgentPackageVersionCannotBeVerified({ packageVersion, logger, }: {
7
14
  packageVersion: string | undefined;
8
15
  logger: bunyan;
9
16
  }): void;
17
+ export declare function waitForArgentToolServerStateAsync({ stateDir, ancestorPid, timeoutMs, pollIntervalMs, }: {
18
+ stateDir: string;
19
+ ancestorPid: number;
20
+ timeoutMs: number;
21
+ pollIntervalMs?: number;
22
+ }): Promise<ArgentToolServerState>;
23
+ export {};
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.MIN_ARGENT_REMOTE_SESSION_VERSION = void 0;
7
7
  exports.createStartArgentRemoteSessionBuildFunction = createStartArgentRemoteSessionBuildFunction;
8
8
  exports.warnIfArgentPackageVersionCannotBeVerified = warnIfArgentPackageVersionCannotBeVerified;
9
+ exports.waitForArgentToolServerStateAsync = waitForArgentToolServerStateAsync;
9
10
  const eas_build_job_1 = require("@expo/eas-build-job");
10
11
  const steps_1 = require("@expo/steps");
11
12
  const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
@@ -14,15 +15,19 @@ const node_os_1 = __importDefault(require("node:os"));
14
15
  const node_path_1 = __importDefault(require("node:path"));
15
16
  const semver_1 = __importDefault(require("semver"));
16
17
  const zod_1 = require("zod");
18
+ const sentry_1 = require("../../sentry");
19
+ const processes_1 = require("../../utils/processes");
20
+ const retry_1 = require("../../utils/retry");
17
21
  const argentArtifacts_1 = require("../utils/argentArtifacts");
18
22
  const remoteDeviceRunSession_1 = require("../utils/remoteDeviceRunSession");
19
23
  const ARGENT_PACKAGE_NAME = '@swmansion/argent';
20
24
  exports.MIN_ARGENT_REMOTE_SESSION_VERSION = '0.12.0';
21
25
  const ARGENT_ARTIFACTS_LIST_ENDPOINT_FLAG = 'artifacts-list-endpoint';
22
- const ARGENT_STATE_FILE = node_path_1.default.join(node_os_1.default.homedir(), '.argent', 'tool-server.json');
26
+ const ARGENT_STATE_DIR = node_path_1.default.join(node_os_1.default.homedir(), '.argent');
23
27
  const STARTUP_TIMEOUT_MS = 60_000;
24
28
  const ArgentToolServerStateSchema = zod_1.z.object({
25
29
  port: zod_1.z.number(),
30
+ pid: zod_1.z.number(),
26
31
  token: zod_1.z.string().optional(),
27
32
  });
28
33
  function createStartArgentRemoteSessionBuildFunction(ctx) {
@@ -38,7 +43,7 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
38
43
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
39
44
  }),
40
45
  ],
41
- fn: async ({ logger, global }, { inputs, env }) => {
46
+ fn: async ({ logger, global }, { inputs, env, signal }) => {
42
47
  // Fail fast before any expensive setup if the injected env
43
48
  // vars are missing: DEVICE_RUN_SESSION_ID (to report the remote config
44
49
  // back to the API server), EAS_SIMULATOR_NGROK_TUNNEL_DOMAIN (base domain
@@ -54,11 +59,11 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
54
59
  if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
55
60
  await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
56
61
  }
57
- // Stale state from a previous run would mask the new server's port.
58
- await node_fs_1.default.promises.rm(ARGENT_STATE_FILE, { force: true });
59
62
  logger.info('Enabling the Argent artifacts list endpoint flag.');
60
63
  await (0, turtle_spawn_1.default)('bunx', [`${ARGENT_PACKAGE_NAME}@${versionSpec}`, 'enable', ARGENT_ARTIFACTS_LIST_ENDPOINT_FLAG], { env, logger });
61
64
  logger.info(`Launching ${ARGENT_PACKAGE_NAME}@${versionSpec} tool-server via bunx.`);
65
+ // Keep Argent itself in foreground mode under the detached bunx process. This preserves
66
+ // the bunx -> Argent CLI -> tool-server ancestry used to identify the matching state file.
62
67
  const argentServer = (0, remoteDeviceRunSession_1.spawnDetached)({
63
68
  command: 'bunx',
64
69
  args: [
@@ -69,19 +74,21 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
69
74
  '0',
70
75
  '--idle-timeout',
71
76
  '0',
72
- '--detach',
77
+ '--force',
73
78
  ],
74
79
  env,
75
80
  });
76
- logger.info(`Waiting for argent tool-server state at ${ARGENT_STATE_FILE}.`);
81
+ if (argentServer.pid === undefined) {
82
+ throw new eas_build_job_1.SystemError('Failed to start Argent: could not determine the PID of the launched process.');
83
+ }
84
+ logger.info(`Waiting for argent tool-server state in ${ARGENT_STATE_DIR}.`);
77
85
  let toolServerPort;
78
86
  let toolServerToken;
79
87
  try {
80
- const toolServerState = await (0, remoteDeviceRunSession_1.waitForFileAsync)({
81
- filePath: ARGENT_STATE_FILE,
88
+ const toolServerState = await waitForArgentToolServerStateAsync({
89
+ stateDir: ARGENT_STATE_DIR,
90
+ ancestorPid: argentServer.pid,
82
91
  timeoutMs: STARTUP_TIMEOUT_MS,
83
- description: 'argent tool-server state',
84
- parse: parseArgentToolServerState,
85
92
  });
86
93
  toolServerPort = toolServerState.port;
87
94
  toolServerToken = toolServerState.token;
@@ -91,47 +98,67 @@ function createStartArgentRemoteSessionBuildFunction(ctx) {
91
98
  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}` : ''}`);
92
99
  }
93
100
  logger.info(`Argent tool-server is listening on port ${toolServerPort}.`);
94
- void (0, argentArtifacts_1.pollArgentArtifactsForUploadAsync)(ctx, {
101
+ const artifactPollAbortController = new AbortController();
102
+ const artifactPollSignal = signal
103
+ ? AbortSignal.any([signal, artifactPollAbortController.signal])
104
+ : artifactPollAbortController.signal;
105
+ const artifactPollingPromise = (0, argentArtifacts_1.pollArgentArtifactsForUploadAsync)(ctx, {
95
106
  deviceRunSessionId,
96
107
  toolsUrl: `http://127.0.0.1:${toolServerPort}`,
97
108
  toolsAuthToken: toolServerToken,
98
109
  logger,
110
+ signal: artifactPollSignal,
99
111
  });
100
- const publicToolsUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
101
- port: toolServerPort,
102
- subdomainPrefix: 'argent',
103
- baseDomain: ngrokTunnelDomain,
104
- authtoken: ngrokAuthtoken,
105
- rewriteHostHeader: true,
106
- logger,
107
- });
108
- logger.info(`Tunnel is ready at ${publicToolsUrl}.`);
109
- // serve-sim is iOS-only — Android sessions go without a preview URL.
110
- let webPreviewUrl;
111
- if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
112
- const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
112
+ try {
113
+ const publicToolsUrl = await (0, remoteDeviceRunSession_1.startNgrokTunnelAsync)({
114
+ port: toolServerPort,
115
+ subdomainPrefix: 'argent',
113
116
  baseDomain: ngrokTunnelDomain,
114
- env,
117
+ authtoken: ngrokAuthtoken,
118
+ rewriteHostHeader: true,
115
119
  logger,
116
- timeoutMs: STARTUP_TIMEOUT_MS,
117
120
  });
118
- webPreviewUrl = serveSim.previewUrl;
119
- logger.info(`Web preview URL: ${webPreviewUrl}`);
121
+ logger.info(`Tunnel is ready at ${publicToolsUrl}.`);
122
+ // serve-sim is iOS-only — Android sessions go without a preview URL.
123
+ let webPreviewUrl;
124
+ if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
125
+ const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
126
+ baseDomain: ngrokTunnelDomain,
127
+ env,
128
+ logger,
129
+ timeoutMs: STARTUP_TIMEOUT_MS,
130
+ });
131
+ webPreviewUrl = serveSim.previewUrl;
132
+ logger.info(`Web preview URL: ${webPreviewUrl}`);
133
+ }
134
+ await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
135
+ ctx,
136
+ deviceRunSessionId,
137
+ remoteConfig: {
138
+ toolsUrl: publicToolsUrl,
139
+ ...(toolServerToken ? { toolsAuthToken: toolServerToken } : {}),
140
+ ...(webPreviewUrl ? { webPreviewUrl } : {}),
141
+ },
142
+ logger,
143
+ });
144
+ await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
145
+ ctx,
146
+ deviceRunSessionId,
147
+ logger,
148
+ signal,
149
+ });
150
+ }
151
+ finally {
152
+ artifactPollAbortController.abort();
153
+ try {
154
+ await artifactPollingPromise;
155
+ }
156
+ catch (err) {
157
+ const error = err instanceof Error ? err : new Error(String(err));
158
+ sentry_1.Sentry.capture('Could not finish Argent remote session artifact polling', error);
159
+ logger.warn({ err: error }, 'Could not finish Argent remote session artifact polling.');
160
+ }
120
161
  }
121
- await (0, remoteDeviceRunSession_1.uploadRemoteSessionConfigAsync)({
122
- ctx,
123
- deviceRunSessionId,
124
- remoteConfig: {
125
- toolsUrl: publicToolsUrl,
126
- ...(toolServerToken ? { toolsAuthToken: toolServerToken } : {}),
127
- ...(webPreviewUrl ? { webPreviewUrl } : {}),
128
- },
129
- logger,
130
- });
131
- logger.info('Remote session is live. Keeping the job alive until the session is stopped.');
132
- // Keep the turtle job alive so the tool-server and tunnel stay reachable
133
- // until stopDeviceRunSession cancels the run.
134
- await new Promise(() => { });
135
162
  },
136
163
  });
137
164
  }
@@ -153,9 +180,43 @@ function warnIfArgentPackageVersionCannotBeVerified({ packageVersion, logger, })
153
180
  }
154
181
  }
155
182
  function parseArgentToolServerState(raw) {
156
- const result = ArgentToolServerStateSchema.safeParse(JSON.parse(raw));
183
+ let json;
184
+ try {
185
+ json = JSON.parse(raw);
186
+ }
187
+ catch (err) {
188
+ throw new eas_build_job_1.SystemError('Expected tool-server state to contain valid JSON.', { cause: err });
189
+ }
190
+ const result = ArgentToolServerStateSchema.safeParse(json);
157
191
  if (!result.success) {
158
- throw new eas_build_job_1.SystemError(`Expected tool-server state to contain { "port": <number>, ... }: ${result.error.message}`);
192
+ throw new eas_build_job_1.SystemError(`Expected tool-server state to contain { "port": <number>, "pid": <number>, ... }: ${result.error.message}`);
159
193
  }
160
194
  return result.data;
161
195
  }
196
+ async function waitForArgentToolServerStateAsync({ stateDir, ancestorPid, timeoutMs, pollIntervalMs = 1_000, }) {
197
+ const deadline = Date.now() + timeoutMs;
198
+ let lastError;
199
+ while (Date.now() < deadline) {
200
+ try {
201
+ const stateFileNames = (await node_fs_1.default.promises.readdir(stateDir)).filter(name => name.startsWith('tool-server') && name.endsWith('.json'));
202
+ for (const stateFileName of stateFileNames) {
203
+ try {
204
+ const state = parseArgentToolServerState(await node_fs_1.default.promises.readFile(node_path_1.default.join(stateDir, stateFileName), 'utf8'));
205
+ if (await (0, processes_1.isProcessDescendantOfAsync)(state.pid, ancestorPid)) {
206
+ return state;
207
+ }
208
+ }
209
+ catch (err) {
210
+ if (err.code !== 'ENOENT' && !(err instanceof eas_build_job_1.SystemError)) {
211
+ throw err;
212
+ }
213
+ }
214
+ }
215
+ }
216
+ catch (err) {
217
+ lastError = err;
218
+ }
219
+ await (0, retry_1.sleepAsync)(pollIntervalMs);
220
+ }
221
+ throw new eas_build_job_1.SystemError(`Timed out waiting for an argent tool-server state file belonging to process ${ancestorPid} in ${stateDir}${lastError instanceof Error ? `: ${lastError.message}` : ''}.`);
222
+ }
@@ -26,6 +26,12 @@ function createStartIosSimulatorBuildFunction() {
26
26
  defaultValue: 1,
27
27
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.NUMBER,
28
28
  }),
29
+ steps_1.BuildStepInput.createProvider({
30
+ id: 'enable_accessibility_settings',
31
+ required: false,
32
+ defaultValue: false,
33
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
34
+ }),
29
35
  ],
30
36
  fn: async ({ logger }, { inputs, env }) => {
31
37
  try {
@@ -45,9 +51,16 @@ function createStartIosSimulatorBuildFunction() {
45
51
  }
46
52
  const deviceIdentifierInput = inputs.device_identifier.value?.toString();
47
53
  const originalDeviceIdentifier = deviceIdentifierInput ?? (await findMostGenericIphoneUuidAsync({ env }));
54
+ const enableAccessibilitySettings = Boolean(inputs.enable_accessibility_settings.value);
48
55
  if (!originalDeviceIdentifier) {
49
56
  throw new Error('Could not find an iPhone among available simulator devices.');
50
57
  }
58
+ if (enableAccessibilitySettings) {
59
+ await IosSimulatorUtils_1.IosSimulatorUtils.enableAccessibilitySettingsAsync({
60
+ deviceIdentifier: originalDeviceIdentifier,
61
+ env,
62
+ });
63
+ }
51
64
  const { udid } = await IosSimulatorUtils_1.IosSimulatorUtils.startAsync({
52
65
  deviceIdentifier: originalDeviceIdentifier,
53
66
  env,
@@ -78,6 +91,12 @@ function createStartIosSimulatorBuildFunction() {
78
91
  destinationDeviceName: cloneDeviceName,
79
92
  env,
80
93
  });
94
+ if (enableAccessibilitySettings) {
95
+ await IosSimulatorUtils_1.IosSimulatorUtils.enableAccessibilitySettingsAsync({
96
+ deviceIdentifier: cloneDeviceName,
97
+ env,
98
+ });
99
+ }
81
100
  const { udid: cloneUdid } = await IosSimulatorUtils_1.IosSimulatorUtils.startAsync({
82
101
  deviceIdentifier: cloneDeviceName,
83
102
  env,
@@ -0,0 +1,2 @@
1
+ import { BuildFunction } from '@expo/steps';
2
+ export declare function createStartIosSimulatorRecordingsBuildFunction(): BuildFunction;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createStartIosSimulatorRecordingsBuildFunction = createStartIosSimulatorRecordingsBuildFunction;
4
+ const steps_1 = require("@expo/steps");
5
+ const IosSimulatorRecordingUtils_1 = require("../utils/IosSimulatorRecordingUtils");
6
+ function createStartIosSimulatorRecordingsBuildFunction() {
7
+ return new steps_1.BuildFunction({
8
+ namespace: 'eas',
9
+ id: 'start_ios_simulator_recordings',
10
+ name: 'Start iOS Simulator recordings',
11
+ __metricsId: 'eas/start_ios_simulator_recordings',
12
+ supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
13
+ fn: async ({ logger }, { env }) => {
14
+ await IosSimulatorRecordingUtils_1.IosSimulatorRecordingUtils.startAsync({
15
+ env,
16
+ logger,
17
+ });
18
+ },
19
+ });
20
+ }
@@ -11,7 +11,7 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
11
11
  name: 'Start serve-sim remote session',
12
12
  __metricsId: 'eas/start_serve_sim_remote_session',
13
13
  supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
14
- fn: async ({ logger }, { env }) => {
14
+ fn: async ({ logger }, { env, signal }) => {
15
15
  const deviceRunSessionId = (0, remoteDeviceRunSession_1.getDeviceRunSessionIdOrThrow)(env);
16
16
  const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
17
17
  logger.info('Starting serve-sim remote session.');
@@ -30,10 +30,12 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
30
30
  remoteConfig: { previewUrl, streamUrl },
31
31
  logger,
32
32
  });
33
- logger.info('Remote session is live. Keeping the job alive until the session is stopped.');
34
- // Keep the turtle job alive so the serve-sim tunnel stays reachable
35
- // until stopDeviceRunSession cancels the run.
36
- await new Promise(() => { });
33
+ await (0, remoteDeviceRunSession_1.waitForDeviceRunSessionStoppedAsync)({
34
+ ctx,
35
+ deviceRunSessionId,
36
+ logger,
37
+ signal,
38
+ });
37
39
  },
38
40
  });
39
41
  }
@@ -0,0 +1,3 @@
1
+ import { BuildFunction } from '@expo/steps';
2
+ import { type CustomBuildContext } from '../../customBuildContext';
3
+ export declare function createUploadDeviceRunSessionScreenRecordingsBuildFunction(ctx: CustomBuildContext): BuildFunction;