UNPKG

eas-cli

Version:
280 lines (279 loc) 12.8 kB
"use strict"; 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; }