UNPKG

eas-cli

Version:
267 lines (266 loc) 13.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const core_1 = require("@oclif/core"); const nullthrows_1 = tslib_1.__importDefault(require("nullthrows")); const queries_1 = require("../../branch/queries"); const url_1 = require("../../build/utils/url"); const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand")); const flags_1 = require("../../commandUtils/flags"); const pagination_1 = require("../../commandUtils/pagination"); const fetch_1 = tslib_1.__importDefault(require("../../fetch")); const generated_1 = require("../../graphql/generated"); const PublishMutation_1 = require("../../graphql/mutations/PublishMutation"); const RuntimeQuery_1 = require("../../graphql/queries/RuntimeQuery"); const log_1 = tslib_1.__importStar(require("../../log")); const ora_1 = require("../../ora"); const projectUtils_1 = require("../../project/projectUtils"); const publish_1 = require("../../project/publish"); const configure_1 = require("../../update/configure"); const utils_1 = require("../../update/utils"); const code_signing_1 = require("../../utils/code-signing"); const uniqBy_1 = tslib_1.__importDefault(require("../../utils/expodash/uniqBy")); const formatFields_1 = tslib_1.__importDefault(require("../../utils/formatFields")); const json_1 = require("../../utils/json"); const relay_1 = require("../../utils/relay"); const statuspageService_1 = require("../../utils/statuspageService"); class UpdateRollBackToEmbedded extends EasCommand_1.default { static description = 'roll back to the embedded update'; static flags = { branch: core_1.Flags.string({ description: 'Branch to publish the rollback to embedded update group on', required: false, }), channel: core_1.Flags.string({ description: 'Channel that the published rollback to embedded update should affect', required: false, }), 'runtime-version': core_1.Flags.string({ description: 'Runtime version that the rollback to embedded update should target', required: false, }), message: core_1.Flags.string({ description: 'A short message describing the rollback to embedded update', required: false, }), platform: core_1.Flags.enum({ char: 'p', options: [ // TODO: Add web when it's fully supported ...publish_1.defaultPublishPlatforms, '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.DynamicProjectConfig, ...this.ContextOptions.LoggedIn, ...this.ContextOptions.Vcs, }; async runAsync() { const { flags: rawFlags } = await this.parse(UpdateRollBackToEmbedded); const paginatedQueryOptions = (0, pagination_1.getPaginatedQueryOptions)(rawFlags); const { platform: platformFlag, channelName: channelNameArg, updateMessage: updateMessageArg, runtimeVersion: runtimeVersionArg, privateKeyPath, json: jsonFlag, nonInteractive, branchName: branchNameArg, } = this.sanitizeFlags(rawFlags); const { getDynamicPublicProjectConfigAsync, getDynamicPrivateProjectConfigAsync, loggedIn: { graphqlClient }, vcsClient, } = await this.getContextAsync(UpdateRollBackToEmbedded, { nonInteractive, withServerSideEnvironment: null, }); if (jsonFlag) { (0, json_1.enableJsonOutput)(); } const { exp: expPossiblyWithoutEasUpdateConfigured, projectId, projectDir, } = await getDynamicPublicProjectConfigAsync(); await (0, statuspageService_1.maybeWarnAboutEasOutagesAsync)(graphqlClient, [generated_1.StatuspageServiceName.EasUpdate]); await (0, configure_1.ensureEASUpdateIsConfiguredAsync)({ exp: expPossiblyWithoutEasUpdateConfigured, platform: platformFlag, projectDir, projectId, vcsClient, env: undefined, }); // check that the expo-updates package version supports roll back to embedded await (0, projectUtils_1.enforceRollBackToEmbeddedUpdateSupportAsync)(projectDir); const { exp } = await getDynamicPublicProjectConfigAsync(); const { exp: expPrivate } = await getDynamicPrivateProjectConfigAsync(); const codeSigningInfo = await (0, code_signing_1.getCodeSigningInfoAsync)(expPrivate, privateKeyPath); const branchName = await (0, publish_1.getBranchNameForCommandAsync)({ graphqlClient, vcsClient, projectId, channelNameArg, branchNameArg, autoFlag: false, nonInteractive, paginatedQueryOptions, }); const updateMessage = await (0, publish_1.getUpdateMessageForCommandAsync)(vcsClient, { updateMessageArg, autoFlag: false, nonInteractive, jsonFlag, }); const realizedPlatforms = platformFlag === 'all' ? publish_1.defaultPublishPlatforms : [platformFlag]; const { branch } = await (0, queries_1.ensureBranchExistsAsync)(graphqlClient, { appId: projectId, branchName, }); const selectedRuntime = runtimeVersionArg ?? (await UpdateRollBackToEmbedded.selectRuntimeAsync(graphqlClient, { appId: projectId, branchName, }))?.version; if (!selectedRuntime) { core_1.Errors.error('Must select a runtime or provide the --runtimeVersion flag', { exit: 1 }); } const runtimeToPlatformsAndFingerprintInfoMapping = (0, publish_1.getRuntimeToPlatformsAndFingerprintInfoMappingFromRuntimeVersionInfoObjects)(realizedPlatforms.map(platform => ({ platform, runtimeVersionInfo: { runtimeVersion: selectedRuntime, fingerprint: null, fingerprintHash: null, }, }))); let newUpdates; const publishSpinner = (0, ora_1.ora)('Publishing...').start(); try { newUpdates = await this.publishRollbacksAsync({ graphqlClient, updateMessage, branchId: branch.id, codeSigningInfo, runtimeToPlatformsAndFingerprintInfoMapping, realizedPlatforms, }); publishSpinner.succeed('Published!'); } catch (e) { publishSpinner.fail('Failed to publish updates'); throw e; } if (jsonFlag) { (0, json_1.printJsonOnlyOutput)((0, utils_1.getUpdateJsonInfosForUpdates)(newUpdates)); } else { log_1.default.addNewLineIfNone(); for (const runtime of (0, uniqBy_1.default)(runtimeToPlatformsAndFingerprintInfoMapping, version => version.runtimeVersion)) { const newUpdatesForRuntimeVersion = newUpdates.filter(update => update.runtimeVersion === runtime.runtimeVersion); if (newUpdatesForRuntimeVersion.length === 0) { throw new Error(`Publish response is missing updates with runtime ${runtime.runtimeVersion}.`); } const platforms = newUpdatesForRuntimeVersion.map(update => update.platform); const newAndroidUpdate = newUpdatesForRuntimeVersion.find(update => update.platform === 'android'); const newIosUpdate = newUpdatesForRuntimeVersion.find(update => update.platform === 'ios'); const updateGroupId = newUpdatesForRuntimeVersion[0].group; const projectName = exp.slug; const accountName = (await (0, projectUtils_1.getOwnerAccountForProjectIdAsync)(graphqlClient, projectId)).name; const updateGroupUrl = (0, url_1.getUpdateGroupUrl)(accountName, projectName, updateGroupId); const updateGroupLink = (0, log_1.link)(updateGroupUrl, { dim: false }); log_1.default.log((0, formatFields_1.default)([ { label: 'Branch', value: branchName }, { label: 'Runtime version', value: runtime.runtimeVersion }, { label: 'Platform', value: platforms.join(', ') }, { label: 'Update group ID', value: updateGroupId }, ...(newAndroidUpdate ? [{ label: 'Android update ID', value: newAndroidUpdate.id }] : []), ...(newIosUpdate ? [{ label: 'iOS update ID', value: newIosUpdate.id }] : []), { label: 'Message', value: updateMessage ?? '' }, { label: 'EAS Dashboard', value: updateGroupLink }, ])); log_1.default.addNewLineIfNone(); } } } async publishRollbacksAsync({ graphqlClient, updateMessage, branchId, codeSigningInfo, runtimeToPlatformsAndFingerprintInfoMapping, realizedPlatforms, }) { const rollbackInfoGroups = Object.fromEntries(realizedPlatforms.map(platform => [platform, true])); // Sort the updates into different groups based on their platform specific runtime versions const updateGroups = runtimeToPlatformsAndFingerprintInfoMapping.map(({ runtimeVersion, platforms }) => { const localRollbackInfoGroup = Object.fromEntries(platforms.map(platform => [platform, rollbackInfoGroups[platform]])); return { branchId, rollBackToEmbeddedInfoGroup: localRollbackInfoGroup, runtimeVersion, message: updateMessage, awaitingCodeSigningInfo: !!codeSigningInfo, }; }); const newUpdates = await PublishMutation_1.PublishMutation.publishUpdateGroupAsync(graphqlClient, updateGroups); if (codeSigningInfo) { log_1.default.log('🔒 Signing roll back'); const updatesTemp = [...newUpdates]; const updateGroupsAndTheirUpdates = updateGroups.map(updateGroup => { const newUpdates = updatesTemp.splice(0, Object.keys((0, nullthrows_1.default)(updateGroup.rollBackToEmbeddedInfoGroup)).length); return { updateGroup, newUpdates, }; }); await Promise.all(updateGroupsAndTheirUpdates.map(async ({ newUpdates }) => { await Promise.all(newUpdates.map(async (newUpdate) => { const response = await (0, fetch_1.default)(newUpdate.manifestPermalink, { method: 'GET', headers: { accept: 'multipart/mixed' }, }); const directiveBody = (0, nullthrows_1.default)(await (0, code_signing_1.getDirectiveBodyAsync)(response)); (0, code_signing_1.checkDirectiveBodyAgainstUpdateInfoGroup)(directiveBody); const directiveSignature = (0, code_signing_1.signBody)(directiveBody, codeSigningInfo); await PublishMutation_1.PublishMutation.setCodeSigningInfoAsync(graphqlClient, newUpdate.id, { alg: codeSigningInfo.codeSigningMetadata.alg, keyid: codeSigningInfo.codeSigningMetadata.keyid, sig: directiveSignature, }); })); })); } return newUpdates; } static async selectRuntimeAsync(graphqlClient, { appId, branchName, batchSize = 5, }) { const queryAsync = async (queryParams) => { return await RuntimeQuery_1.RuntimeQuery.getRuntimesOnBranchAsync(graphqlClient, { appId, name: branchName, first: queryParams.first, after: queryParams.after, last: queryParams.last, before: queryParams.before, }); }; const getTitleAsync = async (runtime) => { return runtime.version; }; return await (0, relay_1.selectPaginatedAsync)({ queryAsync, getTitleAsync, printedType: 'target runtime', pageSize: batchSize, }); } sanitizeFlags(flags) { const nonInteractive = flags['non-interactive'] ?? false; const { branch: branchName, channel: channelName, message: updateMessage, 'runtime-version': runtimeVersion, } = flags; if (nonInteractive && !(updateMessage && (branchName || channelName))) { core_1.Errors.error('--branch and --message, or --channel and --message are required in non-interactive mode', { exit: 1 }); } if (nonInteractive && !runtimeVersion) { core_1.Errors.error('--runtimeVersion is required in non-interactive mode', { exit: 1 }); } return { branchName, channelName, updateMessage, runtimeVersion, platform: flags.platform, privateKeyPath: flags['private-key-path'], nonInteractive, json: flags.json ?? false, }; } } exports.default = UpdateRollBackToEmbedded;