eas-cli 16.1.0 → 16.2.1

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 (41) hide show
  1. package/README.md +117 -81
  2. package/build/build/utils/url.d.ts +1 -0
  3. package/build/build/utils/url.js +5 -1
  4. package/build/commandUtils/EasCommand.js +6 -1
  5. package/build/commandUtils/builds.d.ts +1 -0
  6. package/build/commandUtils/builds.js +3 -0
  7. package/build/commandUtils/context/ServerSideEnvironmentVariablesContextField.d.ts +1 -2
  8. package/build/commands/deploy/index.js +20 -2
  9. package/build/commands/fingerprint/compare.d.ts +3 -0
  10. package/build/commands/fingerprint/compare.js +50 -13
  11. package/build/commands/fingerprint/generate.d.ts +5 -2
  12. package/build/commands/fingerprint/generate.js +44 -7
  13. package/build/commands/update/delete.js +13 -6
  14. package/build/commands/update/index.d.ts +1 -0
  15. package/build/commands/update/index.js +90 -59
  16. package/build/commands/upload.d.ts +18 -0
  17. package/build/commands/upload.js +253 -0
  18. package/build/credentials/ios/appstore/ensureTestFlightGroup.js +4 -3
  19. package/build/graphql/generated.d.ts +170 -5
  20. package/build/graphql/generated.js +6 -2
  21. package/build/graphql/mutations/LocalBuildMutation.d.ts +5 -0
  22. package/build/graphql/mutations/LocalBuildMutation.js +38 -0
  23. package/build/graphql/mutations/UploadSessionMutation.d.ts +1 -1
  24. package/build/graphql/mutations/UploadSessionMutation.js +4 -3
  25. package/build/graphql/queries/BackgroundJobReceiptQuery.d.ts +5 -0
  26. package/build/graphql/queries/BackgroundJobReceiptQuery.js +27 -0
  27. package/build/graphql/types/BackgroundJobReceipt.d.ts +1 -0
  28. package/build/graphql/types/BackgroundJobReceipt.js +20 -0
  29. package/build/metadata/download.js +1 -1
  30. package/build/metadata/upload.js +1 -1
  31. package/build/metadata/utils/telemetry.d.ts +3 -3
  32. package/build/metadata/utils/telemetry.js +9 -9
  33. package/build/project/publish.d.ts +17 -1
  34. package/build/project/publish.js +22 -1
  35. package/build/run/ios/xcode.d.ts +15 -0
  36. package/build/run/ios/xcode.js +24 -1
  37. package/build/uploads.js +2 -1
  38. package/build/utils/pollForBackgroundJobReceiptAsync.d.ts +29 -0
  39. package/build/utils/pollForBackgroundJobReceiptAsync.js +92 -0
  40. package/oclif.manifest.json +80 -5
  41. package/package.json +5 -3
@@ -5,6 +5,7 @@ const eas_build_job_1 = require("@expo/eas-build-job");
5
5
  const core_1 = require("@oclif/core");
6
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
7
  const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
8
+ const api_1 = require("../../api");
8
9
  const queries_1 = require("../../branch/queries");
9
10
  const graphql_1 = require("../../build/graphql");
10
11
  const repository_1 = require("../../build/utils/repository");
@@ -15,6 +16,7 @@ const pagination_1 = require("../../commandUtils/pagination");
15
16
  const fetch_1 = tslib_1.__importDefault(require("../../fetch"));
16
17
  const generated_1 = require("../../graphql/generated");
17
18
  const PublishMutation_1 = require("../../graphql/mutations/PublishMutation");
19
+ const AppQuery_1 = require("../../graphql/queries/AppQuery");
18
20
  const log_1 = tslib_1.__importStar(require("../../log"));
19
21
  const ora_1 = require("../../ora");
20
22
  const platform_1 = require("../../platform");
@@ -374,74 +376,103 @@ class UpdatePublish extends EasCommand_1.default {
374
376
  }
375
377
  if (jsonFlag) {
376
378
  (0, json_1.printJsonOnlyOutput)((0, utils_1.getUpdateJsonInfosForUpdates)(newUpdates));
379
+ return;
377
380
  }
378
- else {
379
- if (new Set(newUpdates.map(update => update.group)).size > 1) {
380
- log_1.default.addNewLineIfNone();
381
- log_1.default.log('👉 Since multiple runtime versions are defined, multiple update groups have been published.');
381
+ if (new Set(newUpdates.map(update => update.group)).size > 1) {
382
+ log_1.default.addNewLineIfNone();
383
+ log_1.default.log('👉 Since multiple runtime versions are defined, multiple update groups have been published.');
384
+ }
385
+ log_1.default.addNewLineIfNone();
386
+ const runtimeToCompatibleBuilds = await Promise.all(runtimeToPlatformsAndFingerprintInfoAndFingerprintSourceMapping.map(obj => (0, publish_1.findCompatibleBuildsAsync)(graphqlClient, projectId, obj)));
387
+ for (const runtime of (0, uniqBy_1.default)(runtimeToPlatformsAndFingerprintInfoMapping, version => version.runtimeVersion)) {
388
+ const newUpdatesForRuntimeVersion = newUpdates.filter(update => update.runtimeVersion === runtime.runtimeVersion);
389
+ if (newUpdatesForRuntimeVersion.length === 0) {
390
+ throw new Error(`Publish response is missing updates with runtime ${runtime.runtimeVersion}.`);
382
391
  }
392
+ const platforms = newUpdatesForRuntimeVersion.map(update => update.platform);
393
+ const newAndroidUpdate = newUpdatesForRuntimeVersion.find(update => update.platform === 'android');
394
+ const newIosUpdate = newUpdatesForRuntimeVersion.find(update => update.platform === 'ios');
395
+ const updateGroupId = newUpdatesForRuntimeVersion[0].group;
396
+ const projectName = exp.slug;
397
+ const accountName = (await (0, projectUtils_1.getOwnerAccountForProjectIdAsync)(graphqlClient, projectId)).name;
398
+ const updateGroupUrl = (0, url_1.getUpdateGroupUrl)(accountName, projectName, updateGroupId);
399
+ const updateGroupLink = (0, log_1.link)(updateGroupUrl, { dim: false });
400
+ log_1.default.log((0, formatFields_1.default)([
401
+ { label: 'Branch', value: branchName },
402
+ { label: 'Runtime version', value: runtime.runtimeVersion },
403
+ { label: 'Platform', value: platforms.join(', ') },
404
+ { label: 'Update group ID', value: updateGroupId },
405
+ ...(newAndroidUpdate ? [{ label: 'Android update ID', value: newAndroidUpdate.id }] : []),
406
+ ...(newIosUpdate ? [{ label: 'iOS update ID', value: newIosUpdate.id }] : []),
407
+ ...(newAndroidUpdate?.rolloutControlUpdate
408
+ ? [
409
+ {
410
+ label: 'Android Rollout',
411
+ value: `${newAndroidUpdate.rolloutPercentage}% (Base update ID: ${newAndroidUpdate.rolloutControlUpdate.id})`,
412
+ },
413
+ ]
414
+ : []),
415
+ ...(newIosUpdate?.rolloutControlUpdate
416
+ ? [
417
+ {
418
+ label: 'iOS Rollout',
419
+ value: `${newIosUpdate.rolloutPercentage}% (Base update ID: ${newIosUpdate.rolloutControlUpdate.id})`,
420
+ },
421
+ ]
422
+ : []),
423
+ { label: 'Message', value: updateMessage ?? '' },
424
+ ...(gitCommitHash
425
+ ? [
426
+ {
427
+ label: 'Commit',
428
+ value: `${gitCommitHash}${isGitWorkingTreeDirty ? '*' : ''}`,
429
+ },
430
+ ]
431
+ : []),
432
+ { label: 'EAS Dashboard', value: updateGroupLink },
433
+ ]));
383
434
  log_1.default.addNewLineIfNone();
384
- for (const runtime of (0, uniqBy_1.default)(runtimeToPlatformsAndFingerprintInfoMapping, version => version.runtimeVersion)) {
385
- const newUpdatesForRuntimeVersion = newUpdates.filter(update => update.runtimeVersion === runtime.runtimeVersion);
386
- if (newUpdatesForRuntimeVersion.length === 0) {
387
- throw new Error(`Publish response is missing updates with runtime ${runtime.runtimeVersion}.`);
388
- }
389
- const platforms = newUpdatesForRuntimeVersion.map(update => update.platform);
390
- const newAndroidUpdate = newUpdatesForRuntimeVersion.find(update => update.platform === 'android');
391
- const newIosUpdate = newUpdatesForRuntimeVersion.find(update => update.platform === 'ios');
392
- const updateGroupId = newUpdatesForRuntimeVersion[0].group;
393
- const projectName = exp.slug;
394
- const accountName = (await (0, projectUtils_1.getOwnerAccountForProjectIdAsync)(graphqlClient, projectId)).name;
395
- const updateGroupUrl = (0, url_1.getUpdateGroupUrl)(accountName, projectName, updateGroupId);
396
- const updateGroupLink = (0, log_1.link)(updateGroupUrl, { dim: false });
397
- log_1.default.log((0, formatFields_1.default)([
398
- { label: 'Branch', value: branchName },
399
- { label: 'Runtime version', value: runtime.runtimeVersion },
400
- { label: 'Platform', value: platforms.join(', ') },
401
- { label: 'Update group ID', value: updateGroupId },
402
- ...(newAndroidUpdate
403
- ? [{ label: 'Android update ID', value: newAndroidUpdate.id }]
404
- : []),
405
- ...(newIosUpdate ? [{ label: 'iOS update ID', value: newIosUpdate.id }] : []),
406
- ...(newAndroidUpdate?.rolloutControlUpdate
407
- ? [
408
- {
409
- label: 'Android Rollout',
410
- value: `${newAndroidUpdate.rolloutPercentage}% (Base update ID: ${newAndroidUpdate.rolloutControlUpdate.id})`,
411
- },
412
- ]
413
- : []),
414
- ...(newIosUpdate?.rolloutControlUpdate
415
- ? [
416
- {
417
- label: 'iOS Rollout',
418
- value: `${newIosUpdate.rolloutPercentage}% (Base update ID: ${newIosUpdate.rolloutControlUpdate.id})`,
419
- },
420
- ]
421
- : []),
422
- { label: 'Message', value: updateMessage ?? '' },
423
- ...(gitCommitHash
424
- ? [
435
+ if ((0, publish_1.isUploadedAssetCountAboveWarningThreshold)(uploadedAssetCount, assetLimitPerUpdateGroup)) {
436
+ log_1.default.warn(`This update group contains ${uploadedAssetCount} assets and is nearing the server cap of ${assetLimitPerUpdateGroup}.\n` +
437
+ `${(0, log_1.learnMore)('https://docs.expo.dev/eas-update/optimize-assets/', {
438
+ learnMoreMessage: 'Consider optimizing your usage of assets',
439
+ dim: false,
440
+ })}.`);
441
+ log_1.default.addNewLineIfNone();
442
+ }
443
+ const fingerprintsWithoutCompatibleBuilds = runtimeToCompatibleBuilds.find(({ runtimeVersion }) => runtimeVersion === runtime.runtimeVersion)?.fingerprintInfoGroupWithCompatibleBuilds;
444
+ if (fingerprintsWithoutCompatibleBuilds) {
445
+ const missingBuilds = Object.entries(fingerprintsWithoutCompatibleBuilds).filter(([_platform, fingerprintInfo]) => !fingerprintInfo.build);
446
+ if (missingBuilds.length > 0) {
447
+ const project = await AppQuery_1.AppQuery.byIdAsync(graphqlClient, projectId);
448
+ log_1.default.warn('No compatible builds found for the following fingerprints:');
449
+ for (const [platform, fingerprintInfo] of missingBuilds) {
450
+ const fingerprintUrl = new URL(`/accounts/${project.ownerAccount.name}/projects/${project.slug}/fingerprints/${fingerprintInfo.fingerprintHash}`, (0, api_1.getExpoWebsiteBaseUrl)());
451
+ log_1.default.warn((0, formatFields_1.default)([
425
452
  {
426
- label: 'Commit',
427
- value: `${gitCommitHash}${isGitWorkingTreeDirty ? '*' : ''}`,
453
+ label: `${this.prettyPlatform(platform)} fingerprint`,
454
+ value: fingerprintInfo.fingerprintHash,
428
455
  },
429
- ]
430
- : []),
431
- { label: 'EAS Dashboard', value: updateGroupLink },
432
- ]));
433
- log_1.default.addNewLineIfNone();
434
- if ((0, publish_1.isUploadedAssetCountAboveWarningThreshold)(uploadedAssetCount, assetLimitPerUpdateGroup)) {
435
- log_1.default.warn(`This update group contains ${uploadedAssetCount} assets and is nearing the server cap of ${assetLimitPerUpdateGroup}.\n` +
436
- `${(0, log_1.learnMore)('https://docs.expo.dev/eas-update/optimize-assets/', {
437
- learnMoreMessage: 'Consider optimizing your usage of assets',
438
- dim: false,
439
- })}.`);
440
- log_1.default.addNewLineIfNone();
456
+ { label: 'URL', value: fingerprintUrl.toString() },
457
+ ], {
458
+ labelFormat: label => ` ${chalk_1.default.dim(label)}:`,
459
+ }));
460
+ log_1.default.addNewLineIfNone();
461
+ }
441
462
  }
442
463
  }
443
464
  }
444
465
  }
466
+ prettyPlatform(updatePlatform) {
467
+ switch (updatePlatform) {
468
+ case 'android':
469
+ return 'Android';
470
+ case 'ios':
471
+ return 'iOS';
472
+ default:
473
+ return updatePlatform;
474
+ }
475
+ }
445
476
  sanitizeFlags(flags) {
446
477
  const nonInteractive = flags['non-interactive'] ?? false;
447
478
  const { auto, branch: branchName, channel: channelName, message: updateMessage } = flags;
@@ -0,0 +1,18 @@
1
+ import { Platform } from '@expo/eas-build-job';
2
+ import EasCommand from '../commandUtils/EasCommand';
3
+ export default class BuildUpload extends EasCommand {
4
+ static description: string;
5
+ static hidden: boolean;
6
+ static flags: {
7
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ platform: import("@oclif/core/lib/interfaces").OptionFlag<Platform | undefined>;
9
+ 'build-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
10
+ fingerprint: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
11
+ };
12
+ static contextDefinition: {
13
+ loggedIn: import("../commandUtils/context/LoggedInContextField").default;
14
+ projectId: import("../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
15
+ };
16
+ runAsync(): Promise<void>;
17
+ private selectPlatformAsync;
18
+ }
@@ -0,0 +1,253 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const config_plugins_1 = require("@expo/config-plugins");
5
+ const eas_build_job_1 = require("@expo/eas-build-job");
6
+ const core_1 = require("@oclif/core");
7
+ const fast_glob_1 = tslib_1.__importDefault(require("fast-glob"));
8
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
9
+ const node_stream_zip_1 = tslib_1.__importDefault(require("node-stream-zip"));
10
+ const path_1 = tslib_1.__importDefault(require("path"));
11
+ const tar_1 = tslib_1.__importDefault(require("tar"));
12
+ const url_1 = require("../build/utils/url");
13
+ const EasCommand_1 = tslib_1.__importDefault(require("../commandUtils/EasCommand"));
14
+ const flags_1 = require("../commandUtils/flags");
15
+ const generated_1 = require("../graphql/generated");
16
+ const FingerprintMutation_1 = require("../graphql/mutations/FingerprintMutation");
17
+ const LocalBuildMutation_1 = require("../graphql/mutations/LocalBuildMutation");
18
+ const AppPlatform_1 = require("../graphql/types/AppPlatform");
19
+ const log_1 = tslib_1.__importDefault(require("../log"));
20
+ const prompts_1 = require("../prompts");
21
+ const xcode = tslib_1.__importStar(require("../run/ios/xcode"));
22
+ const uploads_1 = require("../uploads");
23
+ const progress_1 = require("../utils/progress");
24
+ class BuildUpload extends EasCommand_1.default {
25
+ static description = 'upload a local build and generate a sharable link';
26
+ static hidden = true;
27
+ static flags = {
28
+ platform: core_1.Flags.enum({
29
+ char: 'p',
30
+ options: [eas_build_job_1.Platform.IOS, eas_build_job_1.Platform.ANDROID],
31
+ }),
32
+ 'build-path': core_1.Flags.string({
33
+ description: 'Path for the local build',
34
+ }),
35
+ fingerprint: core_1.Flags.string({
36
+ description: 'Fingerprint hash of the local build',
37
+ }),
38
+ ...flags_1.EASNonInteractiveFlag,
39
+ };
40
+ static contextDefinition = {
41
+ ...this.ContextOptions.ProjectId,
42
+ ...this.ContextOptions.LoggedIn,
43
+ };
44
+ async runAsync() {
45
+ const { flags } = await this.parse(BuildUpload);
46
+ const { 'build-path': buildPath, fingerprint: manualFingerprintHash } = flags;
47
+ const { projectId, loggedIn: { graphqlClient }, } = await this.getContextAsync(BuildUpload, {
48
+ nonInteractive: false,
49
+ });
50
+ const platform = await this.selectPlatformAsync(flags.platform);
51
+ const localBuildPath = await resolveLocalBuildPathAsync(platform, buildPath);
52
+ const { fingerprintHash: buildFingerprintHash, developmentClient, simulator, } = await extractAppMetadataAsync(localBuildPath, platform);
53
+ let fingerprint = manualFingerprintHash ?? buildFingerprintHash;
54
+ if (fingerprint) {
55
+ if (manualFingerprintHash &&
56
+ buildFingerprintHash &&
57
+ manualFingerprintHash !== buildFingerprintHash) {
58
+ const selectedAnswer = await (0, prompts_1.promptAsync)({
59
+ name: 'fingerprint',
60
+ message: `The provided fingerprint hash ${manualFingerprintHash} does not match the fingerprint hash of the build ${buildFingerprintHash}. Which fingerprint do you want to use?`,
61
+ type: 'select',
62
+ choices: [
63
+ { title: manualFingerprintHash, value: manualFingerprintHash },
64
+ { title: buildFingerprintHash, value: buildFingerprintHash },
65
+ ],
66
+ });
67
+ fingerprint = String(selectedAnswer.fingerprint);
68
+ }
69
+ await FingerprintMutation_1.FingerprintMutation.createFingerprintAsync(graphqlClient, projectId, {
70
+ hash: fingerprint,
71
+ });
72
+ }
73
+ log_1.default.log('Uploading your app archive to EAS');
74
+ const bucketKey = await uploadAppArchiveAsync(graphqlClient, localBuildPath);
75
+ const build = await LocalBuildMutation_1.LocalBuildMutation.createLocalBuildAsync(graphqlClient, projectId, { platform: (0, AppPlatform_1.toAppPlatform)(platform), simulator }, { type: generated_1.LocalBuildArchiveSourceType.Gcs, bucketKey }, { distribution: generated_1.DistributionType.Internal, fingerprintHash: fingerprint, developmentClient });
76
+ log_1.default.withTick(`Here is a sharable link of your build: ${(0, url_1.getBuildLogsUrl)(build)}`);
77
+ }
78
+ async selectPlatformAsync(platform) {
79
+ if (platform) {
80
+ return platform;
81
+ }
82
+ const { resolvedPlatform } = await (0, prompts_1.promptAsync)({
83
+ type: 'select',
84
+ message: 'Select platform',
85
+ name: 'resolvedPlatform',
86
+ choices: [
87
+ { title: 'Android', value: eas_build_job_1.Platform.ANDROID },
88
+ { title: 'iOS', value: eas_build_job_1.Platform.IOS },
89
+ ],
90
+ });
91
+ return resolvedPlatform;
92
+ }
93
+ }
94
+ exports.default = BuildUpload;
95
+ async function resolveLocalBuildPathAsync(platform, inputBuildPath) {
96
+ const rootDir = process.cwd();
97
+ let applicationArchivePatternOrPath = [];
98
+ if (inputBuildPath) {
99
+ applicationArchivePatternOrPath.push(inputBuildPath);
100
+ }
101
+ else if (platform === eas_build_job_1.Platform.ANDROID) {
102
+ applicationArchivePatternOrPath.push('android/app/build/outputs/**/*.{apk,aab}');
103
+ }
104
+ else {
105
+ const xcworkspacePath = await xcode.resolveXcodeProjectAsync(rootDir);
106
+ const schemes = config_plugins_1.IOSConfig.BuildScheme.getRunnableSchemesFromXcodeproj(rootDir);
107
+ if (xcworkspacePath && schemes.length > 0) {
108
+ for (const scheme of schemes) {
109
+ const buildSettings = await xcode.getXcodeBuildSettingsAsync(xcworkspacePath, scheme.name);
110
+ applicationArchivePatternOrPath = applicationArchivePatternOrPath.concat(buildSettings.map(({ buildSettings }) => `${buildSettings.BUILD_DIR}/**/*.app`));
111
+ }
112
+ }
113
+ }
114
+ let applicationArchives = await findArtifactsAsync({
115
+ rootDir,
116
+ patternOrPathArray: applicationArchivePatternOrPath,
117
+ });
118
+ if (applicationArchives.length === 0 && !inputBuildPath) {
119
+ log_1.default.warn(`No application archives found at ${applicationArchivePatternOrPath}.`);
120
+ const { path } = await (0, prompts_1.promptAsync)({
121
+ type: 'text',
122
+ name: 'path',
123
+ message: 'Provide a path to the application archive:',
124
+ validate: value => (value ? true : 'Path may not be empty.'),
125
+ });
126
+ applicationArchives = await findArtifactsAsync({
127
+ rootDir,
128
+ patternOrPathArray: [path],
129
+ });
130
+ }
131
+ if (applicationArchives.length === 1) {
132
+ return applicationArchives[0];
133
+ }
134
+ if (applicationArchives.length > 1) {
135
+ const { path } = await (0, prompts_1.promptAsync)({
136
+ type: 'select',
137
+ name: 'path',
138
+ message: 'Found multiple application archives. Select one:',
139
+ choices: applicationArchives.map(archivePath => {
140
+ return {
141
+ title: archivePath,
142
+ value: archivePath,
143
+ };
144
+ }),
145
+ });
146
+ return path;
147
+ }
148
+ throw new Error(`Found no application archives at ${inputBuildPath}.`);
149
+ }
150
+ async function findArtifactsAsync({ rootDir, patternOrPathArray, }) {
151
+ const files = [];
152
+ for (const patternOrPath of patternOrPathArray) {
153
+ if (path_1.default.isAbsolute(patternOrPath) && (await fs_extra_1.default.pathExists(patternOrPath))) {
154
+ files.push(patternOrPath);
155
+ }
156
+ else {
157
+ const filesFound = await (0, fast_glob_1.default)(patternOrPath, {
158
+ cwd: rootDir,
159
+ onlyFiles: false,
160
+ });
161
+ files.push(...filesFound);
162
+ }
163
+ }
164
+ return files.map(filePath => {
165
+ // User may provide an absolute path as input in which case
166
+ // fg will return an absolute path.
167
+ if (path_1.default.isAbsolute(filePath)) {
168
+ return filePath;
169
+ }
170
+ // User may also provide a relative path in which case
171
+ // fg will return a path relative to rootDir.
172
+ return path_1.default.join(rootDir, filePath);
173
+ });
174
+ }
175
+ async function uploadAppArchiveAsync(graphqlClient, path) {
176
+ const fileSize = (await fs_extra_1.default.stat(path)).size;
177
+ const bucketKey = await (0, uploads_1.uploadFileAtPathToGCSAsync)(graphqlClient, generated_1.UploadSessionType.EasShareGcsAppArchive, path, (0, progress_1.createProgressTracker)({
178
+ total: fileSize,
179
+ message: 'Uploading to EAS',
180
+ completedMessage: 'Uploaded to EAS',
181
+ }));
182
+ return bucketKey;
183
+ }
184
+ async function extractAppMetadataAsync(buildPath, platform) {
185
+ let developmentClient = false;
186
+ let fingerprintHash;
187
+ const simulator = platform === eas_build_job_1.Platform.IOS;
188
+ const basePath = platform === eas_build_job_1.Platform.ANDROID ? 'assets/' : buildPath;
189
+ const fingerprintFilePath = platform === eas_build_job_1.Platform.ANDROID ? 'fingerprint' : 'EXUpdates.bundle/fingerprint';
190
+ const devMenuBundlePath = platform === eas_build_job_1.Platform.ANDROID ? 'EXDevMenuApp.android.js' : 'EXDevMenu.bundle/';
191
+ const buildExtension = path_1.default.extname(buildPath);
192
+ if (['.apk', '.aab'].includes(buildExtension)) {
193
+ const zip = new node_stream_zip_1.default.async({ file: buildPath });
194
+ try {
195
+ developmentClient = Boolean(await zip.entry(path_1.default.join(basePath, devMenuBundlePath)));
196
+ if (await zip.entry(path_1.default.join(basePath, fingerprintFilePath))) {
197
+ fingerprintHash = (await zip.entryData(path_1.default.join(basePath, fingerprintFilePath))).toString('utf-8');
198
+ }
199
+ }
200
+ catch (err) {
201
+ log_1.default.error(`Error reading ${buildExtension}: ${err}`);
202
+ }
203
+ finally {
204
+ await zip.close();
205
+ }
206
+ }
207
+ else if (buildExtension === '.app') {
208
+ developmentClient = await fs_extra_1.default.exists(path_1.default.join(basePath, devMenuBundlePath));
209
+ if (await fs_extra_1.default.exists(path_1.default.join(basePath, fingerprintFilePath))) {
210
+ fingerprintHash = await fs_extra_1.default.readFile(path_1.default.join(basePath, fingerprintFilePath), 'utf8');
211
+ }
212
+ }
213
+ else {
214
+ // Use tar to list files in the archive
215
+ try {
216
+ let fingerprintHashPromise;
217
+ await tar_1.default.list({
218
+ file: buildPath,
219
+ // eslint-disable-next-line async-protect/async-suffix
220
+ onentry: entry => {
221
+ if (entry.path.endsWith(devMenuBundlePath)) {
222
+ developmentClient = true;
223
+ }
224
+ if (entry.path.endsWith(fingerprintFilePath)) {
225
+ fingerprintHashPromise = new Promise(async (resolve, reject) => {
226
+ try {
227
+ let content = '';
228
+ for await (const chunk of entry) {
229
+ content += chunk.toString('utf8');
230
+ }
231
+ resolve(content);
232
+ }
233
+ catch (error) {
234
+ reject(error);
235
+ }
236
+ });
237
+ }
238
+ },
239
+ });
240
+ if (fingerprintHashPromise !== undefined) {
241
+ fingerprintHash = await fingerprintHashPromise;
242
+ }
243
+ }
244
+ catch (err) {
245
+ log_1.default.error(`Error reading ${buildExtension}: ${err}`);
246
+ }
247
+ }
248
+ return {
249
+ developmentClient,
250
+ fingerprintHash,
251
+ simulator,
252
+ };
253
+ }
@@ -53,7 +53,7 @@ async function ensureInternalGroupAsync({ groups, app, }) {
53
53
  }, {
54
54
  shouldRetry(error) {
55
55
  if ((0, ensureAppExists_1.isAppleError)(error)) {
56
- spinner.text = `TestFlight not ready, retrying in 25 seconds...`;
56
+ spinner.text = `TestFlight still preparing, retrying in 10 seconds...`;
57
57
  return error.data.errors.some(error => error.code === 'ENTITY_ERROR.RELATIONSHIP.INVALID');
58
58
  }
59
59
  return false;
@@ -154,9 +154,10 @@ async function getTestFlightGroupUrlAsync(group) {
154
154
  }
155
155
  return null;
156
156
  }
157
- async function pollRetryAsync(fn, { shouldRetry, retries = 10,
157
+ async function pollRetryAsync(fn, { shouldRetry, retries = 15,
158
158
  // 25 seconds was the minium interval I calculated when measuring against 5 second intervals.
159
- interval = 25000, } = {}) {
159
+ // Switching to 10 seconds to account for days where Apple APIs are faster.
160
+ interval = 10000, } = {}) {
160
161
  let lastError = null;
161
162
  for (let i = 0; i < retries; i++) {
162
163
  try {