eas-cli 0.53.1 → 0.55.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 (66) hide show
  1. package/README.md +78 -42
  2. package/build/build/android/build.js +2 -1
  3. package/build/build/build.d.ts +2 -2
  4. package/build/build/build.js +9 -3
  5. package/build/build/context.d.ts +2 -0
  6. package/build/build/createContext.d.ts +3 -1
  7. package/build/build/createContext.js +2 -1
  8. package/build/build/ios/build.js +2 -1
  9. package/build/build/runBuildAndSubmit.d.ts +2 -0
  10. package/build/build/runBuildAndSubmit.js +19 -4
  11. package/build/build/types.d.ts +4 -0
  12. package/build/build/types.js +6 -1
  13. package/build/build/utils/url.d.ts +1 -0
  14. package/build/build/utils/url.js +5 -1
  15. package/build/commands/build/index.d.ts +2 -1
  16. package/build/commands/build/index.js +9 -54
  17. package/build/commands/config.js +0 -2
  18. package/build/commands/metadata/pull.d.ts +8 -0
  19. package/build/commands/metadata/pull.js +59 -0
  20. package/build/commands/metadata/push.d.ts +8 -0
  21. package/build/commands/metadata/push.js +53 -0
  22. package/build/commands/submit.js +2 -1
  23. package/build/commands/update/index.d.ts +13 -0
  24. package/build/commands/update/index.js +88 -35
  25. package/build/env.d.ts +11 -0
  26. package/build/env.js +12 -0
  27. package/build/graphql/generated.d.ts +39 -12
  28. package/build/graphql/generated.js +9 -1
  29. package/build/graphql/mutations/BuildMutation.d.ts +3 -1
  30. package/build/graphql/mutations/BuildMutation.js +14 -2
  31. package/build/graphql/queries/UpdateQuery.d.ts +4 -1
  32. package/build/graphql/queries/UpdateQuery.js +8 -7
  33. package/build/graphql/queries/WebhookQuery.d.ts +1 -1
  34. package/build/metadata/apple/config/reader.d.ts +2 -1
  35. package/build/metadata/apple/config/reader.js +68 -10
  36. package/build/metadata/apple/config/writer.d.ts +3 -2
  37. package/build/metadata/apple/config/writer.js +64 -12
  38. package/build/metadata/apple/data.d.ts +2 -1
  39. package/build/metadata/apple/tasks/app-info.js +8 -6
  40. package/build/metadata/apple/tasks/app-review-detail.d.ts +14 -0
  41. package/build/metadata/apple/tasks/app-review-detail.js +49 -0
  42. package/build/metadata/apple/tasks/app-version.js +16 -12
  43. package/build/metadata/apple/tasks/index.js +2 -1
  44. package/build/metadata/apple/types.d.ts +9 -11
  45. package/build/metadata/context.js +7 -14
  46. package/build/metadata/download.js +9 -2
  47. package/build/metadata/errors.d.ts +7 -2
  48. package/build/metadata/errors.js +25 -6
  49. package/build/metadata/upload.d.ts +3 -1
  50. package/build/metadata/upload.js +17 -3
  51. package/build/metadata/utils/date.d.ts +1 -1
  52. package/build/project/android/applicationId.js +4 -0
  53. package/build/project/ensureProjectExists.js +4 -1
  54. package/build/project/publish.d.ts +6 -1
  55. package/build/project/publish.js +23 -5
  56. package/build/project/workflow.js +13 -11
  57. package/build/uploads.d.ts +6 -0
  58. package/build/uploads.js +15 -3
  59. package/build/utils/expodash/uniq.d.ts +1 -0
  60. package/build/{metadata/utils/array.js → utils/expodash/uniq.js} +2 -3
  61. package/build/utils/profiles.d.ts +3 -3
  62. package/build/utils/profiles.js +5 -93
  63. package/oclif.manifest.json +1 -1
  64. package/package.json +11 -6
  65. package/schema/metadata-0.json +988 -0
  66. package/build/metadata/utils/array.d.ts +0 -1
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AppReviewDetailTask = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const assert_1 = tslib_1.__importDefault(require("assert"));
6
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
+ const log_1 = tslib_1.__importDefault(require("../../../log"));
8
+ const log_2 = require("../../utils/log");
9
+ const task_1 = require("../task");
10
+ /** Handle all contact, demo account, or instruction info that are required for the App Store review team. */
11
+ class AppReviewDetailTask extends task_1.AppleTask {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.name = () => 'app review detail';
15
+ }
16
+ async prepareAsync({ context }) {
17
+ var _a;
18
+ (0, assert_1.default)(context.version, `App version not initialized, can't download store review details`);
19
+ context.reviewDetail = (_a = (await context.version.getAppStoreReviewDetailAsync())) !== null && _a !== void 0 ? _a : undefined;
20
+ }
21
+ async downloadAsync({ config, context }) {
22
+ if (context.reviewDetail) {
23
+ config.setReviewDetails(context.reviewDetail.attributes);
24
+ }
25
+ }
26
+ async uploadAsync({ config, context }) {
27
+ const reviewDetail = config.getReviewDetails();
28
+ if (!reviewDetail) {
29
+ return log_1.default.log((0, chalk_1.default) `{dim - Skipped store review details, not configured}`);
30
+ }
31
+ (0, assert_1.default)(context.version, `App version not initialized, can't upload store review details`);
32
+ const { versionString } = context.version.attributes;
33
+ if (!context.reviewDetail) {
34
+ // We can't set the demo required property when creating, omit it from the request
35
+ const { demoAccountRequired, ...reviewDetailsToCreate } = reviewDetail;
36
+ context.reviewDetail = await (0, log_2.logAsync)(() => context.version.createReviewDetailAsync(reviewDetailsToCreate), {
37
+ pending: `Creating store review details for ${chalk_1.default.bold(versionString)}...`,
38
+ success: `Created store review details for ${chalk_1.default.bold(versionString)}`,
39
+ failure: `Failed creating store review details for ${chalk_1.default.bold(versionString)}`,
40
+ });
41
+ }
42
+ context.reviewDetail = await (0, log_2.logAsync)(() => context.reviewDetail.updateAsync(reviewDetail), {
43
+ pending: `Updating store review details for ${chalk_1.default.bold(versionString)}...`,
44
+ success: `Updated store review details for ${chalk_1.default.bold(versionString)}`,
45
+ failure: `Failed updating store review details for ${chalk_1.default.bold(versionString)}`,
46
+ });
47
+ }
48
+ }
49
+ exports.AppReviewDetailTask = AppReviewDetailTask;
@@ -11,11 +11,12 @@ const retry_1 = require("../../utils/retry");
11
11
  const task_1 = require("../task");
12
12
  class AppVersionTask extends task_1.AppleTask {
13
13
  constructor(options = {}) {
14
+ var _a, _b;
14
15
  super();
15
16
  this.name = () => (this.options.editLive ? 'live app version' : 'editable app version');
16
17
  this.options = {
17
- platform: options.platform || apple_utils_1.Platform.IOS,
18
- editLive: options.editLive || false,
18
+ platform: (_a = options.platform) !== null && _a !== void 0 ? _a : apple_utils_1.Platform.IOS,
19
+ editLive: (_b = options.editLive) !== null && _b !== void 0 ? _b : false,
19
20
  };
20
21
  }
21
22
  async prepareAsync({ context }) {
@@ -42,13 +43,14 @@ class AppVersionTask extends task_1.AppleTask {
42
43
  log_1.default.log((0, chalk_1.default) `{dim - Skipped version and release update, not configured}`);
43
44
  }
44
45
  else {
45
- const description = [version && 'version info', release && 'release info']
46
+ const { versionString } = context.version.attributes;
47
+ const description = [version && 'version', release && 'release']
46
48
  .filter(Boolean)
47
49
  .join(' and ');
48
50
  context.version = await (0, log_2.logAsync)(() => context.version.updateAsync({ ...version, ...release }), {
49
- pending: `Updating ${description}...`,
50
- success: `Updated ${description}`,
51
- failure: `Failed updating ${description}`,
51
+ pending: `Updating ${description} info for ${chalk_1.default.bold(versionString)}...`,
52
+ success: `Updated ${description} info for ${chalk_1.default.bold(versionString)}`,
53
+ failure: `Failed updating ${description} info for ${chalk_1.default.bold(versionString)}`,
52
54
  });
53
55
  }
54
56
  const locales = config.getLocales();
@@ -62,12 +64,14 @@ class AppVersionTask extends task_1.AppleTask {
62
64
  continue;
63
65
  }
64
66
  const oldModel = context.versionLocales.find(model => model.attributes.locale === locale);
65
- await (0, log_2.logAsync)(() => oldModel
66
- ? oldModel.updateAsync(attributes)
67
- : context.version.createLocalizationAsync({ ...attributes, locale }), {
68
- pending: `${oldModel ? 'Updating' : 'Creating'} localized version for ${locale}...`,
69
- success: `${oldModel ? 'Updated' : 'Created'} localized version for ${locale}`,
70
- failure: `Failed ${oldModel ? 'updating' : 'creating'} localized version for ${locale}`,
67
+ await (0, log_2.logAsync)(async () => {
68
+ return oldModel
69
+ ? await oldModel.updateAsync(attributes)
70
+ : await context.version.createLocalizationAsync({ ...attributes, locale });
71
+ }, {
72
+ pending: `${oldModel ? 'Updating' : 'Creating'} localized version for ${chalk_1.default.bold(locale)}...`,
73
+ success: `${oldModel ? 'Updated' : 'Created'} localized version for ${chalk_1.default.bold(locale)}`,
74
+ failure: `Failed ${oldModel ? 'updating' : 'creating'} localized version for ${chalk_1.default.bold(locale)}`,
71
75
  });
72
76
  }
73
77
  context.versionLocales = await context.version.getLocalizationsAsync();
@@ -3,11 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createAppleTasks = void 0;
4
4
  const age_rating_1 = require("./age-rating");
5
5
  const app_info_1 = require("./app-info");
6
+ const app_review_detail_1 = require("./app-review-detail");
6
7
  const app_version_1 = require("./app-version");
7
8
  /**
8
9
  * List of all eligible tasks to sync local store configuration to the App store.
9
10
  */
10
11
  function createAppleTasks(_ctx) {
11
- return [new app_version_1.AppVersionTask(), new app_info_1.AppInfoTask(), new age_rating_1.AgeRatingTask()];
12
+ return [new app_version_1.AppVersionTask(), new app_info_1.AppInfoTask(), new age_rating_1.AgeRatingTask(), new app_review_detail_1.AppReviewDetailTask()];
12
13
  }
13
14
  exports.createAppleTasks = createAppleTasks;
@@ -1,20 +1,18 @@
1
1
  /// <reference types="@expo/apple-utils/ts-declarations/expo__app-store" />
2
- import type { AgeRatingDeclarationProps, AppCategoryId, AppSubcategoryId } from '@expo/apple-utils';
2
+ import type { AgeRatingDeclarationProps } from '@expo/apple-utils';
3
3
  export declare type AppleLocale = string;
4
4
  export interface AppleMetadata {
5
5
  copyright?: string;
6
6
  info?: Record<AppleLocale, AppleInfo>;
7
- categories?: AppCategoryId[] | AppleCategory;
7
+ categories?: AppleCategory;
8
8
  release?: AppleRelease;
9
9
  advisory?: AppleAdvisory;
10
10
  preview?: Record<string, string[]>;
11
11
  review?: AppleReview;
12
12
  }
13
13
  export declare type AppleAdvisory = Partial<AgeRatingDeclarationProps>;
14
- export interface AppleCategory {
15
- category: AppCategoryId;
16
- subcategory?: AppSubcategoryId[];
17
- }
14
+ /** Apps can define up to two categories, or categories with up to two subcategories */
15
+ export declare type AppleCategory = (string | string[])[];
18
16
  export interface AppleRelease {
19
17
  isPhasedReleaseEnabled?: boolean;
20
18
  shouldResetRatings?: boolean;
@@ -39,12 +37,12 @@ export interface AppleInfo {
39
37
  supportUrl?: string;
40
38
  }
41
39
  export interface AppleReview {
42
- firstName?: string;
43
- lastName?: string;
44
- phone?: string;
45
- email?: string;
40
+ firstName: string;
41
+ lastName: string;
42
+ phone: string;
43
+ email: string;
46
44
  demoUsername?: string;
47
45
  demoPassword?: string;
46
+ demoRequired?: boolean;
48
47
  notes?: string;
49
- attachment?: string;
50
48
  }
@@ -4,34 +4,27 @@ exports.ensureMetadataAppStoreAuthenticatedAsync = exports.createMetadataContext
4
4
  const tslib_1 = require("tslib");
5
5
  const apple_utils_1 = require("@expo/apple-utils");
6
6
  const eas_build_job_1 = require("@expo/eas-build-job");
7
+ const eas_json_1 = require("@expo/eas-json");
7
8
  const assert_1 = tslib_1.__importDefault(require("assert"));
8
9
  const authenticate_1 = require("../credentials/ios/appstore/authenticate");
9
10
  const expoConfig_1 = require("../project/expoConfig");
10
11
  const bundleIdentifier_1 = require("../project/ios/bundleIdentifier");
11
12
  const actions_1 = require("../user/actions");
12
- const profiles_1 = require("../utils/profiles");
13
13
  /**
14
14
  * Metadata uses the submission profile to find the configured metadata filename.
15
15
  * Note, only iOS is supported for metadata right now.
16
16
  */
17
17
  async function createMetadataContextAsync(params) {
18
- var _a, _b;
19
- const submissionProfiles = await (0, profiles_1.getProfilesAsync)({
20
- type: 'submit',
21
- platforms: [eas_build_job_1.Platform.IOS],
22
- projectDir: params.projectDir,
23
- profileName: params.profileName,
24
- });
25
- const submissionProfile = submissionProfiles.find(profile => profile.platform === eas_build_job_1.Platform.IOS);
26
- (0, assert_1.default)(submissionProfile, 'Could not resolve the iOS submission profile, only iOS is supported for metadata');
27
- const iosSubmissionProfile = submissionProfile.profile;
18
+ var _a, _b, _c;
19
+ const easJsonReader = new eas_json_1.EasJsonReader(params.projectDir);
20
+ const submitProfile = await easJsonReader.getSubmitProfileAsync(eas_build_job_1.Platform.IOS, params.profileName);
28
21
  const exp = (_a = params.exp) !== null && _a !== void 0 ? _a : (0, expoConfig_1.getExpoConfig)(params.projectDir);
29
22
  const user = await (0, actions_1.ensureLoggedInAsync)();
30
- const bundleIdentifier = await (0, bundleIdentifier_1.getBundleIdentifierAsync)(params.projectDir, exp);
23
+ const bundleIdentifier = (_b = submitProfile.bundleIdentifier) !== null && _b !== void 0 ? _b : (await (0, bundleIdentifier_1.getBundleIdentifierAsync)(params.projectDir, exp));
31
24
  return {
32
25
  platform: eas_build_job_1.Platform.IOS,
33
- profile: iosSubmissionProfile,
34
- metadataPath: (_b = iosSubmissionProfile.metadataPath) !== null && _b !== void 0 ? _b : 'store.config.json',
26
+ profile: submitProfile,
27
+ metadataPath: (_c = submitProfile.metadataPath) !== null && _c !== void 0 ? _c : 'store.config.json',
35
28
  user,
36
29
  credentialsCtx: params.credentialsCtx,
37
30
  bundleIdentifier,
@@ -5,6 +5,7 @@ const tslib_1 = require("tslib");
5
5
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
6
6
  const path_1 = tslib_1.__importDefault(require("path"));
7
7
  const events_1 = require("../analytics/events");
8
+ const log_1 = tslib_1.__importDefault(require("../log"));
8
9
  const prompts_1 = require("../prompts");
9
10
  const tasks_1 = require("./apple/tasks");
10
11
  const config_1 = require("./config");
@@ -28,6 +29,8 @@ async function downloadMetadataAsync(metadataCtx) {
28
29
  }
29
30
  const { app, auth } = await (0, context_1.ensureMetadataAppStoreAuthenticatedAsync)(metadataCtx);
30
31
  const { unsubscribeTelemetry, executionId } = (0, telemetry_1.subscribeTelemetry)(events_1.MetadataEvent.APPLE_METADATA_DOWNLOAD, { app, auth });
32
+ log_1.default.addNewLineIfNone();
33
+ log_1.default.log('Downloading App Store configuration...');
31
34
  const errors = [];
32
35
  const config = (0, config_1.createAppleWriter)();
33
36
  const tasks = (0, tasks_1.createAppleTasks)(metadataCtx);
@@ -48,8 +51,12 @@ async function downloadMetadataAsync(metadataCtx) {
48
51
  errors.push(error);
49
52
  }
50
53
  }
51
- await fs_extra_1.default.writeJson(filePath, config.toSchema(), { spaces: 2 });
52
- unsubscribeTelemetry();
54
+ try {
55
+ await fs_extra_1.default.writeJson(filePath, config.toSchema(), { spaces: 2 });
56
+ }
57
+ finally {
58
+ unsubscribeTelemetry();
59
+ }
53
60
  if (errors.length > 0) {
54
61
  throw new errors_1.MetadataDownloadError(errors, executionId);
55
62
  }
@@ -5,8 +5,8 @@ import type { ErrorObject } from 'ajv';
5
5
  * and should contain useful information for the user to solve before trying again.
6
6
  */
7
7
  export declare class MetadataValidationError extends Error {
8
- readonly errors?: ErrorObject[] | undefined;
9
- constructor(message?: string, errors?: ErrorObject[] | undefined);
8
+ readonly errors: ErrorObject[];
9
+ constructor(message?: string, errors?: ErrorObject[]);
10
10
  }
11
11
  /**
12
12
  * If a single entity failed to update, we don't block the other entities from uploading.
@@ -30,6 +30,11 @@ export declare class MetadataDownloadError extends Error {
30
30
  readonly executionId: string;
31
31
  constructor(errors: Error[], executionId: string);
32
32
  }
33
+ /**
34
+ * Log the encountered metadata validation error in detail for the user.
35
+ * This should help communicate any possible configuration error and help the user resolve it.
36
+ */
37
+ export declare function logMetadataValidationError(error: MetadataValidationError): void;
33
38
  /**
34
39
  * Handle a thrown metadata error by informing the user what went wrong.
35
40
  * If a normal error is thrown, this method will re-throw that error to avoid consuming it.
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleMetadataError = exports.MetadataDownloadError = exports.MetadataUploadError = exports.MetadataValidationError = void 0;
3
+ exports.handleMetadataError = exports.logMetadataValidationError = exports.MetadataDownloadError = exports.MetadataUploadError = exports.MetadataValidationError = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
5
6
  const log_1 = tslib_1.__importStar(require("../log"));
6
7
  /**
7
8
  * Before syncing data to the ASC API, we need to validate the metadata config.
@@ -9,7 +10,7 @@ const log_1 = tslib_1.__importStar(require("../log"));
9
10
  * and should contain useful information for the user to solve before trying again.
10
11
  */
11
12
  class MetadataValidationError extends Error {
12
- constructor(message, errors) {
13
+ constructor(message, errors = []) {
13
14
  super(message !== null && message !== void 0 ? message : 'Store configuration validation failed');
14
15
  this.errors = errors;
15
16
  }
@@ -43,6 +44,20 @@ class MetadataDownloadError extends Error {
43
44
  }
44
45
  }
45
46
  exports.MetadataDownloadError = MetadataDownloadError;
47
+ /**
48
+ * Log the encountered metadata validation error in detail for the user.
49
+ * This should help communicate any possible configuration error and help the user resolve it.
50
+ */
51
+ function logMetadataValidationError(error) {
52
+ var _a;
53
+ log_1.default.newLine();
54
+ log_1.default.error(chalk_1.default.bold(error.message));
55
+ if (((_a = error.errors) === null || _a === void 0 ? void 0 : _a.length) > 0) {
56
+ // TODO(cedric): group errors by property to make multiple errors for same property more readable
57
+ log_1.default.log(error.errors.map(err => ` - ${chalk_1.default.bold(err.dataPath)} ${err.message}`).join('\n'));
58
+ }
59
+ }
60
+ exports.logMetadataValidationError = logMetadataValidationError;
46
61
  /**
47
62
  * Handle a thrown metadata error by informing the user what went wrong.
48
63
  * If a normal error is thrown, this method will re-throw that error to avoid consuming it.
@@ -50,12 +65,16 @@ exports.MetadataDownloadError = MetadataDownloadError;
50
65
  function handleMetadataError(error) {
51
66
  var _a;
52
67
  if (error instanceof MetadataValidationError) {
53
- log_1.default.error(error.message);
54
- log_1.default.log((_a = error.errors) === null || _a === void 0 ? void 0 : _a.map(err => ` - ${err.dataPath} ${err.message}`).join('\n'));
55
- return;
68
+ return logMetadataValidationError(error);
56
69
  }
57
70
  if (error instanceof MetadataDownloadError || error instanceof MetadataUploadError) {
58
- log_1.default.error(error.message);
71
+ log_1.default.newLine();
72
+ log_1.default.error(chalk_1.default.bold(error.message));
73
+ if (((_a = error.errors) === null || _a === void 0 ? void 0 : _a.length) > 0) {
74
+ log_1.default.newLine();
75
+ log_1.default.error(error.errors.map(err => err.message).join('\n\n'));
76
+ }
77
+ log_1.default.newLine();
59
78
  log_1.default.log('Please check the logs for any configuration issues.');
60
79
  log_1.default.log('If this issue persists, please open a new issue at:');
61
80
  // TODO: add execution ID to the issue template link
@@ -3,4 +3,6 @@ import { MetadataContext } from './context';
3
3
  * Sync a local store configuration with the stores.
4
4
  * Note, only App Store is supported at this time.
5
5
  */
6
- export declare function uploadMetadataAsync(metadataCtx: MetadataContext): Promise<void>;
6
+ export declare function uploadMetadataAsync(metadataCtx: MetadataContext): Promise<{
7
+ appleLink: string;
8
+ }>;
@@ -5,6 +5,8 @@ const tslib_1 = require("tslib");
5
5
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
6
6
  const path_1 = tslib_1.__importDefault(require("path"));
7
7
  const events_1 = require("../analytics/events");
8
+ const log_1 = tslib_1.__importDefault(require("../log"));
9
+ const prompts_1 = require("../prompts");
8
10
  const tasks_1 = require("./apple/tasks");
9
11
  const config_1 = require("./config");
10
12
  const context_1 = require("./context");
@@ -19,13 +21,24 @@ async function uploadMetadataAsync(metadataCtx) {
19
21
  if (!(await fs_extra_1.default.pathExists(filePath))) {
20
22
  throw new errors_1.MetadataValidationError(`Store configuration file not found "${filePath}"`);
21
23
  }
22
- const { app, auth } = await (0, context_1.ensureMetadataAppStoreAuthenticatedAsync)(metadataCtx);
23
- const { unsubscribeTelemetry, executionId } = (0, telemetry_1.subscribeTelemetry)(events_1.MetadataEvent.APPLE_METADATA_UPLOAD, { app, auth });
24
24
  const fileData = await fs_extra_1.default.readJson(filePath);
25
25
  const { valid, errors: validationErrors } = (0, config_1.validateConfig)(fileData);
26
26
  if (!valid) {
27
- throw new errors_1.MetadataValidationError(`Store configuration errors found`, validationErrors);
27
+ const error = new errors_1.MetadataValidationError(`Store configuration errors found`, validationErrors);
28
+ (0, errors_1.logMetadataValidationError)(error);
29
+ log_1.default.newLine();
30
+ log_1.default.warn('Without further updates, the current store configuration may fail to be synchronized with the App Store or pass App Store review.');
31
+ const attempt = await (0, prompts_1.confirmAsync)({
32
+ message: 'Do you still want to push the store configuration?',
33
+ });
34
+ if (!attempt) {
35
+ throw error;
36
+ }
28
37
  }
38
+ const { app, auth } = await (0, context_1.ensureMetadataAppStoreAuthenticatedAsync)(metadataCtx);
39
+ const { unsubscribeTelemetry, executionId } = (0, telemetry_1.subscribeTelemetry)(events_1.MetadataEvent.APPLE_METADATA_UPLOAD, { app, auth });
40
+ log_1.default.addNewLineIfNone();
41
+ log_1.default.log('Uploading App Store configuration...');
29
42
  const errors = [];
30
43
  const config = (0, config_1.createAppleReader)(fileData);
31
44
  const tasks = (0, tasks_1.createAppleTasks)(metadataCtx);
@@ -50,5 +63,6 @@ async function uploadMetadataAsync(metadataCtx) {
50
63
  if (errors.length > 0) {
51
64
  throw new errors_1.MetadataUploadError(errors, executionId);
52
65
  }
66
+ return { appleLink: `https://appstoreconnect.apple.com/apps/${app.id}/appstore` };
53
67
  }
54
68
  exports.uploadMetadataAsync = uploadMetadataAsync;
@@ -9,4 +9,4 @@
9
9
  * "pointer": "/data/attributes/earliestReleaseDate"
10
10
  * }
11
11
  */
12
- export declare function removeDatePrecision(date: any): null | Date;
12
+ export declare function removeDatePrecision(date: null | undefined | string | number | Date): null | Date;
@@ -10,6 +10,7 @@ const chalk_1 = tslib_1.__importDefault(require("chalk"));
10
10
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
11
11
  const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
12
12
  const appJson_1 = require("../../build/utils/appJson");
13
+ const env_1 = tslib_1.__importDefault(require("../../env"));
13
14
  const log_1 = tslib_1.__importStar(require("../../log"));
14
15
  const projectUtils_1 = require("../../project/projectUtils");
15
16
  const prompts_1 = require("../../prompts");
@@ -37,6 +38,9 @@ class AmbiguousApplicationIdError extends Error {
37
38
  }
38
39
  exports.AmbiguousApplicationIdError = AmbiguousApplicationIdError;
39
40
  async function getApplicationIdFromBareAsync(projectDir, gradleContext) {
41
+ if (env_1.default.overrideAndroidApplicationId) {
42
+ return env_1.default.overrideAndroidApplicationId;
43
+ }
40
44
  const errorMessage = 'Could not read applicationId from Android project.';
41
45
  if (gradleContext) {
42
46
  const buildGradle = await gradleUtils.getAppBuildGradleAsync(projectDir);
@@ -27,7 +27,10 @@ async function ensureProjectExistsAsync(projectInfo) {
27
27
  const account = (0, Account_1.findAccountByName)(actor.accounts, accountName);
28
28
  (0, assert_1.default)(account, `You must have access to the ${accountName} account to run this command`);
29
29
  const projectDashboardUrl = (0, url_1.getProjectDashboardUrl)(accountName, projectName);
30
- const projectLink = (0, terminal_link_1.default)(projectFullName, projectDashboardUrl);
30
+ const projectLink = (0, terminal_link_1.default)(projectFullName, projectDashboardUrl, {
31
+ // https://github.com/sindresorhus/terminal-link/issues/18#issuecomment-1068020361
32
+ fallback: () => `${projectFullName} (${projectDashboardUrl})`,
33
+ });
31
34
  const spinner = (0, ora_1.ora)(`Linking to project ${chalk_1.default.bold(projectFullName)}`).start();
32
35
  const maybeId = await findProjectIdByAccountNameAndSlugNullableAsync(accountName, projectName);
33
36
  if (maybeId) {
@@ -73,5 +73,10 @@ export declare function filterOutAssetsThatAlreadyExistAsync(uniqueAssetsWithSto
73
73
  })[]): Promise<(RawAsset & {
74
74
  storageKey: string;
75
75
  })[]>;
76
- export declare function uploadAssetsAsync(assetsForUpdateInfoGroup: CollectedAssets): Promise<void>;
76
+ declare type AssetUploadResult = {
77
+ assetCount: number;
78
+ uniqueAssetCount: number;
79
+ uniqueUploadedAssetCount: number;
80
+ };
81
+ export declare function uploadAssetsAsync(assetsForUpdateInfoGroup: CollectedAssets, updateSpinnerText?: (updatedText: string) => void): Promise<AssetUploadResult>;
77
82
  export {};
@@ -8,10 +8,10 @@ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
8
8
  const joi_1 = tslib_1.__importDefault(require("joi"));
9
9
  const mime_1 = tslib_1.__importDefault(require("mime"));
10
10
  const path_1 = tslib_1.__importDefault(require("path"));
11
+ const promise_limit_1 = tslib_1.__importDefault(require("promise-limit"));
11
12
  const generated_1 = require("../graphql/generated");
12
13
  const PublishMutation_1 = require("../graphql/mutations/PublishMutation");
13
14
  const PublishQuery_1 = require("../graphql/queries/PublishQuery");
14
- const log_1 = tslib_1.__importDefault(require("../log"));
15
15
  const uploads_1 = require("../uploads");
16
16
  const expoCli_1 = require("../utils/expoCli");
17
17
  const uniqBy_1 = tslib_1.__importDefault(require("../utils/expodash/uniqBy"));
@@ -107,7 +107,13 @@ async function buildBundlesAsync({ projectDir, inputDir, }) {
107
107
  if (!packageJSON) {
108
108
  throw new Error('Could not locate package.json');
109
109
  }
110
- await (0, expoCli_1.expoCommandAsync)(projectDir, ['export', '--output-dir', inputDir, '--experimental-bundle'], { silent: !log_1.default.isDebug });
110
+ await (0, expoCli_1.expoCommandAsync)(projectDir, [
111
+ 'export',
112
+ '--output-dir',
113
+ inputDir,
114
+ '--experimental-bundle',
115
+ '--non-interactive',
116
+ ]);
111
117
  }
112
118
  exports.buildBundlesAsync = buildBundlesAsync;
113
119
  async function resolveInputDirectoryAsync(customInputDirectory) {
@@ -178,7 +184,7 @@ async function filterOutAssetsThatAlreadyExistAsync(uniqueAssetsWithStorageKey)
178
184
  return missingAssets;
179
185
  }
180
186
  exports.filterOutAssetsThatAlreadyExistAsync = filterOutAssetsThatAlreadyExistAsync;
181
- async function uploadAssetsAsync(assetsForUpdateInfoGroup) {
187
+ async function uploadAssetsAsync(assetsForUpdateInfoGroup, updateSpinnerText) {
182
188
  let assets = [];
183
189
  let platform;
184
190
  for (platform in assetsForUpdateInfoGroup) {
@@ -188,6 +194,7 @@ async function uploadAssetsAsync(assetsForUpdateInfoGroup) {
188
194
  ...assetsForUpdateInfoGroup[platform].assets,
189
195
  ];
190
196
  }
197
+ updateSpinnerText === null || updateSpinnerText === void 0 ? void 0 : updateSpinnerText(`${assets.length} ${assets.length === 1 ? 'asset' : 'assets'} present`);
191
198
  const assetsWithStorageKey = await Promise.all(assets.map(async (asset) => {
192
199
  return {
193
200
  ...asset,
@@ -195,12 +202,18 @@ async function uploadAssetsAsync(assetsForUpdateInfoGroup) {
195
202
  };
196
203
  }));
197
204
  const uniqueAssets = (0, uniqBy_1.default)(assetsWithStorageKey, asset => asset.storageKey);
205
+ updateSpinnerText === null || updateSpinnerText === void 0 ? void 0 : updateSpinnerText(`${uniqueAssets.length} unique ${uniqueAssets.length === 1 ? 'asset' : 'assets'} found`);
198
206
  let missingAssets = await filterOutAssetsThatAlreadyExistAsync(uniqueAssets);
207
+ const uniqueUploadedAssetCount = missingAssets.length;
199
208
  const { specifications } = await PublishMutation_1.PublishMutation.getUploadURLsAsync(missingAssets.map(ma => ma.contentType));
209
+ const assetUploadPromiseLimit = (0, promise_limit_1.default)(15);
200
210
  await Promise.all(missingAssets.map((missingAsset, i) => {
201
- const presignedPost = JSON.parse(specifications[i]);
202
- return (0, uploads_1.uploadWithPresignedPostAsync)(missingAsset.path, presignedPost);
211
+ assetUploadPromiseLimit(async () => {
212
+ const presignedPost = JSON.parse(specifications[i]);
213
+ return (0, uploads_1.uploadWithPresignedPostAsync)(missingAsset.path, presignedPost);
214
+ });
203
215
  }));
216
+ updateSpinnerText === null || updateSpinnerText === void 0 ? void 0 : updateSpinnerText(`${missingAssets.length} new ${missingAssets.length === 1 ? 'asset' : 'assets'} uploading`);
204
217
  // Wait up to TIMEOUT_LIMIT for assets to be uploaded and processed
205
218
  const start = Date.now();
206
219
  let timeout = 1;
@@ -213,5 +226,10 @@ async function uploadAssetsAsync(assetsForUpdateInfoGroup) {
213
226
  throw new Error('Asset upload timed out. Please try again.');
214
227
  }
215
228
  }
229
+ return {
230
+ assetCount: assets.length,
231
+ uniqueAssetCount: uniqueAssets.length,
232
+ uniqueUploadedAssetCount,
233
+ };
216
234
  }
217
235
  exports.uploadAssetsAsync = uploadAssetsAsync;
@@ -8,23 +8,25 @@ const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
8
8
  const path_1 = tslib_1.__importDefault(require("path"));
9
9
  const vcs_1 = require("../vcs");
10
10
  async function resolveWorkflowAsync(projectDir, platform) {
11
- let platformWorkflowMarker;
11
+ let platformWorkflowMarkers;
12
12
  try {
13
- platformWorkflowMarker =
13
+ platformWorkflowMarkers =
14
14
  platform === eas_build_job_1.Platform.ANDROID
15
- ? await config_plugins_1.AndroidConfig.Paths.getAndroidManifestAsync(projectDir)
16
- : config_plugins_1.IOSConfig.Paths.getPBXProjectPath(projectDir);
15
+ ? [
16
+ path_1.default.join(projectDir, 'android/app/build.gradle'),
17
+ await config_plugins_1.AndroidConfig.Paths.getAndroidManifestAsync(projectDir),
18
+ ]
19
+ : [config_plugins_1.IOSConfig.Paths.getPBXProjectPath(projectDir)];
17
20
  }
18
21
  catch {
19
22
  return eas_build_job_1.Workflow.MANAGED;
20
23
  }
21
- if (await fs_extra_1.default.pathExists(platformWorkflowMarker)) {
22
- return (await (0, vcs_1.getVcsClient)().isFileIgnoredAsync(path_1.default.relative(projectDir, platformWorkflowMarker)))
23
- ? eas_build_job_1.Workflow.MANAGED
24
- : eas_build_job_1.Workflow.GENERIC;
25
- }
26
- else {
27
- return eas_build_job_1.Workflow.MANAGED;
24
+ for (const marker of platformWorkflowMarkers) {
25
+ if ((await fs_extra_1.default.pathExists(marker)) &&
26
+ !(await (0, vcs_1.getVcsClient)().isFileIgnoredAsync(path_1.default.relative(projectDir, marker)))) {
27
+ return eas_build_job_1.Workflow.GENERIC;
28
+ }
28
29
  }
30
+ return eas_build_job_1.Workflow.MANAGED;
29
31
  }
30
32
  exports.resolveWorkflowAsync = resolveWorkflowAsync;
@@ -6,3 +6,9 @@ export declare function uploadAsync(type: UploadSessionType, path: string, handl
6
6
  bucketKey: string;
7
7
  }>;
8
8
  export declare function uploadWithPresignedPostAsync(file: string, presignedPost: PresignedPost, handleProgressEvent?: ProgressHandler): Promise<string>;
9
+ /**
10
+ * S3 returns broken URLs, sth like:
11
+ * https://submission-service-archives.s3.amazonaws.com/production%2Fdc98ca84-1473-4cb3-ae81-8c7b291cb27e%2F4424aa95-b985-4e2f-8755-9507b1037c1c
12
+ * This function replaces %2F with /.
13
+ */
14
+ export declare function fixArchiveUrl(archiveUrl: string): string;
package/build/uploads.js CHANGED
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.uploadWithPresignedPostAsync = exports.uploadAsync = void 0;
3
+ exports.fixArchiveUrl = exports.uploadWithPresignedPostAsync = exports.uploadAsync = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const assert_1 = tslib_1.__importDefault(require("assert"));
6
6
  const form_data_1 = tslib_1.__importDefault(require("form-data"));
7
7
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
8
+ const url_1 = require("url");
8
9
  const fetch_1 = tslib_1.__importDefault(require("./fetch"));
9
10
  const UploadSessionMutation_1 = require("./graphql/mutations/UploadSessionMutation");
10
11
  async function uploadAsync(type, path, handleProgressEvent) {
@@ -45,7 +46,7 @@ async function uploadWithPresignedPostAsync(file, presignedPost, handleProgressE
45
46
  try {
46
47
  const response = await uploadPromise;
47
48
  handleProgressEvent({ isComplete: true });
48
- return String(response.headers.get('location'));
49
+ return fixArchiveUrl(String(response.headers.get('location')));
49
50
  }
50
51
  catch (error) {
51
52
  handleProgressEvent({ isComplete: true, error });
@@ -54,7 +55,18 @@ async function uploadWithPresignedPostAsync(file, presignedPost, handleProgressE
54
55
  }
55
56
  else {
56
57
  const response = await uploadPromise;
57
- return String(response.headers.get('location'));
58
+ return fixArchiveUrl(String(response.headers.get('location')));
58
59
  }
59
60
  }
60
61
  exports.uploadWithPresignedPostAsync = uploadWithPresignedPostAsync;
62
+ /**
63
+ * S3 returns broken URLs, sth like:
64
+ * https://submission-service-archives.s3.amazonaws.com/production%2Fdc98ca84-1473-4cb3-ae81-8c7b291cb27e%2F4424aa95-b985-4e2f-8755-9507b1037c1c
65
+ * This function replaces %2F with /.
66
+ */
67
+ function fixArchiveUrl(archiveUrl) {
68
+ const parsed = new url_1.URL(archiveUrl);
69
+ parsed.pathname = decodeURIComponent(parsed.pathname);
70
+ return parsed.toString();
71
+ }
72
+ exports.fixArchiveUrl = fixArchiveUrl;
@@ -0,0 +1 @@
1
+ export default function uniq<T = any>(items: T[]): T[];
@@ -1,8 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.unique = void 0;
4
- function unique(items) {
3
+ function uniq(items) {
5
4
  const set = new Set(items);
6
5
  return [...set];
7
6
  }
8
- exports.unique = unique;
7
+ exports.default = uniq;
@@ -1,13 +1,13 @@
1
1
  import { Platform } from '@expo/eas-build-job';
2
- import { BuildProfile, ProfileType, SubmitProfile } from '@expo/eas-json';
2
+ import { BuildProfile, EasJsonReader, ProfileType, SubmitProfile } from '@expo/eas-json';
3
3
  declare type EasProfile<T extends ProfileType> = T extends 'build' ? BuildProfile<Platform> : SubmitProfile<Platform>;
4
4
  export declare type ProfileData<T extends ProfileType> = {
5
5
  profile: EasProfile<T>;
6
6
  platform: Platform;
7
7
  profileName: string;
8
8
  };
9
- export declare function getProfilesAsync<T extends ProfileType>({ projectDir, platforms, profileName, type, }: {
10
- projectDir: string;
9
+ export declare function getProfilesAsync<T extends ProfileType>({ easJsonReader, platforms, profileName, type, }: {
10
+ easJsonReader: EasJsonReader;
11
11
  platforms: Platform[];
12
12
  profileName?: string;
13
13
  type: T;