eas-cli
Version:
EAS command line tool
267 lines (266 loc) • 13.8 kB
JavaScript
"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;