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

Commit

Permalink
[eject] Added no-install and npm args to eject (#2621)
Browse files Browse the repository at this point in the history
* Added no-install and npm args to eject

* fix silent logic

* Update Eject.ts
  • Loading branch information
EvanBacon authored Sep 10, 2020
1 parent 05679a0 commit 1c6236f
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 63 deletions.
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

0 comments on commit 1c6236f

Please sign in to comment.