UNPKG

eas-cli

Version:

EAS command line tool

410 lines (409 loc) 20.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.downloadAndRunAsync = exports.runBuildAndSubmitAsync = 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 logger_1 = require("@expo/logger"); const assert_1 = tslib_1.__importDefault(require("assert")); const chalk_1 = tslib_1.__importDefault(require("chalk")); const fs_extra_1 = require("fs-extra"); const nullthrows_1 = tslib_1.__importDefault(require("nullthrows")); const build_1 = require("./android/build"); const build_2 = require("./build"); const configure_1 = require("./configure"); const createContext_1 = require("./createContext"); const evaluateConfigWithEnvVarsAsync_1 = require("./evaluateConfigWithEnvVarsAsync"); const build_3 = require("./ios/build"); const local_1 = require("./local"); const devClient_1 = require("./utils/devClient"); const printBuildInfo_1 = require("./utils/printBuildInfo"); const repository_1 = require("./utils/repository"); const queries_1 = require("../channel/queries"); const generated_1 = require("../graphql/generated"); const BuildQuery_1 = require("../graphql/queries/BuildQuery"); const AppPlatform_1 = require("../graphql/types/AppPlatform"); const log_1 = tslib_1.__importStar(require("../log")); const platform_1 = require("../platform"); const customBuildConfig_1 = require("../project/customBuildConfig"); const expoSdk_1 = require("../project/expoSdk"); const metroConfig_1 = require("../project/metroConfig"); const projectUtils_1 = require("../project/projectUtils"); const remoteVersionSource_1 = require("../project/remoteVersionSource"); const prompts_1 = require("../prompts"); const run_1 = require("../run/run"); const utils_1 = require("../run/utils"); const context_1 = require("../submit/context"); const submit_1 = require("../submit/submit"); const urls_1 = require("../submit/utils/urls"); const configure_2 = require("../update/configure"); const download_1 = require("../utils/download"); const filter_1 = require("../utils/expodash/filter"); const json_1 = require("../utils/json"); const profiles_1 = require("../utils/profiles"); let metroConfigValidated = false; let sdkVersionChecked = false; async function runBuildAndSubmitAsync({ graphqlClient, analytics, vcsClient, projectDir, flags, actor, getDynamicPrivateProjectConfigAsync, downloadSimBuildAutoConfirm, envOverride, }) { await vcsClient.ensureRepoExistsAsync(); await (0, repository_1.ensureRepoIsCleanAsync)(vcsClient, flags.nonInteractive); await (0, configure_1.ensureProjectConfiguredAsync)({ projectDir, nonInteractive: flags.nonInteractive, vcsClient, }); const easJsonAccessor = eas_json_1.EasJsonAccessor.fromProjectPath(projectDir); const easJsonCliConfig = (await eas_json_1.EasJsonUtils.getCliConfigAsync(easJsonAccessor)) ?? {}; const platforms = (0, platform_1.toPlatforms)(flags.requestedPlatform); const buildProfiles = await (0, profiles_1.getProfilesAsync)({ type: 'build', easJsonAccessor, platforms, profileName: flags.profile ?? undefined, projectDir, }); for (const buildProfile of buildProfiles) { if (buildProfile.profile.image && ['default', 'stable'].includes(buildProfile.profile.image)) { log_1.default.warn(`The "image" field in the build profile "${buildProfile.profileName}" is set to "${buildProfile.profile.image}". This tag is deprecated and will be removed in the future. Use other images or tags listed here: https://docs.expo.dev/build-reference/infrastructure/`); } else if (buildProfile.profile.image && [ 'ubuntu-20.04-jdk-11-ndk-r19c', 'ubuntu-20.04-jdk-8-ndk-r19c', 'ubuntu-20.04-jdk-11-ndk-r21e', 'ubuntu-20.04-jdk-8-ndk-r21e', 'ubuntu-22.04-jdk-8-ndk-r21e', 'ubuntu-20.04-jdk-11-ndk-r23b', ].includes(buildProfile.profile.image)) { log_1.default.warn(`The "image" field in the build profile "${buildProfile.profileName}" is set to "${buildProfile.profile.image}". This image is deprecated and will be removed on September 1st, 2024. ${(0, log_1.learnMore)('https://expo.dev/changelog/2024/07-12-eas-build-upcoming-android-images-updates')}`); } } await (0, devClient_1.ensureExpoDevClientInstalledForDevClientBuildsAsync)({ projectDir, nonInteractive: flags.nonInteractive, buildProfiles, vcsClient, }); const customBuildConfigMetadataByPlatform = {}; for (const buildProfile of buildProfiles) { await (0, remoteVersionSource_1.validateBuildProfileVersionSettingsAsync)(buildProfile, easJsonCliConfig, projectDir, flags); const maybeMetadata = await (0, customBuildConfig_1.validateCustomBuildConfigAsync)({ projectDir, profile: buildProfile.profile, vcsClient, }); if (maybeMetadata) { customBuildConfigMetadataByPlatform[(0, AppPlatform_1.toAppPlatform)(buildProfile.platform)] = maybeMetadata; } } const startedBuilds = []; const buildCtxByPlatform = {}; for (const buildProfile of buildProfiles) { const platform = (0, AppPlatform_1.toAppPlatform)(buildProfile.platform); const { env } = !envOverride ? await (0, evaluateConfigWithEnvVarsAsync_1.evaluateConfigWithEnvVarsAsync)({ buildProfile: buildProfile.profile, buildProfileName: buildProfile.profileName, graphqlClient, getProjectConfig: getDynamicPrivateProjectConfigAsync, opts: { env: buildProfile.profile.env }, }) : { env: envOverride }; const { build: maybeBuild, buildCtx } = await prepareAndStartBuildAsync({ projectDir, flags, moreBuilds: platforms.length > 1, buildProfile, easJsonCliConfig, actor, graphqlClient, analytics, vcsClient, getDynamicPrivateProjectConfigAsync, customBuildConfigMetadata: customBuildConfigMetadataByPlatform[platform], env, easJsonAccessor, }); if (maybeBuild) { startedBuilds.push({ build: maybeBuild, buildProfile }); } buildCtxByPlatform[platform] = buildCtx; } if (flags.localBuildOptions.localBuildMode === local_1.LocalBuildMode.LOCAL_BUILD_PLUGIN) { return { buildIds: startedBuilds.map(({ build }) => build.id), }; } if (flags.localBuildOptions.localBuildMode === local_1.LocalBuildMode.INTERNAL) { const startedBuild = await BuildQuery_1.BuildQuery.byIdAsync(graphqlClient, (0, nullthrows_1.default)(process.env.EAS_BUILD_ID, 'EAS_BUILD_ID is not defined')); startedBuilds.push({ build: startedBuild, buildProfile: buildProfiles[0] }); } if (!flags.localBuildOptions.localBuildMode) { log_1.default.newLine(); (0, printBuildInfo_1.printLogsUrls)(startedBuilds.map(startedBuild => startedBuild.build)); log_1.default.newLine(); } const submissions = []; if (flags.autoSubmit) { const submitProfiles = await (0, profiles_1.getProfilesAsync)({ easJsonAccessor, platforms, profileName: flags.submitProfile, type: 'submit', projectDir, }); for (const startedBuild of startedBuilds) { const submitProfile = (0, nullthrows_1.default)(submitProfiles.find(({ platform }) => (0, AppPlatform_1.toAppPlatform)(platform) === startedBuild.build.platform)).profile; const submission = await prepareAndStartAutoSubmissionAsync({ build: startedBuild.build, buildCtx: (0, nullthrows_1.default)(buildCtxByPlatform[startedBuild.build.platform]), moreBuilds: startedBuilds.length > 1, projectDir, submitProfile, nonInteractive: flags.nonInteractive, selectedSubmitProfileName: flags.submitProfile, }); startedBuild.build = await BuildQuery_1.BuildQuery.withSubmissionsByIdAsync(graphqlClient, startedBuild.build.id); submissions.push(submission); } if (!flags.localBuildOptions.localBuildMode) { log_1.default.newLine(); (0, urls_1.printSubmissionDetailsUrls)(submissions); log_1.default.newLine(); } } if (flags.localBuildOptions.localBuildMode) { return { buildIds: startedBuilds.map(({ build }) => build.id), }; } if (!flags.wait) { if (flags.json) { (0, json_1.printJsonOnlyOutput)(startedBuilds.map(buildInfo => buildInfo.build)); } return { buildIds: startedBuilds.map(({ build }) => build.id), }; } const { accountName } = Object.values(buildCtxByPlatform)[0]; const builds = await (0, build_2.waitForBuildEndAsync)(graphqlClient, { buildIds: startedBuilds.map(({ build }) => build.id), accountName, }); if (!flags.json) { (0, printBuildInfo_1.printBuildResults)(builds); } const haveAllBuildsFailedOrCanceled = builds.every(build => build?.status && [generated_1.BuildStatus.Errored, generated_1.BuildStatus.Canceled, generated_1.BuildStatus.PendingCancel].includes(build?.status)); await maybeDownloadAndRunSimulatorBuildsAsync(builds, flags, downloadSimBuildAutoConfirm); if (haveAllBuildsFailedOrCanceled || !flags.autoSubmit) { if (flags.json) { (0, json_1.printJsonOnlyOutput)(builds); } exitWithNonZeroCodeIfSomeBuildsFailed(builds); } else { const completedSubmissions = await (0, submit_1.waitToCompleteAsync)(graphqlClient, submissions); if (flags.json) { (0, json_1.printJsonOnlyOutput)(await Promise.all(builds .filter((i) => !!i) .map(build => BuildQuery_1.BuildQuery.withSubmissionsByIdAsync(graphqlClient, build.id)))); } (0, submit_1.exitWithNonZeroCodeIfSomeSubmissionsDidntFinish)(completedSubmissions); } return { buildIds: startedBuilds.map(({ build }) => build.id), buildProfiles, }; } exports.runBuildAndSubmitAsync = runBuildAndSubmitAsync; async function prepareAndStartBuildAsync({ projectDir, flags, moreBuilds, buildProfile, easJsonCliConfig, actor, graphqlClient, analytics, vcsClient, getDynamicPrivateProjectConfigAsync, customBuildConfigMetadata, env, easJsonAccessor, }) { const buildCtx = await (0, createContext_1.createBuildContextAsync)({ buildProfileName: buildProfile.profileName, resourceClassFlag: flags.resourceClass, clearCache: flags.clearCache, buildProfile: buildProfile.profile, nonInteractive: flags.nonInteractive, noWait: !flags.wait, platform: buildProfile.platform, projectDir, localBuildOptions: flags.localBuildOptions, easJsonCliConfig, message: flags.message, actor, graphqlClient, analytics, vcsClient, getDynamicPrivateProjectConfigAsync, customBuildConfigMetadata, buildLoggerLevel: flags.buildLoggerLevel ?? (log_1.default.isDebug ? logger_1.LoggerLevel.DEBUG : undefined), freezeCredentials: flags.freezeCredentials, isVerboseLoggingEnabled: flags.isVerboseLoggingEnabled ?? false, whatToTest: flags.whatToTest, env, }); if (moreBuilds) { log_1.default.newLine(); const appPlatform = (0, AppPlatform_1.toAppPlatform)(buildProfile.platform); log_1.default.log(`${platform_1.appPlatformEmojis[appPlatform]} ${chalk_1.default.bold(`${platform_1.appPlatformDisplayNames[appPlatform]} build`)}`); } if (buildProfile.profile.channel) { await validateExpoUpdatesInstalledAsProjectDependencyAsync({ exp: buildCtx.exp, projectId: buildCtx.projectId, projectDir, vcsClient: buildCtx.vcsClient, sdkVersion: buildCtx.exp.sdkVersion, nonInteractive: flags.nonInteractive, buildProfile, env: buildProfile.profile.env, easJsonAccessor, }); const easJsonCliConfig = (await eas_json_1.EasJsonUtils.getCliConfigAsync(easJsonAccessor)) ?? {}; if ((0, projectUtils_1.isUsingEASUpdate)(buildCtx.exp, buildCtx.projectId, easJsonCliConfig.updateManifestHostOverride ?? null)) { const doesChannelExist = await (0, queries_1.doesChannelExistAsync)(graphqlClient, { appId: buildCtx.projectId, channelName: buildProfile.profile.channel, }); if (!doesChannelExist) { await (0, queries_1.createAndLinkChannelAsync)(graphqlClient, { appId: buildCtx.projectId, channelName: buildProfile.profile.channel, }); } } } await (0, projectUtils_1.validateAppVersionRuntimePolicySupportAsync)(buildCtx.projectDir, buildCtx.exp); if (easJsonCliConfig?.appVersionSource === undefined && buildProfile.profile.autoIncrement !== 'version') { if (buildProfile.profile.autoIncrement !== true) { log_1.default.warn(`The field "cli.appVersionSource" is not set, but it will be required in the future. ${(0, log_1.learnMore)('https://docs.expo.dev/build-reference/app-versions/')}`); } else { const easJsonAccessor = eas_json_1.EasJsonAccessor.fromProjectPath(projectDir); easJsonCliConfig = await (0, remoteVersionSource_1.ensureAppVersionSourceIsSetAsync)(easJsonAccessor, easJsonCliConfig, flags.nonInteractive); } } if (easJsonCliConfig?.appVersionSource === eas_json_1.AppVersionSource.REMOTE) { (0, remoteVersionSource_1.validateAppConfigForRemoteVersionSource)(buildCtx.exp, buildProfile.platform); } if (buildCtx.workflow === eas_build_job_1.Workflow.MANAGED) { if (!sdkVersionChecked) { await (0, expoSdk_1.checkExpoSdkIsSupportedAsync)(buildCtx); sdkVersionChecked = true; } if (!metroConfigValidated) { await (0, metroConfig_1.validateMetroConfigForManagedWorkflowAsync)(buildCtx); metroConfigValidated = true; } } const build = await startBuildAsync(buildCtx); return { build, buildCtx, }; } async function startBuildAsync(ctx) { let sendBuildRequestAsync; if (ctx.platform === eas_build_job_1.Platform.ANDROID) { sendBuildRequestAsync = await (0, build_1.prepareAndroidBuildAsync)(ctx); } else { sendBuildRequestAsync = await (0, build_3.prepareIosBuildAsync)(ctx); } return await sendBuildRequestAsync(); } async function prepareAndStartAutoSubmissionAsync({ build, buildCtx, moreBuilds, projectDir, submitProfile, selectedSubmitProfileName, nonInteractive, }) { const platform = (0, AppPlatform_1.toPlatform)(build.platform); const submissionCtx = await (0, context_1.createSubmissionContextAsync)({ platform, projectDir, profile: submitProfile, archiveFlags: { id: build.id }, nonInteractive, env: buildCtx.env, credentialsCtx: buildCtx.credentialsCtx, applicationIdentifier: buildCtx.android?.applicationId ?? buildCtx.ios?.bundleIdentifier, actor: buildCtx.user, graphqlClient: buildCtx.graphqlClient, analytics: buildCtx.analytics, projectId: buildCtx.projectId, exp: buildCtx.exp, vcsClient: buildCtx.vcsClient, isVerboseFastlaneEnabled: false, specifiedProfile: selectedSubmitProfileName, groups: undefined, // use groups from submit profile whatToTest: buildCtx.whatToTest, }); if (moreBuilds) { log_1.default.newLine(); log_1.default.log(`${platform_1.appPlatformEmojis[build.platform]} ${chalk_1.default.bold(`${platform_1.appPlatformDisplayNames[build.platform]} submission`)}`); } return await (0, submit_1.submitAsync)(submissionCtx); } function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds) { const failedBuilds = maybeBuilds.filter(i => i).filter(i => i.status === generated_1.BuildStatus.Errored); if (failedBuilds.length > 0) { process.exit(1); } } async function downloadAndRunAsync(build) { (0, assert_1.default)(build.artifacts?.applicationArchiveUrl); const cachedAppPath = (0, run_1.getEasBuildRunCachedAppPath)(build.project.id, build.id, build.platform); if (await (0, fs_extra_1.pathExists)(cachedAppPath)) { log_1.default.newLine(); log_1.default.log(`Using cached app...`); await (0, run_1.runAsync)(cachedAppPath, build.platform); return; } const buildPath = await (0, download_1.downloadAndMaybeExtractAppAsync)(build.artifacts.applicationArchiveUrl, build.platform, cachedAppPath); await (0, run_1.runAsync)(buildPath, build.platform); } exports.downloadAndRunAsync = downloadAndRunAsync; async function maybeDownloadAndRunSimulatorBuildsAsync(builds, flags, autoConfirm) { const simBuilds = builds.filter(filter_1.truthy).filter(utils_1.isRunnableOnSimulatorOrEmulator); if (simBuilds.length > 0 && !flags.autoSubmit && !flags.nonInteractive) { for (const simBuild of simBuilds) { if (simBuild.platform === generated_1.AppPlatform.Android || process.platform === 'darwin') { log_1.default.newLine(); const confirm = autoConfirm ?? (await (0, prompts_1.confirmAsync)({ message: `Install and run the ${simBuild.platform === generated_1.AppPlatform.Android ? 'Android' : 'iOS'} build on ${simBuild.platform === generated_1.AppPlatform.Android ? 'an emulator' : 'a simulator'}?`, })); if (confirm) { await downloadAndRunAsync(simBuild); } } } } } async function validateExpoUpdatesInstalledAsProjectDependencyAsync({ exp, projectId, projectDir, vcsClient, buildProfile, nonInteractive, sdkVersion, env, easJsonAccessor, }) { if ((0, projectUtils_1.isExpoUpdatesInstalledOrAvailable)(projectDir, sdkVersion)) { return; } if ((0, projectUtils_1.isExpoUpdatesInstalledAsDevDependency)(projectDir)) { log_1.default.warn(`The build profile "${buildProfile.profileName}" uses the channel "${buildProfile.profile.channel}", but you've added "expo-updates" as a dev dependency. To make channels work for your builds, move "expo-updates" from dev dependencies to the main dependencies in your project.`); } else if (nonInteractive) { log_1.default.warn(`The build profile "${buildProfile.profileName}" has specified the channel "${buildProfile.profile.channel}", but the "expo-updates" package hasn't been installed. To use channels for your builds, install the "expo-updates" package by running "npx expo install expo-updates" followed by "eas update:configure".`); } else { log_1.default.warn(`The build profile "${buildProfile.profileName}" specifies the channel "${buildProfile.profile.channel}", but the "expo-updates" package is missing. To use channels in your builds, install the "expo-updates" package and run "eas update:configure".`); const installExpoUpdates = await (0, prompts_1.confirmAsync)({ message: `Would you like to install the "expo-updates" package and configure EAS Update now?`, }); if (installExpoUpdates) { const easJsonCliConfig = (await eas_json_1.EasJsonUtils.getCliConfigAsync(easJsonAccessor)) ?? {}; await (0, configure_2.ensureEASUpdateIsConfiguredAsync)({ exp, projectId, projectDir, platform: platform_1.RequestedPlatform.All, vcsClient, env, manifestHostOverride: easJsonCliConfig.updateManifestHostOverride ?? null, }); log_1.default.withTick('Installed expo-updates and configured EAS Update.'); throw new Error('Command must be re-run to pick up new updates configuration.'); } } }