UNPKG

eas-cli

Version:
287 lines (286 loc) 14 kB
"use strict"; 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;