Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add React logo and spinners to make init UI nicer #292

Merged
merged 15 commits into from
Apr 5, 2019
2 changes: 1 addition & 1 deletion e2e/__tests__/init.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/cliEntry.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import {getCommands} from './commands';
import init from './commands/init/initCompat';
import assertRequiredOptions from './tools/assertRequiredOptions';
import logger from './tools/logger';
import {setProjectDir} from './tools/PackageManager';
import {setProjectDir} from './tools/packageManager';
import pkgJson from '../package.json';
import loadConfig from './tools/config';

Expand Down
18 changes: 10 additions & 8 deletions packages/cli/src/commands/init/__tests__/template.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// @flow
jest.mock('execa', () => jest.fn());
import execa from 'execa';
import path from 'path';
import ChildProcess from 'child_process';
import * as PackageManger from '../../../tools/PackageManager';
import * as PackageManger from '../../../tools/packageManager';
import {
installTemplatePackage,
getTemplateConfig,
Expand All @@ -14,15 +15,17 @@ const TEMPLATE_NAME = 'templateName';

afterEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
});

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,
});
});

Expand Down Expand Up @@ -68,21 +71,20 @@ 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';

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(execa).toHaveBeenCalledWith(RESOLVED_PATH, {
stdio: 'inherit',
});
});
43 changes: 43 additions & 0 deletions packages/cli/src/commands/init/banner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// @flow
import chalk from 'chalk';

const reactLogoArray = [
' ',
' ###### ###### ',
' ### #### #### ### ',
' ## ### ### ## ',
' ## #### ## ',
' ## #### ## ',
' ## ## ## ## ',
' ## ### ### ## ',
' ## ######################## ## ',
' ###### ### ### ###### ',
' ### ## ## ## ## ### ',
' ### ## ### #### ### ## ### ',
' ## #### ######## #### ## ',
' ## ### ########## ### ## ',
' ## #### ######## #### ## ',
' ### ## ### #### ### ## ### ',
' ### ## ## ## ## ### ',
' ###### ### ### ###### ',
' ## ######################## ## ',
' ## ### ### ## ',
' ## ## ## ## ',
' ## #### ## ',
' ## #### ## ',
' ## ### ### ## ',
' ### #### #### ### ',
' ###### ###### ',
' ',
];

const welcomeMessage =
' Welcome to React Native! ';
const learnOnceMessage =
' Learn Once Write Anywhere ';

export default `${chalk.blue(reactLogoArray.join('\n'))}

${chalk.yellow.bold(welcomeMessage)}
${chalk.gray(learnOnceMessage)}
`;
107 changes: 79 additions & 28 deletions packages/cli/src/commands/init/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -39,50 +41,99 @@ 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();
const {postInitScript} = templateConfig;
if (postInitScript) {
// Leaving trailing space because there may be stdout from the script
loader.start('Executing post init script ');
await executePostInitScript(name, postInitScript);
loader.succeed();
}

loader.start('Installing all required dependencies');
Copy link
Member

@thymikee thymikee Apr 5, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe Installing template dependencies? Whatever sounds better, I'm don't have strong opinion on any

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both sound good :P

await PackageManager.installAll({preferYarn: !npm, silent: true});
loader.succeed();
} catch (e) {
loader.fail();
throw new Error(e);
}
}

async function createFromReactNativeTemplate(
projectName: string,
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();
const {postInitScript} = templateConfig;
if (postInitScript) {
loader.start('Executing post init script');
await executePostInitScript(TEMPLATE_NAME, 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) {
Expand Down
16 changes: 8 additions & 8 deletions packages/cli/src/commands/init/initCompat.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

/**
Expand All @@ -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'
Expand All @@ -40,31 +40,31 @@ 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);
}

/**
* Generates a new React Native project based on the template.
* @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,
destinationRoot,
);

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',
Expand Down
12 changes: 8 additions & 4 deletions packages/cli/src/commands/init/template.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// @flow
import {execFileSync} from 'child_process';

import execa from 'execa';
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';

Expand All @@ -13,7 +14,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 {
Expand Down Expand Up @@ -44,5 +48,5 @@ export function executePostInitScript(

logger.debug(`Executing post init script located ${scriptPath}`);

execFileSync(scriptPath, {stdio: 'inherit'});
return execa(scriptPath, {stdio: 'inherit'});
}
4 changes: 2 additions & 2 deletions packages/cli/src/commands/install/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@

import type {ContextT} from '../../tools/types.flow';
import logger from '../../tools/logger';
import * as PackageManager from '../../tools/PackageManager';
import * as PackageManager from '../../tools/packageManager';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you changed module name from PackageManager to packageManager, but the variable name stayed without changes. mind fixing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Variable name left uppercased because it is a grouping of all methods in packageManager through * as importing.

import link from '../link/link';
import loadConfig from '../../tools/config';

async function install(args: Array<string>, ctx: ContextT) {
const name = args[0];

logger.info(`Installing "${name}"...`);
PackageManager.install([name]);
await PackageManager.install([name]);

// Reload configuration to see newly installed dependency
const newConfig = loadConfig();
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/install/uninstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>, ctx: ContextT) {
Expand All @@ -19,7 +19,7 @@ async function uninstall(args: Array<string>, 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}"`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,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);
},
Expand Down
Loading