/** * Copyright (c) 2015-present, Facebook, Inc. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @format */ 'use strict'; const copyProjectTemplateAndReplace = require('./copyProjectTemplateAndReplace'); const execSync = require('child_process').execSync; const fs = require('fs'); const path = require('path'); /** * Templates released as part of react-native in local-cli/templates. */ const builtInTemplates = { navigation: 'HelloNavigation', }; function listTemplatesAndExit(newProjectName, options) { if (options.template === true) { // Just listing templates using 'react-native init --template'. // Not creating a new app. // Print available templates and exit. const templateKeys = Object.keys(builtInTemplates); if (templateKeys.length === 0) { // Just a guard, should never happen as long builtInTemplates // above is defined correctly :) console.log( 'There are no templates available besides ' + 'the default "Hello World" one.', ); } else { console.log( 'The available templates are:\n' + templateKeys.join('\n') + '\nYou can use these to create an app based on a template, for example: ' + 'you could run: ' + 'react-native init ' + newProjectName + ' --template ' + templateKeys[0], ); } // Exit 'react-native init' return true; } // Continue 'react-native init' return false; } /** * @param destPath Create the new project at this path. * @param newProjectName For example 'AwesomeApp'. * @param template Template to use, for example 'navigation'. * @param yarnVersion Version of yarn available on the system, or null if * yarn is not available. For example '0.18.1'. */ function createProjectFromTemplate( destPath, newProjectName, template, yarnVersion, ) { // Expand the basic 'HelloWorld' template copyProjectTemplateAndReplace( path.resolve( 'node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld', ), destPath, newProjectName, ); if (template === undefined) { // No specific template, use just the HelloWorld template above return; } // Keep the files from the 'HelloWorld' template, and overwrite some of them // with the specified project template. // The 'HelloWorld' template contains the native files (these are used by // all templates) and every other template only contains additional JS code. // Reason: // 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. const builtInTemplateName = builtInTemplates[template]; if (builtInTemplateName) { // template is e.g. 'navigation', // use the built-in local-cli/templates/HelloNavigation folder createFromBuiltInTemplate( builtInTemplateName, destPath, newProjectName, yarnVersion, ); } else { // template is e.g. 'ignite', // use the template react-native-template-ignite from npm createFromRemoteTemplate(template, destPath, newProjectName, yarnVersion); } } // (We might want to get rid of built-in templates in the future - // publish them to npm and install from there.) function createFromBuiltInTemplate( templateName, destPath, newProjectName, yarnVersion, ) { const templatePath = path.resolve( 'node_modules', 'react-native', 'local-cli', 'templates', templateName, ); copyProjectTemplateAndReplace(templatePath, destPath, newProjectName); installTemplateDependencies(templatePath, yarnVersion); installTemplateDevDependencies(templatePath, yarnVersion); } /** * The following formats are supported for the template: * - 'demo' -> Fetch the package react-native-template-demo from npm * - git://..., http://..., file://... or any other URL supported by npm */ function createFromRemoteTemplate( template, destPath, newProjectName, yarnVersion, ) { let installPackage; let templateName; if (template.includes('://')) { // URL, e.g. git://, file:// installPackage = template; templateName = template.substr(template.lastIndexOf('/') + 1); } else { // e.g 'demo' installPackage = 'react-native-template-' + template; templateName = installPackage; } // Check if the template exists console.log(`Fetching template ${installPackage}...`); try { if (yarnVersion) { execSync(`yarn add ${installPackage} --ignore-scripts`, { stdio: 'inherit', }); } else { execSync( `npm install ${installPackage} --save --save-exact --ignore-scripts`, {stdio: 'inherit'}, ); } const templatePath = path.resolve('node_modules', templateName); copyProjectTemplateAndReplace(templatePath, destPath, newProjectName, { // Every template contains a dummy package.json file included // only for publishing the template to npm. // We want to ignore this dummy file, otherwise it would overwrite // our project's package.json file. ignorePaths: ['package.json', 'dependencies.json'], }); installTemplateDependencies(templatePath, yarnVersion); installTemplateDevDependencies(templatePath, yarnVersion); } finally { // Clean up the temp files try { if (yarnVersion) { execSync(`yarn remove ${templateName} --ignore-scripts`); } else { execSync(`npm uninstall ${templateName} --ignore-scripts`); } } catch (err) { // Not critical but we still want people to know and report // if this the clean up fails. console.warn( `Failed to clean up template temp files in node_modules/${templateName}. ` + 'This is not a critical error, you can work on your app.', ); } } } function installTemplateDependencies(templatePath, yarnVersion) { // dependencies.json is a special file that lists additional dependencies // that are required by this template const dependenciesJsonPath = path.resolve(templatePath, 'dependencies.json'); console.log('Adding dependencies for the project...'); if (!fs.existsSync(dependenciesJsonPath)) { console.log('No additional dependencies.'); return; } let dependencies; try { dependencies = JSON.parse(fs.readFileSync(dependenciesJsonPath)); } catch (err) { throw new Error( "Could not parse the template's dependencies.json: " + err.message, ); } for (let depName in dependencies) { const depVersion = dependencies[depName]; const depToInstall = depName + '@' + depVersion; console.log('Adding ' + depToInstall + '...'); if (yarnVersion) { execSync(`yarn add ${depToInstall}`, {stdio: 'inherit'}); } else { execSync(`npm install ${depToInstall} --save --save-exact`, { stdio: 'inherit', }); } } console.log("Linking native dependencies into the project's build files..."); execSync('react-native link', {stdio: 'inherit'}); } function installTemplateDevDependencies(templatePath, yarnVersion) { // devDependencies.json is a special file that lists additional develop dependencies // that are required by this template const devDependenciesJsonPath = path.resolve( templatePath, 'devDependencies.json', ); console.log('Adding develop dependencies for the project...'); if (!fs.existsSync(devDependenciesJsonPath)) { console.log('No additional develop dependencies.'); return; } let dependencies; try { dependencies = JSON.parse(fs.readFileSync(devDependenciesJsonPath)); } catch (err) { throw new Error( "Could not parse the template's devDependencies.json: " + err.message, ); } for (let depName in dependencies) { const depVersion = dependencies[depName]; const depToInstall = depName + '@' + depVersion; console.log('Adding ' + depToInstall + '...'); if (yarnVersion) { execSync(`yarn add ${depToInstall} -D`, {stdio: 'inherit'}); } else { execSync(`npm install ${depToInstall} --save-dev --save-exact`, { stdio: 'inherit', }); } } } module.exports = { listTemplatesAndExit, createProjectFromTemplate, };