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

[eject] Added no-install and npm args to eject #2621

Merged
merged 3 commits into from
Sep 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/expo-cli/src/commands/eject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async function userWantsToEjectWithoutUpgradingAsync() {

async function action(
projectDir: string,
options: LegacyEject.EjectAsyncOptions | Eject.EjectAsyncOptions
options: (LegacyEject.EjectAsyncOptions | Eject.EjectAsyncOptions) & { npm?: boolean }
) {
let exp: ExpoConfig;
try {
Expand All @@ -31,6 +31,10 @@ async function action(
process.exit(1);
}

if (options.npm) {
options.packageManager = 'npm';
}

// Set EXPO_VIEW_DIR to universe/exponent to pull expo view code locally instead of from S3 for ExpoKit
if (Versions.lteSdkVersion(exp, '36.0.0')) {
// Don't show a warning if we haven't released SDK 37 yet
Expand Down Expand Up @@ -67,5 +71,7 @@ export default function (program: Command) {
'-f --force',
'Will attempt to generate an iOS project even when the system is not running macOS. Unsafe and may fail.'
)
.option('--no-install', 'Skip installing npm packages and CocoaPods.')
.option('--npm', 'Use npm to install dependencies. (default when Yarn is not installed)')
.asyncActionProjectDir(action);
}
105 changes: 80 additions & 25 deletions packages/expo-cli/src/commands/eject/Eject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
PackageJSONConfig,
WarningAggregator,
getConfig,
projectHasModule,
resolveModule,
} from '@expo/config';
import JsonFile from '@expo/json-file';
Expand Down Expand Up @@ -36,6 +37,7 @@ type DependenciesMap = { [key: string]: string | number };
export type EjectAsyncOptions = {
verbose?: boolean;
force?: boolean;
install?: boolean;
packageManager?: 'npm' | 'yarn';
};

Expand All @@ -53,14 +55,33 @@ export async function ejectAsync(projectRoot: string, options?: EjectAsyncOption
if (await maybeBailOnGitStatusAsync()) return;

await createNativeProjectsFromTemplateAsync(projectRoot);
await installNodeModulesAsync(projectRoot);
// TODO: Set this to true when we can detect that the user is running eject to sync new changes rather than ejecting to bare.
// This will be used to prevent the node modules from being nuked every time.
const isSyncing = false;

// Install node modules
const shouldInstall = options?.install !== false;

const packageManager = CreateApp.resolvePackageManager({
install: shouldInstall,
npm: options?.packageManager === 'npm',
yarn: options?.packageManager === 'yarn',
});

if (shouldInstall) {
await installNodeDependenciesAsync(projectRoot, packageManager, { clean: !isSyncing });
}

// Apply Expo config to native projects
await configureIOSStepAsync(projectRoot);
await configureAndroidStepAsync(projectRoot);

const podsInstalled = await CreateApp.installCocoaPodsAsync(projectRoot);
await warnIfDependenciesRequireAdditionalSetupAsync(projectRoot);
// Install CocoaPods
let podsInstalled: boolean = false;
if (shouldInstall) {
podsInstalled = await CreateApp.installCocoaPodsAsync(projectRoot);
}
await warnIfDependenciesRequireAdditionalSetupAsync(projectRoot, options);

log.newLine();
log.nested(`➡️ ${chalk.bold('Next steps')}`);
Expand All @@ -70,18 +91,24 @@ export async function ejectAsync(projectRoot: string, options?: EjectAsyncOption
`- 👆 Review the logs above and look for any warnings (⚠️ ) that might need follow-up.`
);
}
log.nested(
`- 💡 You may want to run ${chalk.bold(
'npx @react-native-community/cli doctor'
)} to help install any tools that your app may need to run your native projects.`
);

// Log a warning about needing to install node modules
if (options?.install === false) {
const installCmd = packageManager === 'npm' ? 'npm install' : 'yarn';
log.nested(`- ⚠️ Install node modules: ${log.chalk.bold(installCmd)}`);
}
if (!podsInstalled) {
log.nested(
`- 🍫 When CocoaPods is installed, initialize the project workspace: ${chalk.bold(
'npx pod-install'
)}`
);
}
log.nested(
`- 💡 You may want to run ${chalk.bold(
'npx @react-native-community/cli doctor'
)} to help install any tools that your app may need to run your native projects.`
);
log.nested(
`- 🔑 Download your Android keystore (if you're not sure if you need to, just run the command and see): ${chalk.bold(
'expo fetch:android:keystore'
Expand All @@ -107,7 +134,7 @@ export async function ejectAsync(projectRoot: string, options?: EjectAsyncOption
log.nested(
'To compile and run your project in development, execute one of the following commands:'
);
const packageManager = PackageManager.isUsingYarn(projectRoot) ? 'yarn' : 'npm';

log.nested(`- ${chalk.bold(packageManager === 'npm' ? 'npm run ios' : 'yarn ios')}`);
log.nested(`- ${chalk.bold(packageManager === 'npm' ? 'npm run android' : 'yarn android')}`);
log.nested(`- ${chalk.bold(packageManager === 'npm' ? 'npm run web' : 'yarn web')}`);
Expand All @@ -134,20 +161,31 @@ async function configureIOSStepAsync(projectRoot: string) {
*
* @param projectRoot
*/
async function installNodeModulesAsync(projectRoot: string) {
const installingDependenciesStep = CreateApp.logNewSection('Installing JavaScript dependencies.');
await fse.remove('node_modules');
const packageManager = PackageManager.createForProject(projectRoot, { log, silent: true });
async function installNodeDependenciesAsync(
projectRoot: string,
packageManager: 'yarn' | 'npm',
{ clean = true }: { clean: boolean }
) {
if (clean) {
// This step can take a couple seconds, if the installation logs are enabled (with EXPO_DEBUG) then it
// ends up looking odd to see "Installing JavaScript dependencies" for ~5 seconds before the logs start showing up.
const cleanJsDepsStep = CreateApp.logNewSection('Cleaning JavaScript dependencies.');
// nuke the node modules
// TODO: this is substantially slower, we should find a better alternative to ensuring the modules are installed.
await fse.remove('node_modules');
cleanJsDepsStep.succeed('Cleaned JavaScript dependencies.');
}

const installJsDepsStep = CreateApp.logNewSection('Installing JavaScript dependencies.');

try {
await packageManager.installAsync();
installingDependenciesStep.succeed('Installed JavaScript dependencies.');
} catch (e) {
installingDependenciesStep.fail(
await CreateApp.installNodeDependenciesAsync(projectRoot, packageManager);
installJsDepsStep.succeed('Installed JavaScript dependencies.');
} catch {
installJsDepsStep.fail(
chalk.red(
`Something when wrong installing JavaScript dependencies, check your ${
packageManager.name
} logfile or run ${chalk.bold(
`${packageManager.name.toLowerCase()} install`
`Something when wrong installing JavaScript dependencies, check your ${packageManager} logfile or run ${chalk.bold(
`${packageManager} install`
)} again manually.`
)
);
Expand Down Expand Up @@ -496,15 +534,32 @@ ${sourceGitIgnore}`;
* Some packages are not configured automatically on eject and may require
* users to add some code, eg: to their AppDelegate.
*/
async function warnIfDependenciesRequireAdditionalSetupAsync(projectRoot: string): Promise<void> {
async function warnIfDependenciesRequireAdditionalSetupAsync(
projectRoot: string,
options?: EjectAsyncOptions
): Promise<void> {
// We just need the custom `nodeModulesPath` from the config.
const { exp, pkg } = getConfig(projectRoot, {
skipSDKVersionRequirement: true,
});

const pkgsWithExtraSetup = await JsonFile.readAsync(
resolveModule('expo/requiresExtraSetup.json', projectRoot, exp)
);
const extraSetupPath = projectHasModule('expo/requiresExtraSetup.json', projectRoot, exp);
if (!extraSetupPath) {
const expoPath = projectHasModule('expo', projectRoot, exp);
// Check if expo is installed just in case the user has some version of expo that doesn't include a `requiresExtraSetup.json`.
if (!expoPath) {
log.addNewLineIfNone();
// This can occur when --no-install is used.
log.nestedWarn(
`⚠️ Not sure if any modules require extra setup because the ${log.chalk.bold(
'expo'
)} package is not installed.`
);
}
return;
}

const pkgsWithExtraSetup = await JsonFile.readAsync(extraSetupPath);
const packagesToWarn: string[] = Object.keys(pkg.dependencies).filter(
pkgName => pkgName in pkgsWithExtraSetup
);
Expand Down
36 changes: 3 additions & 33 deletions packages/expo-cli/src/commands/init.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { AndroidConfig, BareAppConfig, ExpoConfig, IOSConfig, getConfig } from '@expo/config';
import * as PackageManager from '@expo/package-manager';
import spawnAsync from '@expo/spawn-async';
import { Exp, IosPlist, UserManager } from '@expo/xdl';
import chalk from 'chalk';
Expand Down Expand Up @@ -263,7 +262,7 @@ async function action(projectDir: string, command: Command) {

// Install dependencies

const packageManager = resolvePackageManager(options);
const packageManager = CreateApp.resolvePackageManager(options);

// TODO: not this
const workflow = isBare ? 'bare' : 'managed';
Expand Down Expand Up @@ -330,39 +329,10 @@ async function action(projectDir: string, command: Command) {
}
}

type PackageManagerName = 'npm' | 'yarn';

// TODO: Use in eject as well
function resolvePackageManager(
options: Pick<Options, 'yarn' | 'npm' | 'install'>
): PackageManagerName {
let packageManager: PackageManagerName = 'npm';
if (options.yarn || (!options.npm && PackageManager.shouldUseYarn())) {
packageManager = 'yarn';
} else {
packageManager = 'npm';
}
if (options.install) {
log.addNewLineIfNone();
log(
packageManager === 'yarn'
? '🧶 Using Yarn to install packages. You can pass --npm to use npm instead.'
: '📦 Using npm to install packages.'
);
log.newLine();
}

return packageManager;
}

async function installNodeDependenciesAsync(
projectRoot: string,
packageManager: 'yarn' | 'npm',
flags: { silent: boolean } = { silent: true }
) {
async function installNodeDependenciesAsync(projectRoot: string, packageManager: 'yarn' | 'npm') {
const installJsDepsStep = CreateApp.logNewSection('Installing JavaScript dependencies.');
try {
await CreateApp.installNodeDependenciesAsync(projectRoot, packageManager, flags);
await CreateApp.installNodeDependenciesAsync(projectRoot, packageManager);
installJsDepsStep.succeed('Installed JavaScript dependencies.');
} catch {
installJsDepsStep.fail(
Expand Down
39 changes: 36 additions & 3 deletions packages/expo-cli/src/commands/utils/CreateApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,41 @@ export async function assertFolderEmptyAsync({
return true;
}

export type PackageManagerName = 'npm' | 'yarn';

export function resolvePackageManager(options: {
yarn?: boolean;
npm?: boolean;
install?: boolean;
}): PackageManagerName {
let packageManager: PackageManagerName = 'npm';
if (options.yarn || (!options.npm && PackageManager.shouldUseYarn())) {
packageManager = 'yarn';
} else {
packageManager = 'npm';
}
if (options.install) {
log.addNewLineIfNone();
log(
packageManager === 'yarn'
? '🧶 Using Yarn to install packages. You can pass --npm to use npm instead.'
: '📦 Using npm to install packages.'
);
log.newLine();
}

return packageManager;
}

const EXPO_DEBUG = getenv.boolish('EXPO_DEBUG', false);

export async function installNodeDependenciesAsync(
projectRoot: string,
packageManager: 'yarn' | 'npm',
flags: { silent: boolean } = { silent: false }
packageManager: PackageManagerName,
flags: { silent: boolean } = {
// default to silent
silent: !EXPO_DEBUG,
}
) {
const options = { cwd: projectRoot, silent: flags.silent };
if (packageManager === 'yarn') {
Expand Down Expand Up @@ -116,6 +147,8 @@ export async function installNodeDependenciesAsync(

export function logNewSection(title: string) {
const spinner = ora(log.chalk.bold(title));
// respect loading indicators
log.setSpinner(spinner);
spinner.start();
return spinner;
}
Expand All @@ -138,7 +171,7 @@ export async function installCocoaPodsAsync(projectRoot: string) {
const packageManager = new PackageManager.CocoaPodsPackageManager({
cwd: path.join(projectRoot, 'ios'),
log,
silent: getenv.boolish('EXPO_DEBUG', true),
silent: !EXPO_DEBUG,
});

if (!(await packageManager.isCLIInstalledAsync())) {
Expand Down
18 changes: 17 additions & 1 deletion packages/expo-cli/src/log.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import chalk from 'chalk';
import program from 'commander';
import { Ora } from 'ora';
import terminalLink from 'terminal-link';

type Color = (...text: string[]) => string;
Expand Down Expand Up @@ -120,8 +121,23 @@ log.setBundleProgressBar = function setBundleProgressBar(bar: any) {
_bundleProgressBar = bar;
};

log.setSpinner = function setSpinner(oraSpinner: any) {
log.setSpinner = function setSpinner(oraSpinner: Ora | null) {
_oraSpinner = oraSpinner;
if (_oraSpinner) {
const originalStart = _oraSpinner.start.bind(_oraSpinner);
_oraSpinner.start = (text: any) => {
// Reset the new line tracker
_isLastLineNewLine = false;
return originalStart(text);
};
// All other methods of stopping will invoke the stop method.
const originalStop = _oraSpinner.stop.bind(_oraSpinner);
_oraSpinner.stop = () => {
// Reset the target spinner
log.setSpinner(null);
return originalStop();
};
}
};

log.error = function error(...args: any[]) {
Expand Down