From 090be77dfdd2723797ad78809ba421015a4873aa Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 14:13:33 +0200 Subject: [PATCH 01/14] Implement spinners for initializing flow --- packages/cli/package.json | 1 + packages/cli/src/cliEntry.js | 2 +- .../commands/init/__tests__/template.test.js | 20 ++-- packages/cli/src/commands/init/banner.js | 47 ++++++++ packages/cli/src/commands/init/init.js | 106 +++++++++++++----- packages/cli/src/commands/init/initCompat.js | 16 +-- packages/cli/src/commands/init/template.js | 13 ++- packages/cli/src/commands/install/install.js | 4 +- .../cli/src/commands/install/uninstall.js | 4 +- .../upgrade/__tests__/upgrade.test.js | 2 +- packages/cli/src/commands/upgrade/upgrade.js | 4 +- packages/cli/src/tools/PackageManager.js | 7 +- .../tools/__tests__/PackageManager-test.js | 30 ++--- packages/cli/src/tools/generator/templates.js | 29 +++-- packages/cli/src/tools/loader.js | 14 +++ packages/cli/src/tools/logger.js | 3 + yarn.lock | 38 ++++++- 17 files changed, 256 insertions(+), 84 deletions(-) create mode 100644 packages/cli/src/commands/init/banner.js create mode 100644 packages/cli/src/tools/loader.js diff --git a/packages/cli/package.json b/packages/cli/package.json index e1a7fcfdf..4a7684e49 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -48,6 +48,7 @@ "node-fetch": "^2.2.0", "node-notifier": "^5.2.1", "opn": "^3.0.2", + "ora": "^3.4.0", "plist": "^3.0.0", "semver": "^5.0.3", "serve-static": "^1.13.1", diff --git a/packages/cli/src/cliEntry.js b/packages/cli/src/cliEntry.js index 0b44c5fe4..1ad996b99 100644 --- a/packages/cli/src/cliEntry.js +++ b/packages/cli/src/cliEntry.js @@ -19,7 +19,7 @@ import init from './commands/init/initCompat'; import assertRequiredOptions from './tools/assertRequiredOptions'; import logger from './tools/logger'; import findPlugins from './tools/findPlugins'; -import {setProjectDir} from './tools/PackageManager'; +import {setProjectDir} from './tools/packageManager'; import pkgJson from '../package.json'; import loadConfig from './tools/config'; diff --git a/packages/cli/src/commands/init/__tests__/template.test.js b/packages/cli/src/commands/init/__tests__/template.test.js index 657a2ca1f..ba97779ce 100644 --- a/packages/cli/src/commands/init/__tests__/template.test.js +++ b/packages/cli/src/commands/init/__tests__/template.test.js @@ -1,7 +1,7 @@ // @flow import path from 'path'; -import ChildProcess from 'child_process'; -import * as PackageManger from '../../../tools/PackageManager'; +import Util from 'util'; +import * as PackageManger from '../../../tools/packageManager'; import { installTemplatePackage, getTemplateConfig, @@ -14,15 +14,17 @@ const TEMPLATE_NAME = 'templateName'; afterEach(() => { jest.restoreAllMocks(); + jest.resetModules(); }); -test('installTemplatePackage', () => { +test('installTemplatePackage', async () => { jest.spyOn(PackageManger, 'install').mockImplementationOnce(() => {}); - installTemplatePackage(TEMPLATE_NAME, true); + await installTemplatePackage(TEMPLATE_NAME, true); expect(PackageManger.install).toHaveBeenCalledWith([TEMPLATE_NAME], { preferYarn: false, + silent: true, }); }); @@ -68,21 +70,23 @@ test('copyTemplate', () => { expect(copyFiles.default).toHaveBeenCalledWith(expect.any(String), CWD); }); -test('executePostInitScript', () => { +test('executePostInitScript', async () => { const RESOLVED_PATH = '/some/path/script.js'; const SCRIPT_PATH = './script.js'; + const mockExec = jest.fn(); + + jest.spyOn(Util, 'promisify').mockImplementationOnce(() => mockExec); jest.spyOn(path, 'resolve').mockImplementationOnce(() => RESOLVED_PATH); - jest.spyOn(ChildProcess, 'execFileSync').mockImplementationOnce(() => {}); - executePostInitScript(TEMPLATE_NAME, SCRIPT_PATH); + await executePostInitScript(TEMPLATE_NAME, SCRIPT_PATH); expect(path.resolve).toHaveBeenCalledWith( 'node_modules', TEMPLATE_NAME, SCRIPT_PATH, ); - expect(ChildProcess.execFileSync).toHaveBeenCalledWith(RESOLVED_PATH, { + expect(mockExec).toHaveBeenCalledWith(RESOLVED_PATH, { stdio: 'inherit', }); }); diff --git a/packages/cli/src/commands/init/banner.js b/packages/cli/src/commands/init/banner.js new file mode 100644 index 000000000..d39a6784f --- /dev/null +++ b/packages/cli/src/commands/init/banner.js @@ -0,0 +1,47 @@ +// @flow +import chalk from 'chalk'; + +const reactLogoArray = [ + ' ', + ' ', + ' ', + ' ###### ###### ', + ' ### #### #### ### ', + ' ## ### ### ## ', + ' ## #### ## ', + ' ## #### ## ', + ' ## ## ## ## ', + ' ## ### ### ## ', + ' ## ######################## ## ', + ' ###### ### ### ###### ', + ' ### ## ## ## ## ### ', + ' ### ## ### #### ### ## ### ', + ' ## #### ######## #### ## ', + ' ## ### ########## ### ## ', + ' ## #### ######## #### ## ', + ' ### ## ### #### ### ## ### ', + ' ### ## ## ## ## ### ', + ' ###### ### ### ###### ', + ' ## ######################## ## ', + ' ## ### ### ## ', + ' ## ## ## ## ', + ' ## #### ## ', + ' ## #### ## ', + ' ## ### ### ## ', + ' ### #### #### ### ', + ' ###### ###### ', + ' ', + ' ', + ' ', + ' ', +]; + +const welcomeMessage = ' Welcome to React Native!'; +const heyThere = + 'Hey there! I am going to initialize a fresh react-native project: '; + +export default `${chalk.blue(reactLogoArray.join('\n'))} +${chalk.yellow.bold(welcomeMessage)} + +${heyThere} +`; diff --git a/packages/cli/src/commands/init/init.js b/packages/cli/src/commands/init/init.js index 7b8486723..836c10df1 100644 --- a/packages/cli/src/commands/init/init.js +++ b/packages/cli/src/commands/init/init.js @@ -14,8 +14,10 @@ import { executePostInitScript, } from './template'; import {changePlaceholderInTemplate} from './editTemplate'; -import * as PackageManager from '../../tools/PackageManager'; +import * as PackageManager from '../../tools/packageManager'; import {processTemplateName} from './templateName'; +import banner from './banner'; +import getLoader from '../../tools/loader'; type Options = {| template?: string, @@ -39,21 +41,45 @@ async function createFromExternalTemplate( templateName: string, npm?: boolean, ) { - logger.info('Initializing new project from external template'); + logger.debug('Initializing new project from external template'); + logger.log(banner); - let {uri, name} = await processTemplateName(templateName); + const Loader = getLoader(); - installTemplatePackage(uri, npm); - name = adjustNameIfUrl(name); - const templateConfig = getTemplateConfig(name); - copyTemplate(name, templateConfig.templateDir); - changePlaceholderInTemplate(projectName, templateConfig.placeholderName); + const loader = new Loader({text: 'Downloading template'}); + loader.start(); - if (templateConfig.postInitScript) { - executePostInitScript(name, templateConfig.postInitScript); - } + try { + let {uri, name} = await processTemplateName(templateName); + + await installTemplatePackage(uri, npm); + loader.succeed(); + loader.start('Copying template'); + + name = adjustNameIfUrl(name); + const templateConfig = getTemplateConfig(name); + copyTemplate(name, templateConfig.templateDir); + + loader.succeed(); + loader.start('Preparing template'); - PackageManager.installAll({preferYarn: !npm}); + changePlaceholderInTemplate(projectName, templateConfig.placeholderName); + + loader.succeed(); + if (templateConfig.postInitScript) { + loader.start('Executing post init script'); + // $FlowFixMe + await executePostInitScript(name, templateConfig.postInitScript); + loader.succeed(); + } + + loader.start('Installing all required dependencies'); + await PackageManager.installAll({preferYarn: !npm, silent: true}); + loader.succeed(); + } catch (e) { + loader.fail(); + throw new Error(e); + } } async function createFromReactNativeTemplate( @@ -61,28 +87,52 @@ async function createFromReactNativeTemplate( version: string, npm?: boolean, ) { - logger.info('Initializing new project'); + logger.debug('Initializing new project'); + logger.log(banner); - if (semver.valid(version) && !semver.satisfies(version, '0.60.0')) { - throw new Error( - 'Cannot use React Native CLI to initialize project with version less than 0.60.0', - ); - } + const Loader = getLoader(); + const loader = new Loader({text: 'Downloading template'}); + loader.start(); - const TEMPLATE_NAME = 'react-native'; + try { + if (semver.valid(version) && !semver.gte(version, '0.60.0')) { + throw new Error( + 'Cannot use React Native CLI to initialize project with version less than 0.60.0', + ); + } - const {uri} = await processTemplateName(`${TEMPLATE_NAME}@${version}`); + const TEMPLATE_NAME = 'react-native'; - installTemplatePackage(uri, npm); - const templateConfig = getTemplateConfig(TEMPLATE_NAME); - copyTemplate(TEMPLATE_NAME, templateConfig.templateDir); - changePlaceholderInTemplate(projectName, templateConfig.placeholderName); + const {uri} = await processTemplateName(`${TEMPLATE_NAME}@${version}`); - if (templateConfig.postInitScript) { - executePostInitScript(TEMPLATE_NAME, templateConfig.postInitScript); - } + await installTemplatePackage(uri, npm); + + loader.succeed(); + loader.start('Copying template'); + + const templateConfig = getTemplateConfig(TEMPLATE_NAME); + copyTemplate(TEMPLATE_NAME, templateConfig.templateDir); - PackageManager.installAll({preferYarn: !npm}); + loader.succeed(); + loader.start('Processing template'); + + changePlaceholderInTemplate(projectName, templateConfig.placeholderName); + + loader.succeed(); + if (templateConfig.postInitScript) { + loader.start('Executing post init script'); + // $FlowFixMe + await executePostInitScript(TEMPLATE_NAME, templateConfig.postInitScript); + loader.succeed(); + } + + loader.start('Installing all required dependencies'); + await PackageManager.installAll({preferYarn: !npm, silent: true}); + loader.succeed(); + } catch (e) { + loader.fail(); + throw new Error(e); + } } function createProject(projectName: string, options: Options, version: string) { diff --git a/packages/cli/src/commands/init/initCompat.js b/packages/cli/src/commands/init/initCompat.js index 4d059f29d..9c18fca00 100644 --- a/packages/cli/src/commands/init/initCompat.js +++ b/packages/cli/src/commands/init/initCompat.js @@ -13,7 +13,7 @@ import path from 'path'; import process from 'process'; import printRunInstructions from './printRunInstructions'; import {createProjectFromTemplate} from '../../tools/generator/templates'; -import * as PackageManager from '../../tools/PackageManager'; +import * as PackageManager from '../../tools/packageManager'; import logger from '../../tools/logger'; /** @@ -25,7 +25,7 @@ import logger from '../../tools/logger'; * @param options Command line options passed from the react-native-cli directly. * E.g. `{ version: '0.43.0', template: 'navigation' }` */ -function initCompat(projectDir, argsOrName) { +async function initCompat(projectDir, argsOrName) { const args = Array.isArray(argsOrName) ? argsOrName // argsOrName was e.g. ['AwesomeApp', '--verbose'] : [argsOrName].concat(process.argv.slice(4)); // argsOrName was e.g. 'AwesomeApp' @@ -40,7 +40,7 @@ function initCompat(projectDir, argsOrName) { const options = minimist(args); logger.info(`Setting up new React Native app in ${projectDir}`); - generateProject(projectDir, newProjectName, options); + await generateProject(projectDir, newProjectName, options); } /** @@ -48,12 +48,12 @@ function initCompat(projectDir, argsOrName) { * @param Absolute path at which the project folder should be created. * @param options Command line arguments parsed by minimist. */ -function generateProject(destinationRoot, newProjectName, options) { +async function generateProject(destinationRoot, newProjectName, options) { const pkgJson = require('react-native/package.json'); const reactVersion = pkgJson.peerDependencies.react; - PackageManager.setProjectDir(destinationRoot); - createProjectFromTemplate( + await PackageManager.setProjectDir(destinationRoot); + await createProjectFromTemplate( destinationRoot, newProjectName, options.template, @@ -61,10 +61,10 @@ function generateProject(destinationRoot, newProjectName, options) { ); logger.info('Adding required dependencies'); - PackageManager.install([`react@${reactVersion}`]); + await PackageManager.install([`react@${reactVersion}`]); logger.info('Adding required dev dependencies'); - PackageManager.installDev([ + await PackageManager.installDev([ '@babel/core', '@babel/runtime', '@react-native-community/eslint-config', diff --git a/packages/cli/src/commands/init/template.js b/packages/cli/src/commands/init/template.js index dd7180ece..be544528d 100644 --- a/packages/cli/src/commands/init/template.js +++ b/packages/cli/src/commands/init/template.js @@ -1,7 +1,9 @@ // @flow -import {execFileSync} from 'child_process'; + +import {execFile} from 'child_process'; +import {promisify} from 'util'; import path from 'path'; -import * as PackageManager from '../../tools/PackageManager'; +import * as PackageManager from '../../tools/packageManager'; import logger from '../../tools/logger'; import copyFiles from '../../tools/copyFiles'; @@ -13,7 +15,10 @@ export type TemplateConfig = { export function installTemplatePackage(templateName: string, npm?: boolean) { logger.debug(`Installing template from ${templateName}`); - PackageManager.install([templateName], {preferYarn: !npm}); + return PackageManager.install([templateName], { + preferYarn: !npm, + silent: true, + }); } export function getTemplateConfig(templateName: string): TemplateConfig { @@ -44,5 +49,5 @@ export function executePostInitScript( logger.debug(`Executing post init script located ${scriptPath}`); - execFileSync(scriptPath, {stdio: 'inherit'}); + return promisify(execFile)(scriptPath, {stdio: 'inherit'}); } diff --git a/packages/cli/src/commands/install/install.js b/packages/cli/src/commands/install/install.js index bd9097a0c..6e9a68e8c 100644 --- a/packages/cli/src/commands/install/install.js +++ b/packages/cli/src/commands/install/install.js @@ -9,14 +9,14 @@ import type {ContextT} from '../../tools/types.flow'; import logger from '../../tools/logger'; -import * as PackageManager from '../../tools/PackageManager'; +import * as PackageManager from '../../tools/packageManager'; import link from '../link/link'; async function install(args: Array, ctx: ContextT) { const name = args[0]; logger.info(`Installing "${name}"...`); - PackageManager.install([name]); + await PackageManager.install([name]); logger.info(`Linking "${name}"...`); await link.func([name], ctx, {platforms: undefined}); diff --git a/packages/cli/src/commands/install/uninstall.js b/packages/cli/src/commands/install/uninstall.js index 654c594a8..d0b70ed8a 100644 --- a/packages/cli/src/commands/install/uninstall.js +++ b/packages/cli/src/commands/install/uninstall.js @@ -9,7 +9,7 @@ import type {ContextT} from '../../tools/types.flow'; import logger from '../../tools/logger'; -import * as PackageManager from '../../tools/PackageManager'; +import * as PackageManager from '../../tools/packageManager'; import link from '../link/unlink'; async function uninstall(args: Array, ctx: ContextT) { @@ -19,7 +19,7 @@ async function uninstall(args: Array, ctx: ContextT) { await link.func([name], ctx); logger.info(`Uninstalling "${name}"...`); - PackageManager.uninstall([name]); + await PackageManager.uninstall([name]); logger.success(`Successfully uninstalled and unlinked "${name}"`); } diff --git a/packages/cli/src/commands/upgrade/__tests__/upgrade.test.js b/packages/cli/src/commands/upgrade/__tests__/upgrade.test.js index 5b550fdf2..f13a0728c 100644 --- a/packages/cli/src/commands/upgrade/__tests__/upgrade.test.js +++ b/packages/cli/src/commands/upgrade/__tests__/upgrade.test.js @@ -33,7 +33,7 @@ jest.mock( () => ({name: 'TestApp', dependencies: {'react-native': '^0.57.8'}}), {virtual: true}, ); -jest.mock('../../../tools/PackageManager', () => ({ +jest.mock('../../../tools/packageManager', () => ({ install: args => { mockPushLog('$ yarn add', ...args); }, diff --git a/packages/cli/src/commands/upgrade/upgrade.js b/packages/cli/src/commands/upgrade/upgrade.js index 148cfd99b..514041a12 100644 --- a/packages/cli/src/commands/upgrade/upgrade.js +++ b/packages/cli/src/commands/upgrade/upgrade.js @@ -6,7 +6,7 @@ import semver from 'semver'; import execa from 'execa'; import type {ContextT} from '../../tools/types.flow'; import logger from '../../tools/logger'; -import * as PackageManager from '../../tools/PackageManager'; +import * as PackageManager from '../../tools/packageManager'; import {fetch} from '../../tools/fetch'; import legacyUpgrade from './legacyUpgrade'; @@ -115,7 +115,7 @@ const installDeps = async (newVersion, projectDir) => { `react-native@${newVersion}`, ...Object.keys(peerDeps).map(module => `${module}@${peerDeps[module]}`), ]; - PackageManager.install(deps, { + await PackageManager.install(deps, { silent: true, }); await execa('git', ['add', 'package.json']); diff --git a/packages/cli/src/tools/PackageManager.js b/packages/cli/src/tools/PackageManager.js index fa4b999f1..18d0c210e 100644 --- a/packages/cli/src/tools/PackageManager.js +++ b/packages/cli/src/tools/PackageManager.js @@ -1,5 +1,6 @@ // @flow -import {execSync} from 'child_process'; +import {exec} from 'child_process'; +import {promisify} from 'util'; import {getYarnVersionIfAvailable, isProjectUsingYarn} from './yarn'; type Options = {| @@ -7,10 +8,12 @@ type Options = {| silent?: boolean, |}; +const execute = promisify(exec); + let projectDir; function executeCommand(command: string, options?: Options) { - return execSync(command, { + return execute(command, { stdio: options && options.silent ? 'pipe' : 'inherit', }); } diff --git a/packages/cli/src/tools/__tests__/PackageManager-test.js b/packages/cli/src/tools/__tests__/PackageManager-test.js index 33c4ce210..2066a8cc3 100644 --- a/packages/cli/src/tools/__tests__/PackageManager-test.js +++ b/packages/cli/src/tools/__tests__/PackageManager-test.js @@ -1,17 +1,21 @@ // @flow -import ChildProcess from 'child_process'; -import * as PackageManager from '../PackageManager'; +import Util from 'util'; import * as yarn from '../yarn'; const PACKAGES = ['react', 'react-native']; const EXEC_OPTS = {stdio: 'inherit'}; const PROJECT_ROOT = '/some/dir'; +let PackageManager; +const executeMock = jest.fn(); + beforeEach(() => { - jest.spyOn(ChildProcess, 'execSync').mockImplementation(() => {}); + jest.spyOn(Util, 'promisify').mockImplementation(() => executeMock); + PackageManager = require('../packageManager'); }); afterEach(() => { - (ChildProcess.execSync: any).mockRestore(); + (Util.promisify: any).mockRestore(); + executeMock.mockRestore(); }); describe('yarn', () => { @@ -24,7 +28,7 @@ describe('yarn', () => { it('should install', () => { PackageManager.install(PACKAGES, {preferYarn: true}); - expect(ChildProcess.execSync).toHaveBeenCalledWith( + expect(executeMock).toHaveBeenCalledWith( 'yarn add react react-native', EXEC_OPTS, ); @@ -33,7 +37,7 @@ describe('yarn', () => { it('should installDev', () => { PackageManager.installDev(PACKAGES, {preferYarn: true}); - expect(ChildProcess.execSync).toHaveBeenCalledWith( + expect(executeMock).toHaveBeenCalledWith( 'yarn add -D react react-native', EXEC_OPTS, ); @@ -42,7 +46,7 @@ describe('yarn', () => { it('should uninstall', () => { PackageManager.uninstall(PACKAGES, {preferYarn: true}); - expect(ChildProcess.execSync).toHaveBeenCalledWith( + expect(executeMock).toHaveBeenCalledWith( 'yarn remove react react-native', EXEC_OPTS, ); @@ -53,7 +57,7 @@ describe('npm', () => { it('should install', () => { PackageManager.install(PACKAGES, {preferYarn: false}); - expect(ChildProcess.execSync).toHaveBeenCalledWith( + expect(executeMock).toHaveBeenCalledWith( 'npm install react react-native --save --save-exact', EXEC_OPTS, ); @@ -62,7 +66,7 @@ describe('npm', () => { it('should installDev', () => { PackageManager.installDev(PACKAGES, {preferYarn: false}); - expect(ChildProcess.execSync).toHaveBeenCalledWith( + expect(executeMock).toHaveBeenCalledWith( 'npm install react react-native --save-dev --save-exact', EXEC_OPTS, ); @@ -71,7 +75,7 @@ describe('npm', () => { it('should uninstall', () => { PackageManager.uninstall(PACKAGES, {preferYarn: false}); - expect(ChildProcess.execSync).toHaveBeenCalledWith( + expect(executeMock).toHaveBeenCalledWith( 'npm uninstall react react-native --save', EXEC_OPTS, ); @@ -82,7 +86,7 @@ it('should use npm if yarn is not available', () => { jest.spyOn(yarn, 'getYarnVersionIfAvailable').mockImplementation(() => false); PackageManager.install(PACKAGES, {preferYarn: true}); - expect(ChildProcess.execSync).toHaveBeenCalledWith( + expect(executeMock).toHaveBeenCalledWith( 'npm install react react-native --save --save-exact', EXEC_OPTS, ); @@ -94,7 +98,7 @@ it('should use npm if project is not using yarn', () => { PackageManager.setProjectDir(PROJECT_ROOT); PackageManager.install(PACKAGES); - expect(ChildProcess.execSync).toHaveBeenCalledWith( + expect(executeMock).toHaveBeenCalledWith( 'npm install react react-native --save --save-exact', EXEC_OPTS, ); @@ -108,7 +112,7 @@ it('should use yarn if project is using yarn', () => { PackageManager.setProjectDir(PROJECT_ROOT); PackageManager.install(PACKAGES); - expect(ChildProcess.execSync).toHaveBeenCalledWith( + expect(executeMock).toHaveBeenCalledWith( 'yarn add react react-native', EXEC_OPTS, ); diff --git a/packages/cli/src/tools/generator/templates.js b/packages/cli/src/tools/generator/templates.js index b771efa72..460cdead2 100644 --- a/packages/cli/src/tools/generator/templates.js +++ b/packages/cli/src/tools/generator/templates.js @@ -13,7 +13,7 @@ import fs from 'fs'; import path from 'path'; import copyProjectTemplateAndReplace from './copyProjectTemplateAndReplace'; import logger from '../logger'; -import * as PackageManager from '../PackageManager'; +import * as PackageManager from '../packageManager'; /** * @param destPath Create the new project at this path. @@ -22,7 +22,7 @@ import * as PackageManager from '../PackageManager'; * @param yarnVersion Version of yarn available on the system, or null if * yarn is not available. For example '0.18.1'. */ -function createProjectFromTemplate( +async function createProjectFromTemplate( destPath: string, newProjectName: string, template: string, @@ -44,7 +44,12 @@ function createProjectFromTemplate( // This way we don't have to duplicate the native files in every template. // If we duplicated them we'd make RN larger and risk that people would // forget to maintain all the copies so they would go out of sync. - createFromRemoteTemplate(template, destPath, newProjectName, destinationRoot); + await createFromRemoteTemplate( + template, + destPath, + newProjectName, + destinationRoot, + ); } /** @@ -52,7 +57,7 @@ function createProjectFromTemplate( * - 'demo' -> Fetch the package react-native-template-demo from npm * - git://..., http://..., file://... or any other URL supported by npm */ -function createFromRemoteTemplate( +async function createFromRemoteTemplate( template: string, destPath: string, newProjectName: string, @@ -73,7 +78,7 @@ function createFromRemoteTemplate( // Check if the template exists logger.info(`Fetching template ${installPackage}...`); try { - PackageManager.install([installPackage]); + await PackageManager.install([installPackage]); const templatePath = path.resolve('node_modules', templateName); copyProjectTemplateAndReplace(templatePath, destPath, newProjectName, { // Every template contains a dummy package.json file included @@ -86,12 +91,12 @@ function createFromRemoteTemplate( 'devDependencies.json', ], }); - installTemplateDependencies(templatePath, destinationRoot); - installTemplateDevDependencies(templatePath, destinationRoot); + await installTemplateDependencies(templatePath, destinationRoot); + await installTemplateDevDependencies(templatePath, destinationRoot); } finally { // Clean up the temp files try { - PackageManager.uninstall([templateName]); + await PackageManager.uninstall([templateName]); } catch (err) { // Not critical but we still want people to know and report // if this the clean up fails. @@ -103,7 +108,7 @@ function createFromRemoteTemplate( } } -function installTemplateDependencies(templatePath, destinationRoot) { +async function installTemplateDependencies(templatePath, destinationRoot) { // dependencies.json is a special file that lists additional dependencies // that are required by this template const dependenciesJsonPath = path.resolve(templatePath, 'dependencies.json'); @@ -124,12 +129,12 @@ function installTemplateDependencies(templatePath, destinationRoot) { const dependenciesToInstall = Object.keys(dependencies).map( depName => `${depName}@${dependencies[depName]}`, ); - PackageManager.install(dependenciesToInstall); + await PackageManager.install(dependenciesToInstall); logger.info("Linking native dependencies into the project's build files..."); execSync('react-native link', {stdio: 'inherit'}); } -function installTemplateDevDependencies(templatePath, destinationRoot) { +async function installTemplateDevDependencies(templatePath, destinationRoot) { // devDependencies.json is a special file that lists additional develop dependencies // that are required by this template const devDependenciesJsonPath = path.resolve( @@ -154,7 +159,7 @@ function installTemplateDevDependencies(templatePath, destinationRoot) { const dependenciesToInstall = Object.keys(dependencies).map( depName => `${depName}@${dependencies[depName]}`, ); - PackageManager.installDev(dependenciesToInstall); + await PackageManager.installDev(dependenciesToInstall); } export {createProjectFromTemplate}; diff --git a/packages/cli/src/tools/loader.js b/packages/cli/src/tools/loader.js new file mode 100644 index 000000000..8310c1d54 --- /dev/null +++ b/packages/cli/src/tools/loader.js @@ -0,0 +1,14 @@ +// @flow +import Ora from 'ora'; +import logger from './logger'; + +class OraMock { + succeed() {} + start() {} +} + +function getLoader(): typeof Ora { + return logger.isVerbose() ? OraMock : Ora; +} + +export default getLoader; diff --git a/packages/cli/src/tools/logger.js b/packages/cli/src/tools/logger.js index 42e6e262f..8bb9cbddd 100644 --- a/packages/cli/src/tools/logger.js +++ b/packages/cli/src/tools/logger.js @@ -40,6 +40,8 @@ const setVerbose = (level: boolean) => { verbose = level; }; +const isVerbose = () => verbose; + export default { success, info, @@ -48,4 +50,5 @@ export default { debug, log, setVerbose, + isVerbose, }; diff --git a/yarn.lock b/yarn.lock index 745260fc3..61eb22770 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2042,6 +2042,11 @@ ansi-regex@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" +ansi-regex@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" + integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -2703,6 +2708,11 @@ cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" +cli-spinners@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.0.0.tgz#4b078756fc17a8f72043fdc9f1f14bf4fa87e2df" + integrity sha512-yiEBmhaKPPeBj7wWm4GEdtPZK940p9pl3EANIrnJ3JnvWyrPjcFcsEq6qRUuQ7fzB0+Y82ld3p6B34xo95foWw== + cli-width@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" @@ -5779,6 +5789,13 @@ lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5 version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" +log-symbols@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== + dependencies: + chalk "^2.0.1" + loose-envify@^1.0.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -6719,6 +6736,18 @@ options@>=0.0.5: version "0.0.6" resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" +ora@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" + integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== + dependencies: + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-spinners "^2.0.0" + log-symbols "^2.2.0" + strip-ansi "^5.2.0" + wcwidth "^1.0.1" + os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -8163,6 +8192,13 @@ strip-ansi@^5.0.0: dependencies: ansi-regex "^4.0.0" +strip-ansi@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" + integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== + dependencies: + ansi-regex "^4.1.0" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -8669,7 +8705,7 @@ watch@~0.18.0: exec-sh "^0.2.0" minimist "^1.2.0" -wcwidth@^1.0.0: +wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= From 7bd4bababc93c6e6968baad5903f07a85073895f Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 15:52:58 +0200 Subject: [PATCH 02/14] Use execa module --- .../commands/init/__tests__/template.test.js | 9 +-- packages/cli/src/commands/init/template.js | 5 +- packages/cli/src/tools/PackageManager.js | 31 +++++----- .../tools/__tests__/PackageManager-test.js | 59 ++++++++++--------- 4 files changed, 53 insertions(+), 51 deletions(-) diff --git a/packages/cli/src/commands/init/__tests__/template.test.js b/packages/cli/src/commands/init/__tests__/template.test.js index ba97779ce..ccef26dfb 100644 --- a/packages/cli/src/commands/init/__tests__/template.test.js +++ b/packages/cli/src/commands/init/__tests__/template.test.js @@ -1,6 +1,7 @@ // @flow +jest.mock('execa', () => jest.fn()); +import execa from 'execa'; import path from 'path'; -import Util from 'util'; import * as PackageManger from '../../../tools/packageManager'; import { installTemplatePackage, @@ -14,7 +15,6 @@ const TEMPLATE_NAME = 'templateName'; afterEach(() => { jest.restoreAllMocks(); - jest.resetModules(); }); test('installTemplatePackage', async () => { @@ -74,9 +74,6 @@ test('executePostInitScript', async () => { const RESOLVED_PATH = '/some/path/script.js'; const SCRIPT_PATH = './script.js'; - const mockExec = jest.fn(); - - jest.spyOn(Util, 'promisify').mockImplementationOnce(() => mockExec); jest.spyOn(path, 'resolve').mockImplementationOnce(() => RESOLVED_PATH); await executePostInitScript(TEMPLATE_NAME, SCRIPT_PATH); @@ -86,7 +83,7 @@ test('executePostInitScript', async () => { TEMPLATE_NAME, SCRIPT_PATH, ); - expect(mockExec).toHaveBeenCalledWith(RESOLVED_PATH, { + expect(execa).toHaveBeenCalledWith(RESOLVED_PATH, { stdio: 'inherit', }); }); diff --git a/packages/cli/src/commands/init/template.js b/packages/cli/src/commands/init/template.js index be544528d..ef5066c55 100644 --- a/packages/cli/src/commands/init/template.js +++ b/packages/cli/src/commands/init/template.js @@ -1,7 +1,6 @@ // @flow -import {execFile} from 'child_process'; -import {promisify} from 'util'; +import execa from 'execa'; import path from 'path'; import * as PackageManager from '../../tools/packageManager'; import logger from '../../tools/logger'; @@ -49,5 +48,5 @@ export function executePostInitScript( logger.debug(`Executing post init script located ${scriptPath}`); - return promisify(execFile)(scriptPath, {stdio: 'inherit'}); + return execa(scriptPath, {stdio: 'inherit'}); } diff --git a/packages/cli/src/tools/PackageManager.js b/packages/cli/src/tools/PackageManager.js index 18d0c210e..13c1384a4 100644 --- a/packages/cli/src/tools/PackageManager.js +++ b/packages/cli/src/tools/PackageManager.js @@ -1,6 +1,5 @@ // @flow -import {exec} from 'child_process'; -import {promisify} from 'util'; +import execa from 'execa'; import {getYarnVersionIfAvailable, isProjectUsingYarn} from './yarn'; type Options = {| @@ -8,12 +7,14 @@ type Options = {| silent?: boolean, |}; -const execute = promisify(exec); - let projectDir; -function executeCommand(command: string, options?: Options) { - return execute(command, { +function executeCommand( + command: string, + args: Array, + options?: Options, +) { + return execa(command, args, { stdio: options && options.silent ? 'pipe' : 'inherit', }); } @@ -32,30 +33,32 @@ export function setProjectDir(dir: string) { export function install(packageNames: Array, options?: Options) { return shouldUseYarn(options) - ? executeCommand(`yarn add ${packageNames.join(' ')}`, options) + ? executeCommand('yarn', ['add', ...packageNames], options) : executeCommand( - `npm install ${packageNames.join(' ')} --save --save-exact`, + 'npm', + ['install', ...packageNames, '--save', '--save-exact'], options, ); } export function installDev(packageNames: Array, options?: Options) { return shouldUseYarn(options) - ? executeCommand(`yarn add -D ${packageNames.join(' ')}`, options) + ? executeCommand('yarn', ['add', '-D', ...packageNames], options) : executeCommand( - `npm install ${packageNames.join(' ')} --save-dev --save-exact`, + 'npm', + ['install', ...packageNames, '--save-dev', '--save-exact'], options, ); } export function uninstall(packageNames: Array, options?: Options) { return shouldUseYarn(options) - ? executeCommand(`yarn remove ${packageNames.join(' ')}`, options) - : executeCommand(`npm uninstall ${packageNames.join(' ')} --save`, options); + ? executeCommand('yarn', ['remove', ...packageNames], options) + : executeCommand('npm', ['uninstall', ...packageNames, '--save'], options); } export function installAll(options?: Options) { return shouldUseYarn(options) - ? executeCommand('yarn install') - : executeCommand('npm install'); + ? executeCommand('yarn', ['install'], options) + : executeCommand('npm', ['install'], options); } diff --git a/packages/cli/src/tools/__tests__/PackageManager-test.js b/packages/cli/src/tools/__tests__/PackageManager-test.js index 2066a8cc3..db9b24db9 100644 --- a/packages/cli/src/tools/__tests__/PackageManager-test.js +++ b/packages/cli/src/tools/__tests__/PackageManager-test.js @@ -1,21 +1,15 @@ // @flow -import Util from 'util'; +jest.mock('execa', () => jest.fn()); +import execa from 'execa'; import * as yarn from '../yarn'; +import * as PackageManager from '../packageManager'; const PACKAGES = ['react', 'react-native']; const EXEC_OPTS = {stdio: 'inherit'}; const PROJECT_ROOT = '/some/dir'; -let PackageManager; -const executeMock = jest.fn(); - -beforeEach(() => { - jest.spyOn(Util, 'promisify').mockImplementation(() => executeMock); - PackageManager = require('../packageManager'); -}); afterEach(() => { - (Util.promisify: any).mockRestore(); - executeMock.mockRestore(); + jest.resetAllMocks(); }); describe('yarn', () => { @@ -28,8 +22,9 @@ describe('yarn', () => { it('should install', () => { PackageManager.install(PACKAGES, {preferYarn: true}); - expect(executeMock).toHaveBeenCalledWith( - 'yarn add react react-native', + expect(execa).toHaveBeenCalledWith( + 'yarn', + ['add', 'react', 'react-native'], EXEC_OPTS, ); }); @@ -37,8 +32,9 @@ describe('yarn', () => { it('should installDev', () => { PackageManager.installDev(PACKAGES, {preferYarn: true}); - expect(executeMock).toHaveBeenCalledWith( - 'yarn add -D react react-native', + expect(execa).toHaveBeenCalledWith( + 'yarn', + ['add', '-D', 'react', 'react-native'], EXEC_OPTS, ); }); @@ -46,8 +42,9 @@ describe('yarn', () => { it('should uninstall', () => { PackageManager.uninstall(PACKAGES, {preferYarn: true}); - expect(executeMock).toHaveBeenCalledWith( - 'yarn remove react react-native', + expect(execa).toHaveBeenCalledWith( + 'yarn', + ['remove', 'react', 'react-native'], EXEC_OPTS, ); }); @@ -57,8 +54,9 @@ describe('npm', () => { it('should install', () => { PackageManager.install(PACKAGES, {preferYarn: false}); - expect(executeMock).toHaveBeenCalledWith( - 'npm install react react-native --save --save-exact', + expect(execa).toHaveBeenCalledWith( + 'npm', + ['install', 'react', 'react-native', '--save', '--save-exact'], EXEC_OPTS, ); }); @@ -66,8 +64,9 @@ describe('npm', () => { it('should installDev', () => { PackageManager.installDev(PACKAGES, {preferYarn: false}); - expect(executeMock).toHaveBeenCalledWith( - 'npm install react react-native --save-dev --save-exact', + expect(execa).toHaveBeenCalledWith( + 'npm', + ['install', 'react', 'react-native', '--save-dev', '--save-exact'], EXEC_OPTS, ); }); @@ -75,8 +74,9 @@ describe('npm', () => { it('should uninstall', () => { PackageManager.uninstall(PACKAGES, {preferYarn: false}); - expect(executeMock).toHaveBeenCalledWith( - 'npm uninstall react react-native --save', + expect(execa).toHaveBeenCalledWith( + 'npm', + ['uninstall', 'react', 'react-native', '--save'], EXEC_OPTS, ); }); @@ -86,8 +86,9 @@ it('should use npm if yarn is not available', () => { jest.spyOn(yarn, 'getYarnVersionIfAvailable').mockImplementation(() => false); PackageManager.install(PACKAGES, {preferYarn: true}); - expect(executeMock).toHaveBeenCalledWith( - 'npm install react react-native --save --save-exact', + expect(execa).toHaveBeenCalledWith( + 'npm', + ['install', 'react', 'react-native', '--save', '--save-exact'], EXEC_OPTS, ); }); @@ -98,8 +99,9 @@ it('should use npm if project is not using yarn', () => { PackageManager.setProjectDir(PROJECT_ROOT); PackageManager.install(PACKAGES); - expect(executeMock).toHaveBeenCalledWith( - 'npm install react react-native --save --save-exact', + expect(execa).toHaveBeenCalledWith( + 'npm', + ['install', 'react', 'react-native', '--save', '--save-exact'], EXEC_OPTS, ); expect(yarn.isProjectUsingYarn).toHaveBeenCalledWith(PROJECT_ROOT); @@ -112,8 +114,9 @@ it('should use yarn if project is using yarn', () => { PackageManager.setProjectDir(PROJECT_ROOT); PackageManager.install(PACKAGES); - expect(executeMock).toHaveBeenCalledWith( - 'yarn add react react-native', + expect(execa).toHaveBeenCalledWith( + 'yarn', + ['add', 'react', 'react-native'], EXEC_OPTS, ); expect(yarn.isProjectUsingYarn).toHaveBeenCalledWith(PROJECT_ROOT); From 8c6895e61ad8205209ca9582183a979c6078148b Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 16:20:37 +0200 Subject: [PATCH 03/14] Change banner --- packages/cli/src/commands/init/banner.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/commands/init/banner.js b/packages/cli/src/commands/init/banner.js index d39a6784f..f584bb07f 100644 --- a/packages/cli/src/commands/init/banner.js +++ b/packages/cli/src/commands/init/banner.js @@ -36,12 +36,13 @@ const reactLogoArray = [ ' ', ]; -const welcomeMessage = ' Welcome to React Native!'; -const heyThere = - 'Hey there! I am going to initialize a fresh react-native project: '; +const welcomeMessage = + ' Welcome to React Native! '; +const learnOnceMessage = + ' Learn Once Write Anywhere '; export default `${chalk.blue(reactLogoArray.join('\n'))} -${chalk.yellow.bold(welcomeMessage)} -${heyThere} +${chalk.yellow.bold(welcomeMessage)} +${chalk.gray(learnOnceMessage)} `; From 87ff7fbe2c0c7ecc44ab40caf55ab3d2d9acae73 Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 16:50:10 +0200 Subject: [PATCH 04/14] Ignore silent flag when in verbose mode --- packages/cli/src/tools/PackageManager.js | 4 +- .../tools/__tests__/PackageManager-test.js | 53 +++++++++++++------ packages/cli/src/tools/loader.js | 1 + 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/tools/PackageManager.js b/packages/cli/src/tools/PackageManager.js index 13c1384a4..941a218c7 100644 --- a/packages/cli/src/tools/PackageManager.js +++ b/packages/cli/src/tools/PackageManager.js @@ -1,5 +1,6 @@ // @flow import execa from 'execa'; +import logger from './logger'; import {getYarnVersionIfAvailable, isProjectUsingYarn} from './yarn'; type Options = {| @@ -15,7 +16,8 @@ function executeCommand( options?: Options, ) { return execa(command, args, { - stdio: options && options.silent ? 'pipe' : 'inherit', + stdio: + options && options.silent && !logger.isVerbose() ? 'pipe' : 'inherit', }); } diff --git a/packages/cli/src/tools/__tests__/PackageManager-test.js b/packages/cli/src/tools/__tests__/PackageManager-test.js index db9b24db9..1e597197d 100644 --- a/packages/cli/src/tools/__tests__/PackageManager-test.js +++ b/packages/cli/src/tools/__tests__/PackageManager-test.js @@ -2,6 +2,7 @@ jest.mock('execa', () => jest.fn()); import execa from 'execa'; import * as yarn from '../yarn'; +import logger from '../logger'; import * as PackageManager from '../packageManager'; const PACKAGES = ['react', 'react-native']; @@ -17,16 +18,14 @@ describe('yarn', () => { jest .spyOn(yarn, 'getYarnVersionIfAvailable') .mockImplementation(() => true); + + jest.spyOn(logger, 'isVerbose').mockImplementation(() => false); }); it('should install', () => { PackageManager.install(PACKAGES, {preferYarn: true}); - expect(execa).toHaveBeenCalledWith( - 'yarn', - ['add', 'react', 'react-native'], - EXEC_OPTS, - ); + expect(execa).toHaveBeenCalledWith('yarn', ['add', ...PACKAGES], EXEC_OPTS); }); it('should installDev', () => { @@ -34,7 +33,7 @@ describe('yarn', () => { expect(execa).toHaveBeenCalledWith( 'yarn', - ['add', '-D', 'react', 'react-native'], + ['add', '-D', ...PACKAGES], EXEC_OPTS, ); }); @@ -44,7 +43,7 @@ describe('yarn', () => { expect(execa).toHaveBeenCalledWith( 'yarn', - ['remove', 'react', 'react-native'], + ['remove', ...PACKAGES], EXEC_OPTS, ); }); @@ -56,7 +55,7 @@ describe('npm', () => { expect(execa).toHaveBeenCalledWith( 'npm', - ['install', 'react', 'react-native', '--save', '--save-exact'], + ['install', ...PACKAGES, '--save', '--save-exact'], EXEC_OPTS, ); }); @@ -66,7 +65,7 @@ describe('npm', () => { expect(execa).toHaveBeenCalledWith( 'npm', - ['install', 'react', 'react-native', '--save-dev', '--save-exact'], + ['install', ...PACKAGES, '--save-dev', '--save-exact'], EXEC_OPTS, ); }); @@ -76,7 +75,7 @@ describe('npm', () => { expect(execa).toHaveBeenCalledWith( 'npm', - ['uninstall', 'react', 'react-native', '--save'], + ['uninstall', ...PACKAGES, '--save'], EXEC_OPTS, ); }); @@ -88,7 +87,7 @@ it('should use npm if yarn is not available', () => { expect(execa).toHaveBeenCalledWith( 'npm', - ['install', 'react', 'react-native', '--save', '--save-exact'], + ['install', ...PACKAGES, '--save', '--save-exact'], EXEC_OPTS, ); }); @@ -101,7 +100,7 @@ it('should use npm if project is not using yarn', () => { expect(execa).toHaveBeenCalledWith( 'npm', - ['install', 'react', 'react-native', '--save', '--save-exact'], + ['install', ...PACKAGES, '--save', '--save-exact'], EXEC_OPTS, ); expect(yarn.isProjectUsingYarn).toHaveBeenCalledWith(PROJECT_ROOT); @@ -114,10 +113,30 @@ it('should use yarn if project is using yarn', () => { PackageManager.setProjectDir(PROJECT_ROOT); PackageManager.install(PACKAGES); - expect(execa).toHaveBeenCalledWith( - 'yarn', - ['add', 'react', 'react-native'], - EXEC_OPTS, - ); + expect(execa).toHaveBeenCalledWith('yarn', ['add', ...PACKAGES], EXEC_OPTS); expect(yarn.isProjectUsingYarn).toHaveBeenCalledWith(PROJECT_ROOT); }); + +it('should install in silent mode', () => { + jest.spyOn(yarn, 'getYarnVersionIfAvailable').mockImplementation(() => true); + jest.spyOn(yarn, 'isProjectUsingYarn').mockImplementation(() => true); + jest.spyOn(logger, 'isVerbose').mockImplementation(() => false); + + PackageManager.install(PACKAGES, {silent: true}); + + expect(execa).toHaveBeenCalledWith('yarn', ['add', ...PACKAGES], { + stdio: 'pipe', + }); +}); + +it('should ignore silent when in verbose mode', () => { + jest.spyOn(yarn, 'getYarnVersionIfAvailable').mockImplementation(() => true); + jest.spyOn(yarn, 'isProjectUsingYarn').mockImplementation(() => true); + jest.spyOn(logger, 'isVerbose').mockImplementation(() => true); + + PackageManager.install(PACKAGES, {silent: true}); + + expect(execa).toHaveBeenCalledWith('yarn', ['add', ...PACKAGES], { + stdio: 'inherit', + }); +}); diff --git a/packages/cli/src/tools/loader.js b/packages/cli/src/tools/loader.js index 8310c1d54..c0f5031a4 100644 --- a/packages/cli/src/tools/loader.js +++ b/packages/cli/src/tools/loader.js @@ -4,6 +4,7 @@ import logger from './logger'; class OraMock { succeed() {} + fail() {} start() {} } From 333b371c748449094b3e4ffdbb5198633c98ca9f Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 17:04:01 +0200 Subject: [PATCH 05/14] Refactor packageManager test --- .../tools/__tests__/PackageManager-test.js | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/packages/cli/src/tools/__tests__/PackageManager-test.js b/packages/cli/src/tools/__tests__/PackageManager-test.js index 1e597197d..647daea21 100644 --- a/packages/cli/src/tools/__tests__/PackageManager-test.js +++ b/packages/cli/src/tools/__tests__/PackageManager-test.js @@ -117,26 +117,19 @@ it('should use yarn if project is using yarn', () => { expect(yarn.isProjectUsingYarn).toHaveBeenCalledWith(PROJECT_ROOT); }); -it('should install in silent mode', () => { - jest.spyOn(yarn, 'getYarnVersionIfAvailable').mockImplementation(() => true); - jest.spyOn(yarn, 'isProjectUsingYarn').mockImplementation(() => true); - jest.spyOn(logger, 'isVerbose').mockImplementation(() => false); - - PackageManager.install(PACKAGES, {silent: true}); - - expect(execa).toHaveBeenCalledWith('yarn', ['add', ...PACKAGES], { - stdio: 'pipe', - }); -}); - -it('should ignore silent when in verbose mode', () => { - jest.spyOn(yarn, 'getYarnVersionIfAvailable').mockImplementation(() => true); - jest.spyOn(yarn, 'isProjectUsingYarn').mockImplementation(() => true); - jest.spyOn(logger, 'isVerbose').mockImplementation(() => true); +test.each([[false, 'pipe'], [true, 'inherit']])( + 'when verbose is set to %s should use "%s" stdio', + (isVerbose: boolean, stdioType: string) => { + jest + .spyOn(yarn, 'getYarnVersionIfAvailable') + .mockImplementation(() => true); + jest.spyOn(yarn, 'isProjectUsingYarn').mockImplementation(() => true); + jest.spyOn(logger, 'isVerbose').mockImplementation(() => isVerbose); - PackageManager.install(PACKAGES, {silent: true}); + PackageManager.install(PACKAGES, {silent: true}); - expect(execa).toHaveBeenCalledWith('yarn', ['add', ...PACKAGES], { - stdio: 'inherit', - }); -}); + expect(execa).toHaveBeenCalledWith('yarn', ['add', ...PACKAGES], { + stdio: stdioType, + }); + }, +); From 132448456f43d0492d520d83ae40a1e7bfca7d28 Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 17:09:54 +0200 Subject: [PATCH 06/14] Change loader exports --- packages/cli/src/commands/init/init.js | 2 +- packages/cli/src/tools/loader.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/commands/init/init.js b/packages/cli/src/commands/init/init.js index 836c10df1..08240eef0 100644 --- a/packages/cli/src/commands/init/init.js +++ b/packages/cli/src/commands/init/init.js @@ -17,7 +17,7 @@ import {changePlaceholderInTemplate} from './editTemplate'; import * as PackageManager from '../../tools/packageManager'; import {processTemplateName} from './templateName'; import banner from './banner'; -import getLoader from '../../tools/loader'; +import {getLoader} from '../../tools/loader'; type Options = {| template?: string, diff --git a/packages/cli/src/tools/loader.js b/packages/cli/src/tools/loader.js index c0f5031a4..525cc5c9d 100644 --- a/packages/cli/src/tools/loader.js +++ b/packages/cli/src/tools/loader.js @@ -12,4 +12,4 @@ function getLoader(): typeof Ora { return logger.isVerbose() ? OraMock : Ora; } -export default getLoader; +export {getLoader}; From ce3d2e31f2555ea116cbff713b6fbf5cd8aca50b Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 17:15:17 +0200 Subject: [PATCH 07/14] Fix e2e test --- e2e/__tests__/init.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/__tests__/init.test.js b/e2e/__tests__/init.test.js index 08cd0f780..d8760d17b 100644 --- a/e2e/__tests__/init.test.js +++ b/e2e/__tests__/init.test.js @@ -50,7 +50,7 @@ test('init --template', () => { 'TestInit', ]); - expect(stdout).toContain('Initializing new project from external template'); + expect(stdout).toContain('Welcome to React Native!'); expect(stdout).toContain('Run instructions'); // make sure we don't leave garbage From 45cf4224e2a6c2ea9913916af82643bd6deeef3f Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 17:23:03 +0200 Subject: [PATCH 08/14] Rename packageManager --- packages/cli/src/tools/{PackageManager.js => packageManager.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/cli/src/tools/{PackageManager.js => packageManager.js} (100%) diff --git a/packages/cli/src/tools/PackageManager.js b/packages/cli/src/tools/packageManager.js similarity index 100% rename from packages/cli/src/tools/PackageManager.js rename to packages/cli/src/tools/packageManager.js From 47679a00416d2d4dce7057c6405f677e5e1175d8 Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 18:47:21 +0200 Subject: [PATCH 09/14] Add e2e test --- e2e/__tests__/init.test.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/e2e/__tests__/init.test.js b/e2e/__tests__/init.test.js index d8760d17b..7780fef39 100644 --- a/e2e/__tests__/init.test.js +++ b/e2e/__tests__/init.test.js @@ -84,3 +84,17 @@ test('init --template file:/tmp/custom/template', () => { expect(stdout).toContain('Run instructions'); }); + +test('init --verbose', () => { + const {stdout} = run(DIR, [ + 'init', + '--template', + 'react-native-new-template', + 'TestInit', + '--verbose', + ]); + + expect(stdout).toContain('Initializing new project'); + expect(stdout).toContain('No lockfile found'); + expect(stdout).toContain('Run instructions'); +}); From 5a587d556f7b739e4b5d7d3e4d3b2ac43f25a121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 5 Apr 2019 20:02:51 +0200 Subject: [PATCH 10/14] smaller paddings for React logo --- packages/cli/src/commands/init/banner.js | 63 +++++++++++------------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/packages/cli/src/commands/init/banner.js b/packages/cli/src/commands/init/banner.js index f584bb07f..bf865302d 100644 --- a/packages/cli/src/commands/init/banner.js +++ b/packages/cli/src/commands/init/banner.js @@ -2,44 +2,39 @@ import chalk from 'chalk'; const reactLogoArray = [ - ' ', - ' ', - ' ', - ' ###### ###### ', - ' ### #### #### ### ', - ' ## ### ### ## ', - ' ## #### ## ', - ' ## #### ## ', - ' ## ## ## ## ', - ' ## ### ### ## ', - ' ## ######################## ## ', - ' ###### ### ### ###### ', - ' ### ## ## ## ## ### ', - ' ### ## ### #### ### ## ### ', - ' ## #### ######## #### ## ', - ' ## ### ########## ### ## ', - ' ## #### ######## #### ## ', - ' ### ## ### #### ### ## ### ', - ' ### ## ## ## ## ### ', - ' ###### ### ### ###### ', - ' ## ######################## ## ', - ' ## ### ### ## ', - ' ## ## ## ## ', - ' ## #### ## ', - ' ## #### ## ', - ' ## ### ### ## ', - ' ### #### #### ### ', - ' ###### ###### ', - ' ', - ' ', - ' ', - ' ', + ' ', + ' ###### ###### ', + ' ### #### #### ### ', + ' ## ### ### ## ', + ' ## #### ## ', + ' ## #### ## ', + ' ## ## ## ## ', + ' ## ### ### ## ', + ' ## ######################## ## ', + ' ###### ### ### ###### ', + ' ### ## ## ## ## ### ', + ' ### ## ### #### ### ## ### ', + ' ## #### ######## #### ## ', + ' ## ### ########## ### ## ', + ' ## #### ######## #### ## ', + ' ### ## ### #### ### ## ### ', + ' ### ## ## ## ## ### ', + ' ###### ### ### ###### ', + ' ## ######################## ## ', + ' ## ### ### ## ', + ' ## ## ## ## ', + ' ## #### ## ', + ' ## #### ## ', + ' ## ### ### ## ', + ' ### #### #### ### ', + ' ###### ###### ', + ' ', ]; const welcomeMessage = - ' Welcome to React Native! '; + ' Welcome to React Native! '; const learnOnceMessage = - ' Learn Once Write Anywhere '; + ' Learn Once Write Anywhere '; export default `${chalk.blue(reactLogoArray.join('\n'))} From da844c58e04a738831ce68eafcb0535371273bbd Mon Sep 17 00:00:00 2001 From: Kacper Wiszczuk Date: Fri, 5 Apr 2019 20:10:36 +0200 Subject: [PATCH 11/14] Cleanup tests --- e2e/__tests__/init.test.js | 14 -------------- .../src/commands/init/__tests__/template.test.js | 1 + 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/e2e/__tests__/init.test.js b/e2e/__tests__/init.test.js index 7780fef39..d8760d17b 100644 --- a/e2e/__tests__/init.test.js +++ b/e2e/__tests__/init.test.js @@ -84,17 +84,3 @@ test('init --template file:/tmp/custom/template', () => { expect(stdout).toContain('Run instructions'); }); - -test('init --verbose', () => { - const {stdout} = run(DIR, [ - 'init', - '--template', - 'react-native-new-template', - 'TestInit', - '--verbose', - ]); - - expect(stdout).toContain('Initializing new project'); - expect(stdout).toContain('No lockfile found'); - expect(stdout).toContain('Run instructions'); -}); diff --git a/packages/cli/src/commands/init/__tests__/template.test.js b/packages/cli/src/commands/init/__tests__/template.test.js index ccef26dfb..829dcd2f6 100644 --- a/packages/cli/src/commands/init/__tests__/template.test.js +++ b/packages/cli/src/commands/init/__tests__/template.test.js @@ -15,6 +15,7 @@ const TEMPLATE_NAME = 'templateName'; afterEach(() => { jest.restoreAllMocks(); + jest.clearAllMocks(); }); test('installTemplatePackage', async () => { From f544d9d5ab7e7da0a47b3f3335f774f78a535f64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 5 Apr 2019 20:11:05 +0200 Subject: [PATCH 12/14] fix flowfixme --- packages/cli/src/commands/init/init.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/init/init.js b/packages/cli/src/commands/init/init.js index fb86aed1f..9c4f87088 100644 --- a/packages/cli/src/commands/init/init.js +++ b/packages/cli/src/commands/init/init.js @@ -66,10 +66,10 @@ async function createFromExternalTemplate( changePlaceholderInTemplate(projectName, templateConfig.placeholderName); loader.succeed(); - if (templateConfig.postInitScript) { + const {postInitScript} = templateConfig; + if (postInitScript) { loader.start('Executing post init script'); - // $FlowFixMe - await executePostInitScript(name, templateConfig.postInitScript); + await executePostInitScript(name, postInitScript); loader.succeed(); } @@ -121,7 +121,7 @@ async function createFromReactNativeTemplate( loader.succeed(); if (templateConfig.postInitScript) { loader.start('Executing post init script'); - // $FlowFixMe + await executePostInitScript(TEMPLATE_NAME, templateConfig.postInitScript); loader.succeed(); } From 8280944c8242394d5635263b635754197ca1d2c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 5 Apr 2019 20:20:33 +0200 Subject: [PATCH 13/14] add space for post install script --- packages/cli/src/commands/init/init.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/commands/init/init.js b/packages/cli/src/commands/init/init.js index 9c4f87088..e39421d70 100644 --- a/packages/cli/src/commands/init/init.js +++ b/packages/cli/src/commands/init/init.js @@ -68,7 +68,8 @@ async function createFromExternalTemplate( loader.succeed(); const {postInitScript} = templateConfig; if (postInitScript) { - loader.start('Executing post init script'); + // Leaving trailing space because there may be stdout from the script + loader.start('Executing post init script '); await executePostInitScript(name, postInitScript); loader.succeed(); } @@ -119,10 +120,10 @@ async function createFromReactNativeTemplate( changePlaceholderInTemplate(projectName, templateConfig.placeholderName); loader.succeed(); - if (templateConfig.postInitScript) { + const {postInitScript} = templateConfig; + if (postInitScript) { loader.start('Executing post init script'); - - await executePostInitScript(TEMPLATE_NAME, templateConfig.postInitScript); + await executePostInitScript(TEMPLATE_NAME, postInitScript); loader.succeed(); } From b972cf78c732fc8630c573c422ceaa45e05bb104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Fri, 5 Apr 2019 20:23:46 +0200 Subject: [PATCH 14/14] rename packageManager.test as well --- .../__tests__/{PackageManager-test.js => packageManager-test.js} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/cli/src/tools/__tests__/{PackageManager-test.js => packageManager-test.js} (100%) diff --git a/packages/cli/src/tools/__tests__/PackageManager-test.js b/packages/cli/src/tools/__tests__/packageManager-test.js similarity index 100% rename from packages/cli/src/tools/__tests__/PackageManager-test.js rename to packages/cli/src/tools/__tests__/packageManager-test.js