eas-cli 0.30.0 → 0.32.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 (81) hide show
  1. package/README.md +26 -26
  2. package/build/build/android/prepareJob.js +3 -3
  3. package/build/build/ios/prepareJob.js +3 -14
  4. package/build/build/utils/printBuildInfo.js +1 -1
  5. package/build/build/utils/updates.js +1 -1
  6. package/build/commands/branch/create.js +1 -4
  7. package/build/commands/branch/delete.js +1 -4
  8. package/build/commands/branch/list.js +1 -4
  9. package/build/commands/branch/publish.js +1 -4
  10. package/build/commands/branch/rename.js +1 -4
  11. package/build/commands/branch/view.js +1 -4
  12. package/build/commands/build/cancel.js +1 -2
  13. package/build/commands/build/configure.js +2 -2
  14. package/build/commands/build/index.js +3 -3
  15. package/build/commands/build/list.js +1 -2
  16. package/build/commands/build/view.js +1 -2
  17. package/build/commands/channel/create.js +1 -4
  18. package/build/commands/channel/edit.js +1 -4
  19. package/build/commands/channel/list.js +1 -4
  20. package/build/commands/channel/rollout.js +1 -4
  21. package/build/commands/channel/view.js +1 -4
  22. package/build/commands/config.js +1 -2
  23. package/build/commands/device/list.js +1 -2
  24. package/build/commands/device/view.js +1 -2
  25. package/build/commands/project/info.js +1 -4
  26. package/build/commands/project/init.js +1 -4
  27. package/build/commands/secret/create.js +1 -2
  28. package/build/commands/secret/delete.js +1 -2
  29. package/build/commands/secret/list.js +1 -2
  30. package/build/commands/submit.js +2 -5
  31. package/build/commands/webhook/create.js +1 -4
  32. package/build/commands/webhook/delete.js +1 -4
  33. package/build/commands/webhook/list.js +1 -4
  34. package/build/credentials/android/actions/SetupGoogleServiceAccountKey.js +3 -3
  35. package/build/credentials/android/credentials.d.ts +2 -0
  36. package/build/credentials/android/utils/googleServiceAccountKey.js +1 -0
  37. package/build/credentials/context.d.ts +1 -1
  38. package/build/credentials/context.js +5 -5
  39. package/build/credentials/errors.d.ts +3 -0
  40. package/build/credentials/errors.js +7 -1
  41. package/build/credentials/ios/actions/AscApiKeyUtils.d.ts +5 -0
  42. package/build/credentials/ios/actions/AscApiKeyUtils.js +63 -0
  43. package/build/credentials/ios/actions/DistributionCertificateUtils.js +5 -5
  44. package/build/credentials/ios/actions/SetupAdhocProvisioningProfile.d.ts +1 -0
  45. package/build/credentials/ios/actions/SetupAdhocProvisioningProfile.js +48 -4
  46. package/build/credentials/ios/appstore/AppStoreApi.d.ts +7 -1
  47. package/build/credentials/ios/appstore/AppStoreApi.js +17 -0
  48. package/build/credentials/ios/appstore/Credentials.js +3 -3
  49. package/build/credentials/ios/appstore/Credentials.types.d.ts +13 -0
  50. package/build/credentials/ios/appstore/ascApiKey.d.ts +9 -0
  51. package/build/credentials/ios/appstore/ascApiKey.js +99 -0
  52. package/build/credentials/ios/credentials.d.ts +17 -0
  53. package/build/credentials/ios/credentials.js +16 -1
  54. package/build/credentials/ios/utils/printCredentials.js +12 -7
  55. package/build/devices/context.js +1 -1
  56. package/build/graphql/generated.d.ts +24 -1
  57. package/build/graphql/generated.js +2 -0
  58. package/build/log.d.ts +11 -1
  59. package/build/log.js +21 -10
  60. package/build/project/projectUtils.d.ts +4 -1
  61. package/build/project/projectUtils.js +13 -3
  62. package/build/submit/ArchiveSource.d.ts +7 -2
  63. package/build/submit/ArchiveSource.js +91 -8
  64. package/build/submit/android/AndroidSubmitCommand.js +3 -8
  65. package/build/submit/android/AndroidSubmitter.js +31 -9
  66. package/build/submit/android/ServiceAccountSource.d.ts +24 -5
  67. package/build/submit/android/ServiceAccountSource.js +54 -75
  68. package/build/submit/context.d.ts +4 -0
  69. package/build/submit/context.js +7 -1
  70. package/build/submit/ios/AscApiKeySource.d.ts +28 -0
  71. package/build/submit/ios/AscApiKeySource.js +51 -0
  72. package/build/submit/ios/IosSubmitCommand.d.ts +2 -0
  73. package/build/submit/ios/IosSubmitCommand.js +51 -3
  74. package/build/submit/ios/IosSubmitter.d.ts +3 -1
  75. package/build/submit/ios/IosSubmitter.js +48 -4
  76. package/build/submit/submit.js +1 -1
  77. package/build/submit/utils/builds.d.ts +3 -1
  78. package/build/submit/utils/builds.js +6 -6
  79. package/build/utils/json.js +2 -6
  80. package/oclif.manifest.json +1 -1
  81. package/package.json +8 -7
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.provisioningProfileSchema = exports.pushKeySchema = exports.distributionCertificateSchema = exports.getAppLookupParams = void 0;
3
+ exports.provisioningProfileSchema = exports.pushKeySchema = exports.ascApiKeyMetadataSchema = exports.distributionCertificateSchema = exports.getAppLookupParams = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const log_1 = (0, tslib_1.__importDefault)(require("../../log"));
6
6
  const p12Certificate_1 = require("./utils/p12Certificate");
@@ -50,6 +50,21 @@ exports.distributionCertificateSchema = {
50
50
  return answers;
51
51
  },
52
52
  };
53
+ exports.ascApiKeyMetadataSchema = {
54
+ name: 'App Store Connect API Key',
55
+ questions: [
56
+ {
57
+ field: 'keyId',
58
+ type: 'string',
59
+ question: 'Key ID:',
60
+ },
61
+ {
62
+ field: 'issuerId',
63
+ type: 'string',
64
+ question: 'Issuer ID:',
65
+ },
66
+ ],
67
+ };
53
68
  exports.pushKeySchema = {
54
69
  name: 'Apple Push Notifications service key',
55
70
  questions: [
@@ -99,10 +99,9 @@ function displayProjectCredentials(app, appBuildCredentials, targets) {
99
99
  }, {});
100
100
  const isMultitarget = targets.length > 1;
101
101
  log_1.default.addNewLineIfNone();
102
- const fields = [
103
- { label: 'Project Credentials Configuration', value: '' },
104
- { label: 'Project', value: projectFullName },
105
- ];
102
+ log_1.default.log(chalk_1.default.cyan.bold('Project Credentials Configuration'));
103
+ log_1.default.newLine();
104
+ const fields = [{ label: 'Project', value: projectFullName }];
106
105
  for (const [targetName, buildCredentials] of Object.entries(appBuildCredentials)) {
107
106
  if (isMultitarget) {
108
107
  fields.push({ label: '', value: '' });
@@ -115,10 +114,12 @@ function displayProjectCredentials(app, appBuildCredentials, targets) {
115
114
  }
116
115
  exports.displayProjectCredentials = displayProjectCredentials;
117
116
  function displayIosAppBuildCredentials(buildCredentials, fields) {
117
+ fields.push({ label: '', value: '' });
118
118
  fields.push({
119
119
  label: `${prettyIosDistributionType(buildCredentials.iosDistributionType)} Configuration`,
120
120
  value: '',
121
121
  });
122
+ fields.push({ label: '', value: '' });
122
123
  const maybeDistCert = buildCredentials.distributionCertificate;
123
124
  fields.push({ label: 'Distribution Certificate', value: '' });
124
125
  if (maybeDistCert) {
@@ -158,9 +159,13 @@ function displayIosAppBuildCredentials(buildCredentials, fields) {
158
159
  });
159
160
  }
160
161
  if (appleDevices && appleDevices.length > 0) {
161
- fields.push({ label: 'Provisioned devices', value: '' });
162
- for (const appleDevice of appleDevices) {
163
- fields.push({ label: ' -', value: formatAppleDevice(appleDevice) });
162
+ const [firstAppleDevice, ...rest] = appleDevices;
163
+ fields.push({
164
+ label: 'Provisioned devices',
165
+ value: `- ${formatAppleDevice(firstAppleDevice)}`,
166
+ });
167
+ for (const appleDevice of rest) {
168
+ fields.push({ label: '', value: `- ${formatAppleDevice(appleDevice)}` });
164
169
  }
165
170
  }
166
171
  fields.push({ label: 'Updated', value: `${(0, date_1.fromNow)(new Date(updatedAt))} ago` });
@@ -4,7 +4,7 @@ exports.createContextAsync = void 0;
4
4
  const config_1 = require("@expo/config");
5
5
  const projectUtils_1 = require("../project/projectUtils");
6
6
  async function createContextAsync({ appStore, cwd, user, }) {
7
- const projectDir = await (0, projectUtils_1.findProjectRootAsync)(cwd);
7
+ const projectDir = await (0, projectUtils_1.findProjectRootAsync)({ cwd });
8
8
  let exp = null;
9
9
  if (projectDir) {
10
10
  const config = (0, config_1.getConfig)(projectDir, { skipSDKVersionRequirement: true });
@@ -312,12 +312,15 @@ export declare type Offer = {
312
312
  trialLength?: Maybe<Scalars['Int']>;
313
313
  type: OfferType;
314
314
  features?: Maybe<Array<Maybe<Feature>>>;
315
+ prerequisite?: Maybe<OfferPrerequisite>;
315
316
  };
316
317
  export declare enum OfferType {
317
318
  /** Term subscription */
318
319
  Subscription = "SUBSCRIPTION",
319
320
  /** Advanced Purchase of Paid Resource */
320
- Prepaid = "PREPAID"
321
+ Prepaid = "PREPAID",
322
+ /** Addon, or supplementary subscription */
323
+ Addon = "ADDON"
321
324
  }
322
325
  export declare enum Feature {
323
326
  /** Top Tier Support */
@@ -329,6 +332,11 @@ export declare enum Feature {
329
332
  /** Funds support for open source development */
330
333
  OpenSource = "OPEN_SOURCE"
331
334
  }
335
+ export declare type OfferPrerequisite = {
336
+ __typename?: 'OfferPrerequisite';
337
+ type: Scalars['String'];
338
+ stripeIds: Array<Scalars['String']>;
339
+ };
332
340
  export declare type Snack = Project & {
333
341
  __typename?: 'Snack';
334
342
  id: Scalars['ID'];
@@ -1206,6 +1214,7 @@ export declare type UserPermission = {
1206
1214
  };
1207
1215
  export declare type Billing = {
1208
1216
  __typename?: 'Billing';
1217
+ id: Scalars['ID'];
1209
1218
  payment?: Maybe<PaymentDetails>;
1210
1219
  subscription?: Maybe<SubscriptionDetails>;
1211
1220
  /** History of invoices */
@@ -1236,6 +1245,8 @@ export declare type Address = {
1236
1245
  export declare type SubscriptionDetails = {
1237
1246
  __typename?: 'SubscriptionDetails';
1238
1247
  id: Scalars['ID'];
1248
+ planId?: Maybe<Scalars['String']>;
1249
+ addons: Array<AddonDetails>;
1239
1250
  name?: Maybe<Scalars['String']>;
1240
1251
  nextInvoice?: Maybe<Scalars['DateTime']>;
1241
1252
  cancelledAt?: Maybe<Scalars['DateTime']>;
@@ -1244,6 +1255,12 @@ export declare type SubscriptionDetails = {
1244
1255
  trialEnd?: Maybe<Scalars['DateTime']>;
1245
1256
  status?: Maybe<Scalars['String']>;
1246
1257
  };
1258
+ export declare type AddonDetails = {
1259
+ __typename?: 'AddonDetails';
1260
+ id: Scalars['ID'];
1261
+ planId: Scalars['String'];
1262
+ name: Scalars['String'];
1263
+ };
1247
1264
  export declare type Charge = {
1248
1265
  __typename?: 'Charge';
1249
1266
  id: Scalars['ID'];
@@ -2473,10 +2490,16 @@ export declare type CreateIosSubmissionInput = {
2473
2490
  export declare type IosSubmissionConfigInput = {
2474
2491
  appleAppSpecificPasswordId?: Maybe<Scalars['String']>;
2475
2492
  appleAppSpecificPassword?: Maybe<Scalars['String']>;
2493
+ ascApiKey?: Maybe<AscApiKeyInput>;
2476
2494
  archiveUrl?: Maybe<Scalars['String']>;
2477
2495
  appleIdUsername: Scalars['String'];
2478
2496
  ascAppIdentifier: Scalars['String'];
2479
2497
  };
2498
+ export declare type AscApiKeyInput = {
2499
+ keyP8: Scalars['String'];
2500
+ keyIdentifier: Scalars['String'];
2501
+ issuerIdentifier: Scalars['String'];
2502
+ };
2480
2503
  export declare type CreateAndroidSubmissionInput = {
2481
2504
  appId: Scalars['ID'];
2482
2505
  config: AndroidSubmissionConfigInput;
@@ -13,6 +13,8 @@ var OfferType;
13
13
  OfferType["Subscription"] = "SUBSCRIPTION";
14
14
  /** Advanced Purchase of Paid Resource */
15
15
  OfferType["Prepaid"] = "PREPAID";
16
+ /** Addon, or supplementary subscription */
17
+ OfferType["Addon"] = "ADDON";
16
18
  })(OfferType = exports.OfferType || (exports.OfferType = {}));
17
19
  var Feature;
18
20
  (function (Feature) {
package/build/log.d.ts CHANGED
@@ -18,10 +18,20 @@ export default class Log {
18
18
  private static updateIsLastLineNewLine;
19
19
  }
20
20
  /**
21
+ * Prints a link for given URL, using text if provided, otherwise text is just the URL.
22
+ * Format links as dim (unless disabled) and with an underline.
23
+ *
24
+ * @example https://expo.dev
25
+ */
26
+ export declare function link(url: string, { text, dim }?: {
27
+ text?: string;
28
+ dim?: boolean;
29
+ }): string;
30
+ /**
31
+ * Provide a consistent "Learn more" link experience.
21
32
  * Format links as dim (unless disabled) with an underline.
22
33
  *
23
34
  * @example Learn more: https://expo.dev
24
- * @param url
25
35
  */
26
36
  export declare function learnMore(url: string, { learnMoreMessage: maybeLearnMoreMessage, dim, }?: {
27
37
  learnMoreMessage?: string;
package/build/log.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.learnMore = void 0;
3
+ exports.learnMore = exports.link = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
6
6
  const figures_1 = (0, tslib_1.__importDefault)(require("figures"));
@@ -79,19 +79,30 @@ exports.default = Log;
79
79
  Log.isDebug = (0, getenv_1.boolish)('EXPO_DEBUG', false);
80
80
  Log.isLastLineNewLine = false;
81
81
  /**
82
- * Format links as dim (unless disabled) with an underline.
82
+ * Prints a link for given URL, using text if provided, otherwise text is just the URL.
83
+ * Format links as dim (unless disabled) and with an underline.
83
84
  *
84
- * @example Learn more: https://expo.dev
85
- * @param url
85
+ * @example https://expo.dev
86
86
  */
87
- function learnMore(url, { learnMoreMessage: maybeLearnMoreMessage, dim = true, } = {}) {
87
+ function link(url, { text = url, dim = true } = {}) {
88
+ let output;
88
89
  // Links can be disabled via env variables https://github.com/jamestalmage/supports-hyperlinks/blob/master/index.js
89
90
  if (terminal_link_1.default.isSupported) {
90
- const text = (0, terminal_link_1.default)(maybeLearnMoreMessage !== null && maybeLearnMoreMessage !== void 0 ? maybeLearnMoreMessage : 'Learn more.', url);
91
- return dim ? chalk_1.default.dim(text) : text;
91
+ output = (0, terminal_link_1.default)(text, url);
92
92
  }
93
- const learnMoreMessage = maybeLearnMoreMessage !== null && maybeLearnMoreMessage !== void 0 ? maybeLearnMoreMessage : 'Learn more';
94
- const text = `${learnMoreMessage === '' ? learnMoreMessage : `${learnMoreMessage}: `}${chalk_1.default.underline(url)}`;
95
- return dim ? chalk_1.default.dim(text) : text;
93
+ else {
94
+ output = `${text === url ? '' : text + ': '}${chalk_1.default.underline(url)}`;
95
+ }
96
+ return dim ? chalk_1.default.dim(output) : output;
97
+ }
98
+ exports.link = link;
99
+ /**
100
+ * Provide a consistent "Learn more" link experience.
101
+ * Format links as dim (unless disabled) with an underline.
102
+ *
103
+ * @example Learn more: https://expo.dev
104
+ */
105
+ function learnMore(url, { learnMoreMessage: maybeLearnMoreMessage, dim = true, } = {}) {
106
+ return link(url, { text: maybeLearnMoreMessage !== null && maybeLearnMoreMessage !== void 0 ? maybeLearnMoreMessage : 'Learn more', dim });
96
107
  }
97
108
  exports.learnMore = learnMore;
@@ -5,7 +5,10 @@ import { Actor } from '../user/User';
5
5
  export declare function getProjectAccountName(exp: ExpoConfig, user: Actor): string;
6
6
  export declare function getUsername(exp: ExpoConfig, user: Actor): string | undefined;
7
7
  export declare function getProjectAccountNameAsync(exp: ExpoConfig): Promise<string>;
8
- export declare function findProjectRootAsync(cwd?: string): Promise<string | null>;
8
+ export declare function findProjectRootAsync({ cwd, defaultToProcessCwd, }?: {
9
+ cwd?: string;
10
+ defaultToProcessCwd?: boolean;
11
+ }): Promise<string>;
9
12
  export declare function setProjectIdAsync(projectDir: string, options?: {
10
13
  env?: Env;
11
14
  }): Promise<ExpoConfig | undefined>;
@@ -45,9 +45,19 @@ async function getProjectAccountNameAsync(exp) {
45
45
  return getProjectAccountName(exp, user);
46
46
  }
47
47
  exports.getProjectAccountNameAsync = getProjectAccountNameAsync;
48
- async function findProjectRootAsync(cwd) {
48
+ async function findProjectRootAsync({ cwd, defaultToProcessCwd = false, } = {}) {
49
49
  const projectRootDir = await (0, pkg_dir_1.default)(cwd);
50
- return projectRootDir !== null && projectRootDir !== void 0 ? projectRootDir : null;
50
+ if (!projectRootDir) {
51
+ if (!defaultToProcessCwd) {
52
+ throw new Error('Please run this command inside a project directory.');
53
+ }
54
+ else {
55
+ return process.cwd();
56
+ }
57
+ }
58
+ else {
59
+ return projectRootDir;
60
+ }
51
61
  }
52
62
  exports.findProjectRootAsync = findProjectRootAsync;
53
63
  async function setProjectIdAsync(projectDir, options = {}) {
@@ -106,7 +116,7 @@ async function getProjectIdAsync(exp, options = {}) {
106
116
  return localProjectId;
107
117
  }
108
118
  // Set the project ID if it is missing.
109
- const projectDir = await findProjectRootAsync(process.cwd());
119
+ const projectDir = await findProjectRootAsync();
110
120
  if (!projectDir) {
111
121
  throw new Error('Please run this command inside a project directory.');
112
122
  }
@@ -1,11 +1,13 @@
1
1
  import { Platform } from '@expo/eas-build-job';
2
2
  import { BuildFragment } from '../graphql/generated';
3
+ export declare const BUILD_LIST_ITEM_COUNT = 4;
3
4
  export declare enum ArchiveSourceType {
4
5
  url = 0,
5
6
  latest = 1,
6
7
  path = 2,
7
8
  buildId = 3,
8
- prompt = 4
9
+ buildList = 4,
10
+ prompt = 5
9
11
  }
10
12
  interface ArchiveSourceBase {
11
13
  sourceType: ArchiveSourceType;
@@ -28,6 +30,9 @@ interface ArchiveBuildIdSource extends ArchiveSourceBase {
28
30
  sourceType: ArchiveSourceType.buildId;
29
31
  id: string;
30
32
  }
33
+ interface ArchiveBuildListSource extends ArchiveSourceBase {
34
+ sourceType: ArchiveSourceType.buildList;
35
+ }
31
36
  interface ArchivePromptSource extends ArchiveSourceBase {
32
37
  sourceType: ArchiveSourceType.prompt;
33
38
  }
@@ -36,7 +41,7 @@ export interface Archive {
36
41
  source: ArchiveSource;
37
42
  url?: string;
38
43
  }
39
- export declare type ArchiveSource = ArchiveUrlSource | ArchiveLatestSource | ArchivePathSource | ArchiveBuildIdSource | ArchivePromptSource;
44
+ export declare type ArchiveSource = ArchiveUrlSource | ArchiveLatestSource | ArchivePathSource | ArchiveBuildIdSource | ArchiveBuildListSource | ArchivePromptSource;
40
45
  export declare function getArchiveAsync(source: ArchiveSource): Promise<Archive>;
41
46
  export declare function isUuidV4(s: string): boolean;
42
47
  export {};
@@ -1,11 +1,12 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isUuidV4 = exports.getArchiveAsync = exports.ArchiveSourceType = void 0;
3
+ exports.isUuidV4 = exports.getArchiveAsync = exports.ArchiveSourceType = exports.BUILD_LIST_ITEM_COUNT = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const eas_build_job_1 = require("@expo/eas-build-job");
6
6
  const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
7
7
  const url_1 = require("url");
8
8
  const uuid = (0, tslib_1.__importStar)(require("uuid"));
9
+ const generated_1 = require("../graphql/generated");
9
10
  const BuildQuery_1 = require("../graphql/queries/BuildQuery");
10
11
  const AppPlatform_1 = require("../graphql/types/AppPlatform");
11
12
  const log_1 = (0, tslib_1.__importStar)(require("../log"));
@@ -13,13 +14,15 @@ const platform_1 = require("../platform");
13
14
  const prompts_1 = require("../prompts");
14
15
  const builds_1 = require("./utils/builds");
15
16
  const files_1 = require("./utils/files");
17
+ exports.BUILD_LIST_ITEM_COUNT = 4;
16
18
  var ArchiveSourceType;
17
19
  (function (ArchiveSourceType) {
18
20
  ArchiveSourceType[ArchiveSourceType["url"] = 0] = "url";
19
21
  ArchiveSourceType[ArchiveSourceType["latest"] = 1] = "latest";
20
22
  ArchiveSourceType[ArchiveSourceType["path"] = 2] = "path";
21
23
  ArchiveSourceType[ArchiveSourceType["buildId"] = 3] = "buildId";
22
- ArchiveSourceType[ArchiveSourceType["prompt"] = 4] = "prompt";
24
+ ArchiveSourceType[ArchiveSourceType["buildList"] = 4] = "buildList";
25
+ ArchiveSourceType[ArchiveSourceType["prompt"] = 5] = "prompt";
23
26
  })(ArchiveSourceType = exports.ArchiveSourceType || (exports.ArchiveSourceType = {}));
24
27
  async function getArchiveAsync(source) {
25
28
  switch (source.sourceType) {
@@ -38,6 +41,9 @@ async function getArchiveAsync(source) {
38
41
  case ArchiveSourceType.buildId: {
39
42
  return await handleBuildIdSourceAsync(source);
40
43
  }
44
+ case ArchiveSourceType.buildList: {
45
+ return await handleBuildListSourceAsync(source);
46
+ }
41
47
  }
42
48
  }
43
49
  exports.getArchiveAsync = getArchiveAsync;
@@ -67,7 +73,7 @@ async function handleUrlSourceAsync(source) {
67
73
  }
68
74
  async function handleLatestSourceAsync(source) {
69
75
  try {
70
- const latestBuild = await (0, builds_1.getLatestBuildForSubmissionAsync)((0, AppPlatform_1.toAppPlatform)(source.platform), source.projectId);
76
+ const [latestBuild] = await (0, builds_1.getRecentBuildsForSubmissionAsync)((0, AppPlatform_1.toAppPlatform)(source.platform), source.projectId);
71
77
  if (!latestBuild) {
72
78
  log_1.default.error(chalk_1.default.bold("Couldn't find any builds for this project on EAS servers. It looks like you haven't run 'eas build' yet."));
73
79
  return getArchiveAsync({
@@ -128,6 +134,83 @@ async function handleBuildIdSourceAsync(source) {
128
134
  });
129
135
  }
130
136
  }
137
+ async function handleBuildListSourceAsync(source) {
138
+ try {
139
+ const appPlatform = (0, AppPlatform_1.toAppPlatform)(source.platform);
140
+ const expiryDate = new Date(); // artifacts expire after 30 days
141
+ expiryDate.setDate(expiryDate.getDate() - 30);
142
+ const recentBuilds = await (0, builds_1.getRecentBuildsForSubmissionAsync)(appPlatform, source.projectId, {
143
+ limit: exports.BUILD_LIST_ITEM_COUNT,
144
+ });
145
+ if (recentBuilds.length < 1) {
146
+ log_1.default.error(chalk_1.default.bold(`Couldn't find any ${platform_1.appPlatformDisplayNames[appPlatform]} builds for this project on EAS servers. ` +
147
+ "It looks like you haven't run 'eas build' yet."));
148
+ return getArchiveAsync({
149
+ ...source,
150
+ sourceType: ArchiveSourceType.prompt,
151
+ });
152
+ }
153
+ if (recentBuilds.every(it => new Date(it.updatedAt) < expiryDate)) {
154
+ log_1.default.error(chalk_1.default.bold('It looks like all of your build artifacts have expired. ' +
155
+ 'EAS keeps your build artifacts only for 30 days.'));
156
+ return getArchiveAsync({
157
+ ...source,
158
+ sourceType: ArchiveSourceType.prompt,
159
+ });
160
+ }
161
+ const choices = recentBuilds.map(build => formatBuildChoice(build, expiryDate));
162
+ choices.push({
163
+ title: 'None of the above (select another option)',
164
+ value: null,
165
+ });
166
+ const { selectedBuild } = await (0, prompts_1.promptAsync)({
167
+ name: 'selectedBuild',
168
+ type: 'select',
169
+ message: 'Which build would you like to submit?',
170
+ choices: choices.map(choice => ({ ...choice, title: `- ${choice.title}` })),
171
+ // @ts-expect-error field documented in npm, but not defined in typescript
172
+ warn: 'This artifact has expired',
173
+ });
174
+ if (selectedBuild == null) {
175
+ return getArchiveAsync({
176
+ ...source,
177
+ sourceType: ArchiveSourceType.prompt,
178
+ });
179
+ }
180
+ return {
181
+ build: selectedBuild,
182
+ source,
183
+ };
184
+ }
185
+ catch (err) {
186
+ log_1.default.error(err);
187
+ throw err;
188
+ }
189
+ }
190
+ function formatBuildChoice(build, expiryDate) {
191
+ const { id, platform, updatedAt, appVersion, sdkVersion, runtimeVersion, buildProfile, appBuildVersion, releaseChannel, } = build;
192
+ const formatValue = (field) => field ? chalk_1.default.bold(field) : chalk_1.default.dim('Unknown');
193
+ const buildDate = new Date(updatedAt);
194
+ const maybeRuntimeVersion = runtimeVersion ? `Runtime: ${formatValue(runtimeVersion)}` : null;
195
+ const maybeSdkVersion = sdkVersion ? `SDK: ${formatValue(sdkVersion)}` : null;
196
+ const appBuildVersionString = `${platform === generated_1.AppPlatform.Android ? 'Version code' : 'Build number'}: ${formatValue(appBuildVersion)}`;
197
+ const title = [
198
+ `ID: ${chalk_1.default.dim(id)}, Finished at: ${chalk_1.default.bold(buildDate.toLocaleString())}`,
199
+ [
200
+ `\tApp version: ${formatValue(appVersion)}, ${appBuildVersionString}`,
201
+ maybeRuntimeVersion,
202
+ maybeSdkVersion,
203
+ ]
204
+ .filter(it => it != null)
205
+ .join(', '),
206
+ `\tProfile: ${formatValue(buildProfile)}, Release channel: ${formatValue(releaseChannel)}`,
207
+ ].join('\n');
208
+ return {
209
+ title,
210
+ value: build,
211
+ disabled: buildDate < expiryDate,
212
+ };
213
+ }
131
214
  async function handlePromptSourceAsync(source) {
132
215
  const { sourceType: sourceTypeRaw } = await (0, prompts_1.promptAsync)({
133
216
  name: 'sourceType',
@@ -135,8 +218,8 @@ async function handlePromptSourceAsync(source) {
135
218
  message: 'What would you like to submit?',
136
219
  choices: [
137
220
  {
138
- title: 'Latest finished build from EAS',
139
- value: ArchiveSourceType.latest,
221
+ title: 'Selected build from EAS',
222
+ value: ArchiveSourceType.buildList,
140
223
  },
141
224
  { title: 'I have a url to the app archive', value: ArchiveSourceType.url },
142
225
  {
@@ -167,10 +250,10 @@ async function handlePromptSourceAsync(source) {
167
250
  path,
168
251
  });
169
252
  }
170
- case ArchiveSourceType.latest: {
253
+ case ArchiveSourceType.buildList: {
171
254
  return getArchiveAsync({
172
255
  ...source,
173
- sourceType: ArchiveSourceType.latest,
256
+ sourceType: ArchiveSourceType.buildList,
174
257
  });
175
258
  }
176
259
  case ArchiveSourceType.buildId: {
@@ -181,7 +264,7 @@ async function handlePromptSourceAsync(source) {
181
264
  id,
182
265
  });
183
266
  }
184
- case ArchiveSourceType.prompt:
267
+ default:
185
268
  throw new Error('This should never happen');
186
269
  }
187
270
  }
@@ -120,14 +120,9 @@ class AndroidSubmitCommand {
120
120
  path: serviceAccountKeyPath,
121
121
  });
122
122
  }
123
- else if (this.ctx.nonInteractive) {
124
- return (0, results_1.result)(new Error('Set serviceAccountKeyPath in the submit profile (eas.json).'));
125
- }
126
- else {
127
- return (0, results_1.result)({
128
- sourceType: ServiceAccountSource_1.ServiceAccountSourceType.detect,
129
- });
130
- }
123
+ return (0, results_1.result)({
124
+ sourceType: ServiceAccountSource_1.ServiceAccountSourceType.credentialsService,
125
+ });
131
126
  }
132
127
  }
133
128
  exports.default = AndroidSubmitCommand;
@@ -1,8 +1,9 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const tslib_1 = require("tslib");
4
- const fs_extra_1 = (0, tslib_1.__importDefault)(require("fs-extra"));
4
+ const chalk_1 = (0, tslib_1.__importDefault)(require("chalk"));
5
5
  const SubmissionMutation_1 = require("../../graphql/mutations/SubmissionMutation");
6
+ const formatFields_1 = (0, tslib_1.__importDefault)(require("../../utils/formatFields"));
6
7
  const ArchiveSource_1 = require("../ArchiveSource");
7
8
  const BaseSubmitter_1 = (0, tslib_1.__importDefault)(require("../BaseSubmitter"));
8
9
  const summary_1 = require("../utils/summary");
@@ -30,15 +31,14 @@ class AndroidSubmitter extends BaseSubmitter_1.default {
30
31
  async resolveSourceOptionsAsync() {
31
32
  const androidPackage = await (0, AndroidPackageSource_1.getAndroidPackageAsync)(this.options.androidPackageSource);
32
33
  const archive = await (0, ArchiveSource_1.getArchiveAsync)(this.options.archiveSource);
33
- const serviceAccountPath = await (0, ServiceAccountSource_1.getServiceAccountAsync)(this.options.serviceAccountSource);
34
+ const serviceAccountKeyResult = await (0, ServiceAccountSource_1.getServiceAccountKeyResultAsync)(this.ctx, this.options.serviceAccountSource, androidPackage);
34
35
  return {
35
36
  androidPackage,
36
37
  archive,
37
- serviceAccountPath,
38
+ serviceAccountKeyResult,
38
39
  };
39
40
  }
40
- async formatSubmissionConfigAsync(options, { archive, androidPackage, serviceAccountPath }) {
41
- const serviceAccount = await fs_extra_1.default.readFile(serviceAccountPath, 'utf-8');
41
+ async formatSubmissionConfigAsync(options, { archive, androidPackage, serviceAccountKeyResult }) {
42
42
  const { track, releaseStatus, changesNotSentForReview } = options;
43
43
  return {
44
44
  applicationIdentifier: androidPackage,
@@ -46,10 +46,10 @@ class AndroidSubmitter extends BaseSubmitter_1.default {
46
46
  track,
47
47
  changesNotSentForReview,
48
48
  releaseStatus,
49
- googleServiceAccountKeyJson: serviceAccount,
49
+ ...serviceAccountKeyResult.result,
50
50
  };
51
51
  }
52
- prepareSummaryData(options, { archive, androidPackage, serviceAccountPath }) {
52
+ prepareSummaryData(options, { archive, androidPackage, serviceAccountKeyResult }) {
53
53
  const { projectId, track, releaseStatus, changesNotSentForReview } = options;
54
54
  // structuring order affects table rows order
55
55
  return {
@@ -58,7 +58,7 @@ class AndroidSubmitter extends BaseSubmitter_1.default {
58
58
  track,
59
59
  changesNotSentForReview: changesNotSentForReview !== null && changesNotSentForReview !== void 0 ? changesNotSentForReview : undefined,
60
60
  releaseStatus: releaseStatus !== null && releaseStatus !== void 0 ? releaseStatus : undefined,
61
- serviceAccountPath,
61
+ formattedServiceAccount: formatServiceAccountSummary(serviceAccountKeyResult),
62
62
  ...(0, summary_1.formatArchiveSourceSummary)(archive),
63
63
  };
64
64
  }
@@ -70,8 +70,30 @@ const SummaryHumanReadableKeys = {
70
70
  archiveUrl: 'Download URL',
71
71
  changesNotSentForReview: 'Changes not sent for a review',
72
72
  formattedBuild: 'Build',
73
+ formattedServiceAccount: 'Google Service Account Key',
73
74
  projectId: 'Project ID',
74
75
  releaseStatus: 'Release status',
75
- serviceAccountPath: 'Google Service Key',
76
76
  track: 'Release track',
77
77
  };
78
+ function formatServiceAccountSummary({ summary }) {
79
+ const { email: serviceAccountEmail, path: serviceAccountKeyPath, source: serviceAccountKeySource, } = summary;
80
+ const fields = [
81
+ {
82
+ label: 'Key Source',
83
+ value: serviceAccountKeySource,
84
+ },
85
+ {
86
+ label: 'Key Path',
87
+ value: serviceAccountKeyPath,
88
+ },
89
+ {
90
+ label: 'Account E-mail',
91
+ value: serviceAccountEmail,
92
+ },
93
+ ];
94
+ const filteredFields = fields.filter(({ value }) => value !== undefined && value !== null);
95
+ return ('\n' +
96
+ (0, formatFields_1.default)(filteredFields, {
97
+ labelFormat: label => ` ${chalk_1.default.dim(label)}:`,
98
+ }));
99
+ }
@@ -1,7 +1,9 @@
1
+ import { Platform } from '@expo/eas-build-job';
2
+ import { SubmissionContext } from '../context';
1
3
  export declare enum ServiceAccountSourceType {
2
4
  path = 0,
3
5
  prompt = 1,
4
- detect = 2
6
+ credentialsService = 2
5
7
  }
6
8
  interface ServiceAccountSourceBase {
7
9
  sourceType: ServiceAccountSourceType;
@@ -13,9 +15,26 @@ interface ServiceAccountPathSource extends ServiceAccountSourceBase {
13
15
  interface ServiceAccountPromptSource extends ServiceAccountSourceBase {
14
16
  sourceType: ServiceAccountSourceType.prompt;
15
17
  }
16
- interface ServiceAccountDetectSource extends ServiceAccountSourceBase {
17
- sourceType: ServiceAccountSourceType.detect;
18
+ export interface ServiceAccountCredentialsServiceSource extends ServiceAccountSourceBase {
19
+ sourceType: ServiceAccountSourceType.credentialsService;
18
20
  }
19
- export declare type ServiceAccountSource = ServiceAccountPathSource | ServiceAccountPromptSource | ServiceAccountDetectSource;
20
- export declare function getServiceAccountAsync(source: ServiceAccountSource): Promise<string>;
21
+ export declare type ServiceAccountKeyResult = {
22
+ result: ServiceAccountKeyFile | ServiceAccountKeyFromExpoServers;
23
+ summary: ServiceAccountKeySummary;
24
+ };
25
+ declare type ServiceAccountKeySummary = {
26
+ source: 'local' | 'EAS servers';
27
+ path?: string;
28
+ email: string;
29
+ };
30
+ declare type ServiceAccountKeyFile = {
31
+ googleServiceAccountKeyJson: string;
32
+ };
33
+ declare type ServiceAccountKeyFromExpoServers = {
34
+ googleServiceAccountKeyId: string;
35
+ };
36
+ export declare type ServiceAccountSource = ServiceAccountPathSource | ServiceAccountPromptSource | ServiceAccountCredentialsServiceSource;
37
+ export declare function getServiceAccountKeyResultAsync(ctx: SubmissionContext<Platform.ANDROID>, source: ServiceAccountSource, androidApplicationIdentifier: string): Promise<ServiceAccountKeyResult>;
38
+ export declare function getServiceAccountKeyPathAsync(source: ServiceAccountSource): Promise<string>;
39
+ export declare function getServiceAccountFromCredentialsServiceAsync(ctx: SubmissionContext<Platform.ANDROID>, androidApplicationIdentifier: string): Promise<ServiceAccountKeyResult>;
21
40
  export {};