eas-cli 1.1.0 → 2.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 (49) hide show
  1. package/README.md +68 -46
  2. package/build/analytics/events.d.ts +4 -1
  3. package/build/analytics/events.js +3 -0
  4. package/build/build/createContext.js +1 -0
  5. package/build/build/ios/graphql.js +0 -1
  6. package/build/commandUtils/EasCommand.d.ts +2 -0
  7. package/build/commandUtils/EasCommand.js +44 -3
  8. package/build/commands/build/index.d.ts +2 -1
  9. package/build/commands/build/index.js +36 -18
  10. package/build/commands/submit.d.ts +2 -1
  11. package/build/commands/submit.js +28 -14
  12. package/build/commands/update/configure.js +54 -1
  13. package/build/commands/update/index.js +3 -0
  14. package/build/credentials/android/api/GraphqlClient.js +1 -1
  15. package/build/credentials/context.d.ts +3 -0
  16. package/build/credentials/context.js +1 -0
  17. package/build/credentials/ios/IosCredentialsProvider.d.ts +1 -0
  18. package/build/credentials/ios/IosCredentialsProvider.js +33 -5
  19. package/build/credentials/ios/api/GraphqlClient.js +1 -1
  20. package/build/credentials/ios/appstore/ascApiKey.d.ts +11 -0
  21. package/build/credentials/ios/appstore/ascApiKey.js +53 -3
  22. package/build/credentials/ios/appstore/bundleIdCapabilities.js +176 -15
  23. package/build/credentials/manager/ManageAndroid.js +2 -8
  24. package/build/credentials/manager/ManageIos.js +2 -4
  25. package/build/graphql/generated.d.ts +191 -39
  26. package/build/graphql/generated.js +39 -1
  27. package/build/{credentials/ios/api/graphql → graphql}/queries/AppQuery.d.ts +1 -1
  28. package/build/{credentials/ios/api/graphql → graphql}/queries/AppQuery.js +2 -2
  29. package/build/graphql/queries/EnvironmentSecretsQuery.d.ts +1 -1
  30. package/build/graphql/queries/EnvironmentSecretsQuery.js +2 -2
  31. package/build/graphql/queries/StatuspageServiceQuery.d.ts +4 -0
  32. package/build/graphql/queries/StatuspageServiceQuery.js +28 -0
  33. package/build/graphql/types/StatuspageService.d.ts +1 -0
  34. package/build/graphql/types/StatuspageService.js +19 -0
  35. package/build/project/ensureProjectExists.d.ts +0 -7
  36. package/build/project/ensureProjectExists.js +4 -14
  37. package/build/project/projectUtils.d.ts +0 -1
  38. package/build/project/projectUtils.js +9 -29
  39. package/build/submit/android/AndroidSubmitter.js +1 -1
  40. package/build/submit/submit.js +2 -2
  41. package/build/submit/utils/errors.js +2 -0
  42. package/build/utils/statuspageService.d.ts +2 -0
  43. package/build/utils/statuspageService.js +41 -0
  44. package/build/vcs/local.d.ts +1 -0
  45. package/build/vcs/local.js +11 -2
  46. package/oclif.manifest.json +1 -1
  47. package/package.json +4 -4
  48. package/build/graphql/queries/ProjectQuery.d.ts +0 -6
  49. package/build/graphql/queries/ProjectQuery.js +0 -24
@@ -14,5 +14,6 @@ export default class Submit extends EasCommand {
14
14
  'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
15
15
  };
16
16
  runAsync(): Promise<void>;
17
- private sanitizeFlagsAsync;
17
+ private sanitizeFlags;
18
+ private ensurePlatformSelectedAsync;
18
19
  }
@@ -5,6 +5,7 @@ const eas_json_1 = require("@expo/eas-json");
5
5
  const core_1 = require("@oclif/core");
6
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
7
  const EasCommand_1 = tslib_1.__importDefault(require("../commandUtils/EasCommand"));
8
+ const generated_1 = require("../graphql/generated");
8
9
  const AppPlatform_1 = require("../graphql/types/AppPlatform");
9
10
  const log_1 = tslib_1.__importDefault(require("../log"));
10
11
  const platform_1 = require("../platform");
@@ -14,19 +15,22 @@ const context_1 = require("../submit/context");
14
15
  const submit_1 = require("../submit/submit");
15
16
  const urls_1 = require("../submit/utils/urls");
16
17
  const profiles_1 = require("../utils/profiles");
18
+ const statuspageService_1 = require("../utils/statuspageService");
17
19
  class Submit extends EasCommand_1.default {
18
20
  async runAsync() {
19
21
  const { flags: rawFlags } = await this.parse(Submit);
20
- const flags = await this.sanitizeFlagsAsync(rawFlags);
22
+ const flags = this.sanitizeFlags(rawFlags);
21
23
  const projectDir = await (0, projectUtils_1.findProjectRootAsync)();
22
24
  const exp = (0, expoConfig_1.getExpoConfig)(projectDir);
23
25
  const projectId = await (0, projectUtils_1.getProjectIdAsync)(exp);
24
- const platforms = (0, platform_1.toPlatforms)(flags.requestedPlatform);
26
+ await (0, statuspageService_1.maybeWarnAboutEasOutagesAsync)([generated_1.StatuspageServiceName.EasSubmit]);
27
+ const flagsWithPlatform = await this.ensurePlatformSelectedAsync(flags);
28
+ const platforms = (0, platform_1.toPlatforms)(flagsWithPlatform.requestedPlatform);
25
29
  const submissionProfiles = await (0, profiles_1.getProfilesAsync)({
26
30
  type: 'submit',
27
31
  easJsonReader: new eas_json_1.EasJsonReader(projectDir),
28
32
  platforms,
29
- profileName: flags.profile,
33
+ profileName: flagsWithPlatform.profile,
30
34
  });
31
35
  const submissions = [];
32
36
  for (const submissionProfile of submissionProfiles) {
@@ -35,8 +39,8 @@ class Submit extends EasCommand_1.default {
35
39
  projectDir,
36
40
  projectId,
37
41
  profile: submissionProfile.profile,
38
- archiveFlags: flags.archiveFlags,
39
- nonInteractive: flags.nonInteractive,
42
+ archiveFlags: flagsWithPlatform.archiveFlags,
43
+ nonInteractive: flagsWithPlatform.nonInteractive,
40
44
  });
41
45
  if (submissionProfiles.length > 1) {
42
46
  log_1.default.newLine();
@@ -48,24 +52,22 @@ class Submit extends EasCommand_1.default {
48
52
  }
49
53
  log_1.default.newLine();
50
54
  (0, urls_1.printSubmissionDetailsUrls)(submissions);
51
- if (flags.wait) {
55
+ if (flagsWithPlatform.wait) {
52
56
  const completedSubmissions = await (0, submit_1.waitToCompleteAsync)(submissions, {
53
- verbose: flags.verbose,
57
+ verbose: flagsWithPlatform.verbose,
54
58
  });
55
59
  (0, submit_1.exitWithNonZeroCodeIfSomeSubmissionsDidntFinish)(completedSubmissions);
56
60
  }
57
61
  }
58
- async sanitizeFlagsAsync(flags) {
62
+ sanitizeFlags(flags) {
59
63
  const { platform, verbose, wait, profile, 'non-interactive': nonInteractive, ...archiveFlags } = flags;
60
64
  if (!flags.platform && nonInteractive) {
61
65
  core_1.Errors.error('--platform is required when building in non-interactive mode', { exit: 1 });
62
66
  }
63
- const requestedPlatform = await (0, platform_1.selectRequestedPlatformAsync)(flags.platform);
64
- if (requestedPlatform === platform_1.RequestedPlatform.All) {
65
- if (archiveFlags.id || archiveFlags.path || archiveFlags.url) {
66
- core_1.Errors.error('--id, --path, and --url params are only supported when performing a single-platform submit', { exit: 1 });
67
- }
68
- }
67
+ const requestedPlatform = flags.platform &&
68
+ Object.values(platform_1.RequestedPlatform).includes(flags.platform.toLowerCase())
69
+ ? flags.platform.toLowerCase()
70
+ : undefined;
69
71
  return {
70
72
  archiveFlags,
71
73
  requestedPlatform,
@@ -75,6 +77,18 @@ class Submit extends EasCommand_1.default {
75
77
  nonInteractive,
76
78
  };
77
79
  }
80
+ async ensurePlatformSelectedAsync(flags) {
81
+ const requestedPlatform = await (0, platform_1.selectRequestedPlatformAsync)(flags.requestedPlatform);
82
+ if (requestedPlatform === platform_1.RequestedPlatform.All) {
83
+ if (flags.archiveFlags.id || flags.archiveFlags.path || flags.archiveFlags.url) {
84
+ core_1.Errors.error('--id, --path, and --url params are only supported when performing a single-platform submit', { exit: 1 });
85
+ }
86
+ }
87
+ return {
88
+ ...flags,
89
+ requestedPlatform,
90
+ };
91
+ }
78
92
  }
79
93
  exports.default = Submit;
80
94
  Submit.description = 'submit app binary to App Store and/or Play Store';
@@ -81,6 +81,7 @@ async function configureAppJSONForEASUpdateAsync({ projectDir, exp, platform, wo
81
81
  const newAndroidRuntimeVersion = (_c = (_b = (_a = exp.android) === null || _a === void 0 ? void 0 : _a.runtimeVersion) !== null && _b !== void 0 ? _b : exp.runtimeVersion) !== null && _c !== void 0 ? _c : androidDefaultRuntimeVersion;
82
82
  const newIosRuntimeVersion = (_f = (_e = (_d = exp.ios) === null || _d === void 0 ? void 0 : _d.runtimeVersion) !== null && _e !== void 0 ? _e : exp.runtimeVersion) !== null && _f !== void 0 ? _f : iosDefaultRuntimeVersion;
83
83
  let newConfig;
84
+ let newConfigOnlyAddedValues;
84
85
  switch (platform) {
85
86
  case platform_1.RequestedPlatform.All: {
86
87
  if (isRuntimeEqual(newAndroidRuntimeVersion, newIosRuntimeVersion)) {
@@ -93,6 +94,26 @@ async function configureAppJSONForEASUpdateAsync({ projectDir, exp, platform, wo
93
94
  ios: { ...exp.ios, runtimeVersion: undefined },
94
95
  updates,
95
96
  };
97
+ newConfigOnlyAddedValues = {
98
+ runtimeVersion: newAndroidRuntimeVersion,
99
+ ...(exp.android && 'runtimeVersion' in exp.android
100
+ ? {
101
+ android: {
102
+ runtimeVersion: '<remove this key>',
103
+ },
104
+ }
105
+ : {}),
106
+ ...(exp.ios && 'runtimeVersion' in exp.ios
107
+ ? {
108
+ ios: {
109
+ runtimeVersion: '<remove this key>',
110
+ },
111
+ }
112
+ : {}),
113
+ updates: {
114
+ url: easUpdateURL,
115
+ },
116
+ };
96
117
  }
97
118
  else {
98
119
  newConfig = {
@@ -107,6 +128,22 @@ async function configureAppJSONForEASUpdateAsync({ projectDir, exp, platform, wo
107
128
  },
108
129
  updates,
109
130
  };
131
+ newConfigOnlyAddedValues = {
132
+ ...('runtimeVersion' in exp
133
+ ? {
134
+ runtimeVersion: '<remove this key>', // top level runtime is redundant if it is specified in both android and ios
135
+ }
136
+ : {}),
137
+ android: {
138
+ runtimeVersion: newAndroidRuntimeVersion,
139
+ },
140
+ ios: {
141
+ runtimeVersion: newIosRuntimeVersion,
142
+ },
143
+ updates: {
144
+ url: easUpdateURL,
145
+ },
146
+ };
110
147
  }
111
148
  break;
112
149
  }
@@ -118,6 +155,14 @@ async function configureAppJSONForEASUpdateAsync({ projectDir, exp, platform, wo
118
155
  },
119
156
  updates,
120
157
  };
158
+ newConfigOnlyAddedValues = {
159
+ android: {
160
+ runtimeVersion: newAndroidRuntimeVersion,
161
+ },
162
+ updates: {
163
+ url: easUpdateURL,
164
+ },
165
+ };
121
166
  break;
122
167
  }
123
168
  case platform_1.RequestedPlatform.Ios: {
@@ -128,6 +173,14 @@ async function configureAppJSONForEASUpdateAsync({ projectDir, exp, platform, wo
128
173
  },
129
174
  updates,
130
175
  };
176
+ newConfigOnlyAddedValues = {
177
+ ios: {
178
+ runtimeVersion: newIosRuntimeVersion,
179
+ },
180
+ updates: {
181
+ url: easUpdateURL,
182
+ },
183
+ };
131
184
  break;
132
185
  }
133
186
  default: {
@@ -160,7 +213,7 @@ async function configureAppJSONForEASUpdateAsync({ projectDir, exp, platform, wo
160
213
  log_1.default.addNewLineIfNone();
161
214
  log_1.default.warn(`It looks like you are using a dynamic configuration! ${(0, log_1.learnMore)('https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs)')}`);
162
215
  log_1.default.warn(`In order to finish configuring your project for EAS Update, you are going to need manually add the following to your app.config.js:\n${(0, log_1.learnMore)('https://expo.fyi/eas-update-config.md')}\n`);
163
- log_1.default.log(chalk_1.default.bold(JSON.stringify(newConfig, null, 2)));
216
+ log_1.default.log(chalk_1.default.bold(JSON.stringify(newConfigOnlyAddedValues, null, 2)));
164
217
  log_1.default.addNewLineIfNone();
165
218
  if (workflows['android'] === eas_build_job_1.Workflow.GENERIC || workflows['ios'] === eas_build_job_1.Workflow.GENERIC) {
166
219
  log_1.default.warn(`You will also have to manually edit the projects ${chalk_1.default.bold('Expo.plist/AndroidManifest.xml')}. ${(0, log_1.learnMore)('https://expo.fyi/eas-update-config.md#native-configuration')}`);
@@ -16,6 +16,7 @@ const url_1 = require("../../build/utils/url");
16
16
  const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
17
17
  const fetch_1 = tslib_1.__importDefault(require("../../fetch"));
18
18
  const client_1 = require("../../graphql/client");
19
+ const generated_1 = require("../../graphql/generated");
19
20
  const PublishMutation_1 = require("../../graphql/mutations/PublishMutation");
20
21
  const BranchQuery_1 = require("../../graphql/queries/BranchQuery");
21
22
  const UpdateQuery_1 = require("../../graphql/queries/UpdateQuery");
@@ -32,6 +33,7 @@ const code_signing_1 = require("../../utils/code-signing");
32
33
  const uniqBy_1 = tslib_1.__importDefault(require("../../utils/expodash/uniqBy"));
33
34
  const formatFields_1 = tslib_1.__importDefault(require("../../utils/formatFields"));
34
35
  const json_1 = require("../../utils/json");
36
+ const statuspageService_1 = require("../../utils/statuspageService");
35
37
  const vcs_1 = require("../../vcs");
36
38
  const create_1 = require("../branch/create");
37
39
  const create_2 = require("../channel/create");
@@ -109,6 +111,7 @@ class UpdatePublish extends EasCommand_1.default {
109
111
  const expPrivate = (0, expoConfig_1.getExpoConfig)(projectDir, {
110
112
  isPublicConfig: false,
111
113
  });
114
+ await (0, statuspageService_1.maybeWarnAboutEasOutagesAsync)([generated_1.StatuspageServiceName.EasUpdate]);
112
115
  const codeSigningInfo = await (0, code_signing_1.getCodeSigningInfoAsync)(expPrivate, privateKeyPath);
113
116
  const hasExpoUpdates = (0, projectUtils_1.isExpoUpdatesInstalledOrAvailable)(projectDir, exp.sdkVersion);
114
117
  if (!hasExpoUpdates && nonInteractive) {
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatProjectFullName = exports.getGoogleServiceAccountKeysForAccountAsync = exports.deleteGoogleServiceAccountKeyAsync = exports.createGoogleServiceAccountKeyAsync = exports.deleteFcmAsync = exports.createFcmAsync = exports.deleteKeystoreAsync = exports.createKeystoreAsync = exports.createOrUpdateDefaultIosAppBuildCredentialsAsync = exports.createOrUpdateAndroidAppBuildCredentialsByNameAsync = exports.getAndroidAppBuildCredentialsByNameAsync = exports.getDefaultAndroidAppBuildCredentialsAsync = exports.createAndroidAppBuildCredentialsAsync = exports.updateAndroidAppBuildCredentialsAsync = exports.updateAndroidAppCredentialsAsync = exports.createOrGetExistingAndroidAppCredentialsWithBuildCredentialsAsync = exports.getLegacyAndroidAppBuildCredentialsAsync = exports.getLegacyAndroidAppCredentialsWithCommonFieldsAsync = exports.getAndroidAppBuildCredentialsListAsync = exports.getAndroidAppCredentialsWithCommonFieldsAsync = void 0;
4
- const AppQuery_1 = require("../../ios/api/graphql/queries/AppQuery");
4
+ const AppQuery_1 = require("../../../graphql/queries/AppQuery");
5
5
  const AndroidAppBuildCredentialsMutation_1 = require("./graphql/mutations/AndroidAppBuildCredentialsMutation");
6
6
  const AndroidAppCredentialsMutation_1 = require("./graphql/mutations/AndroidAppCredentialsMutation");
7
7
  const AndroidFcmMutation_1 = require("./graphql/mutations/AndroidFcmMutation");
@@ -1,5 +1,6 @@
1
1
  import { ExpoConfig } from '@expo/config';
2
2
  import { Env } from '@expo/eas-build-job';
3
+ import { EasJson } from '@expo/eas-json';
3
4
  import { Actor } from '../user/User';
4
5
  import * as AndroidGraphqlClient from './android/api/GraphqlClient';
5
6
  import * as IosGraphqlClient from './ios/api/GraphqlClient';
@@ -12,10 +13,12 @@ export declare class CredentialsContext {
12
13
  readonly nonInteractive: boolean;
13
14
  readonly projectDir: string;
14
15
  readonly user: Actor;
16
+ readonly easJsonCliConfig?: EasJson['cli'];
15
17
  private shouldAskAuthenticateAppStore;
16
18
  private resolvedExp?;
17
19
  constructor(options: {
18
20
  exp?: ExpoConfig;
21
+ easJsonCliConfig?: EasJson['cli'];
19
22
  nonInteractive?: boolean;
20
23
  projectDir: string;
21
24
  user: Actor;
@@ -20,6 +20,7 @@ class CredentialsContext {
20
20
  this.appStore = new AppStoreApi_1.default();
21
21
  this.ios = IosGraphqlClient;
22
22
  this.shouldAskAuthenticateAppStore = true;
23
+ this.easJsonCliConfig = options.easJsonCliConfig;
23
24
  this.projectDir = options.projectDir;
24
25
  this.user = options.user;
25
26
  this.nonInteractive = (_a = options.nonInteractive) !== null && _a !== void 0 ? _a : false;
@@ -17,6 +17,7 @@ export default class IosCredentialsProvider {
17
17
  private getLocalAsync;
18
18
  private getRemoteAsync;
19
19
  private getPushKeyAsync;
20
+ private disablePushNotificationsSetupInEasJsonAsync;
20
21
  private assertProvisioningProfileType;
21
22
  }
22
23
  export {};
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
4
  const eas_build_job_1 = require("@expo/eas-build-job");
5
5
  const eas_json_1 = require("@expo/eas-json");
6
+ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
6
7
  const log_1 = tslib_1.__importDefault(require("../../log"));
7
8
  const target_1 = require("../../project/ios/target");
8
9
  const prompts_1 = require("../../prompts");
@@ -12,6 +13,12 @@ const BuildCredentialsUtils_1 = require("./actions/BuildCredentialsUtils");
12
13
  const SetUpBuildCredentials_1 = require("./actions/SetUpBuildCredentials");
13
14
  const SetUpPushKey_1 = require("./actions/SetUpPushKey");
14
15
  const provisioningProfile_1 = require("./utils/provisioningProfile");
16
+ var PushNotificationSetupOption;
17
+ (function (PushNotificationSetupOption) {
18
+ PushNotificationSetupOption[PushNotificationSetupOption["YES"] = 0] = "YES";
19
+ PushNotificationSetupOption[PushNotificationSetupOption["NO"] = 1] = "NO";
20
+ PushNotificationSetupOption[PushNotificationSetupOption["NO_DONT_ASK_AGAIN"] = 2] = "NO_DONT_ASK_AGAIN";
21
+ })(PushNotificationSetupOption || (PushNotificationSetupOption = {}));
15
22
  class IosCredentialsProvider {
16
23
  constructor(ctx, options) {
17
24
  this.ctx = ctx;
@@ -47,6 +54,7 @@ class IosCredentialsProvider {
47
54
  }).runAsync(this.ctx);
48
55
  }
49
56
  async getPushKeyAsync(ctx, targets) {
57
+ var _a;
50
58
  if (ctx.nonInteractive) {
51
59
  return null;
52
60
  }
@@ -63,13 +71,33 @@ class IosCredentialsProvider {
63
71
  log_1.default.succeed(`Push Notifications setup for ${app.projectName}: ${applicationTarget.bundleIdentifier}`);
64
72
  return null;
65
73
  }
66
- const confirmSetup = await (0, prompts_1.confirmAsync)({
67
- message: `Would you like to set up Push Notifications for your project?`,
68
- });
69
- if (!confirmSetup) {
74
+ if (((_a = ctx.easJsonCliConfig) === null || _a === void 0 ? void 0 : _a.promptToConfigurePushNotifications) === false) {
70
75
  return null;
71
76
  }
72
- return await setupPushKeyAction.runAsync(ctx);
77
+ const setupOption = await (0, prompts_1.selectAsync)(`Would you like to set up Push Notifications for your project?`, [
78
+ { title: 'Yes', value: PushNotificationSetupOption.YES },
79
+ { title: 'No', value: PushNotificationSetupOption.NO },
80
+ {
81
+ title: `No, don't ask again (preference will be saved to eas.json)`,
82
+ value: PushNotificationSetupOption.NO_DONT_ASK_AGAIN,
83
+ },
84
+ ]);
85
+ if (setupOption === PushNotificationSetupOption.YES) {
86
+ return await setupPushKeyAction.runAsync(ctx);
87
+ }
88
+ else {
89
+ if (setupOption === PushNotificationSetupOption.NO_DONT_ASK_AGAIN) {
90
+ await this.disablePushNotificationsSetupInEasJsonAsync(ctx);
91
+ }
92
+ return null;
93
+ }
94
+ }
95
+ async disablePushNotificationsSetupInEasJsonAsync(ctx) {
96
+ const easJsonPath = eas_json_1.EasJsonReader.formatEasJsonPath(ctx.projectDir);
97
+ const easJson = await fs_extra_1.default.readJSON(easJsonPath);
98
+ easJson.cli = { ...easJson === null || easJson === void 0 ? void 0 : easJson.cli, promptToConfigurePushNotifications: false };
99
+ await fs_extra_1.default.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
100
+ log_1.default.withTick('Updated eas.json');
73
101
  }
74
102
  assertProvisioningProfileType(provisioningProfile, targetName) {
75
103
  const isAdHoc = (0, provisioningProfile_1.isAdHocProfile)(provisioningProfile);
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
5
5
  const apple_utils_1 = require("@expo/apple-utils");
6
6
  const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
7
7
  const generated_1 = require("../../../graphql/generated");
8
+ const AppQuery_1 = require("../../../graphql/queries/AppQuery");
8
9
  const bundleIdentifier_1 = require("../../../project/ios/bundleIdentifier");
9
10
  const errors_1 = require("../errors");
10
11
  const AppStoreConnectApiKeyMutation_1 = require("./graphql/mutations/AppStoreConnectApiKeyMutation");
@@ -15,7 +16,6 @@ const ApplePushKeyMutation_1 = require("./graphql/mutations/ApplePushKeyMutation
15
16
  const AppleTeamMutation_1 = require("./graphql/mutations/AppleTeamMutation");
16
17
  const IosAppBuildCredentialsMutation_1 = require("./graphql/mutations/IosAppBuildCredentialsMutation");
17
18
  const IosAppCredentialsMutation_1 = require("./graphql/mutations/IosAppCredentialsMutation");
18
- const AppQuery_1 = require("./graphql/queries/AppQuery");
19
19
  const AppStoreConnectApiKeyQuery_1 = require("./graphql/queries/AppStoreConnectApiKeyQuery");
20
20
  const AppleAppIdentifierQuery_1 = require("./graphql/queries/AppleAppIdentifierQuery");
21
21
  const AppleDeviceQuery_1 = require("./graphql/queries/AppleDeviceQuery");
@@ -12,6 +12,17 @@ export declare function listAscApiKeysAsync(userAuthCtx: UserAuthCtx): Promise<A
12
12
  * **Does not support App Store Connect API (CI).**
13
13
  */
14
14
  export declare function getAscApiKeyAsync(userAuthCtx: UserAuthCtx, keyId: string): Promise<AscApiKeyInfo | null>;
15
+ /**
16
+ * There is a bug in Apple's infrastructure that does not propagate newly created objects for a
17
+ * while. If the key has not propagated and you try to download it, Apple will error saying that
18
+ * the resource does not exist. We retry with exponential backoff until the key propagates and
19
+ * is available for download.
20
+ * */
21
+ export declare function downloadWithRetryAsync(key: ApiKey, { minTimeout, retries, factor, }?: {
22
+ minTimeout?: number;
23
+ retries?: number;
24
+ factor?: number;
25
+ }): Promise<string | null>;
15
26
  /**
16
27
  * Create an App Store Connect API Key.
17
28
  * **Does not support App Store Connect API (CI).**
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getAscApiKeyInfo = exports.revokeAscApiKeyAsync = exports.createAscApiKeyAsync = exports.getAscApiKeyAsync = exports.listAscApiKeysAsync = void 0;
3
+ exports.getAscApiKeyInfo = exports.revokeAscApiKeyAsync = exports.createAscApiKeyAsync = exports.downloadWithRetryAsync = exports.getAscApiKeyAsync = exports.listAscApiKeysAsync = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const apple_utils_1 = require("@expo/apple-utils");
6
- const log_1 = tslib_1.__importDefault(require("../../../log"));
6
+ const promise_retry_1 = tslib_1.__importDefault(require("promise-retry"));
7
+ const events_1 = require("../../../analytics/events");
8
+ const log_1 = tslib_1.__importStar(require("../../../log"));
7
9
  const ora_1 = require("../../../ora");
8
10
  const authenticate_1 = require("./authenticate");
9
11
  /**
@@ -49,6 +51,54 @@ async function getAscApiKeyAsync(userAuthCtx, keyId) {
49
51
  }
50
52
  }
51
53
  exports.getAscApiKeyAsync = getAscApiKeyAsync;
54
+ /**
55
+ * There is a bug in Apple's infrastructure that does not propagate newly created objects for a
56
+ * while. If the key has not propagated and you try to download it, Apple will error saying that
57
+ * the resource does not exist. We retry with exponential backoff until the key propagates and
58
+ * is available for download.
59
+ * */
60
+ async function downloadWithRetryAsync(key, { minTimeout = 1000, retries = 6, factor = 2, } = {}) {
61
+ const RESOURCE_DOES_NOT_EXIST_MESSAGE = 'The specified resource does not exist - There is no resource of type';
62
+ try {
63
+ const keyP8 = await (0, promise_retry_1.default)(async (retry, number) => {
64
+ try {
65
+ return await key.downloadAsync();
66
+ }
67
+ catch (e) {
68
+ if (e.name === 'UnexpectedAppleResponse' &&
69
+ e.message.includes(RESOURCE_DOES_NOT_EXIST_MESSAGE)) {
70
+ const secondsToRetry = Math.pow(factor, number);
71
+ log_1.default.log(`Received an unexpected response from Apple, retrying in ${secondsToRetry} seconds...`);
72
+ events_1.Analytics.logEvent(events_1.SubmissionEvent.API_KEY_DOWNLOAD_RETRY, {
73
+ errorName: e.name,
74
+ reason: e.message,
75
+ retry: number,
76
+ });
77
+ return retry(e);
78
+ }
79
+ throw e;
80
+ }
81
+ }, {
82
+ retries,
83
+ factor,
84
+ minTimeout,
85
+ });
86
+ events_1.Analytics.logEvent(events_1.SubmissionEvent.API_KEY_DOWNLOAD_SUCCESS, {});
87
+ return keyP8;
88
+ }
89
+ catch (e) {
90
+ if (e.name === 'UnexpectedAppleResponse' &&
91
+ e.message.includes(RESOURCE_DOES_NOT_EXIST_MESSAGE)) {
92
+ log_1.default.warn(`Unable to download Api Key from Apple at this time. Create and upload your key manually by running 'eas credentials' ${(0, log_1.learnMore)('https://expo.fyi/creating-asc-api-key')}`);
93
+ }
94
+ events_1.Analytics.logEvent(events_1.SubmissionEvent.API_KEY_DOWNLOAD_FAIL, {
95
+ errorName: e.name,
96
+ reason: e.message,
97
+ });
98
+ throw e;
99
+ }
100
+ }
101
+ exports.downloadWithRetryAsync = downloadWithRetryAsync;
52
102
  /**
53
103
  * Create an App Store Connect API Key.
54
104
  * **Does not support App Store Connect API (CI).**
@@ -63,7 +113,7 @@ async function createAscApiKeyAsync(userAuthCtx, { nickname, allAppsVisible, rol
63
113
  roles: roles !== null && roles !== void 0 ? roles : [apple_utils_1.UserRole.ADMIN],
64
114
  keyType: keyType !== null && keyType !== void 0 ? keyType : apple_utils_1.ApiKeyType.PUBLIC_API,
65
115
  });
66
- const keyP8 = await key.downloadAsync();
116
+ const keyP8 = await downloadWithRetryAsync(key);
67
117
  if (!keyP8) {
68
118
  const { nickname, roles } = key.attributes;
69
119
  const humanReadableKey = `App Store Connect Key '${nickname}' (${key.id}) with roles {${roles.join(',')}}`;