eas-cli 16.13.3 → 16.14.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.
- package/README.md +128 -78
- package/build/commands/env/create.d.ts +0 -1
- package/build/commands/env/create.js +1 -28
- package/build/commands/update/delete.js +2 -20
- package/build/commands/update/republish.js +2 -139
- package/build/commands/update/revert-update-rollout.d.ts +24 -0
- package/build/commands/update/revert-update-rollout.js +264 -0
- package/build/commands/update/roll-back-to-embedded.d.ts +0 -2
- package/build/commands/update/roll-back-to-embedded.js +13 -133
- package/build/commands/upload.d.ts +0 -1
- package/build/commands/upload.js +0 -1
- package/build/graphql/generated.d.ts +91 -109
- package/build/graphql/generated.js +15 -3
- package/build/graphql/mutations/EnvironmentVariableMutation.d.ts +0 -2
- package/build/graphql/mutations/EnvironmentVariableMutation.js +0 -48
- package/build/graphql/types/Update.js +1 -0
- package/build/project/publish.js +1 -0
- package/build/update/delete.d.ts +5 -0
- package/build/update/delete.js +24 -0
- package/build/update/queries.d.ts +13 -1
- package/build/update/queries.js +62 -1
- package/build/update/republish.d.ts +27 -0
- package/build/update/republish.js +242 -1
- package/build/update/roll-back-to-embedded.d.ts +18 -0
- package/build/update/roll-back-to-embedded.js +119 -0
- package/build/user/fetchUser.js +15 -13
- package/build/utils/statuspageService.js +1 -0
- package/oclif.manifest.json +78 -115
- package/package.json +3 -2
- package/build/commands/env/link.d.ts +0 -23
- package/build/commands/env/link.js +0 -128
- package/build/commands/env/unlink.d.ts +0 -22
- package/build/commands/env/unlink.js +0 -117
|
@@ -15,7 +15,6 @@ export default class EnvCreate extends EasCommand {
|
|
|
15
15
|
visibility: import("@oclif/core/lib/interfaces").OptionFlag<"plaintext" | "sensitive" | "secret" | undefined>;
|
|
16
16
|
name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
|
|
17
17
|
value: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
|
|
18
|
-
link: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
19
18
|
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
20
19
|
type: import("@oclif/core/lib/interfaces").OptionFlag<"string" | "file" | undefined>;
|
|
21
20
|
};
|
|
@@ -31,10 +31,6 @@ class EnvCreate extends EasCommand_1.default {
|
|
|
31
31
|
value: core_1.Flags.string({
|
|
32
32
|
description: 'Text value or the variable',
|
|
33
33
|
}),
|
|
34
|
-
link: core_1.Flags.boolean({
|
|
35
|
-
description: 'Link account-wide variable to the current project',
|
|
36
|
-
hidden: true, // every account-wide variable is global for now so it's not user facing
|
|
37
|
-
}),
|
|
38
34
|
force: core_1.Flags.boolean({
|
|
39
35
|
description: 'Overwrite existing variable',
|
|
40
36
|
default: false,
|
|
@@ -56,7 +52,7 @@ class EnvCreate extends EasCommand_1.default {
|
|
|
56
52
|
async runAsync() {
|
|
57
53
|
const { args, flags } = await this.parse(EnvCreate);
|
|
58
54
|
const validatedFlags = this.sanitizeFlags(flags);
|
|
59
|
-
const { name, value, scope, 'non-interactive': nonInteractive, environment: environments, visibility,
|
|
55
|
+
const { name, value, scope, 'non-interactive': nonInteractive, environment: environments, visibility, force, type, fileName, } = await this.promptForMissingFlagsAsync(validatedFlags, args);
|
|
60
56
|
const { projectId, loggedIn: { graphqlClient }, } = await this.getContextAsync(EnvCreate, {
|
|
61
57
|
nonInteractive,
|
|
62
58
|
});
|
|
@@ -81,18 +77,6 @@ class EnvCreate extends EasCommand_1.default {
|
|
|
81
77
|
});
|
|
82
78
|
overwrite = true;
|
|
83
79
|
}
|
|
84
|
-
if (existingVariable.scope === generated_1.EnvironmentVariableScope.Shared) {
|
|
85
|
-
await this.promptForOverwriteAsync({
|
|
86
|
-
nonInteractive,
|
|
87
|
-
force,
|
|
88
|
-
message: `Account-wide variable with ${name} name already exists on this account.`,
|
|
89
|
-
suggestion: 'Do you want to unlink it first?',
|
|
90
|
-
});
|
|
91
|
-
log_1.default.withTick(`Unlinking account-wide variable ${chalk_1.default.bold(name)} on project ${chalk_1.default.bold(projectDisplayName)}.`);
|
|
92
|
-
await (0, variableUtils_1.performForEnvironmentsAsync)(environments, async (environment) => {
|
|
93
|
-
await EnvironmentVariableMutation_1.EnvironmentVariableMutation.unlinkSharedEnvironmentVariableAsync(graphqlClient, existingVariable.id, projectId, environment);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
80
|
}
|
|
97
81
|
const variable = overwrite && existingVariable
|
|
98
82
|
? await EnvironmentVariableMutation_1.EnvironmentVariableMutation.updateAsync(graphqlClient, {
|
|
@@ -153,13 +137,6 @@ class EnvCreate extends EasCommand_1.default {
|
|
|
153
137
|
throw new Error(`Could not create variable with name ${name} on account ${ownerAccount.name}`);
|
|
154
138
|
}
|
|
155
139
|
log_1.default.withTick(`Created a new variable ${chalk_1.default.bold(name)} on account ${chalk_1.default.bold(ownerAccount.name)}.`);
|
|
156
|
-
if (link) {
|
|
157
|
-
log_1.default.withTick(`Linking account-wide variable ${chalk_1.default.bold(name)} to project ${chalk_1.default.bold(projectDisplayName)}.`);
|
|
158
|
-
await (0, variableUtils_1.performForEnvironmentsAsync)(environments, async (environment) => {
|
|
159
|
-
await EnvironmentVariableMutation_1.EnvironmentVariableMutation.linkSharedEnvironmentVariableAsync(graphqlClient, variable.id, projectId, environment);
|
|
160
|
-
});
|
|
161
|
-
log_1.default.withTick(`Linked account-wide variable ${chalk_1.default.bold(name)} to project ${chalk_1.default.bold(projectDisplayName)}.`);
|
|
162
|
-
}
|
|
163
140
|
}
|
|
164
141
|
}
|
|
165
142
|
async promptForOverwriteAsync({ nonInteractive, force, message, suggestion, }) {
|
|
@@ -231,7 +208,6 @@ class EnvCreate extends EasCommand_1.default {
|
|
|
231
208
|
value,
|
|
232
209
|
environment: newEnvironments,
|
|
233
210
|
visibility: newVisibility,
|
|
234
|
-
link: rest.link ?? false,
|
|
235
211
|
force: rest.force ?? false,
|
|
236
212
|
'non-interactive': nonInteractive,
|
|
237
213
|
type: newType,
|
|
@@ -240,9 +216,6 @@ class EnvCreate extends EasCommand_1.default {
|
|
|
240
216
|
};
|
|
241
217
|
}
|
|
242
218
|
sanitizeFlags(flags) {
|
|
243
|
-
if (flags.scope !== 'account' && flags.link) {
|
|
244
|
-
throw new Error(`Unexpected argument: --link can only be used when creating account-wide variables`);
|
|
245
|
-
}
|
|
246
219
|
return {
|
|
247
220
|
...flags,
|
|
248
221
|
scope: flags.scope === 'account'
|
|
@@ -2,31 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
5
|
-
const graphql_tag_1 = tslib_1.__importDefault(require("graphql-tag"));
|
|
6
5
|
const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
|
|
7
6
|
const flags_1 = require("../../commandUtils/flags");
|
|
8
|
-
const client_1 = require("../../graphql/client");
|
|
9
|
-
const BackgroundJobReceipt_1 = require("../../graphql/types/BackgroundJobReceipt");
|
|
10
7
|
const log_1 = tslib_1.__importDefault(require("../../log"));
|
|
11
8
|
const prompts_1 = require("../../prompts");
|
|
9
|
+
const delete_1 = require("../../update/delete");
|
|
12
10
|
const json_1 = require("../../utils/json");
|
|
13
11
|
const pollForBackgroundJobReceiptAsync_1 = require("../../utils/pollForBackgroundJobReceiptAsync");
|
|
14
|
-
async function scheduleUpdateGroupDeletionAsync(graphqlClient, { group, }) {
|
|
15
|
-
const result = await (0, client_1.withErrorHandlingAsync)(graphqlClient
|
|
16
|
-
.mutation((0, graphql_tag_1.default) `
|
|
17
|
-
mutation ScheduleUpdateGroupDeletion($group: ID!) {
|
|
18
|
-
update {
|
|
19
|
-
scheduleUpdateGroupDeletion(group: $group) {
|
|
20
|
-
id
|
|
21
|
-
...BackgroundJobReceiptData
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
${BackgroundJobReceipt_1.BackgroundJobReceiptNode}
|
|
26
|
-
`, { group })
|
|
27
|
-
.toPromise());
|
|
28
|
-
return result.update.scheduleUpdateGroupDeletion;
|
|
29
|
-
}
|
|
30
12
|
class UpdateDelete extends EasCommand_1.default {
|
|
31
13
|
static description = 'delete all the updates in an update group';
|
|
32
14
|
static args = [
|
|
@@ -61,7 +43,7 @@ class UpdateDelete extends EasCommand_1.default {
|
|
|
61
43
|
return;
|
|
62
44
|
}
|
|
63
45
|
}
|
|
64
|
-
const receipt = await scheduleUpdateGroupDeletionAsync(graphqlClient, { group });
|
|
46
|
+
const receipt = await (0, delete_1.scheduleUpdateGroupDeletionAsync)(graphqlClient, { group });
|
|
65
47
|
const successfulReceipt = await (0, pollForBackgroundJobReceiptAsync_1.pollForBackgroundJobReceiptAsync)(graphqlClient, receipt);
|
|
66
48
|
log_1.default.debug('Deletion result', { successfulReceipt });
|
|
67
49
|
if (jsonFlag) {
|
|
@@ -2,19 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const tslib_1 = require("tslib");
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
|
-
const queries_1 = require("../../branch/queries");
|
|
6
|
-
const queries_2 = require("../../channel/queries");
|
|
7
5
|
const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
|
|
8
6
|
const flags_1 = require("../../commandUtils/flags");
|
|
9
|
-
const pagination_1 = require("../../commandUtils/pagination");
|
|
10
7
|
const BranchQuery_1 = require("../../graphql/queries/BranchQuery");
|
|
11
|
-
const UpdateQuery_1 = require("../../graphql/queries/UpdateQuery");
|
|
12
8
|
const log_1 = tslib_1.__importDefault(require("../../log"));
|
|
13
|
-
const prompts_1 = require("../../prompts");
|
|
14
9
|
const getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync_1 = require("../../update/getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync");
|
|
15
|
-
const queries_3 = require("../../update/queries");
|
|
16
10
|
const republish_1 = require("../../update/republish");
|
|
17
|
-
const utils_1 = require("../../update/utils");
|
|
18
11
|
const code_signing_1 = require("../../utils/code-signing");
|
|
19
12
|
const json_1 = require("../../utils/json");
|
|
20
13
|
const defaultRepublishPlatforms = ['android', 'ios'];
|
|
@@ -79,7 +72,7 @@ class UpdateRepublish extends EasCommand_1.default {
|
|
|
79
72
|
(0, json_1.enableJsonOutput)();
|
|
80
73
|
}
|
|
81
74
|
const codeSigningInfo = await (0, code_signing_1.getCodeSigningInfoAsync)(exp, flags.privateKeyPath);
|
|
82
|
-
const existingUpdates = await
|
|
75
|
+
const existingUpdates = await (0, republish_1.getUpdateGroupOrAskForUpdateGroupAsync)(graphqlClient, projectId, flags);
|
|
83
76
|
const updatesToPublish = existingUpdates.filter(update => flags.platform.includes(update.platform));
|
|
84
77
|
if (existingUpdates.length === 0) {
|
|
85
78
|
throw new Error(`There are no published updates found`);
|
|
@@ -99,7 +92,7 @@ class UpdateRepublish extends EasCommand_1.default {
|
|
|
99
92
|
}
|
|
100
93
|
const arbitraryUpdate = updatesToPublish[0];
|
|
101
94
|
const targetBranch = await getOrAskTargetBranchAsync(graphqlClient, projectId, flags, arbitraryUpdate);
|
|
102
|
-
const updateMessage = await getOrAskUpdateMessageAsync(updatesToPublish, flags);
|
|
95
|
+
const updateMessage = await (0, republish_1.getOrAskUpdateMessageAsync)(updatesToPublish, flags);
|
|
103
96
|
await (0, republish_1.republishAsync)({
|
|
104
97
|
graphqlClient,
|
|
105
98
|
app: { exp, projectId },
|
|
@@ -155,133 +148,3 @@ async function getOrAskTargetBranchAsync(graphqlClient, projectId, flags, arbitr
|
|
|
155
148
|
// if neither provided, assume republish on same branch
|
|
156
149
|
return { branchId: arbitraryUpdate.branchId, branchName: arbitraryUpdate.branchName };
|
|
157
150
|
}
|
|
158
|
-
/** Retrieve the update group from either the update group id, or select from branch name. */
|
|
159
|
-
async function getOrAskUpdatesAsync(graphqlClient, projectId, flags) {
|
|
160
|
-
if (flags.groupId) {
|
|
161
|
-
const updateGroup = await UpdateQuery_1.UpdateQuery.viewUpdateGroupAsync(graphqlClient, {
|
|
162
|
-
groupId: flags.groupId,
|
|
163
|
-
});
|
|
164
|
-
return updateGroup.map(update => ({
|
|
165
|
-
...update,
|
|
166
|
-
groupId: update.group,
|
|
167
|
-
branchId: update.branch.id,
|
|
168
|
-
branchName: update.branch.name,
|
|
169
|
-
}));
|
|
170
|
-
}
|
|
171
|
-
if (flags.nonInteractive) {
|
|
172
|
-
throw new Error('Must supply --group when in non-interactive mode');
|
|
173
|
-
}
|
|
174
|
-
if (flags.branchName) {
|
|
175
|
-
return await askUpdatesFromBranchNameAsync(graphqlClient, {
|
|
176
|
-
...flags,
|
|
177
|
-
branchName: flags.branchName,
|
|
178
|
-
projectId,
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
if (flags.channelName) {
|
|
182
|
-
return await askUpdatesFromChannelNameAsync(graphqlClient, {
|
|
183
|
-
...flags,
|
|
184
|
-
channelName: flags.channelName,
|
|
185
|
-
projectId,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
const { choice } = await (0, prompts_1.promptAsync)({
|
|
189
|
-
type: 'select',
|
|
190
|
-
message: 'Find update by branch or channel?',
|
|
191
|
-
name: 'choice',
|
|
192
|
-
choices: [
|
|
193
|
-
{ title: 'Branch', value: 'branch' },
|
|
194
|
-
{ title: 'Channel', value: 'channel' },
|
|
195
|
-
],
|
|
196
|
-
});
|
|
197
|
-
if (choice === 'channel') {
|
|
198
|
-
const { name } = await (0, queries_2.selectChannelOnAppAsync)(graphqlClient, {
|
|
199
|
-
projectId,
|
|
200
|
-
selectionPromptTitle: 'Select a channel to view',
|
|
201
|
-
paginatedQueryOptions: {
|
|
202
|
-
json: flags.json,
|
|
203
|
-
nonInteractive: flags.nonInteractive,
|
|
204
|
-
offset: 0,
|
|
205
|
-
},
|
|
206
|
-
});
|
|
207
|
-
return await askUpdatesFromChannelNameAsync(graphqlClient, {
|
|
208
|
-
...flags,
|
|
209
|
-
channelName: name,
|
|
210
|
-
projectId,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
else if (choice === 'branch') {
|
|
214
|
-
const { name } = await (0, queries_1.selectBranchOnAppAsync)(graphqlClient, {
|
|
215
|
-
projectId,
|
|
216
|
-
promptTitle: 'Select branch from which to choose update',
|
|
217
|
-
displayTextForListItem: updateBranch => ({
|
|
218
|
-
title: updateBranch.name,
|
|
219
|
-
}),
|
|
220
|
-
// discard limit and offset because this query is not their intended target
|
|
221
|
-
paginatedQueryOptions: {
|
|
222
|
-
json: flags.json,
|
|
223
|
-
nonInteractive: flags.nonInteractive,
|
|
224
|
-
offset: 0,
|
|
225
|
-
},
|
|
226
|
-
});
|
|
227
|
-
return await askUpdatesFromBranchNameAsync(graphqlClient, {
|
|
228
|
-
...flags,
|
|
229
|
-
branchName: name,
|
|
230
|
-
projectId,
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
else {
|
|
234
|
-
throw new Error('Must choose update via channel or branch');
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
/** Ask the user which update needs to be republished by branch name, this requires interactive mode */
|
|
238
|
-
async function askUpdatesFromBranchNameAsync(graphqlClient, { projectId, branchName, json, nonInteractive, }) {
|
|
239
|
-
const updateGroup = await (0, queries_3.selectUpdateGroupOnBranchAsync)(graphqlClient, {
|
|
240
|
-
projectId,
|
|
241
|
-
branchName,
|
|
242
|
-
paginatedQueryOptions: (0, pagination_1.getPaginatedQueryOptions)({ json, 'non-interactive': nonInteractive }),
|
|
243
|
-
});
|
|
244
|
-
return updateGroup.map(update => ({
|
|
245
|
-
...update,
|
|
246
|
-
groupId: update.group,
|
|
247
|
-
branchId: update.branch.id,
|
|
248
|
-
branchName: update.branch.name,
|
|
249
|
-
}));
|
|
250
|
-
}
|
|
251
|
-
/** Ask the user which update needs to be republished by channel name, this requires interactive mode */
|
|
252
|
-
async function askUpdatesFromChannelNameAsync(graphqlClient, { projectId, channelName, json, nonInteractive, }) {
|
|
253
|
-
const { branchName } = await (0, getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync_1.getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync)(graphqlClient, projectId, channelName);
|
|
254
|
-
return await askUpdatesFromBranchNameAsync(graphqlClient, {
|
|
255
|
-
projectId,
|
|
256
|
-
branchName,
|
|
257
|
-
json,
|
|
258
|
-
nonInteractive,
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
/** Get or ask the user for the update (group) message for the republish */
|
|
262
|
-
async function getOrAskUpdateMessageAsync(updates, flags) {
|
|
263
|
-
if (flags.updateMessage) {
|
|
264
|
-
return sanitizeUpdateMessage(flags.updateMessage);
|
|
265
|
-
}
|
|
266
|
-
if (flags.nonInteractive || flags.json) {
|
|
267
|
-
throw new Error('Must supply --message when in non-interactive mode');
|
|
268
|
-
}
|
|
269
|
-
// This command only uses a single update group to republish, meaning these values are always identical
|
|
270
|
-
const oldGroupId = updates[0].groupId;
|
|
271
|
-
const oldUpdateMessage = updates[0].message;
|
|
272
|
-
const { updateMessage } = await (0, prompts_1.promptAsync)({
|
|
273
|
-
type: 'text',
|
|
274
|
-
name: 'updateMessage',
|
|
275
|
-
message: 'Provide an update message.',
|
|
276
|
-
initial: `Republish "${oldUpdateMessage}" - group: ${oldGroupId}`,
|
|
277
|
-
validate: (value) => (value ? true : 'Update message may not be empty.'),
|
|
278
|
-
});
|
|
279
|
-
return sanitizeUpdateMessage(updateMessage);
|
|
280
|
-
}
|
|
281
|
-
function sanitizeUpdateMessage(updateMessage) {
|
|
282
|
-
if (updateMessage !== (0, utils_1.truncateString)(updateMessage, 1024)) {
|
|
283
|
-
log_1.default.warn('Update message exceeds the allowed 1024 character limit, truncated update message.');
|
|
284
|
-
return (0, utils_1.truncateString)(updateMessage, 1024);
|
|
285
|
-
}
|
|
286
|
-
return updateMessage;
|
|
287
|
-
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import EasCommand from '../../commandUtils/EasCommand';
|
|
2
|
+
export declare function nonNullish<TValue>(value: TValue | null | undefined): value is NonNullable<TValue>;
|
|
3
|
+
export default class UpdateRevertUpdateRollout extends EasCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static flags: {
|
|
6
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
channel: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
|
|
9
|
+
branch: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
|
|
10
|
+
group: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
|
|
11
|
+
message: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
|
|
12
|
+
'private-key-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined>;
|
|
13
|
+
};
|
|
14
|
+
static contextDefinition: {
|
|
15
|
+
vcsClient: import("../../commandUtils/context/VcsClientContextField").default;
|
|
16
|
+
loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
|
|
17
|
+
privateProjectConfig: import("../../commandUtils/context/PrivateProjectConfigContextField").PrivateProjectConfigContextField;
|
|
18
|
+
};
|
|
19
|
+
runAsync(): Promise<void>;
|
|
20
|
+
private deleteRolloutUpdateGroupAndRepublishControlUpdatesAsync;
|
|
21
|
+
private deleteRolloutUpdateGroupAndPublishRollBackToEmbeddedAsync;
|
|
22
|
+
private deleteRolloutUpdateGroupAsync;
|
|
23
|
+
private sanitizeFlags;
|
|
24
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nonNullish = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const core_1 = require("@oclif/core");
|
|
6
|
+
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
7
|
+
const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
|
|
8
|
+
const flags_1 = require("../../commandUtils/flags");
|
|
9
|
+
const log_1 = tslib_1.__importStar(require("../../log"));
|
|
10
|
+
const projectUtils_1 = require("../../project/projectUtils");
|
|
11
|
+
const publish_1 = require("../../project/publish");
|
|
12
|
+
const prompts_1 = require("../../prompts");
|
|
13
|
+
const delete_1 = require("../../update/delete");
|
|
14
|
+
const republish_1 = require("../../update/republish");
|
|
15
|
+
const roll_back_to_embedded_1 = require("../../update/roll-back-to-embedded");
|
|
16
|
+
const code_signing_1 = require("../../utils/code-signing");
|
|
17
|
+
const json_1 = require("../../utils/json");
|
|
18
|
+
const pollForBackgroundJobReceiptAsync_1 = require("../../utils/pollForBackgroundJobReceiptAsync");
|
|
19
|
+
function nonNullish(value) {
|
|
20
|
+
return value !== null && value !== undefined;
|
|
21
|
+
}
|
|
22
|
+
exports.nonNullish = nonNullish;
|
|
23
|
+
class UpdateRevertUpdateRollout extends EasCommand_1.default {
|
|
24
|
+
static description = 'revert a rollout update for a project';
|
|
25
|
+
static flags = {
|
|
26
|
+
channel: core_1.Flags.string({
|
|
27
|
+
description: 'Channel name to select an update group to revert the rollout update from',
|
|
28
|
+
exclusive: ['branch', 'group'],
|
|
29
|
+
}),
|
|
30
|
+
branch: core_1.Flags.string({
|
|
31
|
+
description: 'Branch name to select an update group to revert the rollout update from',
|
|
32
|
+
exclusive: ['channel', 'group'],
|
|
33
|
+
}),
|
|
34
|
+
group: core_1.Flags.string({
|
|
35
|
+
description: 'Rollout update group ID to revert',
|
|
36
|
+
exclusive: ['branch', 'channel'],
|
|
37
|
+
}),
|
|
38
|
+
message: core_1.Flags.string({
|
|
39
|
+
char: 'm',
|
|
40
|
+
description: 'Short message describing the revert',
|
|
41
|
+
required: false,
|
|
42
|
+
}),
|
|
43
|
+
'private-key-path': core_1.Flags.string({
|
|
44
|
+
description: `File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named "private-key.pem" in the certificate's directory. Only relevant if you are using code signing: https://docs.expo.dev/eas-update/code-signing/`,
|
|
45
|
+
required: false,
|
|
46
|
+
}),
|
|
47
|
+
...flags_1.EasNonInteractiveAndJsonFlags,
|
|
48
|
+
};
|
|
49
|
+
static contextDefinition = {
|
|
50
|
+
...this.ContextOptions.ProjectConfig,
|
|
51
|
+
...this.ContextOptions.LoggedIn,
|
|
52
|
+
...this.ContextOptions.Vcs,
|
|
53
|
+
};
|
|
54
|
+
async runAsync() {
|
|
55
|
+
const { flags: rawFlags } = await this.parse(UpdateRevertUpdateRollout);
|
|
56
|
+
const flags = this.sanitizeFlags(rawFlags);
|
|
57
|
+
const { privateProjectConfig: { exp, projectId, projectDir }, loggedIn: { graphqlClient }, vcsClient, } = await this.getContextAsync(UpdateRevertUpdateRollout, {
|
|
58
|
+
nonInteractive: flags.nonInteractive,
|
|
59
|
+
withServerSideEnvironment: null,
|
|
60
|
+
});
|
|
61
|
+
if (flags.json) {
|
|
62
|
+
(0, json_1.enableJsonOutput)();
|
|
63
|
+
}
|
|
64
|
+
const codeSigningInfo = await (0, code_signing_1.getCodeSigningInfoAsync)(exp, flags.privateKeyPath);
|
|
65
|
+
let updateGroupToRepublish;
|
|
66
|
+
if (flags.groupId) {
|
|
67
|
+
const updateGroup = await (0, republish_1.getUpdateGroupAsync)(graphqlClient, flags.groupId);
|
|
68
|
+
if (!updateGroupIsRolloutUpdateGroup(updateGroup)) {
|
|
69
|
+
throw new Error(`The update group with ID "${flags.groupId}" is not a rollout update group.`);
|
|
70
|
+
}
|
|
71
|
+
updateGroupToRepublish = updateGroup;
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const latestUpdateGroupForEachPublishPlatform = await (0, republish_1.askUpdateGroupForEachPublishPlatformFilteringByRuntimeVersionAsync)(graphqlClient, projectId, flags);
|
|
75
|
+
const uniqueUpdateGroups = getUniqueUpdateGroups(Object.values(latestUpdateGroupForEachPublishPlatform).filter(nonNullish));
|
|
76
|
+
const rolloutUpdateGroups = uniqueUpdateGroups.filter(updateGroupIsRolloutUpdateGroup);
|
|
77
|
+
if (rolloutUpdateGroups.length === 0) {
|
|
78
|
+
throw new Error(`No rollout update groups found.`);
|
|
79
|
+
}
|
|
80
|
+
if (rolloutUpdateGroups.length === 1) {
|
|
81
|
+
updateGroupToRepublish = rolloutUpdateGroups[0];
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const { choice: chosenId } = await (0, prompts_1.promptAsync)({
|
|
85
|
+
type: 'select',
|
|
86
|
+
message: 'Which rollout update group would you like to revert?',
|
|
87
|
+
name: 'choice',
|
|
88
|
+
choices: rolloutUpdateGroups.map(ug => ({
|
|
89
|
+
title: `Rollout update group ID: ${ug[0].groupId}, Platform: ${ug[0].platform}, Rollout Percentage: ${ug[0].rolloutPercentage}`,
|
|
90
|
+
value: ug[0].groupId,
|
|
91
|
+
})),
|
|
92
|
+
});
|
|
93
|
+
if (!chosenId) {
|
|
94
|
+
throw new Error('No rollout update group selected.');
|
|
95
|
+
}
|
|
96
|
+
const chosenUpdateGroup = rolloutUpdateGroups.find(ug => ug[0].groupId === chosenId);
|
|
97
|
+
if (!chosenUpdateGroup) {
|
|
98
|
+
throw new Error('No rollout update group selected.');
|
|
99
|
+
}
|
|
100
|
+
updateGroupToRepublish = chosenUpdateGroup;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const rolloutUpdateGroupWithControlUpdates = updateGroupIsUpdateGroupWithControlUpdate(updateGroupToRepublish)
|
|
104
|
+
? updateGroupToRepublish
|
|
105
|
+
: null;
|
|
106
|
+
if (rolloutUpdateGroupWithControlUpdates) {
|
|
107
|
+
await this.deleteRolloutUpdateGroupAndRepublishControlUpdatesAsync({
|
|
108
|
+
graphqlClient,
|
|
109
|
+
exp,
|
|
110
|
+
projectId,
|
|
111
|
+
rolloutUpdateGroupWithControlUpdates,
|
|
112
|
+
codeSigningInfo,
|
|
113
|
+
flags,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
await this.deleteRolloutUpdateGroupAndPublishRollBackToEmbeddedAsync({
|
|
118
|
+
graphqlClient,
|
|
119
|
+
vcsClient,
|
|
120
|
+
exp,
|
|
121
|
+
projectDir,
|
|
122
|
+
projectId,
|
|
123
|
+
rolloutUpdateGroup: updateGroupToRepublish,
|
|
124
|
+
codeSigningInfo,
|
|
125
|
+
flags,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async deleteRolloutUpdateGroupAndRepublishControlUpdatesAsync({ graphqlClient, exp, projectId, rolloutUpdateGroupWithControlUpdates, codeSigningInfo, flags, }) {
|
|
130
|
+
const controlUpdateGroupIdsToRepublish = Array.from(new Set(rolloutUpdateGroupWithControlUpdates.map(update => update.rolloutControlUpdate.group)));
|
|
131
|
+
const updateGroupsToRepublish = await Promise.all(controlUpdateGroupIdsToRepublish.map(controlUpdateGroupIdToRepublish => (0, republish_1.getUpdateGroupAsync)(graphqlClient, controlUpdateGroupIdToRepublish)));
|
|
132
|
+
const updateGroupOrGroupsClause = controlUpdateGroupIdsToRepublish.length > 1
|
|
133
|
+
? `control update groups (IDs: ${controlUpdateGroupIdsToRepublish
|
|
134
|
+
.map(id => `"${id}"`)
|
|
135
|
+
.join(', ')})`
|
|
136
|
+
: `control update group (ID: "${controlUpdateGroupIdsToRepublish[0]}")`;
|
|
137
|
+
if (!flags.nonInteractive) {
|
|
138
|
+
const confirmMessage = `Are you sure you want to revert the rollout update group with ID "${rolloutUpdateGroupWithControlUpdates[0].groupId}"? This will delete the rollout update group and republish the ${updateGroupOrGroupsClause}.`;
|
|
139
|
+
const didConfirm = await (0, prompts_1.confirmAsync)({ message: confirmMessage });
|
|
140
|
+
if (!didConfirm) {
|
|
141
|
+
throw new Error('Aborting...');
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
const updateMessages = [];
|
|
145
|
+
for (const updateGroup of updateGroupsToRepublish) {
|
|
146
|
+
updateMessages.push(await (0, republish_1.getOrAskUpdateMessageAsync)(updateGroup, flags));
|
|
147
|
+
}
|
|
148
|
+
// assert all updateGroupsToRepublish have the same branch name and id
|
|
149
|
+
const branchNames = updateGroupsToRepublish.flatMap(updateGroup => updateGroup[0].branchName);
|
|
150
|
+
const branchIds = updateGroupsToRepublish.map(updateGroup => updateGroup[0].branchId);
|
|
151
|
+
(0, assert_1.default)(branchNames.every(name => name === branchNames[0]), 'All update groups being republished must belong to the same branch.');
|
|
152
|
+
(0, assert_1.default)(branchIds.every(id => id === branchIds[0]), 'All update groups being republished must belong to the same branch.');
|
|
153
|
+
const targetBranch = {
|
|
154
|
+
branchName: branchNames[0],
|
|
155
|
+
branchId: branchIds[0],
|
|
156
|
+
};
|
|
157
|
+
await this.deleteRolloutUpdateGroupAsync({
|
|
158
|
+
graphqlClient,
|
|
159
|
+
rolloutUpdateGroup: rolloutUpdateGroupWithControlUpdates,
|
|
160
|
+
});
|
|
161
|
+
for (let i = 0; i < updateGroupsToRepublish.length; i++) {
|
|
162
|
+
const updateGroupToRepublish = updateGroupsToRepublish[i];
|
|
163
|
+
const updateMessage = updateMessages[i];
|
|
164
|
+
await (0, republish_1.republishAsync)({
|
|
165
|
+
graphqlClient,
|
|
166
|
+
app: { exp, projectId },
|
|
167
|
+
updatesToPublish: updateGroupToRepublish,
|
|
168
|
+
targetBranch,
|
|
169
|
+
updateMessage,
|
|
170
|
+
codeSigningInfo,
|
|
171
|
+
json: flags.json,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
async deleteRolloutUpdateGroupAndPublishRollBackToEmbeddedAsync({ graphqlClient, vcsClient, exp, projectDir, projectId, rolloutUpdateGroup, codeSigningInfo, flags, }) {
|
|
176
|
+
const rolloutUpdateGroupId = rolloutUpdateGroup[0].groupId;
|
|
177
|
+
if (!flags.nonInteractive) {
|
|
178
|
+
const confirmMessage = `Are you sure you want to revert the rollout update group with ID "${rolloutUpdateGroupId}"? This will delete the rollout update group and publish a new roll-back-to-embedded update (no control update to roll back to), whose behavior may not be a true revert depending on the previous state of the branch. ${(0, log_1.learnMore)('https://expo.fyi/eas-update-update-rollouts', { learnMoreMessage: 'More info' })})`;
|
|
179
|
+
const didConfirm = await (0, prompts_1.confirmAsync)({ message: confirmMessage });
|
|
180
|
+
if (!didConfirm) {
|
|
181
|
+
throw new Error('Aborting...');
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
// check that the expo-updates package version supports roll back to embedded
|
|
185
|
+
await (0, projectUtils_1.enforceRollBackToEmbeddedUpdateSupportAsync)(projectDir);
|
|
186
|
+
const updateMessage = await (0, publish_1.getUpdateMessageForCommandAsync)(vcsClient, {
|
|
187
|
+
updateMessageArg: flags.updateMessage,
|
|
188
|
+
autoFlag: false,
|
|
189
|
+
nonInteractive: flags.nonInteractive,
|
|
190
|
+
jsonFlag: flags.json,
|
|
191
|
+
});
|
|
192
|
+
await this.deleteRolloutUpdateGroupAsync({
|
|
193
|
+
graphqlClient,
|
|
194
|
+
rolloutUpdateGroup,
|
|
195
|
+
});
|
|
196
|
+
const platforms = rolloutUpdateGroup.map(update => update.platform);
|
|
197
|
+
const runtimeVersion = rolloutUpdateGroup[0].runtimeVersion;
|
|
198
|
+
const targetBranch = {
|
|
199
|
+
name: rolloutUpdateGroup[0].branchName,
|
|
200
|
+
id: rolloutUpdateGroup[0].branchId,
|
|
201
|
+
};
|
|
202
|
+
await (0, roll_back_to_embedded_1.publishRollBackToEmbeddedUpdateAsync)({
|
|
203
|
+
graphqlClient,
|
|
204
|
+
projectId,
|
|
205
|
+
exp,
|
|
206
|
+
updateMessage,
|
|
207
|
+
branch: targetBranch,
|
|
208
|
+
codeSigningInfo,
|
|
209
|
+
platforms,
|
|
210
|
+
runtimeVersion,
|
|
211
|
+
json: flags.json,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
async deleteRolloutUpdateGroupAsync({ graphqlClient, rolloutUpdateGroup, }) {
|
|
215
|
+
const rolloutUpdateGroupId = rolloutUpdateGroup[0].groupId;
|
|
216
|
+
const updateGroupDeletionReceipt = await (0, delete_1.scheduleUpdateGroupDeletionAsync)(graphqlClient, {
|
|
217
|
+
group: rolloutUpdateGroupId,
|
|
218
|
+
});
|
|
219
|
+
const successfulReceipt = await (0, pollForBackgroundJobReceiptAsync_1.pollForBackgroundJobReceiptAsync)(graphqlClient, updateGroupDeletionReceipt);
|
|
220
|
+
log_1.default.debug('Rollout update group deletion result', { successfulReceipt });
|
|
221
|
+
}
|
|
222
|
+
sanitizeFlags(rawFlags) {
|
|
223
|
+
const branchName = rawFlags.branch;
|
|
224
|
+
const channelName = rawFlags.channel;
|
|
225
|
+
const groupId = rawFlags.group;
|
|
226
|
+
const nonInteractive = rawFlags['non-interactive'];
|
|
227
|
+
const privateKeyPath = rawFlags['private-key-path'];
|
|
228
|
+
if (nonInteractive && !groupId) {
|
|
229
|
+
throw new Error('Only --group can be used in non-interactive mode');
|
|
230
|
+
}
|
|
231
|
+
return {
|
|
232
|
+
branchName,
|
|
233
|
+
channelName,
|
|
234
|
+
groupId,
|
|
235
|
+
updateMessage: rawFlags.message,
|
|
236
|
+
privateKeyPath,
|
|
237
|
+
json: rawFlags.json ?? false,
|
|
238
|
+
nonInteractive,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
exports.default = UpdateRevertUpdateRollout;
|
|
243
|
+
function getUniqueUpdateGroups(updateGroups) {
|
|
244
|
+
const uniqueUpdateGroups = new Map();
|
|
245
|
+
for (const updateGroup of updateGroups) {
|
|
246
|
+
const groupId = updateGroup[0].groupId;
|
|
247
|
+
if (!uniqueUpdateGroups.has(groupId)) {
|
|
248
|
+
uniqueUpdateGroups.set(groupId, updateGroup);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return Array.from(uniqueUpdateGroups.values());
|
|
252
|
+
}
|
|
253
|
+
function updateGroupIsRolloutUpdateGroup(updateGroup) {
|
|
254
|
+
return updateGroup.every(updateIsRolloutUpdate);
|
|
255
|
+
}
|
|
256
|
+
function updateIsRolloutUpdate(updateGroup) {
|
|
257
|
+
return updateGroup.rolloutPercentage !== undefined && updateGroup.rolloutPercentage !== null;
|
|
258
|
+
}
|
|
259
|
+
function updateGroupIsUpdateGroupWithControlUpdate(updateGroup) {
|
|
260
|
+
return updateGroup.every(updateIsRolloutWithControlUpdate);
|
|
261
|
+
}
|
|
262
|
+
function updateIsRolloutWithControlUpdate(updateGroup) {
|
|
263
|
+
return !!updateGroup.rolloutControlUpdate;
|
|
264
|
+
}
|
|
@@ -18,7 +18,5 @@ export default class UpdateRollBackToEmbedded extends EasCommand {
|
|
|
18
18
|
getDynamicPrivateProjectConfigAsync: import("../../commandUtils/context/DynamicProjectConfigContextField").DynamicPrivateProjectConfigContextField;
|
|
19
19
|
};
|
|
20
20
|
runAsync(): Promise<void>;
|
|
21
|
-
private publishRollbacksAsync;
|
|
22
|
-
private static selectRuntimeAsync;
|
|
23
21
|
private sanitizeFlags;
|
|
24
22
|
}
|