diff --git a/local-cli/commands.js b/local-cli/commands.js index 5f2fe27bb42e26..534a59f364531d 100644 --- a/local-cli/commands.js +++ b/local-cli/commands.js @@ -42,6 +42,7 @@ const documentedCommands = [ require('./library/library'), require('./bundle/bundle'), require('./bundle/unbundle'), + require('./eject/eject'), require('./link/link'), require('./link/unlink'), require('./install/install'), diff --git a/local-cli/eject/eject.js b/local-cli/eject/eject.js new file mode 100644 index 00000000000000..b18a5176ec1c93 --- /dev/null +++ b/local-cli/eject/eject.js @@ -0,0 +1,96 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +'use strict'; + +const copyProjectTemplateAndReplace = require('../generator/copyProjectTemplateAndReplace'); +const path = require('path'); +const fs = require('fs'); + +/** + * The eject command re-creates the `android` and `ios` native folders. Because native code can be + * difficult to maintain, this new script allows an `app.json` to be defined for the project, which + * is used to configure the native app. + * + * The `app.json` config may contain the following keys: + * + * - `name` - The short name used for the project, should be TitleCase + * - `displayName` - The app's name on the home screen + */ + +function eject() { + + const doesIOSExist = fs.existsSync(path.resolve('ios')); + const doesAndroidExist = fs.existsSync(path.resolve('android')); + if (doesIOSExist && doesAndroidExist) { + console.error( + 'Both the iOS and Android folders already exist! Please delete `ios` and/or `android` ' + + 'before ejecting.' + ); + process.exit(1); + } + + let appConfig = null; + try { + appConfig = require(path.resolve('app.json')); + } catch(e) { + console.error( + `Eject requires an \`app.json\` config file to be located at ` + + `${path.resolve('app.json')}, and it must at least specify a \`name\` for the project ` + + `name, and a \`displayName\` for the app's home screen label.` + ); + process.exit(1); + } + + const appName = appConfig.name; + if (!appName) { + console.error( + `App \`name\` must be defined in the \`app.json\` config file to define the project name. `+ + `It must not contain any spaces or dashes.` + ); + process.exit(1); + } + const displayName = appConfig.displayName; + if (!displayName) { + console.error( + `App \`displayName\` must be defined in the \`app.json\` config file, to define the label ` + + `of the app on the home screen.` + ); + process.exit(1); + } + + const templateOptions = { displayName }; + + if (!doesIOSExist) { + console.log('Generating the iOS folder.'); + copyProjectTemplateAndReplace( + path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld', 'ios'), + path.resolve('ios'), + appName, + templateOptions + ); + } + + if (!doesAndroidExist) { + console.log('Generating the Android folder.'); + copyProjectTemplateAndReplace( + path.resolve('node_modules', 'react-native', 'local-cli', 'templates', 'HelloWorld', 'android'), + path.resolve('android'), + appName, + templateOptions + ); + } + +} + +module.exports = { + name: 'eject', + description: 'Re-create the iOS and Android folders and native code', + func: eject, + options: [], +}; diff --git a/local-cli/generator/copyProjectTemplateAndReplace.js b/local-cli/generator/copyProjectTemplateAndReplace.js index 7818022715ebe2..c24a3d3b0ea3cf 100644 --- a/local-cli/generator/copyProjectTemplateAndReplace.js +++ b/local-cli/generator/copyProjectTemplateAndReplace.js @@ -27,10 +27,12 @@ function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, option if (!destPath) { throw new Error('Need a path to copy to'); } if (!newProjectName) { throw new Error('Need a project name'); } + options = options || {}; + walk(srcPath).forEach(absoluteSrcFilePath => { // 'react-native upgrade' - if (options && options.upgrade) { + if (options.upgrade) { // Don't upgrade these files const fileName = path.basename(absoluteSrcFilePath); // This also includes __tests__/index.*.js @@ -44,7 +46,7 @@ function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, option .replace(/helloworld/g, newProjectName.toLowerCase()); let contentChangedCallback = null; - if (options && options.upgrade && (!options.force)) { + if (options.upgrade && (!options.force)) { contentChangedCallback = (_, contentChanged) => { return upgradeFileContentChangedCallback( absoluteSrcFilePath, @@ -57,6 +59,7 @@ function copyProjectTemplateAndReplace(srcPath, destPath, newProjectName, option absoluteSrcFilePath, path.resolve(destPath, relativeRenamedPath), { + 'Hello App Display Name': options.displayName || newProjectName, 'HelloWorld': newProjectName, 'helloworld': newProjectName.toLowerCase(), }, diff --git a/local-cli/templates/HelloWorld/android/app/src/main/res/values/strings.xml b/local-cli/templates/HelloWorld/android/app/src/main/res/values/strings.xml index b89cc148915cb8..0c79c4bad47cb7 100644 --- a/local-cli/templates/HelloWorld/android/app/src/main/res/values/strings.xml +++ b/local-cli/templates/HelloWorld/android/app/src/main/res/values/strings.xml @@ -1,3 +1,3 @@ - HelloWorld + Hello App Display Name diff --git a/local-cli/templates/HelloWorld/app.json b/local-cli/templates/HelloWorld/app.json new file mode 100644 index 00000000000000..114d929c1581c5 --- /dev/null +++ b/local-cli/templates/HelloWorld/app.json @@ -0,0 +1,4 @@ +{ + "name": "HelloWorld", + "displayName": "HelloWorld" +} \ No newline at end of file diff --git a/local-cli/templates/HelloWorld/ios/HelloWorld/Info.plist b/local-cli/templates/HelloWorld/ios/HelloWorld/Info.plist index 2fb6a11c2c3356..a58201cfa82b98 100644 --- a/local-cli/templates/HelloWorld/ios/HelloWorld/Info.plist +++ b/local-cli/templates/HelloWorld/ios/HelloWorld/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion en + CFBundleDisplayName + Hello App Display Name CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier