@expo/build-tools 22.2.0 → 22.4.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 (35) hide show
  1. package/dist/builders/android.js +2 -2
  2. package/dist/builders/ios.js +2 -2
  3. package/dist/ios/xcode.d.ts +3 -0
  4. package/dist/ios/xcode.js +22 -0
  5. package/dist/steps/easFunctions.js +4 -0
  6. package/dist/steps/functions/downloadBuild.d.ts +9 -2
  7. package/dist/steps/functions/downloadBuild.js +108 -15
  8. package/dist/steps/functions/installBuild.d.ts +12 -0
  9. package/dist/steps/functions/installBuild.js +85 -0
  10. package/dist/steps/functions/installMaestro.js +92 -11
  11. package/dist/steps/functions/launchApplication.d.ts +12 -0
  12. package/dist/steps/functions/launchApplication.js +121 -0
  13. package/dist/steps/functions/readIpaInfo.d.ts +1 -0
  14. package/dist/steps/functions/readIpaInfo.js +2 -0
  15. package/dist/steps/functions/repack.d.ts +5 -2
  16. package/dist/steps/functions/repack.js +50 -2
  17. package/dist/steps/functions/restoreBuildCache.d.ts +3 -3
  18. package/dist/steps/functions/restoreBuildCache.js +19 -6
  19. package/dist/steps/functions/saveBuildCache.d.ts +3 -3
  20. package/dist/steps/functions/saveBuildCache.js +17 -4
  21. package/dist/steps/functions/startServeSimRemoteSession.js +7 -0
  22. package/dist/steps/functions/uploadToAsc.js +11 -1
  23. package/dist/steps/utils/ios/AscApiClient.d.ts +1 -0
  24. package/dist/steps/utils/ios/AscApiUtils.d.ts +11 -2
  25. package/dist/steps/utils/ios/AscApiUtils.js +36 -2
  26. package/dist/steps/utils/ios/xcactivitylog.js +2 -8
  27. package/dist/steps/utils/remoteDeviceRunSession.d.ts +4 -2
  28. package/dist/steps/utils/remoteDeviceRunSession.js +9 -6
  29. package/dist/utils/IosSimulatorUtils.d.ts +8 -0
  30. package/dist/utils/IosSimulatorUtils.js +9 -0
  31. package/dist/utils/cacheKey.d.ts +8 -2
  32. package/dist/utils/cacheKey.js +12 -8
  33. package/dist/utils/download.d.ts +4 -0
  34. package/dist/utils/download.js +10 -0
  35. package/package.json +6 -4
@@ -0,0 +1,121 @@
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.createLaunchApplicationFunction = createLaunchApplicationFunction;
7
+ exports.launchApplicationAsync = launchApplicationAsync;
8
+ const eas_build_job_1 = require("@expo/eas-build-job");
9
+ const steps_1 = require("@expo/steps");
10
+ const turtle_spawn_1 = __importDefault(require("@expo/turtle-spawn"));
11
+ function createLaunchApplicationFunction() {
12
+ return new steps_1.BuildFunction({
13
+ namespace: 'eas',
14
+ id: 'launch_application',
15
+ name: 'Launch application',
16
+ __metricsId: 'eas/launch_application',
17
+ inputProviders: [
18
+ steps_1.BuildStepInput.createProvider({
19
+ id: 'application_identifier',
20
+ required: true,
21
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
22
+ }),
23
+ steps_1.BuildStepInput.createProvider({
24
+ id: 'activity_name',
25
+ required: false,
26
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
27
+ }),
28
+ steps_1.BuildStepInput.createProvider({
29
+ id: 'launch_args',
30
+ required: false,
31
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.JSON,
32
+ }),
33
+ steps_1.BuildStepInput.createProvider({
34
+ id: 'open_url',
35
+ required: false,
36
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
37
+ }),
38
+ ],
39
+ fn: async ({ global, logger }, { inputs, env }) => {
40
+ const applicationIdentifier = parseNonEmptyStringInput(inputs.application_identifier.value, 'application_identifier');
41
+ const activityName = inputs.activity_name.value === undefined
42
+ ? undefined
43
+ : parseNonEmptyStringInput(inputs.activity_name.value, 'activity_name');
44
+ const launchArgs = parseLaunchArgsInput(inputs.launch_args.value);
45
+ const openUrl = inputs.open_url.value === undefined ? undefined : parseOpenUrlInput(inputs.open_url.value);
46
+ await launchApplicationAsync({
47
+ applicationIdentifier,
48
+ activityName,
49
+ launchArgs,
50
+ openUrl,
51
+ runtimePlatform: global.runtimePlatform,
52
+ env,
53
+ logger,
54
+ });
55
+ },
56
+ });
57
+ }
58
+ async function launchApplicationAsync({ applicationIdentifier, activityName, launchArgs = [], openUrl, runtimePlatform, env, logger, }) {
59
+ if (runtimePlatform === steps_1.BuildRuntimePlatform.DARWIN) {
60
+ logApplicationLaunch(logger, applicationIdentifier, launchArgs);
61
+ await (0, turtle_spawn_1.default)('xcrun', ['simctl', 'launch', 'booted', applicationIdentifier, ...launchArgs], {
62
+ env,
63
+ logger,
64
+ });
65
+ if (openUrl) {
66
+ logger.info(`Opening ${openUrl} in ${applicationIdentifier}.`);
67
+ await (0, turtle_spawn_1.default)('xcrun', ['simctl', 'openurl', 'booted', openUrl], { env, logger });
68
+ }
69
+ return;
70
+ }
71
+ if (!activityName) {
72
+ throw new eas_build_job_1.UserError('EAS_LAUNCH_APPLICATION_MISSING_ACTIVITY', 'Launching an Android application requires activity_name.');
73
+ }
74
+ logApplicationLaunch(logger, applicationIdentifier, launchArgs);
75
+ // Android does not support process arguments like iOS. Pass raw `am start` Intent
76
+ // arguments instead, such as `--es key value` or `--ez key true`.
77
+ await (0, turtle_spawn_1.default)('adb', ['shell', 'am', 'start', ...launchArgs, '-n', `${applicationIdentifier}/${activityName}`], {
78
+ env,
79
+ logger,
80
+ });
81
+ if (openUrl) {
82
+ logger.info(`Opening ${openUrl} in ${applicationIdentifier}.`);
83
+ await (0, turtle_spawn_1.default)('adb', [
84
+ 'shell',
85
+ 'am',
86
+ 'start',
87
+ '-a',
88
+ 'android.intent.action.VIEW',
89
+ '-d',
90
+ openUrl,
91
+ '-n',
92
+ `${applicationIdentifier}/${activityName}`,
93
+ ], { env, logger });
94
+ }
95
+ }
96
+ function logApplicationLaunch(logger, applicationIdentifier, launchArgs) {
97
+ const argumentsDescription = launchArgs.length > 0 ? ` with arguments ${JSON.stringify(launchArgs)}` : '';
98
+ logger.info(`Launching ${applicationIdentifier}${argumentsDescription}.`);
99
+ }
100
+ function parseNonEmptyStringInput(value, inputName) {
101
+ if (typeof value !== 'string' || value.length === 0) {
102
+ throw new eas_build_job_1.UserError('EAS_LAUNCH_APPLICATION_INVALID_INPUT', `Input "${inputName}" must be a non-empty string. Pass the "${inputName}" output from eas/install_build.`);
103
+ }
104
+ return value;
105
+ }
106
+ function parseLaunchArgsInput(value) {
107
+ if (value === undefined) {
108
+ return [];
109
+ }
110
+ if (!Array.isArray(value) || !value.every(argument => typeof argument === 'string')) {
111
+ throw new eas_build_job_1.UserError('EAS_LAUNCH_APPLICATION_INVALID_INPUT', 'Input "launch_args" must be an array of strings.');
112
+ }
113
+ return value;
114
+ }
115
+ function parseOpenUrlInput(value) {
116
+ const openUrl = parseNonEmptyStringInput(value, 'open_url');
117
+ if (!URL.canParse(openUrl)) {
118
+ throw new eas_build_job_1.UserError('EAS_LAUNCH_APPLICATION_INVALID_INPUT', 'Input "open_url" must be a valid URL.');
119
+ }
120
+ return openUrl;
121
+ }
@@ -3,6 +3,7 @@ export type IpaInfo = {
3
3
  bundleIdentifier: string;
4
4
  bundleShortVersion: string;
5
5
  bundleVersion: string;
6
+ dtPlatformName: string | null;
6
7
  };
7
8
  export declare function createReadIpaInfoBuildFunction(): BuildFunction;
8
9
  export declare function readIpaInfoAsync(ipaPath: string): Promise<IpaInfo>;
@@ -70,10 +70,12 @@ async function readIpaInfoAsync(ipaPath) {
70
70
  if (typeof bundleVersion !== 'string') {
71
71
  throw new eas_build_job_1.UserError('EAS_READ_IPA_INFO_INVALID_INFO_PLIST', 'Failed to read IPA info: Missing or invalid CFBundleVersion in Info.plist');
72
72
  }
73
+ const dtPlatformName = typeof infoPlist.DTPlatformName === 'string' ? infoPlist.DTPlatformName : null;
73
74
  return {
74
75
  bundleIdentifier,
75
76
  bundleShortVersion,
76
77
  bundleVersion,
78
+ dtPlatformName,
77
79
  };
78
80
  }
79
81
  catch (error) {
@@ -11,11 +11,14 @@ export declare function resolveAndroidSigningOptionsAsync({ job, tmpDir, }: {
11
11
  tmpDir: string;
12
12
  }): Promise<AndroidSigningOptions | undefined>;
13
13
  /**
14
- * Resolves iOS signing options from the job secrets.
14
+ * Resolves iOS signing options from the job secrets, dispatching on the
15
+ * requested signing backend.
15
16
  */
16
- export declare function resolveIosSigningOptionsAsync({ job, logger, useAppEntitlements, entitlementsPath, }: {
17
+ export declare function resolveIosSigningOptionsAsync({ job, logger, backend, useAppEntitlements, entitlementsPath, tmpDir, }: {
17
18
  job: Job;
18
19
  logger: bunyan;
20
+ backend?: 'fastlane' | 'zsign';
19
21
  useAppEntitlements?: boolean;
20
22
  entitlementsPath?: string;
23
+ tmpDir: string;
21
24
  }): Promise<IosSigningOptions | undefined>;
@@ -53,6 +53,12 @@ function createRepackBuildFunction() {
53
53
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
54
54
  required: false,
55
55
  }),
56
+ steps_1.BuildStepInput.createProvider({
57
+ id: 'ios_signing_backend',
58
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
59
+ required: false,
60
+ allowedValues: ['fastlane', 'zsign'],
61
+ }),
56
62
  steps_1.BuildStepInput.createProvider({
57
63
  id: 'repack_version',
58
64
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
@@ -123,8 +129,10 @@ function createRepackBuildFunction() {
123
129
  iosSigningOptions: await resolveIosSigningOptionsAsync({
124
130
  job: stepsCtx.global.staticContext.job,
125
131
  logger: stepsCtx.logger,
132
+ backend: inputs.ios_signing_backend.value,
126
133
  useAppEntitlements: inputs.ios_signing_use_source_app_entitlements.value,
127
134
  entitlementsPath: inputs.ios_signing_app_entitlements_path.value,
135
+ tmpDir,
128
136
  }),
129
137
  logger: stepsCtx.logger,
130
138
  spawnAsync: repackSpawnAsync,
@@ -234,14 +242,26 @@ async function resolveAndroidSigningOptionsAsync({ job, tmpDir, }) {
234
242
  };
235
243
  }
236
244
  /**
237
- * Resolves iOS signing options from the job secrets.
245
+ * Resolves iOS signing options from the job secrets, dispatching on the
246
+ * requested signing backend.
238
247
  */
239
- async function resolveIosSigningOptionsAsync({ job, logger, useAppEntitlements, entitlementsPath, }) {
248
+ async function resolveIosSigningOptionsAsync({ job, logger, backend, useAppEntitlements, entitlementsPath, tmpDir, }) {
240
249
  const iosJob = job;
241
250
  const buildCredentials = iosJob.secrets?.buildCredentials;
242
251
  if (iosJob.simulator || buildCredentials == null) {
243
252
  return undefined;
244
253
  }
254
+ const commonOptions = { buildCredentials, logger, useAppEntitlements, entitlementsPath };
255
+ return backend === 'zsign'
256
+ ? await createIosZsignOptionsAsync({ ...commonOptions, tmpDir })
257
+ : await createIosFastlaneOptionsAsync(commonOptions);
258
+ }
259
+ /**
260
+ * Creates signing options for the fastlane backend: certificates are imported
261
+ * into a temporary keychain and provisioning profiles are parsed with the
262
+ * macOS `security` tool.
263
+ */
264
+ async function createIosFastlaneOptionsAsync({ buildCredentials, logger, useAppEntitlements, entitlementsPath, }) {
245
265
  const credentialsManager = new manager_1.default(buildCredentials);
246
266
  const credentials = await credentialsManager.prepare(logger);
247
267
  const provisioningProfile = {};
@@ -256,3 +276,31 @@ async function resolveIosSigningOptionsAsync({ job, logger, useAppEntitlements,
256
276
  entitlementsPath,
257
277
  };
258
278
  }
279
+ /**
280
+ * Creates signing options for the zsign backend. The distribution certificate
281
+ * secret is already a PKCS#12 file, so it goes to disk as-is together with the
282
+ * provisioning profiles.
283
+ */
284
+ async function createIosZsignOptionsAsync({ buildCredentials, logger, useAppEntitlements, entitlementsPath, tmpDir, }) {
285
+ const targets = Object.entries(buildCredentials);
286
+ const [targetName, targetCredentials] = targets[0];
287
+ logger.info(`Using the distribution certificate from target '${targetName}' for zsign`);
288
+ const certificatePath = node_path_1.default.join(tmpDir, `dist-cert-${(0, node_crypto_1.randomUUID)()}.p12`);
289
+ await node_fs_1.default.promises.writeFile(certificatePath, new Uint8Array(Buffer.from(targetCredentials.distributionCertificate.dataBase64, 'base64')));
290
+ // zsign matches profiles to bundles by the app-id suffix itself, so the
291
+ // record keys are informational only.
292
+ const provisioningProfile = {};
293
+ for (const [target, credentials] of targets) {
294
+ const profilePath = node_path_1.default.join(tmpDir, `profile-${target}-${(0, node_crypto_1.randomUUID)()}.mobileprovision`);
295
+ await node_fs_1.default.promises.writeFile(profilePath, new Uint8Array(Buffer.from(credentials.provisioningProfileBase64, 'base64')));
296
+ provisioningProfile[target] = profilePath;
297
+ }
298
+ return {
299
+ backend: 'zsign',
300
+ certificatePath,
301
+ keyPassword: targetCredentials.distributionCertificate.password,
302
+ provisioningProfile,
303
+ useAppEntitlements,
304
+ entitlementsPath,
305
+ };
306
+ }
@@ -1,12 +1,12 @@
1
- import { Platform } from '@expo/eas-build-job';
2
1
  import { bunyan } from '@expo/logger';
3
2
  import { BuildFunction } from '@expo/steps';
3
+ import { CcacheBuildTarget } from '../../utils/cacheKey';
4
4
  export declare function createRestoreBuildCacheFunction(): BuildFunction;
5
5
  export declare function createCacheStatsBuildFunction(): BuildFunction;
6
- export declare function restoreCcacheAsync({ logger, workingDirectory, platform, env, secrets, }: {
6
+ export declare function restoreCcacheAsync({ logger, workingDirectory, target, env, secrets, }: {
7
7
  logger: bunyan;
8
8
  workingDirectory: string;
9
- platform: Platform;
9
+ target: CcacheBuildTarget;
10
10
  env: Record<string, string | undefined>;
11
11
  secrets?: {
12
12
  robotAccessToken?: string;
@@ -33,6 +33,11 @@ function createRestoreBuildCacheFunction() {
33
33
  required: false,
34
34
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
35
35
  }),
36
+ steps_1.BuildStepInput.createProvider({
37
+ id: 'simulator',
38
+ required: false,
39
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
40
+ }),
36
41
  ],
37
42
  fn: async (stepCtx, { env, inputs }) => {
38
43
  const { logger } = stepCtx;
@@ -42,10 +47,18 @@ function createRestoreBuildCacheFunction() {
42
47
  if (!platform || ![eas_build_job_1.Platform.ANDROID, eas_build_job_1.Platform.IOS].includes(platform)) {
43
48
  throw new Error(`Unsupported platform: ${platform}. Platform must be "${eas_build_job_1.Platform.ANDROID}" or "${eas_build_job_1.Platform.IOS}"`);
44
49
  }
50
+ const target = platform === eas_build_job_1.Platform.IOS
51
+ ? {
52
+ platform,
53
+ simulator: inputs.simulator.value ??
54
+ (stepCtx.global.staticContext.job.platform === eas_build_job_1.Platform.IOS &&
55
+ stepCtx.global.staticContext.job.simulator === true),
56
+ }
57
+ : { platform };
45
58
  await restoreCcacheAsync({
46
59
  logger,
47
60
  workingDirectory,
48
- platform,
61
+ target,
49
62
  env,
50
63
  secrets: stepCtx.global.staticContext.job.secrets,
51
64
  });
@@ -80,7 +93,7 @@ function createCacheStatsBuildFunction() {
80
93
  },
81
94
  });
82
95
  }
83
- async function restoreCcacheAsync({ logger, workingDirectory, platform, env, secrets, }) {
96
+ async function restoreCcacheAsync({ logger, workingDirectory, target, env, secrets, }) {
84
97
  const enabled = env.EAS_RESTORE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_RESTORE_CACHE !== '0');
85
98
  if (!enabled) {
86
99
  return;
@@ -103,7 +116,7 @@ async function restoreCcacheAsync({ logger, workingDirectory, platform, env, sec
103
116
  env,
104
117
  stdio: 'pipe',
105
118
  }));
106
- const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory, platform);
119
+ const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory, target);
107
120
  logger.info(`Restoring cache key: ${cacheKey}`);
108
121
  const jobId = (0, nullthrows_1.default)(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');
109
122
  const { archivePath, matchedKey } = await (0, restoreCache_1.downloadCacheAsync)({
@@ -113,8 +126,8 @@ async function restoreCcacheAsync({ logger, workingDirectory, platform, env, sec
113
126
  robotAccessToken,
114
127
  paths: [cachePath],
115
128
  key: cacheKey,
116
- keyPrefixes: [cacheKey_1.CACHE_KEY_PREFIX_BY_PLATFORM[platform]],
117
- platform,
129
+ keyPrefixes: [(0, cacheKey_1.getCcacheKeyPrefix)(target)],
130
+ platform: target.platform,
118
131
  });
119
132
  await (0, restoreCache_1.decompressCacheAsync)({
120
133
  archivePath,
@@ -139,7 +152,7 @@ async function restoreCcacheAsync({ logger, workingDirectory, platform, env, sec
139
152
  expoApiServerURL,
140
153
  robotAccessToken,
141
154
  paths: [cachePath],
142
- platform,
155
+ platform: target.platform,
143
156
  });
144
157
  await (0, restoreCache_1.decompressCacheAsync)({
145
158
  archivePath,
@@ -1,11 +1,11 @@
1
- import { Platform } from '@expo/eas-build-job';
2
1
  import { bunyan } from '@expo/logger';
3
2
  import { BuildFunction } from '@expo/steps';
3
+ import { CcacheBuildTarget } from '../../utils/cacheKey';
4
4
  export declare function createSaveBuildCacheFunction(evictUsedBefore: Date): BuildFunction;
5
- export declare function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBefore, env, secrets, }: {
5
+ export declare function saveCcacheAsync({ logger, workingDirectory, target, evictUsedBefore, env, secrets, }: {
6
6
  logger: bunyan;
7
7
  workingDirectory: string;
8
- platform: Platform;
8
+ target: CcacheBuildTarget;
9
9
  evictUsedBefore: Date;
10
10
  env: Record<string, string | undefined>;
11
11
  secrets?: {
@@ -29,6 +29,11 @@ function createSaveBuildCacheFunction(evictUsedBefore) {
29
29
  required: false,
30
30
  allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
31
31
  }),
32
+ steps_1.BuildStepInput.createProvider({
33
+ id: 'simulator',
34
+ required: false,
35
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.BOOLEAN,
36
+ }),
32
37
  ],
33
38
  fn: async (stepCtx, { env, inputs }) => {
34
39
  const { logger } = stepCtx;
@@ -38,10 +43,18 @@ function createSaveBuildCacheFunction(evictUsedBefore) {
38
43
  if (!platform || ![eas_build_job_1.Platform.ANDROID, eas_build_job_1.Platform.IOS].includes(platform)) {
39
44
  throw new Error(`Unsupported platform: ${platform}. Platform must be "${eas_build_job_1.Platform.ANDROID}" or "${eas_build_job_1.Platform.IOS}"`);
40
45
  }
46
+ const target = platform === eas_build_job_1.Platform.IOS
47
+ ? {
48
+ platform,
49
+ simulator: inputs.simulator.value ??
50
+ (stepCtx.global.staticContext.job.platform === eas_build_job_1.Platform.IOS &&
51
+ stepCtx.global.staticContext.job.simulator === true),
52
+ }
53
+ : { platform };
41
54
  await saveCcacheAsync({
42
55
  logger,
43
56
  workingDirectory,
44
- platform,
57
+ target,
45
58
  evictUsedBefore,
46
59
  env,
47
60
  secrets: stepCtx.global.staticContext.job.secrets,
@@ -57,7 +70,7 @@ function createSaveBuildCacheFunction(evictUsedBefore) {
57
70
  },
58
71
  });
59
72
  }
60
- async function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBefore, env, secrets, }) {
73
+ async function saveCcacheAsync({ logger, workingDirectory, target, evictUsedBefore, env, secrets, }) {
61
74
  const enabled = env.EAS_SAVE_CACHE === '1' || (env.EAS_USE_CACHE === '1' && env.EAS_SAVE_CACHE !== '0');
62
75
  if (!enabled) {
63
76
  return;
@@ -72,7 +85,7 @@ async function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBe
72
85
  return;
73
86
  }
74
87
  try {
75
- const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory, platform);
88
+ const cacheKey = await (0, cacheKey_1.generateDefaultBuildCacheKeyAsync)(workingDirectory, target);
76
89
  logger.info(`Saving cache key: ${cacheKey}`);
77
90
  const jobId = (0, nullthrows_1.default)(env.EAS_BUILD_ID, 'EAS_BUILD_ID is not set');
78
91
  const robotAccessToken = (0, nullthrows_1.default)(secrets?.robotAccessToken, 'Robot access token is required for cache operations');
@@ -104,7 +117,7 @@ async function saveCcacheAsync({ logger, workingDirectory, platform, evictUsedBe
104
117
  key: cacheKey,
105
118
  paths: [cachePath],
106
119
  size,
107
- platform,
120
+ platform: target.platform,
108
121
  });
109
122
  }
110
123
  catch (err) {
@@ -12,6 +12,11 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
12
12
  __metricsId: 'eas/start_serve_sim_remote_session',
13
13
  supportedRuntimePlatforms: [steps_1.BuildRuntimePlatform.DARWIN],
14
14
  inputProviders: [
15
+ steps_1.BuildStepInput.createProvider({
16
+ id: 'package_version',
17
+ required: false,
18
+ allowedValueTypeName: steps_1.BuildStepInputValueTypeName.STRING,
19
+ }),
15
20
  steps_1.BuildStepInput.createProvider({
16
21
  id: 'max_duration_seconds',
17
22
  required: false,
@@ -22,6 +27,7 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
22
27
  const deviceRunSessionId = (0, remoteDeviceRunSession_1.getDeviceRunSessionIdOrThrow)(env);
23
28
  const ngrokTunnelDomain = (0, remoteDeviceRunSession_1.getNgrokTunnelDomainOrThrow)(env);
24
29
  const maxDurationSeconds = inputs.max_duration_seconds?.value;
30
+ const packageVersion = inputs.package_version?.value;
25
31
  logger.info('Starting serve-sim remote session.');
26
32
  await (0, remoteDeviceRunSession_1.selectXcodeDeveloperDirectoryAsync)({ env, logger });
27
33
  const serveSim = await (0, remoteDeviceRunSession_1.startServeSimWithTunnelAsync)(ctx, {
@@ -29,6 +35,7 @@ function createStartServeSimRemoteSessionBuildFunction(ctx) {
29
35
  env,
30
36
  logger,
31
37
  timeoutMs: STARTUP_TIMEOUT_MS,
38
+ packageVersion,
32
39
  });
33
40
  logger.info(`Preview URL: ${serveSim.previewUrl}`);
34
41
  try {
@@ -141,15 +141,25 @@ function createUploadToAscBuildFunction() {
141
141
  const appResponse = await AscApiUtils_1.AscApiUtils.getAppInfoAsync({ client, appleAppIdentifier });
142
142
  const ascAppBundleIdentifier = appResponse.data.attributes.bundleId;
143
143
  stepsCtx.logger.info(`Uploading Build to "${appResponse.data.attributes.name}" (${ascAppBundleIdentifier})...`);
144
+ // Derive the App Store Connect platform from the IPA itself so tvOS (and
145
+ // other) binaries are uploaded to the correct version train instead of the
146
+ // iOS one. Falls back to `IOS` if the platform cannot be read.
147
+ const ipaInfoResult = await (0, results_1.asyncResult)((0, readIpaInfo_1.readIpaInfoAsync)(ipaPath));
148
+ const platform = ipaInfoResult.ok
149
+ ? AscApiUtils_1.AscApiUtils.ascPlatformFromDtPlatformName(ipaInfoResult.value.dtPlatformName)
150
+ : 'IOS';
151
+ stepsCtx.logger.info(`Detected App Store Connect platform: ${platform}`);
144
152
  stepsCtx.logger.info('Creating Build Upload...');
145
153
  const buildUploadResponse = await AscApiUtils_1.AscApiUtils.createBuildUploadAsync({
146
154
  client,
147
155
  appleAppIdentifier,
148
156
  bundleShortVersion,
149
157
  bundleVersion,
158
+ platform,
150
159
  });
151
160
  const buildUploadId = buildUploadResponse.data.id;
152
- const buildUploadUrl = `https://appstoreconnect.apple.com/apps/${appleAppIdentifier}/testflight/ios/${buildUploadId}`;
161
+ const platformPathSegment = AscApiUtils_1.AscApiUtils.testFlightPlatformPathSegment(platform);
162
+ const buildUploadUrl = `https://appstoreconnect.apple.com/apps/${appleAppIdentifier}/testflight/${platformPathSegment}/${buildUploadId}`;
153
163
  outputs.build_upload_id.set(buildUploadId);
154
164
  outputs.build_upload_url.set(buildUploadUrl);
155
165
  stepsCtx.logger.info(`Build Upload initialized (ID: ${buildUploadId}). Preparing IPA upload...`);
@@ -264,6 +264,7 @@ export type AscApiClientPostApi = {
264
264
  response: z.output<(typeof PostApi)[Path]['response']>;
265
265
  };
266
266
  };
267
+ export type AscPlatform = AscApiClientPostApi['/v1/buildUploads']['request']['data']['attributes']['platform'];
267
268
  export type AscApiClientPatchApi = {
268
269
  [Path in keyof typeof PatchApi]: {
269
270
  request: z.input<(typeof PatchApi)[Path]['request']>;
@@ -1,14 +1,23 @@
1
- import { AscApiClient, AscApiClientGetApi, AscApiClientPostApi } from './AscApiClient';
1
+ import { AscApiClient, AscApiClientGetApi, AscApiClientPostApi, AscPlatform } from './AscApiClient';
2
2
  export declare namespace AscApiUtils {
3
+ /**
4
+ * Maps a bundle's `DTPlatformName` (from its Info.plist) to the App Store
5
+ * Connect platform used for a build upload. Unknown or missing values fall
6
+ * back to `IOS` to preserve the previous default.
7
+ */
8
+ function ascPlatformFromDtPlatformName(dtPlatformName: string | null): AscPlatform;
9
+ /** The App Store Connect TestFlight URL path segment for a platform. */
10
+ function testFlightPlatformPathSegment(platform: AscPlatform): string;
3
11
  function getAppInfoAsync({ client, appleAppIdentifier, }: {
4
12
  client: Pick<AscApiClient, 'getAsync'>;
5
13
  appleAppIdentifier: string;
6
14
  }): Promise<AscApiClientGetApi['/v1/apps/:id']['response']>;
7
- function createBuildUploadAsync({ client, appleAppIdentifier, bundleShortVersion, bundleVersion, }: {
15
+ function createBuildUploadAsync({ client, appleAppIdentifier, bundleShortVersion, bundleVersion, platform, }: {
8
16
  client: Pick<AscApiClient, 'postAsync'>;
9
17
  appleAppIdentifier: string;
10
18
  bundleShortVersion: string;
11
19
  bundleVersion: string;
20
+ platform: AscPlatform;
12
21
  }): Promise<AscApiClientPostApi['/v1/buildUploads']['response']>;
13
22
  function getAppsAsync({ client, limit, }: {
14
23
  client: Pick<AscApiClient, 'getAsync'>;
@@ -5,6 +5,40 @@ const eas_build_job_1 = require("@expo/eas-build-job");
5
5
  const AscApiClient_1 = require("./AscApiClient");
6
6
  var AscApiUtils;
7
7
  (function (AscApiUtils) {
8
+ /**
9
+ * Maps a bundle's `DTPlatformName` (from its Info.plist) to the App Store
10
+ * Connect platform used for a build upload. Unknown or missing values fall
11
+ * back to `IOS` to preserve the previous default.
12
+ */
13
+ function ascPlatformFromDtPlatformName(dtPlatformName) {
14
+ switch (dtPlatformName) {
15
+ case 'appletvos':
16
+ return 'TV_OS';
17
+ case 'macosx':
18
+ return 'MAC_OS';
19
+ case 'xros':
20
+ return 'VISION_OS';
21
+ case 'iphoneos':
22
+ default:
23
+ return 'IOS';
24
+ }
25
+ }
26
+ AscApiUtils.ascPlatformFromDtPlatformName = ascPlatformFromDtPlatformName;
27
+ /** The App Store Connect TestFlight URL path segment for a platform. */
28
+ function testFlightPlatformPathSegment(platform) {
29
+ switch (platform) {
30
+ case 'TV_OS':
31
+ return 'tvos';
32
+ case 'MAC_OS':
33
+ return 'macos';
34
+ case 'VISION_OS':
35
+ return 'visionos';
36
+ case 'IOS':
37
+ default:
38
+ return 'ios';
39
+ }
40
+ }
41
+ AscApiUtils.testFlightPlatformPathSegment = testFlightPlatformPathSegment;
8
42
  async function getAppInfoAsync({ client, appleAppIdentifier, }) {
9
43
  try {
10
44
  return await client.getAsync('/v1/apps/:id', { 'fields[apps]': ['bundleId', 'name'] }, { id: appleAppIdentifier });
@@ -37,13 +71,13 @@ var AscApiUtils;
37
71
  }
38
72
  }
39
73
  AscApiUtils.getAppInfoAsync = getAppInfoAsync;
40
- async function createBuildUploadAsync({ client, appleAppIdentifier, bundleShortVersion, bundleVersion, }) {
74
+ async function createBuildUploadAsync({ client, appleAppIdentifier, bundleShortVersion, bundleVersion, platform, }) {
41
75
  try {
42
76
  return await client.postAsync('/v1/buildUploads', {
43
77
  data: {
44
78
  type: 'buildUploads',
45
79
  attributes: {
46
- platform: 'IOS',
80
+ platform,
47
81
  cfBundleShortVersionString: bundleShortVersion,
48
82
  cfBundleVersion: bundleVersion,
49
83
  },
@@ -16,6 +16,7 @@ const os_1 = __importDefault(require("os"));
16
16
  const path_1 = __importDefault(require("path"));
17
17
  const zod_1 = require("zod");
18
18
  const sentry_1 = require("../../../sentry");
19
+ const download_1 = require("../../../utils/download");
19
20
  const DEFAULT_XCLOGPARSER_VERSION = 'v0.2.47';
20
21
  const XCLOGPARSER_DOWNLOAD_URL = 'https://storage.googleapis.com/turtle-v2/xclogparser';
21
22
  const XCLOGPARSER_DOWNLOAD_TIMEOUT_MS = 20_000;
@@ -123,7 +124,7 @@ async function downloadXclogparser({ tempDir, version, logger, proxyBaseUrl, env
123
124
  const zipName = getXclogparserZipName(version);
124
125
  const zipPath = path_1.default.join(tempDir, zipName);
125
126
  const directUrl = `${XCLOGPARSER_DOWNLOAD_URL}/${zipName}`;
126
- const proxiedUrl = getProxiedDownloadUrl({ directUrl, proxyBaseUrl });
127
+ const proxiedUrl = (0, download_1.getProxiedDownloadUrl)({ directUrl, proxyBaseUrl });
127
128
  if (proxiedUrl) {
128
129
  const proxiedDownloadResult = await (0, results_1.asyncResult)(downloadAndUnpackXclogparser({ tempDir, zipPath, sourceUrl: proxiedUrl, env }));
129
130
  if (!proxiedDownloadResult.ok) {
@@ -188,13 +189,6 @@ const COMPILE_SIGNATURE_PREFIXES = ['SwiftCompile ', 'SwiftGeneratePch '];
188
189
  function getXclogparserZipName(version) {
189
190
  return `XCLogParser-macOS-x86-64-arm64-${version}.zip`;
190
191
  }
191
- function getProxiedDownloadUrl({ directUrl, proxyBaseUrl, }) {
192
- if (!proxyBaseUrl) {
193
- return null;
194
- }
195
- const parsedUrl = new URL(directUrl);
196
- return directUrl.replace(`${parsedUrl.protocol}//${parsedUrl.host}`, `${proxyBaseUrl}/${parsedUrl.host}`);
197
- }
198
192
  function isCompileStep(step) {
199
193
  const detailStepType = step.detailStepType ?? '';
200
194
  const signature = step.signature ?? '';
@@ -92,10 +92,11 @@ export declare function spawnDetached({ command, args, cwd, env, }: {
92
92
  env: BuildStepEnv;
93
93
  }): DetachedProcessHandle;
94
94
  export declare function metricsCorsOriginToServeSimArgs(env: BuildStepEnv): string[];
95
- export declare function createServeSimArgs({ port, turnArgs, metricsCorsArgs, }: {
95
+ export declare function createServeSimArgs({ port, turnArgs, metricsCorsArgs, packageVersion, }: {
96
96
  port: number;
97
97
  turnArgs?: string[];
98
98
  metricsCorsArgs?: string[];
99
+ packageVersion?: string;
99
100
  }): string[];
100
101
  export declare function waitForServeSimReadyAsync({ serveSim, port, timeoutMs, }: {
101
102
  serveSim: Pick<DetachedProcessHandle, 'pid' | 'getOutput'>;
@@ -106,11 +107,12 @@ export type ServeSimPreviewHandle = {
106
107
  previewUrl: string;
107
108
  stopAsync: () => Promise<void>;
108
109
  };
109
- export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, }: {
110
+ export declare function startServeSimWithTunnelAsync(ctx: CustomBuildContext, { baseDomain, env, logger, timeoutMs, packageVersion, }: {
110
111
  baseDomain: string;
111
112
  env: BuildStepEnv;
112
113
  logger: bunyan;
113
114
  timeoutMs: number;
115
+ packageVersion?: string;
114
116
  }): Promise<ServeSimPreviewHandle>;
115
117
  export type NgrokTunnelHandle = {
116
118
  url: string;