eas-cli
Version:
EAS command line tool
410 lines (409 loc) • 20.6 kB
JavaScript
"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.');
}
}
}