Skip to content
This repository has been archived by the owner on Jan 18, 2024. It is now read-only.

Commit

Permalink
[expo-cli] Add a eas:build:init command
Browse files Browse the repository at this point in the history
We want to add a separate command to configure the project for eas builds without having to actually start a build.
  • Loading branch information
satya164 committed Aug 25, 2020
1 parent 7cd8e3f commit 0e920fd
Show file tree
Hide file tree
Showing 10 changed files with 236 additions and 93 deletions.
54 changes: 13 additions & 41 deletions packages/expo-cli/src/commands/eas-build/build/action.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { getConfig } from '@expo/config';
import { ApiV2, User, UserManager } from '@expo/xdl';
import { ApiV2 } from '@expo/xdl';
import chalk from 'chalk';
import delayAsync from 'delay-async';
import fs from 'fs-extra';
Expand All @@ -8,18 +7,19 @@ import os from 'os';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';

import CommandError from '../../../CommandError';
import { EasConfig, EasJsonReader } from '../../../easJson';
import log from '../../../log';
import { ensureProjectExistsAsync } from '../../../projects';
import { UploadType, uploadAsync } from '../../../uploads';
import { createProgressTracker } from '../../utils/progress';
import { platformDisplayNames } from '../constants';
import { Build, BuildCommandPlatform, BuildStatus } from '../types';
import { Build, BuildCommandPlatform, BuildStatus, Builder, BuilderContext } from '../types';
import createBuilderContextAsync from '../utils/createBuilderContextAsync';
import { ensureGitStatusIsCleanAsync, makeProjectTarballAsync } from '../utils/git';
import { printBuildResults, printLogsUrls } from '../utils/misc';
import AndroidBuilder from './builders/AndroidBuilder';
import iOSBuilder from './builders/iOSBuilder';
import { Builder, BuilderContext } from './types';
import { ensureGitStatusIsCleanAsync, makeProjectTarballAsync } from './utils/git';
import { printBuildResults, printLogsUrls } from './utils/misc';

interface BuildOptions {
platform: BuildCommandPlatform;
Expand Down Expand Up @@ -72,40 +72,6 @@ async function buildAction(projectDir: string, options: BuildOptions): Promise<v
}
}

async function createBuilderContextAsync(
projectDir: string,
eas: EasConfig,
{
platform = BuildCommandPlatform.ALL,
nonInteractive = false,
skipCredentialsCheck = false,
skipProjectConfiguration = false,
}: {
platform?: BuildCommandPlatform;
nonInteractive?: boolean;
skipCredentialsCheck?: boolean;
skipProjectConfiguration?: boolean;
}
): Promise<BuilderContext> {
const user: User = await UserManager.ensureLoggedInAsync();
const { exp } = getConfig(projectDir, { skipSDKVersionRequirement: true });
const accountName = exp.owner || user.username;
const projectName = exp.slug;

return {
eas,
projectDir,
user,
accountName,
projectName,
exp,
platform,
nonInteractive,
skipCredentialsCheck,
skipProjectConfiguration,
};
}

async function startBuildsAsync(
ctx: BuilderContext,
projectId: string
Expand Down Expand Up @@ -140,7 +106,13 @@ async function startBuildAsync(
await builder.ensureCredentialsAsync();

if (!builder.ctx.skipProjectConfiguration) {
await builder.configureProjectAsync();
if (builder.ctx.platform === BuildCommandPlatform.IOS) {
await builder.configureProjectAsync();
} else if (!(await builder.isProjectConfiguredAsync())) {
throw new CommandError(
'Project is not configured. Please run "expo eas:build:init" first to configure the project'
);
}
}

const fileSize = await makeProjectTarballAsync(tarPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@ import {
} from '../../../../easJson';
import { gitAddAsync } from '../../../../git';
import log from '../../../../log';
import { Builder, BuilderContext } from '../../types';
import * as gitUtils from '../../utils/git';
import { ensureCredentialsAsync } from '../credentials';
import gradleContent from '../templates/gradleContent';
import { Builder, BuilderContext } from '../types';
import * as gitUtils from '../utils/git';

interface CommonJobProperties {
platform: Platform.Android;
Expand Down Expand Up @@ -60,9 +60,29 @@ class AndroidBuilder implements Builder {
this.credentials = await provider.getCredentialsAsync(credentialsSource);
}

public async isProjectConfiguredAsync(): Promise<boolean> {
const androidAppDir = path.join(this.ctx.projectDir, 'android', 'app');
const buildGradlePath = path.join(androidAppDir, 'build.gradle');

const buildGradleContent = await fs.readFile(path.join(buildGradlePath), 'utf-8');
const applyEasGradle = 'apply from: "./eas-build.gradle"';

const isAlreadyConfigured = buildGradleContent
.split('\n')
// Check for both single and double quotes
.some(line => line === applyEasGradle || line === applyEasGradle.replace(/"/g, "'"));

return isAlreadyConfigured;
}

public async configureProjectAsync(): Promise<void> {
const spinner = ora('Making sure your Android project is set up properly');

if (await this.isProjectConfiguredAsync()) {
spinner.succeed('Android project is already configured');
return;
}

const { projectDir } = this.ctx;

const androidAppDir = path.join(projectDir, 'android', 'app');
Expand All @@ -75,14 +95,7 @@ class AndroidBuilder implements Builder {
const buildGradleContent = await fs.readFile(path.join(buildGradlePath), 'utf-8');
const applyEasGradle = 'apply from: "./eas-build.gradle"';

const isAlreadyConfigured = buildGradleContent
.split('\n')
// Check for both single and double quotes
.some(line => line === applyEasGradle || line === applyEasGradle.replace(/"/g, "'"));

if (!isAlreadyConfigured) {
await fs.writeFile(buildGradlePath, `${buildGradleContent.trim()}\n${applyEasGradle}\n`);
}
await fs.writeFile(buildGradlePath, `${buildGradleContent.trim()}\n${applyEasGradle}\n`);

try {
await gitUtils.ensureGitStatusIsCleanAsync();
Expand All @@ -100,7 +113,7 @@ class AndroidBuilder implements Builder {
log(`${chalk.green(figures.tick)} Successfully committed the configuration changes.`);
} catch (e) {
throw new Error(
"Aborting, run the build command once you're ready. Make sure to commit any changes you've made."
"Aborting, run the command again once you're ready. Make sure to commit any changes you've made."
);
}
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ import {
iOSManagedBuildProfile,
} from '../../../../easJson';
import log from '../../../../log';
import { Builder, BuilderContext } from '../../types';
import * as gitUtils from '../../utils/git';
import { ensureCredentialsAsync } from '../credentials';
import { Builder, BuilderContext } from '../types';
import * as gitUtils from '../utils/git';
import { getBundleIdentifier } from '../utils/ios';

interface CommonJobProperties {
Expand Down Expand Up @@ -73,16 +73,22 @@ class iOSBuilder implements Builder {
this.credentials = await provider.getCredentialsAsync(credentialsSource);
}

public async isProjectConfiguredAsync(): Promise<boolean> {
// TODO: implement this later
throw new Error('Not implemented.');
}

public async configureProjectAsync(): Promise<void> {
// TODO: add simulator flow
// assuming we're building for app store
if (!this.credentials) {
throw new Error('Call ensureCredentialsAsync first!');
}
const bundleIdentifier = await getBundleIdentifier(this.ctx.projectDir, this.ctx.exp);

const spinner = ora('Making sure your iOS project is set up properly');

const bundleIdentifier = await getBundleIdentifier(this.ctx.projectDir, this.ctx.exp);

const profileName = ProvisioningProfileUtils.readProfileName(
this.credentials.provisioningProfile
);
Expand Down Expand Up @@ -111,7 +117,7 @@ class iOSBuilder implements Builder {
log(`${chalk.green(figures.tick)} Successfully committed the configuration changes.`);
} catch (e) {
throw new Error(
"Aborting, run the build command once you're ready. Make sure to commit any changes you've made."
"Aborting, run the command again once you're ready. Make sure to commit any changes you've made."
);
}
} else {
Expand Down
26 changes: 0 additions & 26 deletions packages/expo-cli/src/commands/eas-build/build/types.ts

This file was deleted.

16 changes: 14 additions & 2 deletions packages/expo-cli/src/commands/eas-build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,24 @@ import path from 'path';

import buildAction from './build/action';
import credentialsSyncAction from './credentialsSync/action';
import initAction from './init/action';
import statusAction from './status/action';

export default function (program: Command) {
// don't register `expo eas:build:*` commands if eas.json doesn't exist
const easJsonPath = path.join(process.cwd(), 'eas.json');
if (!fs.pathExistsSync(easJsonPath)) {
const hasEasJson = fs.pathExistsSync(easJsonPath);

if (hasEasJson || process.argv[2] !== '--help') {
// We don't want to show this in the help output for now
program
.command('eas:build:init [project-dir]')
.description('Initialize build configuration for your project.')
.option('--skip-credentials-check', 'Skip checking credentials', false)
.asyncActionProjectDir(initAction, { checkConfig: true });
}

if (!hasEasJson) {
return;
}

Expand All @@ -26,7 +38,7 @@ export default function (program: Command) {
.description('Build an app binary for your project.')
.option(
'-p --platform <platform>',
'Build for specified platform: ios, android, all',
'Build for the specified platform: ios, android, all',
/^(all|android|ios)$/i
)
.option('--skip-credentials-check', 'Skip checking credentials', false)
Expand Down
102 changes: 102 additions & 0 deletions packages/expo-cli/src/commands/eas-build/init/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import chalk from 'chalk';
import figures from 'figures';
import fs from 'fs-extra';
import ora from 'ora';
import path from 'path';

import { EasConfig, EasJsonReader } from '../../../easJson';
import { gitAddAsync } from '../../../git';
import log from '../../../log';
import AndroidBuilder from '../build/builders/AndroidBuilder';
import iOSBuilder from '../build/builders/iOSBuilder';
import { BuildCommandPlatform } from '../types';
import createBuilderContextAsync from '../utils/createBuilderContextAsync';
import {
DirtyGitTreeError,
ensureGitStatusIsCleanAsync,
reviewAndCommitChangesAsync,
} from '../utils/git';

interface BuildOptions {
platform: BuildCommandPlatform;
skipCredentialsCheck?: boolean; // TODO: noop for now
profile: string;
parent?: {
nonInteractive: boolean;
};
}

async function initAction(projectDir: string, options: BuildOptions): Promise<void> {
const nonInteractive = options.parent?.nonInteractive === true;

const spinner = ora('Checking for eas.json file');

await ensureGitStatusIsCleanAsync();

const easJsonPath = path.join(projectDir, 'eas.json');
const easJson = {
builds: {
android: {
release: {
workflow: 'generic',
},
},
ios: {
release: {
workflow: 'generic',
},
},
},
};

if (!(await fs.pathExists(easJsonPath))) {
await fs.writeFile(easJsonPath, `${JSON.stringify(easJson, null, 2)}\n`);
await gitAddAsync(easJsonPath, { intentToAdd: true });
}

try {
await ensureGitStatusIsCleanAsync();
spinner.succeed('Found existing eas.json file');
} catch (err) {
if (err instanceof DirtyGitTreeError) {
spinner.succeed('We created a minimal eas.json file');
log.newLine();

try {
await reviewAndCommitChangesAsync('Create minimal eas.json', { nonInteractive });

log(`${chalk.green(figures.tick)} Successfully committed eas.json.`);
} catch (e) {
throw new Error(
"Aborting, run the command again once you're ready. Make sure to commit any changes you've made."
);
}
} else {
spinner.fail();
throw err;
}
}

const easConfig: EasConfig = await new EasJsonReader(
projectDir,
BuildCommandPlatform.ALL
).readAsync('release');

const ctx = await createBuilderContextAsync(projectDir, easConfig, {
platform: BuildCommandPlatform.ALL,
nonInteractive,
skipCredentialsCheck: options?.skipCredentialsCheck,
skipProjectConfiguration: false,
});

const androidBuilder = new AndroidBuilder(ctx);

await androidBuilder.configureProjectAsync();

const iosBuilder = new iOSBuilder(ctx);

await iosBuilder.ensureCredentialsAsync();
await iosBuilder.configureProjectAsync();
}

export default initAction;
27 changes: 26 additions & 1 deletion packages/expo-cli/src/commands/eas-build/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Platform } from '@expo/build-tools';
import { Job, Platform } from '@expo/build-tools';
import { ExpoConfig } from '@expo/config';
import { User } from '@expo/xdl';

import { EasConfig } from '../../easJson';

export enum BuildCommandPlatform {
ANDROID = 'android',
Expand All @@ -25,3 +29,24 @@ interface BuildArtifacts {
buildUrl?: string;
logsUrl: string;
}

export interface Builder {
ctx: BuilderContext;
ensureCredentialsAsync(): Promise<void>;
isProjectConfiguredAsync(): Promise<boolean>;
configureProjectAsync(): Promise<void>;
prepareJobAsync(archiveUrl: string): Promise<Job>;
}

export interface BuilderContext {
projectDir: string;
eas: EasConfig;
user: User;
accountName: string;
projectName: string;
exp: ExpoConfig;
platform: BuildCommandPlatform;
nonInteractive: boolean;
skipCredentialsCheck: boolean;
skipProjectConfiguration: boolean;
}
Loading

0 comments on commit 0e920fd

Please sign in to comment.