eas-cli
Version:
EAS command line tool
287 lines (286 loc) • 14 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.ensureEASUpdateIsConfiguredAsync = exports.ensureEASUpdateIsConfiguredInEasJsonAsync = exports.getDefaultRuntimeVersion = exports.DEFAULT_BARE_RUNTIME_VERSION = exports.DEFAULT_MANAGED_RUNTIME_VERSION_LTE_SDK_48 = exports.DEFAULT_MANAGED_RUNTIME_VERSION_GTE_SDK_49 = void 0;
const tslib_1 = require("tslib");
const eas_build_job_1 = require("@expo/eas-build-job");
const eas_json_1 = require("@expo/eas-json");
const chalk_1 = tslib_1.__importDefault(require("chalk"));
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
const semver_1 = tslib_1.__importDefault(require("semver"));
const UpdatesModule_1 = require("./android/UpdatesModule");
const UpdatesModule_2 = require("./ios/UpdatesModule");
const api_1 = require("../api");
const generated_1 = require("../graphql/generated");
const log_1 = tslib_1.__importStar(require("../log"));
const platform_1 = require("../platform");
const expoConfig_1 = require("../project/expoConfig");
const projectUtils_1 = require("../project/projectUtils");
const workflow_1 = require("../project/workflow");
exports.DEFAULT_MANAGED_RUNTIME_VERSION_GTE_SDK_49 = { policy: 'appVersion' };
exports.DEFAULT_MANAGED_RUNTIME_VERSION_LTE_SDK_48 = { policy: 'sdkVersion' };
exports.DEFAULT_BARE_RUNTIME_VERSION = '1.0.0';
function getDefaultRuntimeVersion(workflow, sdkVersion) {
if (workflow === eas_build_job_1.Workflow.GENERIC) {
return exports.DEFAULT_BARE_RUNTIME_VERSION;
}
// Expo Go supports loading appVersion SDK 49 and above
const hasSupportedSdk = sdkVersion && semver_1.default.satisfies(sdkVersion, '>= 49.0.0');
return hasSupportedSdk
? exports.DEFAULT_MANAGED_RUNTIME_VERSION_GTE_SDK_49
: exports.DEFAULT_MANAGED_RUNTIME_VERSION_LTE_SDK_48;
}
exports.getDefaultRuntimeVersion = getDefaultRuntimeVersion;
function isRuntimeEqual(runtimeVersionA, runtimeVersionB) {
if (typeof runtimeVersionA === 'string' && typeof runtimeVersionB === 'string') {
return runtimeVersionA === runtimeVersionB;
}
else if (typeof runtimeVersionA === 'object' && typeof runtimeVersionB === 'object') {
return runtimeVersionA.policy === runtimeVersionB.policy;
}
else {
return false;
}
}
function replaceUndefinedObjectValues(value, replacement) {
for (const key in value) {
if (value[key] === undefined) {
value[key] = replacement;
}
else if (typeof value[key] === 'object') {
value[key] = replaceUndefinedObjectValues(value[key], replacement);
}
}
return value;
}
/**
* Partially merge the EAS Update config with the existing Expo config.
* This preserves and merges the nested update-related properties.
*/
function mergeExpoConfig(exp, modifyExp) {
return {
runtimeVersion: modifyExp.runtimeVersion ?? exp.runtimeVersion,
updates: { ...exp.updates, ...modifyExp.updates },
android: {
...exp.android,
...modifyExp.android,
},
ios: {
...exp.ios,
...modifyExp.ios,
},
};
}
/**
* Make sure the `app.json` is configured to use EAS Updates.
* This does a couple of things:
* - Ensure update URL is set to the project EAS endpoint
* - Ensure runtimeVersion is defined for both or individual platforms
* - Output the changes made, or the changes required to make manually
*/
async function ensureEASUpdatesIsConfiguredInExpoConfigAsync({ exp, projectId, projectDir, platform, workflows, }) {
const modifyConfig = {};
if (exp.updates?.url !== (0, api_1.getEASUpdateURL)(projectId)) {
modifyConfig.updates = { url: (0, api_1.getEASUpdateURL)(projectId) };
}
let androidRuntimeVersion = exp.android?.runtimeVersion ?? exp.runtimeVersion;
let iosRuntimeVersion = exp.ios?.runtimeVersion ?? exp.runtimeVersion;
if ((['all', 'android'].includes(platform) && !androidRuntimeVersion) ||
(['all', 'ios'].includes(platform) && !iosRuntimeVersion)) {
androidRuntimeVersion =
androidRuntimeVersion ?? getDefaultRuntimeVersion(workflows.android, exp.sdkVersion);
iosRuntimeVersion =
iosRuntimeVersion ?? getDefaultRuntimeVersion(workflows.ios, exp.sdkVersion);
if (platform === 'all' && isRuntimeEqual(androidRuntimeVersion, iosRuntimeVersion)) {
modifyConfig.runtimeVersion = androidRuntimeVersion;
}
else {
if (['all', 'android'].includes(platform)) {
modifyConfig.runtimeVersion = undefined;
modifyConfig.android = { runtimeVersion: androidRuntimeVersion };
}
if (['all', 'ios'].includes(platform)) {
modifyConfig.runtimeVersion = undefined;
modifyConfig.ios = { runtimeVersion: iosRuntimeVersion };
}
}
}
if (Object.keys(modifyConfig).length === 0) {
return { exp, projectChanged: false };
}
const mergedExp = mergeExpoConfig(exp, modifyConfig);
const result = await (0, expoConfig_1.createOrModifyExpoConfigAsync)(projectDir, mergedExp);
switch (result.type) {
case 'success':
logEasUpdatesAutoConfig({ exp, modifyConfig });
return {
projectChanged: true,
// TODO(cedric): fix return type of `modifyConfigAsync` to avoid `null` for type === success repsonses
exp: (0, nullthrows_1.default)(result.config, 'Expected config to be defined'),
};
case 'warn':
warnEASUpdatesManualConfig({ modifyConfig, workflows });
throw new Error(result.message);
case 'fail':
throw new Error(result.message);
default:
throw new Error(`Unexpected result type "${result.type}" received when modifying the project config.`);
}
}
function serializeRuntimeVersionToString(runtimeVersion) {
if (typeof runtimeVersion === 'object') {
return JSON.stringify(runtimeVersion);
}
else {
return runtimeVersion;
}
}
function logEasUpdatesAutoConfig({ modifyConfig, exp, }) {
if (modifyConfig.updates?.url) {
log_1.default.withTick(exp.updates?.url
? `Overwrote updates.url "${exp.updates.url}" with "${modifyConfig.updates.url}"`
: `Configured updates.url to "${modifyConfig.updates.url}"`);
}
const androidRuntime = modifyConfig.android?.runtimeVersion ?? modifyConfig.runtimeVersion;
const iosRuntime = modifyConfig.ios?.runtimeVersion ?? modifyConfig.runtimeVersion;
if (androidRuntime && iosRuntime && androidRuntime === iosRuntime) {
log_1.default.withTick(`Configured runtimeVersion for ${platform_1.appPlatformDisplayNames[generated_1.AppPlatform.Android]} and ${platform_1.appPlatformDisplayNames[generated_1.AppPlatform.Ios]} with "${serializeRuntimeVersionToString(androidRuntime)}"`);
}
else {
if (androidRuntime) {
log_1.default.withTick(`Configured runtimeVersion for ${platform_1.appPlatformDisplayNames[generated_1.AppPlatform.Android]} with "${serializeRuntimeVersionToString(androidRuntime)}"`);
}
if (iosRuntime) {
log_1.default.withTick(`Configured runtimeVersion for ${platform_1.appPlatformDisplayNames[generated_1.AppPlatform.Ios]} with "${serializeRuntimeVersionToString(iosRuntime)}"`);
}
}
}
function warnEASUpdatesManualConfig({ modifyConfig, workflows, }) {
log_1.default.addNewLineIfNone();
log_1.default.warn(`It looks like you are using a dynamic configuration! ${(0, log_1.learnMore)('https://docs.expo.dev/workflow/configuration/#dynamic-configuration-with-appconfigjs)')}`);
log_1.default.warn(`Add the following EAS Update key-values to the project app.config.js:\n${(0, log_1.learnMore)('https://expo.fyi/eas-update-config')}\n`);
log_1.default.log(chalk_1.default.bold(JSON.stringify(replaceUndefinedObjectValues(modifyConfig, '<remove this key>'), null, 2)));
log_1.default.addNewLineIfNone();
if (workflows.android === eas_build_job_1.Workflow.GENERIC || workflows.ios === eas_build_job_1.Workflow.GENERIC) {
log_1.default.warn((0, chalk_1.default) `The native config files {bold Expo.plist & AndroidManifest.xml} must be updated to support EAS Update. ${(0, log_1.learnMore)('https://expo.fyi/eas-update-config.md#native-configuration')}`);
}
log_1.default.addNewLineIfNone();
}
/**
* Make sure that the current `app.json` configuration for EAS Updates is set natively.
*/
async function ensureEASUpdateIsConfiguredNativelyAsync(vcsClient, { exp, projectDir, platform, workflows, env, }) {
if (['all', 'android'].includes(platform) && workflows.android === eas_build_job_1.Workflow.GENERIC) {
await (0, UpdatesModule_1.syncUpdatesConfigurationAsync)({
projectDir,
exp,
workflow: workflows.android,
env,
});
log_1.default.withTick(`Configured ${chalk_1.default.bold('AndroidManifest.xml')} for EAS Update`);
}
if (['all', 'ios'].includes(platform) && workflows.ios === eas_build_job_1.Workflow.GENERIC) {
await (0, UpdatesModule_2.syncUpdatesConfigurationAsync)({
vcsClient,
projectDir,
exp,
workflow: workflows.ios,
env,
});
log_1.default.withTick(`Configured ${chalk_1.default.bold('Expo.plist')} for EAS Update`);
}
}
/**
* Make sure EAS Build profiles are configured to work with EAS Update by adding channels to build profiles.
*/
async function ensureEASUpdateIsConfiguredInEasJsonAsync(projectDir) {
const easJsonPath = eas_json_1.EasJsonAccessor.formatEasJsonPath(projectDir);
if (!(await fs_extra_1.default.pathExists(easJsonPath))) {
log_1.default.warn(`EAS Build is not configured. If you'd like to use EAS Build with EAS Update, run ${chalk_1.default.bold('eas build:configure')}.`);
return;
}
try {
const easJsonAccessor = eas_json_1.EasJsonAccessor.fromProjectPath(projectDir);
await easJsonAccessor.readRawJsonAsync();
easJsonAccessor.patch(easJsonRawObject => {
const easBuildProfilesWithChannels = Object.keys(easJsonRawObject.build).reduce((acc, profileNameKey) => {
const buildProfile = easJsonRawObject.build[profileNameKey];
const isNotAlreadyConfigured = !buildProfile.channel;
if (isNotAlreadyConfigured) {
return {
...acc,
[profileNameKey]: {
...buildProfile,
channel: profileNameKey,
},
};
}
return {
...acc,
[profileNameKey]: {
...easJsonRawObject.build[profileNameKey],
},
};
}, {});
return {
...easJsonRawObject,
build: easBuildProfilesWithChannels,
};
});
await easJsonAccessor.writeAsync();
log_1.default.withTick(`Configured ${chalk_1.default.bold('eas.json')}.`);
}
catch (error) {
log_1.default.error(`We were not able to configure ${chalk_1.default.bold('eas.json')}. Error: ${error}.`);
}
}
exports.ensureEASUpdateIsConfiguredInEasJsonAsync = ensureEASUpdateIsConfiguredInEasJsonAsync;
/**
* Make sure EAS Update is fully configured in the current project.
* This goes over a checklist and performs the following checks or changes:
* - Ensure `updates.useClassicUpdates` (SDK 49) is not set in the app config
* - Ensure the `expo-updates` package is currently installed.
* - Ensure `app.json` is configured for EAS Updates
* - Sets `runtimeVersion` if not set
* - Sets `updates.url` if not set
* - Ensure latest changes are reflected in the native config, if any
*/
async function ensureEASUpdateIsConfiguredAsync({ exp: expMaybeWithoutUpdates, projectId, projectDir, vcsClient, platform, env, }) {
const hasExpoUpdates = (0, projectUtils_1.isExpoUpdatesInstalledOrAvailable)(projectDir, expMaybeWithoutUpdates.sdkVersion);
const hasExpoUpdatesInDevDependencies = (0, projectUtils_1.isExpoUpdatesInstalledAsDevDependency)(projectDir);
if (!hasExpoUpdates && !hasExpoUpdatesInDevDependencies) {
await (0, projectUtils_1.installExpoUpdatesAsync)(projectDir, { silent: false });
log_1.default.withTick('Installed expo-updates');
}
else if (hasExpoUpdatesInDevDependencies) {
log_1.default.warn(`The "expo-updates" package is installed as a dev dependency. This is not recommended. Move "expo-updates" to your main dependencies.`);
}
// Bail out if using a platform that doesn't require runtime versions
// or native setup, i.e. web.
if (!platform) {
return;
}
const workflows = await (0, workflow_1.resolveWorkflowPerPlatformAsync)(projectDir, vcsClient);
const { projectChanged, exp: expWithUpdates } = await ensureEASUpdatesIsConfiguredInExpoConfigAsync({
exp: expMaybeWithoutUpdates,
projectDir,
projectId,
platform,
workflows,
});
if (projectChanged || !hasExpoUpdates) {
await ensureEASUpdateIsConfiguredNativelyAsync(vcsClient, {
exp: expWithUpdates,
projectDir,
platform,
workflows,
env,
});
}
if (projectChanged) {
log_1.default.addNewLineIfNone();
log_1.default.warn(`All builds of your app going forward will be eligible to receive updates published with EAS Update.`);
log_1.default.newLine();
}
}
exports.ensureEASUpdateIsConfiguredAsync = ensureEASUpdateIsConfiguredAsync;
;