eas-cli
Version:
EAS command line tool
280 lines (279 loc) • 12.8 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const tslib_1 = require("tslib");
const core_1 = require("@oclif/core");
const queries_1 = require("../../branch/queries");
const queries_2 = require("../../channel/queries");
const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
const flags_1 = require("../../commandUtils/flags");
const pagination_1 = require("../../commandUtils/pagination");
const BranchQuery_1 = require("../../graphql/queries/BranchQuery");
const UpdateQuery_1 = require("../../graphql/queries/UpdateQuery");
const log_1 = tslib_1.__importDefault(require("../../log"));
const prompts_1 = require("../../prompts");
const getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync_1 = require("../../update/getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync");
const queries_3 = require("../../update/queries");
const republish_1 = require("../../update/republish");
const utils_1 = require("../../update/utils");
const code_signing_1 = require("../../utils/code-signing");
const json_1 = require("../../utils/json");
const defaultRepublishPlatforms = ['android', 'ios'];
class UpdateRepublish extends EasCommand_1.default {
static description = 'roll back to an existing update';
static flags = {
channel: core_1.Flags.string({
description: 'Channel name to select an update group to republish from',
exclusive: ['branch', 'group'],
}),
branch: core_1.Flags.string({
description: 'Branch name to select an update group to republish from',
exclusive: ['channel', 'group'],
}),
group: core_1.Flags.string({
description: 'Update group ID to republish',
exclusive: ['branch', 'channel'],
}),
'destination-channel': core_1.Flags.string({
description: 'Channel name to select a branch to republish to if republishing to a different branch',
exclusive: ['destination-branch'],
}),
'destination-branch': core_1.Flags.string({
description: 'Branch name to republish to if republishing to a different branch',
exclusive: ['destination-channel'],
}),
message: core_1.Flags.string({
char: 'm',
description: 'Short message describing the republished update group',
required: false,
}),
platform: core_1.Flags.enum({
char: 'p',
options: [...defaultRepublishPlatforms, 'all'],
default: 'all',
required: false,
}),
'private-key-path': core_1.Flags.string({
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/`,
required: false,
}),
...flags_1.EasNonInteractiveAndJsonFlags,
};
static contextDefinition = {
...this.ContextOptions.ProjectConfig,
...this.ContextOptions.LoggedIn,
};
async runAsync() {
const { flags: rawFlags } = await this.parse(UpdateRepublish);
const flags = this.sanitizeFlags(rawFlags);
const { privateProjectConfig: { exp, projectId }, loggedIn: { graphqlClient }, } = await this.getContextAsync(UpdateRepublish, {
nonInteractive: flags.nonInteractive,
withServerSideEnvironment: null,
});
if (flags.json) {
(0, json_1.enableJsonOutput)();
}
const codeSigningInfo = await (0, code_signing_1.getCodeSigningInfoAsync)(exp, flags.privateKeyPath);
const existingUpdates = await getOrAskUpdatesAsync(graphqlClient, projectId, flags);
const updatesToPublish = existingUpdates.filter(update => flags.platform.includes(update.platform));
if (existingUpdates.length === 0) {
throw new Error(`There are no published updates found`);
}
if (updatesToPublish.length === 0) {
throw new Error(`There are no updates on branch "${existingUpdates[0].branchName}" published for the platform(s) "${rawFlags.platform}" with group ID "${flags.groupId ? flags.groupId : updatesToPublish[0].groupId}". Did you mean to publish a new update instead?`);
}
if (rawFlags.platform === 'all') {
log_1.default.withTick(`The republished update group will appear only on: ${rawFlags.platform}`);
}
else {
const platformsFromUpdates = updatesToPublish.map(update => update.platform);
if (platformsFromUpdates.length < defaultRepublishPlatforms.length) {
log_1.default.warn(`You are republishing an update group that wasn't published for all platforms.`);
}
log_1.default.withTick(`The republished update group will appear on the same platforms it was originally published on: ${platformsFromUpdates.join(', ')}`);
}
const arbitraryUpdate = updatesToPublish[0];
const targetBranch = await getOrAskTargetBranchAsync(graphqlClient, projectId, flags, arbitraryUpdate);
const updateMessage = await getOrAskUpdateMessageAsync(updatesToPublish, flags);
await (0, republish_1.republishAsync)({
graphqlClient,
app: { exp, projectId },
updatesToPublish,
targetBranch,
updateMessage,
codeSigningInfo,
json: flags.json,
});
}
sanitizeFlags(rawFlags) {
const branchName = rawFlags.branch;
const channelName = rawFlags.channel;
const groupId = rawFlags.group;
const destinationChannelName = rawFlags['destination-channel'];
const destinationBranchName = rawFlags['destination-branch'];
const nonInteractive = rawFlags['non-interactive'];
const privateKeyPath = rawFlags['private-key-path'];
if (nonInteractive && !groupId) {
throw new Error('Only --group can be used in non-interactive mode');
}
const platform = rawFlags.platform === 'all' ? defaultRepublishPlatforms : [rawFlags.platform];
return {
branchName,
channelName,
destinationChannelName,
destinationBranchName,
groupId,
platform,
updateMessage: rawFlags.message,
privateKeyPath,
json: rawFlags.json ?? false,
nonInteractive,
};
}
}
exports.default = UpdateRepublish;
async function getOrAskTargetBranchAsync(graphqlClient, projectId, flags, arbitraryUpdate) {
// if branch name supplied, use that
if (flags.destinationBranchName) {
const branch = await BranchQuery_1.BranchQuery.getBranchByNameAsync(graphqlClient, {
appId: projectId,
name: flags.destinationBranchName,
});
return { branchId: branch.id, branchName: branch.name };
}
// if provided channel name but was non-interactive
if (flags.destinationChannelName) {
return await (0, getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync_1.getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync)(graphqlClient, projectId, flags.destinationChannelName);
}
// if neither provided, assume republish on same branch
return { branchId: arbitraryUpdate.branchId, branchName: arbitraryUpdate.branchName };
}
/** Retrieve the update group from either the update group id, or select from branch name. */
async function getOrAskUpdatesAsync(graphqlClient, projectId, flags) {
if (flags.groupId) {
const updateGroup = await UpdateQuery_1.UpdateQuery.viewUpdateGroupAsync(graphqlClient, {
groupId: flags.groupId,
});
return updateGroup.map(update => ({
...update,
groupId: update.group,
branchId: update.branch.id,
branchName: update.branch.name,
}));
}
if (flags.nonInteractive) {
throw new Error('Must supply --group when in non-interactive mode');
}
if (flags.branchName) {
return await askUpdatesFromBranchNameAsync(graphqlClient, {
...flags,
branchName: flags.branchName,
projectId,
});
}
if (flags.channelName) {
return await askUpdatesFromChannelNameAsync(graphqlClient, {
...flags,
channelName: flags.channelName,
projectId,
});
}
const { choice } = await (0, prompts_1.promptAsync)({
type: 'select',
message: 'Find update by branch or channel?',
name: 'choice',
choices: [
{ title: 'Branch', value: 'branch' },
{ title: 'Channel', value: 'channel' },
],
});
if (choice === 'channel') {
const { name } = await (0, queries_2.selectChannelOnAppAsync)(graphqlClient, {
projectId,
selectionPromptTitle: 'Select a channel to view',
paginatedQueryOptions: {
json: flags.json,
nonInteractive: flags.nonInteractive,
offset: 0,
},
});
return await askUpdatesFromChannelNameAsync(graphqlClient, {
...flags,
channelName: name,
projectId,
});
}
else if (choice === 'branch') {
const { name } = await (0, queries_1.selectBranchOnAppAsync)(graphqlClient, {
projectId,
promptTitle: 'Select branch from which to choose update',
displayTextForListItem: updateBranch => ({
title: updateBranch.name,
}),
// discard limit and offset because this query is not their intended target
paginatedQueryOptions: {
json: flags.json,
nonInteractive: flags.nonInteractive,
offset: 0,
},
});
return await askUpdatesFromBranchNameAsync(graphqlClient, {
...flags,
branchName: name,
projectId,
});
}
else {
throw new Error('Must choose update via channel or branch');
}
}
/** Ask the user which update needs to be republished by branch name, this requires interactive mode */
async function askUpdatesFromBranchNameAsync(graphqlClient, { projectId, branchName, json, nonInteractive, }) {
const updateGroup = await (0, queries_3.selectUpdateGroupOnBranchAsync)(graphqlClient, {
projectId,
branchName,
paginatedQueryOptions: (0, pagination_1.getPaginatedQueryOptions)({ json, 'non-interactive': nonInteractive }),
});
return updateGroup.map(update => ({
...update,
groupId: update.group,
branchId: update.branch.id,
branchName: update.branch.name,
}));
}
/** Ask the user which update needs to be republished by channel name, this requires interactive mode */
async function askUpdatesFromChannelNameAsync(graphqlClient, { projectId, channelName, json, nonInteractive, }) {
const { branchName } = await (0, getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync_1.getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync)(graphqlClient, projectId, channelName);
return await askUpdatesFromBranchNameAsync(graphqlClient, {
projectId,
branchName,
json,
nonInteractive,
});
}
/** Get or ask the user for the update (group) message for the republish */
async function getOrAskUpdateMessageAsync(updates, flags) {
if (flags.updateMessage) {
return sanitizeUpdateMessage(flags.updateMessage);
}
if (flags.nonInteractive || flags.json) {
throw new Error('Must supply --message when in non-interactive mode');
}
// This command only uses a single update group to republish, meaning these values are always identical
const oldGroupId = updates[0].groupId;
const oldUpdateMessage = updates[0].message;
const { updateMessage } = await (0, prompts_1.promptAsync)({
type: 'text',
name: 'updateMessage',
message: 'Provide an update message.',
initial: `Republish "${oldUpdateMessage}" - group: ${oldGroupId}`,
validate: (value) => (value ? true : 'Update message may not be empty.'),
});
return sanitizeUpdateMessage(updateMessage);
}
function sanitizeUpdateMessage(updateMessage) {
if (updateMessage !== (0, utils_1.truncateString)(updateMessage, 1024)) {
log_1.default.warn('Update message exceeds the allowed 1024 character limit, truncated update message.');
return (0, utils_1.truncateString)(updateMessage, 1024);
}
return updateMessage;
}
;