eas-cli 3.18.3 → 4.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 (44) hide show
  1. package/README.md +72 -64
  2. package/build/channel/utils.d.ts +0 -10
  3. package/build/channel/utils.js +1 -60
  4. package/build/commands/channel/rollout.d.ts +14 -2
  5. package/build/commands/channel/rollout.js +106 -270
  6. package/build/commands/update/republish.js +6 -6
  7. package/build/devices/actions/create/action.d.ts +2 -1
  8. package/build/devices/actions/create/action.js +10 -1
  9. package/build/devices/actions/create/currentMachineMethod.d.ts +3 -0
  10. package/build/devices/actions/create/currentMachineMethod.js +100 -0
  11. package/build/devices/actions/create/developerPortalMethod.js +2 -1
  12. package/build/devices/actions/create/inputMethod.d.ts +0 -1
  13. package/build/devices/actions/create/inputMethod.js +6 -64
  14. package/build/devices/actions/create/utils.d.ts +10 -0
  15. package/build/devices/actions/create/utils.js +73 -0
  16. package/build/devices/manager.js +3 -3
  17. package/build/devices/utils/errors.d.ts +3 -0
  18. package/build/devices/utils/errors.js +7 -1
  19. package/build/devices/utils/formatDevice.js +1 -0
  20. package/build/graphql/generated.d.ts +5 -4
  21. package/build/graphql/generated.js +1 -0
  22. package/build/graphql/types/credentials/AppleDevice.js +1 -0
  23. package/build/rollout/actions/CreateRollout.d.ts +2 -0
  24. package/build/rollout/actions/CreateRollout.js +79 -5
  25. package/build/rollout/actions/EditRollout.js +5 -2
  26. package/build/rollout/actions/EndRollout.d.ts +6 -3
  27. package/build/rollout/actions/EndRollout.js +22 -21
  28. package/build/rollout/actions/ManageRollout.d.ts +3 -3
  29. package/build/rollout/actions/ManageRollout.js +1 -1
  30. package/build/rollout/actions/NonInteractiveRollout.d.ts +6 -4
  31. package/build/rollout/actions/NonInteractiveRollout.js +1 -1
  32. package/build/rollout/actions/RolloutMainMenu.d.ts +4 -4
  33. package/build/rollout/actions/RolloutMainMenu.js +14 -5
  34. package/build/rollout/actions/SelectRuntime.d.ts +4 -7
  35. package/build/rollout/actions/SelectRuntime.js +22 -39
  36. package/build/rollout/branch-mapping.js +2 -2
  37. package/build/rollout/utils.d.ts +1 -1
  38. package/build/rollout/utils.js +5 -5
  39. package/build/update/republish.js +6 -6
  40. package/build/utils/relay.js +3 -9
  41. package/oclif.manifest.json +1 -1
  42. package/package.json +3 -3
  43. package/build/commands/channel/rollout-preview.d.ts +0 -32
  44. package/build/commands/channel/rollout-preview.js +0 -140
@@ -1,15 +1,13 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.promptForUDIDAsync = exports.runInputMethodAsync = void 0;
3
+ exports.runInputMethodAsync = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
6
6
  const AppleDeviceMutation_1 = require("../../../credentials/ios/api/graphql/mutations/AppleDeviceMutation");
7
- const generated_1 = require("../../../graphql/generated");
8
7
  const log_1 = tslib_1.__importDefault(require("../../../log"));
9
8
  const ora_1 = require("../../../ora");
10
9
  const prompts_1 = require("../../../prompts");
11
- const udids_1 = require("../../udids");
12
- const formatDevice_1 = require("../../utils/formatDevice");
10
+ const utils_1 = require("./utils");
13
11
  async function runInputMethodAsync(graphqlClient, accountId, appleTeam) {
14
12
  log_1.default.newLine();
15
13
  log_1.default.log(chalk_1.default.yellow('This is an advanced option. Use at your own risk.'));
@@ -42,20 +40,15 @@ async function collectDataAndRegisterDeviceAsync(graphqlClient, { accountId, app
42
40
  spinner.succeed();
43
41
  }
44
42
  async function collectDeviceDataAsync(appleTeam, initialValues = {}) {
45
- const udid = await promptForUDIDAsync(initialValues.udid);
46
- const name = await promptForNameAsync(initialValues.name);
47
- const deviceClass = await promptForDeviceClassAsync(initialValues.deviceClass);
43
+ const udid = await (0, utils_1.promptForUDIDAsync)(initialValues.udid);
44
+ const name = await (0, utils_1.promptForNameAsync)(initialValues.name);
45
+ const deviceClass = await (0, utils_1.promptForDeviceClassAsync)(initialValues.deviceClass);
48
46
  const deviceData = {
49
47
  udid,
50
48
  name,
51
49
  deviceClass,
52
50
  };
53
- log_1.default.newLine();
54
- log_1.default.log(`We are going to register the following device in our database.
55
- This device will ${chalk_1.default.bold('not')} be registered on the Apple Developer Portal until it is chosen for an internal distribution build.`);
56
- log_1.default.newLine();
57
- log_1.default.log((0, formatDevice_1.formatNewDevice)({ ...deviceData, identifier: deviceData.udid }, appleTeam));
58
- log_1.default.newLine();
51
+ (0, utils_1.printDeviceData)(deviceData, appleTeam);
59
52
  const registrationConfirmed = await (0, prompts_1.confirmAsync)({
60
53
  message: 'Is this what you want to register?',
61
54
  });
@@ -68,54 +61,3 @@ This device will ${chalk_1.default.bold('not')} be registered on the Apple Devel
68
61
  return deviceData;
69
62
  }
70
63
  }
71
- async function promptForUDIDAsync(initial) {
72
- const { udid } = await (0, prompts_1.promptAsync)({
73
- type: 'text',
74
- name: 'udid',
75
- message: 'UDID:',
76
- initial,
77
- validate: (rawVal) => {
78
- const val = (0, udids_1.normalizeUDID)(rawVal);
79
- if (!val || val === '') {
80
- return 'UDID cannot be empty';
81
- }
82
- else if (val.length !== 25 && val.length !== 40) {
83
- return 'UDID should be a 25 or 40-character string';
84
- }
85
- else if (!(0, udids_1.isValidUDID)(val)) {
86
- return 'UDID is invalid';
87
- }
88
- else {
89
- return true;
90
- }
91
- },
92
- format: (val) => (0, udids_1.normalizeUDID)(val),
93
- });
94
- return udid;
95
- }
96
- exports.promptForUDIDAsync = promptForUDIDAsync;
97
- async function promptForNameAsync(initial) {
98
- const { name } = await (0, prompts_1.promptAsync)({
99
- type: 'text',
100
- name: 'name',
101
- message: 'Device name (optional):',
102
- initial,
103
- });
104
- return name;
105
- }
106
- async function promptForDeviceClassAsync(initial) {
107
- const choices = [
108
- { title: 'iPhone', value: generated_1.AppleDeviceClass.Iphone },
109
- { title: 'iPad', value: generated_1.AppleDeviceClass.Ipad },
110
- { title: 'Not sure (leave empty)', value: null },
111
- ];
112
- const values = choices.map(({ value }) => value);
113
- const { deviceClass } = await (0, prompts_1.promptAsync)({
114
- type: 'select',
115
- name: 'deviceClass',
116
- message: 'Device class (optional):',
117
- choices,
118
- initial: initial !== undefined && values.indexOf(initial),
119
- });
120
- return deviceClass;
121
- }
@@ -0,0 +1,10 @@
1
+ import { AppleDeviceClass, AppleTeam } from '../../../graphql/generated';
2
+ export interface DeviceData {
3
+ udid: string;
4
+ name?: string;
5
+ deviceClass: AppleDeviceClass | null;
6
+ }
7
+ export declare function promptForDeviceClassAsync(initial?: AppleDeviceClass | null): Promise<AppleDeviceClass | null>;
8
+ export declare function promptForNameAsync(initial?: string): Promise<string | undefined>;
9
+ export declare function promptForUDIDAsync(initial?: string): Promise<string>;
10
+ export declare function printDeviceData(deviceData: DeviceData, appleTeam: Pick<AppleTeam, 'appleTeamIdentifier' | 'appleTeamName'>): void;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.printDeviceData = exports.promptForUDIDAsync = exports.promptForNameAsync = exports.promptForDeviceClassAsync = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
6
+ const generated_1 = require("../../../graphql/generated");
7
+ const log_1 = tslib_1.__importDefault(require("../../../log"));
8
+ const prompts_1 = require("../../../prompts");
9
+ const udids_1 = require("../../udids");
10
+ const formatDevice_1 = require("../../utils/formatDevice");
11
+ async function promptForDeviceClassAsync(initial) {
12
+ const choices = [
13
+ { title: 'iPhone', value: generated_1.AppleDeviceClass.Iphone },
14
+ { title: 'iPad', value: generated_1.AppleDeviceClass.Ipad },
15
+ { title: 'Mac', value: generated_1.AppleDeviceClass.Mac },
16
+ { title: 'Not sure (leave empty)', value: null },
17
+ ];
18
+ const values = choices.map(({ value }) => value);
19
+ const { deviceClass } = await (0, prompts_1.promptAsync)({
20
+ type: 'select',
21
+ name: 'deviceClass',
22
+ message: 'Device class (optional):',
23
+ choices,
24
+ initial: initial !== undefined && values.indexOf(initial),
25
+ });
26
+ return deviceClass;
27
+ }
28
+ exports.promptForDeviceClassAsync = promptForDeviceClassAsync;
29
+ async function promptForNameAsync(initial) {
30
+ const { name } = await (0, prompts_1.promptAsync)({
31
+ type: 'text',
32
+ name: 'name',
33
+ message: 'Device name (optional):',
34
+ initial,
35
+ });
36
+ return name;
37
+ }
38
+ exports.promptForNameAsync = promptForNameAsync;
39
+ async function promptForUDIDAsync(initial) {
40
+ const { udid } = await (0, prompts_1.promptAsync)({
41
+ type: 'text',
42
+ name: 'udid',
43
+ message: 'UDID:',
44
+ initial,
45
+ validate: (rawVal) => {
46
+ const val = (0, udids_1.normalizeUDID)(rawVal);
47
+ if (!val || val === '') {
48
+ return 'UDID cannot be empty';
49
+ }
50
+ else if (val.length !== 25 && val.length !== 40) {
51
+ return 'UDID should be a 25 or 40-character string';
52
+ }
53
+ else if (!(0, udids_1.isValidUDID)(val)) {
54
+ return 'UDID is invalid';
55
+ }
56
+ else {
57
+ return true;
58
+ }
59
+ },
60
+ format: (val) => (0, udids_1.normalizeUDID)(val),
61
+ });
62
+ return udid;
63
+ }
64
+ exports.promptForUDIDAsync = promptForUDIDAsync;
65
+ function printDeviceData(deviceData, appleTeam) {
66
+ log_1.default.newLine();
67
+ log_1.default.log(`We are going to register the following device in our database.
68
+ This device will ${chalk_1.default.bold('not')} be registered on the Apple Developer Portal until it is chosen for an internal distribution build.`);
69
+ log_1.default.newLine();
70
+ log_1.default.log((0, formatDevice_1.formatNewDevice)({ ...deviceData, identifier: deviceData.udid }, appleTeam));
71
+ log_1.default.newLine();
72
+ }
73
+ exports.printDeviceData = printDeviceData;
@@ -10,10 +10,10 @@ const log_1 = tslib_1.__importDefault(require("../log"));
10
10
  const projectUtils_1 = require("../project/projectUtils");
11
11
  const prompts_1 = require("../prompts");
12
12
  const action_1 = tslib_1.__importDefault(require("./actions/create/action"));
13
- const CREATE_COMMAND_DESCRIPTION = `This command lets you register your Apple devices (iPhones and iPads) for internal distribution of your app.
13
+ const CREATE_COMMAND_DESCRIPTION = `This command lets you register your Apple devices (iPhones, iPads and Macs) for internal distribution of your app.
14
14
  Internal distribution means that you won't need to upload your app archive to App Store / Testflight.
15
- Your app archive (.ipa) will be installable on your equipment as long as you sign your application with an adhoc provisiong profile.
16
- The provisioning profile needs to contain the UDIDs (unique identifiers) of your iPhones and iPads.
15
+ Your app archive (.ipa) will be installable on your equipment as long as you sign your application with an adhoc provisioning profile.
16
+ The provisioning profile needs to contain the UDIDs (unique identifiers) of your iPhones, iPads and Macs.
17
17
 
18
18
  First of all, choose the Expo account under which you want to register your devices.
19
19
  Later, authenticate with Apple and choose your desired Apple Team (if your Apple ID has access to multiple teams).`;
@@ -1,3 +1,6 @@
1
1
  export declare class DeviceNotFoundError extends Error {
2
2
  constructor(message?: string);
3
3
  }
4
+ export declare class DeviceCreateError extends Error {
5
+ constructor(message?: string);
6
+ }
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DeviceNotFoundError = void 0;
3
+ exports.DeviceCreateError = exports.DeviceNotFoundError = void 0;
4
4
  class DeviceNotFoundError extends Error {
5
5
  constructor(message) {
6
6
  super(message !== null && message !== void 0 ? message : 'Device not found.');
7
7
  }
8
8
  }
9
9
  exports.DeviceNotFoundError = DeviceNotFoundError;
10
+ class DeviceCreateError extends Error {
11
+ constructor(message) {
12
+ super(message !== null && message !== void 0 ? message : 'Failed to create a device.');
13
+ }
14
+ }
15
+ exports.DeviceCreateError = DeviceCreateError;
@@ -7,6 +7,7 @@ const formatFields_1 = tslib_1.__importDefault(require("../../utils/formatFields
7
7
  const DEVICE_CLASS_DISPLAY_NAMES = {
8
8
  [generated_1.AppleDeviceClass.Iphone]: 'iPhone',
9
9
  [generated_1.AppleDeviceClass.Ipad]: 'iPad',
10
+ [generated_1.AppleDeviceClass.Mac]: 'Mac',
10
11
  };
11
12
  function formatDeviceClass(device) {
12
13
  if (!device.deviceClass || !DEVICE_CLASS_DISPLAY_NAMES[device.deviceClass]) {
@@ -1391,7 +1391,8 @@ export type AppleDevice = {
1391
1391
  };
1392
1392
  export declare enum AppleDeviceClass {
1393
1393
  Ipad = "IPAD",
1394
- Iphone = "IPHONE"
1394
+ Iphone = "IPHONE",
1395
+ Mac = "MAC"
1395
1396
  }
1396
1397
  export type AppleDeviceInput = {
1397
1398
  appleTeamId: Scalars['ID'];
@@ -3713,7 +3714,7 @@ export type SsoUser = Actor & UserActor & {
3713
3714
  twitterUsername?: Maybe<Scalars['String']>;
3714
3715
  username: Scalars['String'];
3715
3716
  /** Web notifications linked to a user */
3716
- webNotifications: Array<Notification>;
3717
+ websiteNotifications: Array<Notification>;
3717
3718
  };
3718
3719
  /** Represents a human SSO (not robot) actor. */
3719
3720
  export type SsoUserActivityTimelineProjectActivitiesArgs = {
@@ -4356,7 +4357,7 @@ export type User = Actor & UserActor & {
4356
4357
  /** @deprecated No longer supported */
4357
4358
  twitterUsername?: Maybe<Scalars['String']>;
4358
4359
  username: Scalars['String'];
4359
- webNotifications: Array<Notification>;
4360
+ websiteNotifications: Array<Notification>;
4360
4361
  };
4361
4362
  /** Represents a human (not robot) actor. */
4362
4363
  export type UserActivityTimelineProjectActivitiesArgs = {
@@ -4435,7 +4436,7 @@ export type UserActor = {
4435
4436
  twitterUsername?: Maybe<Scalars['String']>;
4436
4437
  username: Scalars['String'];
4437
4438
  /** Web notifications linked to a user */
4438
- webNotifications: Array<Notification>;
4439
+ websiteNotifications: Array<Notification>;
4439
4440
  };
4440
4441
  /** A human user (type User or SSOUser) that can login to the Expo website, use Expo services, and be a member of accounts. */
4441
4442
  export type UserActorActivityTimelineProjectActivitiesArgs = {
@@ -83,6 +83,7 @@ var AppleDeviceClass;
83
83
  (function (AppleDeviceClass) {
84
84
  AppleDeviceClass["Ipad"] = "IPAD";
85
85
  AppleDeviceClass["Iphone"] = "IPHONE";
86
+ AppleDeviceClass["Mac"] = "MAC";
86
87
  })(AppleDeviceClass = exports.AppleDeviceClass || (exports.AppleDeviceClass = {}));
87
88
  var AppsFilter;
88
89
  (function (AppsFilter) {
@@ -7,6 +7,7 @@ const generated_1 = require("../../generated");
7
7
  exports.APPLE_DEVICE_CLASS_LABELS = {
8
8
  [generated_1.AppleDeviceClass.Ipad]: 'iPad',
9
9
  [generated_1.AppleDeviceClass.Iphone]: 'iPhone',
10
+ [generated_1.AppleDeviceClass.Mac]: 'Mac',
10
11
  };
11
12
  exports.AppleDeviceFragmentNode = (0, graphql_tag_1.default) `
12
13
  fragment AppleDeviceFragment on AppleDevice {
@@ -18,6 +18,8 @@ export declare class CreateRollout implements EASUpdateAction<UpdateChannelBasic
18
18
  getChannelObjectAsync(ctx: EASUpdateContext, runtimeVersion: string): Promise<UpdateChannelObject>;
19
19
  getLatestUpdateGroupOnBranchAsync(ctx: EASUpdateContext, branchInfo: UpdateBranchBasicInfoFragment, runtimeVersion: string): Promise<UpdateFragment[] | null>;
20
20
  selectRuntimeVersionAsync(ctx: EASUpdateContext, branchToRollout: UpdateBranchBasicInfoFragment, defaultBranchId: string): Promise<string>;
21
+ selectRuntimeVersionFromAlternativeSourceAsync(ctx: EASUpdateContext, branchToRollout: UpdateBranchBasicInfoFragment, defaultBranch: UpdateBranchBasicInfoFragment): Promise<string>;
22
+ selectRuntimeVersionFromProjectConfigAsync(ctx: EASUpdateContext): Promise<string>;
21
23
  selectBranchAsync(ctx: EASUpdateContext, defaultBranchId: string): Promise<UpdateBranchBasicInfoFragment>;
22
24
  resolveBranchNameAsync(ctx: EASUpdateContext, branchName: string): Promise<UpdateBranchBasicInfoFragment>;
23
25
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CreateRollout = void 0;
4
4
  const tslib_1 = require("tslib");
5
+ const config_plugins_1 = require("@expo/config-plugins");
5
6
  const assert_1 = tslib_1.__importDefault(require("assert"));
6
7
  const SelectBranch_1 = require("../../branch/actions/SelectBranch");
7
8
  const branch_mapping_1 = require("../../channel/branch-mapping");
@@ -12,6 +13,7 @@ const ChannelQuery_1 = require("../../graphql/queries/ChannelQuery");
12
13
  const UpdateQuery_1 = require("../../graphql/queries/UpdateQuery");
13
14
  const log_1 = tslib_1.__importDefault(require("../../log"));
14
15
  const prompts_1 = require("../../prompts");
16
+ const filter_1 = require("../../utils/expodash/filter");
15
17
  const branch_mapping_2 = require("../branch-mapping");
16
18
  const utils_2 = require("../utils");
17
19
  const SelectRuntime_1 = require("./SelectRuntime");
@@ -51,7 +53,7 @@ class CreateRollout {
51
53
  }
52
54
  const runtimeVersion = (_a = this.options.runtimeVersion) !== null && _a !== void 0 ? _a : (await this.selectRuntimeVersionAsync(ctx, branchInfoToRollout, defaultBranchId));
53
55
  log_1.default.newLine();
54
- const promptMessage = `What percent of users should be routed to branch ${branchInfoToRollout.name}?`;
56
+ const promptMessage = `What percent of users should be rolled out to branch ${branchInfoToRollout.name}?`;
55
57
  const percent = (_b = this.options.percent) !== null && _b !== void 0 ? _b : (await (0, utils_2.promptForRolloutPercentAsync)({ promptMessage }));
56
58
  const rolloutBranchMapping = (0, branch_mapping_2.createRolloutBranchMapping)({
57
59
  defaultBranchId,
@@ -125,20 +127,92 @@ class CreateRollout {
125
127
  channelName: this.channelInfo.name,
126
128
  });
127
129
  const defaultBranchRtvAgnostic = (0, utils_1.getUpdateBranch)(channelObjectRtvAgnostic, defaultBranchId);
128
- const selectRuntimeAction = new SelectRuntime_1.SelectRuntime(branchToRollout, {
130
+ const selectSharedRuntimeAction = new SelectRuntime_1.SelectRuntime(branchToRollout, {
129
131
  anotherBranchToIntersectRuntimesBy: defaultBranchRtvAgnostic,
130
132
  });
131
- return await selectRuntimeAction.runAsync(ctx);
133
+ const sharedRuntime = await selectSharedRuntimeAction.runAsync(ctx);
134
+ if (sharedRuntime) {
135
+ return sharedRuntime;
136
+ }
137
+ return await this.selectRuntimeVersionFromAlternativeSourceAsync(ctx, branchToRollout, defaultBranchRtvAgnostic);
138
+ }
139
+ async selectRuntimeVersionFromAlternativeSourceAsync(ctx, branchToRollout, defaultBranch) {
140
+ const { runtimeSource: selectedRuntimeSource } = await (0, prompts_1.promptAsync)({
141
+ type: 'select',
142
+ name: 'runtimeSource',
143
+ message: `What would you like to do?`,
144
+ choices: [
145
+ {
146
+ value: 'DEFAULT_BRANCH_RUNTIME',
147
+ title: `Find a runtime supported on the ${defaultBranch.name} branch`,
148
+ },
149
+ {
150
+ value: 'ROLLED_OUT_BRANCH_RUNTIME',
151
+ title: `Find a runtime supported on the ${branchToRollout.name} branch`,
152
+ },
153
+ {
154
+ value: 'PROJECT_RUNTIME',
155
+ title: 'Use the runtime specified in your project config',
156
+ },
157
+ ],
158
+ });
159
+ if (selectedRuntimeSource === 'DEFAULT_BRANCH_RUNTIME') {
160
+ const selectDefaultBranchRuntimeAction = new SelectRuntime_1.SelectRuntime(defaultBranch);
161
+ const defaultBranchRuntime = await selectDefaultBranchRuntimeAction.runAsync(ctx);
162
+ if (defaultBranchRuntime) {
163
+ return defaultBranchRuntime;
164
+ }
165
+ }
166
+ else if (selectedRuntimeSource === 'ROLLED_OUT_BRANCH_RUNTIME') {
167
+ const selectBranchToRolloutRuntimeAction = new SelectRuntime_1.SelectRuntime(branchToRollout);
168
+ const branchToRolloutRuntime = await selectBranchToRolloutRuntimeAction.runAsync(ctx);
169
+ if (branchToRolloutRuntime) {
170
+ return branchToRolloutRuntime;
171
+ }
172
+ }
173
+ else if (selectedRuntimeSource === 'PROJECT_RUNTIME') {
174
+ return await this.selectRuntimeVersionFromProjectConfigAsync(ctx);
175
+ }
176
+ else {
177
+ throw new Error(`Unexpected runtime source: ${selectedRuntimeSource}`);
178
+ }
179
+ return await this.selectRuntimeVersionFromAlternativeSourceAsync(ctx, branchToRollout, defaultBranch);
180
+ }
181
+ async selectRuntimeVersionFromProjectConfigAsync(ctx) {
182
+ const platforms = ['ios', 'android'];
183
+ const runtimes = platforms
184
+ .map(platform => config_plugins_1.Updates.getRuntimeVersion(ctx.app.exp, platform))
185
+ .filter(filter_1.truthy);
186
+ const dedupedRuntimes = [...new Set(runtimes)];
187
+ if (dedupedRuntimes.length === 0) {
188
+ throw new Error(`Your project config doesn't specify a runtime. Ensure your project is configured correctly for EAS Update by running \`eas update:configure\``);
189
+ }
190
+ else if (dedupedRuntimes.length === 1) {
191
+ const runtime = dedupedRuntimes[0];
192
+ log_1.default.log(`🔧 Your project config currently supports runtime ${runtime}`);
193
+ return runtime;
194
+ }
195
+ const { runtime: selectedRuntime } = await (0, prompts_1.promptAsync)({
196
+ type: 'select',
197
+ name: 'runtime',
198
+ message: `Select a runtime supported by your project config`,
199
+ choices: runtimes.map((runtime, index) => ({
200
+ title: `${runtime} ${index === 0 ? '[iOS runtime]' : '[Android runtime]'}`,
201
+ value: runtime,
202
+ })),
203
+ });
204
+ return selectedRuntime;
132
205
  }
133
206
  async selectBranchAsync(ctx, defaultBranchId) {
134
207
  const selectBranchAction = new SelectBranch_1.SelectBranch({
135
- printedType: 'branch to rollout',
208
+ printedType: 'branch to roll out',
136
209
  // we don't want to show the default branch as an option
137
210
  filterPredicate: (branchInfo) => branchInfo.id !== defaultBranchId,
138
211
  });
139
212
  const branchInfo = await selectBranchAction.runAsync(ctx);
140
213
  if (!branchInfo) {
141
- throw new Error(`You dont have any branches. Create one with 'eas branch:create'`);
214
+ // We know the user has at least one branch, since we have `defaultBranchId`
215
+ throw new Error(`You don't have a second branch to roll out. Create it with 'eas branch:create'`);
142
216
  }
143
217
  return branchInfo;
144
218
  }
@@ -33,12 +33,15 @@ class EditRollout {
33
33
  const channelObject = await this.getChannelObjectAsync(ctx);
34
34
  const rollout = (0, branch_mapping_1.getRollout)(channelObject);
35
35
  const { rolledOutBranch, defaultBranch } = rollout;
36
- const promptMessage = `What percent of users should be directed to the ${rolledOutBranch.name} branch ?`;
36
+ const promptMessage = `What percent of users should be rolled out to the ${rolledOutBranch.name} branch ?`;
37
37
  const percent = (_a = this.options.percent) !== null && _a !== void 0 ? _a : (await (0, utils_1.promptForRolloutPercentAsync)({ promptMessage }));
38
+ if (percent === 0 || percent === 100) {
39
+ log_1.default.warn(`Editing the percent to ${percent} will not end the rollout. You'll need to end the rollout from the main menu.`);
40
+ }
38
41
  const oldBranchMapping = (0, branch_mapping_1.getRolloutBranchMapping)(channelObject.branchMapping);
39
42
  const newBranchMapping = (0, branch_mapping_1.editRolloutBranchMapping)(oldBranchMapping, percent);
40
43
  log_1.default.newLine();
41
- log_1.default.log(`📝 The updated rollout will send ${chalk_1.default.bold(percent)}% of users to the ${chalk_1.default.bold(rolledOutBranch.name)} branch and ${chalk_1.default.bold(100 - percent)}% to the ${chalk_1.default.bold(defaultBranch.name)} branch.`);
44
+ log_1.default.log(`📝 ${chalk_1.default.bold(percent)}% of users will be rolled out to the ${chalk_1.default.bold(rolledOutBranch.name)} branch and ${chalk_1.default.bold(100 - percent)}% will remain on the ${chalk_1.default.bold(defaultBranch.name)} branch.`);
42
45
  const confirmEdit = await this.confirmEditAsync(ctx);
43
46
  if (!confirmEdit) {
44
47
  throw new Error('Aborting...');
@@ -3,9 +3,12 @@ import { UpdateChannelBasicInfoFragment } from '../../graphql/generated';
3
3
  import { UpdateChannelObject } from '../../graphql/queries/ChannelQuery';
4
4
  import { Rollout } from '../branch-mapping';
5
5
  export declare enum EndOutcome {
6
- REPUBLISH_AND_ROUTE_BACK = "republish-and-route-back",
7
- ROUTE_BACK = "route-back"
6
+ REPUBLISH_AND_REVERT = "republish-and-revert",
7
+ REVERT = "revert"
8
8
  }
9
+ export type GeneralOptions = {
10
+ privateKeyPath: string | null;
11
+ };
9
12
  export type NonInteractiveOptions = {
10
13
  outcome: EndOutcome;
11
14
  };
@@ -15,7 +18,7 @@ export type NonInteractiveOptions = {
15
18
  export declare class EndRollout implements EASUpdateAction<UpdateChannelBasicInfoFragment> {
16
19
  private channelInfo;
17
20
  private options;
18
- constructor(channelInfo: UpdateChannelBasicInfoFragment, options?: Partial<NonInteractiveOptions>);
21
+ constructor(channelInfo: UpdateChannelBasicInfoFragment, options: Partial<NonInteractiveOptions> & GeneralOptions);
19
22
  runAsync(ctx: EASUpdateContext): Promise<UpdateChannelBasicInfoFragment>;
20
23
  getChannelObjectAsync(ctx: EASUpdateContext): Promise<UpdateChannelObject>;
21
24
  selectOutcomeAsync(rollout: Rollout): Promise<EndOutcome>;
@@ -10,13 +10,14 @@ const ChannelQuery_1 = require("../../graphql/queries/ChannelQuery");
10
10
  const log_1 = tslib_1.__importDefault(require("../../log"));
11
11
  const prompts_1 = require("../../prompts");
12
12
  const republish_1 = require("../../update/republish");
13
+ const code_signing_1 = require("../../utils/code-signing");
13
14
  const formatFields_1 = tslib_1.__importDefault(require("../../utils/formatFields"));
14
15
  const branch_mapping_2 = require("../branch-mapping");
15
16
  const utils_1 = require("../utils");
16
17
  var EndOutcome;
17
18
  (function (EndOutcome) {
18
- EndOutcome["REPUBLISH_AND_ROUTE_BACK"] = "republish-and-route-back";
19
- EndOutcome["ROUTE_BACK"] = "route-back";
19
+ EndOutcome["REPUBLISH_AND_REVERT"] = "republish-and-revert";
20
+ EndOutcome["REVERT"] = "revert";
20
21
  })(EndOutcome = exports.EndOutcome || (exports.EndOutcome = {}));
21
22
  function isNonInteractiveOptions(options) {
22
23
  return !!options.outcome;
@@ -28,7 +29,7 @@ function assertNonInteractiveOptions(options) {
28
29
  * End an existing rollout for the project.
29
30
  */
30
31
  class EndRollout {
31
- constructor(channelInfo, options = {}) {
32
+ constructor(channelInfo, options) {
32
33
  this.channelInfo = channelInfo;
33
34
  this.options = options;
34
35
  }
@@ -44,18 +45,15 @@ class EndRollout {
44
45
  const rolledOutUpdateGroup = rolledOutBranch.updateGroups[0];
45
46
  let outcome;
46
47
  if (!rolledOutUpdateGroup) {
47
- log_1.default.log(`⚠️ There is no Update Group being served on ${rolledOutBranch.name}.`);
48
- (0, assert_1.default)(this.options.outcome !== EndOutcome.REPUBLISH_AND_ROUTE_BACK, 'The only valid outcome for this rollout is to route back to the default branch. ');
49
- outcome = EndOutcome.ROUTE_BACK;
48
+ log_1.default.log(`⚠️ There is no update group being served on the ${rolledOutBranch.name} branch.`);
49
+ (0, assert_1.default)(this.options.outcome !== EndOutcome.REPUBLISH_AND_REVERT, `The only valid outcome for this rollout is to revert users back to the ${rollout.defaultBranch.name} branch. `);
50
+ outcome = EndOutcome.REVERT;
50
51
  }
51
52
  else {
52
53
  outcome = (_a = this.options.outcome) !== null && _a !== void 0 ? _a : (await this.selectOutcomeAsync(rollout));
53
54
  }
54
55
  const didConfirm = await this.confirmOutcomeAsync(ctx, outcome, rollout);
55
56
  if (!didConfirm) {
56
- if (!rolledOutUpdateGroup) {
57
- log_1.default.log(`If you wish to stop serving updates to your users, you can edit your rollout to 100% on ${chalk_1.default.bold(rolledOutBranch.name)} instead`);
58
- }
59
57
  throw new Error('Aborting...');
60
58
  }
61
59
  return await this.performOutcomeAsync(ctx, rollout, outcome);
@@ -81,34 +79,36 @@ class EndRollout {
81
79
  const defaultUpdateGroup = defaultBranch.updateGroups[0];
82
80
  const outcomes = [
83
81
  {
84
- value: EndOutcome.REPUBLISH_AND_ROUTE_BACK,
82
+ value: EndOutcome.REPUBLISH_AND_REVERT,
85
83
  title: (0, utils_1.formatBranchWithUpdateGroup)(rolledOutUpdateGroup, rolledOutBranch, percentRolledOut),
86
84
  },
87
85
  {
88
- value: EndOutcome.ROUTE_BACK,
86
+ value: EndOutcome.REVERT,
89
87
  title: (0, utils_1.formatBranchWithUpdateGroup)(defaultUpdateGroup, defaultBranch, 100 - percentRolledOut),
90
88
  },
91
89
  ];
92
90
  const { outcome: selectedOutcome } = await (0, prompts_1.promptAsync)({
93
91
  type: 'select',
94
92
  name: 'outcome',
95
- message: `Which Update Group would you like to serve?`,
93
+ message: `Which update group would you like to serve?`,
96
94
  choices: outcomes,
97
95
  });
98
96
  log_1.default.newLine();
99
- if (selectedOutcome === EndOutcome.REPUBLISH_AND_ROUTE_BACK) {
100
- log_1.default.log(`🍽️ The Update Group you chose is served by branch ${chalk_1.default.bold(rolledOutBranch.name)}`);
97
+ if (selectedOutcome === EndOutcome.REPUBLISH_AND_REVERT) {
98
+ log_1.default.log(`➡️ 📱 The update group you chose is served by branch ${chalk_1.default.bold(rolledOutBranch.name)}`);
101
99
  }
102
100
  else {
103
- log_1.default.log(`🍽️ The Update Group you chose is served by branch ${chalk_1.default.bold(defaultBranch.name)}`);
101
+ log_1.default.log(`➡️ 📱 The update group you chose is served by branch ${chalk_1.default.bold(defaultBranch.name)}`);
104
102
  }
105
103
  return selectedOutcome;
106
104
  }
107
105
  async performOutcomeAsync(ctx, rollout, outcome) {
106
+ var _a;
108
107
  const { graphqlClient, app } = ctx;
109
108
  const { rolledOutBranch, defaultBranch } = rollout;
110
109
  const rolledOutUpdateGroup = rolledOutBranch.updateGroups[0];
111
- if (outcome === EndOutcome.REPUBLISH_AND_ROUTE_BACK) {
110
+ if (outcome === EndOutcome.REPUBLISH_AND_REVERT) {
111
+ const codeSigningInfo = await (0, code_signing_1.getCodeSigningInfoAsync)(ctx.app.exp, (_a = this.options.privateKeyPath) !== null && _a !== void 0 ? _a : undefined);
112
112
  const arbitraryUpdate = rolledOutUpdateGroup[0];
113
113
  const { message: oldUpdateMessage, group: oldGroupId } = arbitraryUpdate;
114
114
  const newUpdateMessage = `Republish "${oldUpdateMessage}" - group: ${oldGroupId}`;
@@ -121,6 +121,7 @@ class EndRollout {
121
121
  branchId: update.branch.id,
122
122
  branchName: update.branch.name,
123
123
  })),
124
+ codeSigningInfo,
124
125
  targetBranch: { branchId: defaultBranch.id, branchName: defaultBranch.name },
125
126
  updateMessage: newUpdateMessage,
126
127
  });
@@ -131,7 +132,7 @@ class EndRollout {
131
132
  branchMapping: JSON.stringify(alwaysTrueDefaultBranchMapping),
132
133
  });
133
134
  log_1.default.addNewLineIfNone();
134
- log_1.default.log(`🚗 Routed all traffic back to branch ${chalk_1.default.bold(defaultBranch.name)}`);
135
+ log_1.default.log(`⬅️ Reverted all users back to branch ${chalk_1.default.bold(defaultBranch.name)}`);
135
136
  log_1.default.log(`✅ Successfully ended rollout`);
136
137
  return newChannelInfo;
137
138
  }
@@ -142,19 +143,19 @@ class EndRollout {
142
143
  }
143
144
  const { rolledOutBranch, defaultBranch } = rollout;
144
145
  log_1.default.newLine();
145
- if (selectedOutcome === EndOutcome.REPUBLISH_AND_ROUTE_BACK) {
146
+ if (selectedOutcome === EndOutcome.REPUBLISH_AND_REVERT) {
146
147
  log_1.default.log(`Ending the rollout will do the following:`);
147
148
  const actions = (0, formatFields_1.default)([
148
149
  {
149
150
  label: '1.',
150
- value: `🔁 Republish the Update Group from ${chalk_1.default.bold(rolledOutBranch.name)} onto ${chalk_1.default.bold(defaultBranch.name)}`,
151
+ value: `🔁 Republish the update group from ${chalk_1.default.bold(rolledOutBranch.name)} onto ${chalk_1.default.bold(defaultBranch.name)}`,
151
152
  },
152
- { label: '2.', value: `⬅️ Route all users back to ${chalk_1.default.bold(defaultBranch.name)}` },
153
+ { label: '2.', value: `⬅️ Revert all users back to ${chalk_1.default.bold(defaultBranch.name)}` },
153
154
  ]);
154
155
  log_1.default.log(actions);
155
156
  }
156
157
  else {
157
- log_1.default.log(`⬅️ Ending the rollout will route all users back to ${chalk_1.default.bold(defaultBranch.name)}`);
158
+ log_1.default.log(`⬅️ Ending the rollout will revert all users back to ${chalk_1.default.bold(defaultBranch.name)}`);
158
159
  }
159
160
  return await (0, prompts_1.confirmAsync)({
160
161
  message: `Continue?`,
@@ -2,7 +2,7 @@ import { EASUpdateAction, EASUpdateContext } from '../../eas-update/utils';
2
2
  import { UpdateChannelBasicInfoFragment } from '../../graphql/generated';
3
3
  import { UpdateChannelObject } from '../../graphql/queries/ChannelQuery';
4
4
  import { NonInteractiveOptions as EditRolloutNonInteractiveOptions } from './EditRollout';
5
- import { NonInteractiveOptions as EndRolloutNonInteractiveOptions } from './EndRollout';
5
+ import { GeneralOptions as EndRolloutGeneralOptions, NonInteractiveOptions as EndRolloutNonInteractiveOptions } from './EndRollout';
6
6
  export declare enum ManageRolloutActions {
7
7
  EDIT = "Edit",
8
8
  END = "End",
@@ -14,10 +14,10 @@ export declare enum ManageRolloutActions {
14
14
  export declare class ManageRollout implements EASUpdateAction<EASUpdateAction> {
15
15
  private channelInfo;
16
16
  private options;
17
- constructor(channelInfo: UpdateChannelBasicInfoFragment, options?: {
17
+ constructor(channelInfo: UpdateChannelBasicInfoFragment, options: {
18
18
  callingAction?: EASUpdateAction;
19
19
  action?: ManageRolloutActions.EDIT | ManageRolloutActions.END;
20
- } & Partial<EditRolloutNonInteractiveOptions> & Partial<EndRolloutNonInteractiveOptions>);
20
+ } & Partial<EditRolloutNonInteractiveOptions> & Partial<EndRolloutNonInteractiveOptions> & EndRolloutGeneralOptions);
21
21
  runAsync(ctx: EASUpdateContext): Promise<EASUpdateAction>;
22
22
  selectActionAsync(): Promise<ManageRolloutActions>;
23
23
  getChannelObjectAsync(ctx: EASUpdateContext): Promise<UpdateChannelObject>;
@@ -20,7 +20,7 @@ var ManageRolloutActions;
20
20
  * Manage a rollout for the project.
21
21
  */
22
22
  class ManageRollout {
23
- constructor(channelInfo, options = {}) {
23
+ constructor(channelInfo, options) {
24
24
  this.channelInfo = channelInfo;
25
25
  this.options = options;
26
26
  }