diff --git a/jestSupport/env.js b/jestSupport/env.js index d9f63301eadd9b..186f3e247b0118 100644 --- a/jestSupport/env.js +++ b/jestSupport/env.js @@ -23,6 +23,7 @@ global.regeneratorRuntime = require.requireActual('regenerator-runtime/runtime') jest .mock('ensureComponentIsNative') .mock('Image') + .mock('npmlog') .mock('NativeModules') .mock('Text') .mock('View'); diff --git a/local-cli/bundle/buildBundle.js b/local-cli/bundle/buildBundle.js index d229d0ebf7fce1..414403f1cae57c 100644 --- a/local-cli/bundle/buildBundle.js +++ b/local-cli/bundle/buildBundle.js @@ -21,69 +21,56 @@ function saveBundle(output, bundle, args) { } function buildBundle(args, config, output = outputBundle, packagerInstance) { - return new Promise((resolve, reject) => { + // This is used by a bazillion of npm modules we don't control so we don't + // have other choice than defining it as an env variable here. + process.env.NODE_ENV = args.dev ? 'development' : 'production'; - // This is used by a bazillion of npm modules we don't control so we don't - // have other choice than defining it as an env variable here. - if (!process.env.NODE_ENV) { - // If you're inlining environment variables, you can use babel to remove - // this line: - // https://www.npmjs.com/package/babel-remove-process-env-assignment - process.env.NODE_ENV = args.dev ? 'development' : 'production'; - } + const options = { + projectRoots: config.getProjectRoots(), + assetRoots: config.getAssetRoots(), + blacklistRE: config.getBlacklistRE(args.platform), + getTransformOptionsModulePath: config.getTransformOptionsModulePath, + transformModulePath: args.transformer, + extraNodeModules: config.extraNodeModules, + nonPersistent: true, + resetCache: args.resetCache, + }; - const transformModulePath = - args.transformer ? path.resolve(args.transformer) : - typeof config.getTransformModulePath === 'function' ? config.getTransformModulePath() : - undefined; + const requestOpts = { + entryFile: args.entryFile, + sourceMapUrl: args.sourcemapOutput, + dev: args.dev, + minify: !args.dev, + platform: args.platform, + }; - const options = { - projectRoots: config.getProjectRoots(), - assetRoots: config.getAssetRoots(), - blacklistRE: config.getBlacklistRE(args.platform), - getTransformOptionsModulePath: config.getTransformOptionsModulePath, - transformModulePath: transformModulePath, - extraNodeModules: config.extraNodeModules, - nonPersistent: true, - resetCache: args['reset-cache'], - }; + // If a packager instance was not provided, then just create one for this + // bundle command and close it down afterwards. + var shouldClosePackager = false; + if (!packagerInstance) { + packagerInstance = new Server(options); + shouldClosePackager = true; + } - const requestOpts = { - entryFile: args['entry-file'], - sourceMapUrl: args['sourcemap-output'], - dev: args.dev, - minify: !args.dev, - platform: args.platform, - }; + const bundlePromise = output.build(packagerInstance, requestOpts) + .then(bundle => { + if (shouldClosePackager) { + packagerInstance.end(); + } + return saveBundle(output, bundle, args); + }); - // If a packager instance was not provided, then just create one for this - // bundle command and close it down afterwards. - var shouldClosePackager = false; - if (!packagerInstance) { - packagerInstance = new Server(options); - shouldClosePackager = true; - } + // Save the assets of the bundle + const assets = bundlePromise + .then(bundle => bundle.getAssets()) + .then(outputAssets => saveAssets( + outputAssets, + args.platform, + args.assetsDest, + )); - const bundlePromise = output.build(packagerInstance, requestOpts) - .then(bundle => { - if (shouldClosePackager) { - packagerInstance.end(); - } - return saveBundle(output, bundle, args); - }); - - // Save the assets of the bundle - const assets = bundlePromise - .then(bundle => bundle.getAssets()) - .then(outputAssets => saveAssets( - outputAssets, - args.platform, - args['assets-dest'] - )); - - // When we're done saving bundle output and the assets, we're done. - resolve(assets); - }); + // When we're done saving bundle output and the assets, we're done. + return assets; } module.exports = buildBundle; diff --git a/local-cli/bundle/bundle.js b/local-cli/bundle/bundle.js index e2a6441e054fce..8f2b1816dca6ac 100644 --- a/local-cli/bundle/bundle.js +++ b/local-cli/bundle/bundle.js @@ -8,26 +8,30 @@ */ const buildBundle = require('./buildBundle'); -const bundleCommandLineArgs = require('./bundleCommandLineArgs'); -const parseCommandLine = require('../util/parseCommandLine'); const outputBundle = require('./output/bundle'); const outputPrepack = require('./output/prepack'); +const bundleCommandLineArgs = require('./bundleCommandLineArgs'); /** * Builds the bundle starting to look for dependencies at the given entry path. */ -function bundleWithOutput(argv, config, output, packagerInstance) { - const args = parseCommandLine(bundleCommandLineArgs, argv); +function bundleWithOutput(argv, config, args, output, packagerInstance) { if (!output) { output = args.prepack ? outputPrepack : outputBundle; } return buildBundle(args, config, output, packagerInstance); - } -function bundle(argv, config, packagerInstance) { - return bundleWithOutput(argv, config, undefined, packagerInstance); +function bundle(argv, config, args, packagerInstance) { + return bundleWithOutput(argv, config, args, undefined, packagerInstance); } -module.exports = bundle; -module.exports.withOutput = bundleWithOutput; +module.exports = { + name: 'bundle', + description: 'builds the javascript bundle for offline use', + func: bundle, + options: bundleCommandLineArgs, + + // not used by the CLI itself + withOutput: bundleWithOutput, +}; diff --git a/local-cli/bundle/bundleCommandLineArgs.js b/local-cli/bundle/bundleCommandLineArgs.js index 5d54ad83504280..46bb72789875d4 100644 --- a/local-cli/bundle/bundleCommandLineArgs.js +++ b/local-cli/bundle/bundleCommandLineArgs.js @@ -10,56 +10,47 @@ module.exports = [ { - command: 'entry-file', + command: '--entry-file ', description: 'Path to the root JS file, either absolute or relative to JS root', - type: 'string', - required: true, }, { - command: 'platform', + command: '--platform [string]', description: 'Either "ios" or "android"', - type: 'string', + default: 'ios', }, { - command: 'transformer', - description: 'Specify a custom transformer to be used', - type: 'string', - default: null, + command: '--transformer [string]', + description: 'Specify a custom transformer to be used (absolute path)', + default: require.resolve('../../packager/transformer'), }, { - command: 'dev', + command: '--dev [boolean]', description: 'If false, warnings are disabled and the bundle is minified', + parse: (val) => val === 'false' ? false : true, default: true, }, { - command: 'prepack', - description: 'If true, the output bundle will use the Prepack format.', - default: false + command: '--prepack', + description: 'When passed, the output bundle will use the Prepack format.', }, { - command: 'bridge-config', + command: '--bridge-config [string]', description: 'File name of a a JSON export of __fbBatchedBridgeConfig. Used by Prepack. Ex. ./bridgeconfig.json', - type: 'string' }, { - command: 'bundle-output', + command: '--bundle-output ', description: 'File name where to store the resulting bundle, ex. /tmp/groups.bundle', - type: 'string', - required: true, }, { - command: 'bundle-encoding', + command: '--bundle-encoding [string]', description: 'Encoding the bundle should be written in (https://nodejs.org/api/buffer.html#buffer_buffer).', - type: 'string', default: 'utf8', }, { - command: 'sourcemap-output', + command: '--sourcemap-output [string]', description: 'File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map', - type: 'string', }, { - command: 'assets-dest', + command: '--assets-dest [string]', description: 'Directory name where to store assets referenced in the bundle', - type: 'string', }, { - command: 'verbose', + command: '--verbose', description: 'Enables logging', default: false, }, { - command: 'reset-cache', + command: '--reset-cache', description: 'Removes cached files', - default: false - } + default: false, + }, ]; diff --git a/local-cli/bundle/output/bundle.js b/local-cli/bundle/output/bundle.js index 70e8a1941dc811..34d1fa71a1b000 100644 --- a/local-cli/bundle/output/bundle.js +++ b/local-cli/bundle/output/bundle.js @@ -28,10 +28,10 @@ function createCodeWithMap(bundle, dev) { function saveBundleAndMap(bundle, options, log) { const { - 'bundle-output': bundleOutput, - 'bundle-encoding': encoding, + bundleOutput, + bundleEncoding: encoding, dev, - 'sourcemap-output': sourcemapOutput, + sourcemapOutput } = options; log('start'); diff --git a/local-cli/bundle/output/prepack.js b/local-cli/bundle/output/prepack.js index 9165b876357195..d13d44f8f852ae 100644 --- a/local-cli/bundle/output/prepack.js +++ b/local-cli/bundle/output/prepack.js @@ -16,8 +16,8 @@ function buildPrepackBundle(packagerClient, requestOptions) { function savePrepackBundle(bundle, options, log) { const { - 'bundle-output': bundleOutput, - 'bridge-config': bridgeConfig, + bundleOutput, + bridgeConfig, } = options; const result = bundle.build({ diff --git a/local-cli/bundle/output/unbundle/as-assets.js b/local-cli/bundle/output/unbundle/as-assets.js index 740a8564a54273..0698d2a3732bfb 100644 --- a/local-cli/bundle/output/unbundle/as-assets.js +++ b/local-cli/bundle/output/unbundle/as-assets.js @@ -30,9 +30,9 @@ const MODULES_DIR = 'js-modules'; */ function saveAsAssets(bundle, options, log) { const { - 'bundle-output': bundleOutput, - 'bundle-encoding': encoding, - 'sourcemap-output': sourcemapOutput, + bundleOutput, + bundleEncoding: encoding, + sourcemapOutput } = options; log('start'); diff --git a/local-cli/bundle/output/unbundle/as-indexed-file.js b/local-cli/bundle/output/unbundle/as-indexed-file.js index 45eb412c8fc60a..3e3ece669000ee 100644 --- a/local-cli/bundle/output/unbundle/as-indexed-file.js +++ b/local-cli/bundle/output/unbundle/as-indexed-file.js @@ -26,9 +26,9 @@ const SIZEOF_UINT32 = 4; */ function saveAsIndexedFile(bundle, options, log) { const { - 'bundle-output': bundleOutput, - 'bundle-encoding': encoding, - 'sourcemap-output': sourcemapOutput, + bundleOutput, + bundleEncoding: encoding, + sourcemapOutput } = options; log('start'); diff --git a/local-cli/bundle/unbundle.js b/local-cli/bundle/unbundle.js index 41460d3eb37f42..b8715501d67cdb 100644 --- a/local-cli/bundle/unbundle.js +++ b/local-cli/bundle/unbundle.js @@ -8,13 +8,19 @@ */ const bundleWithOutput = require('./bundle').withOutput; +const bundleCommandLineArgs = require('./bundleCommandLineArgs'); const outputUnbundle = require('./output/unbundle'); /** * Builds the bundle starting to look for dependencies at the given entry path. */ -function unbundle(argv, config, packagerInstance) { - return bundleWithOutput(argv, config, outputUnbundle, packagerInstance); +function unbundle(argv, config, args, packagerInstance) { + return bundleWithOutput(argv, config, args, outputUnbundle, packagerInstance); } -module.exports = unbundle; +module.exports = { + name: 'unbundle', + description: 'builds javascript as "unbundle" for offline use', + func: unbundle, + options: bundleCommandLineArgs, +}; diff --git a/local-cli/cliEntry.js b/local-cli/cliEntry.js index dd1c06dd87d3fd..303686ba1c6618 100644 --- a/local-cli/cliEntry.js +++ b/local-cli/cliEntry.js @@ -5,171 +5,151 @@ * 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. + * + * @flow */ 'use strict'; -const bundle = require('./bundle/bundle'); -const childProcess = require('child_process'); +const commander = require('commander'); + const Config = require('./util/Config'); -const defaultConfig = require('./default.config'); -const dependencies = require('./dependencies/dependencies'); -const generate = require('./generate/generate'); -const library = require('./library/library'); -const link = require('./rnpm/link/src/link'); -const path = require('path'); +const childProcess = require('child_process'); const Promise = require('promise'); -const runAndroid = require('./runAndroid/runAndroid'); -const logAndroid = require('./logAndroid/logAndroid'); -const runIOS = require('./runIOS/runIOS'); -const logIOS = require('./logIOS/logIOS'); -const server = require('./server/server'); -const TerminalAdapter = require('yeoman-environment/lib/adapter.js'); -const yeoman = require('yeoman-environment'); -const unbundle = require('./bundle/unbundle'); -const upgrade = require('./upgrade/upgrade'); -const version = require('./version/version'); - +const chalk = require('chalk'); +const path = require('path'); const fs = require('fs'); const gracefulFs = require('graceful-fs'); -// Just a helper to proxy 'react-native link' to rnpm -const linkWrapper = (args, config) => { - const rnpmConfig = require('./rnpm/core/src/config'); - return new Promise((resolve, reject) => { - link(rnpmConfig, args.slice(1)).then(resolve, reject); - }); -} +const init = require('./init/init'); +const commands = require('./commands'); +const assertRequiredOptions = require('./util/assertRequiredOptions'); +const pkg = require('../package.json'); +const defaultConfig = require('./default.config'); + +import type { Command } from './commands'; // graceful-fs helps on getting an error when we run out of file // descriptors. When that happens it will enqueue the operation and retry it. gracefulFs.gracefulify(fs); -const documentedCommands = { - 'start': [server, 'starts the webserver'], - 'bundle': [bundle, 'builds the javascript bundle for offline use'], - 'unbundle': [unbundle, 'builds javascript as "unbundle" for offline use'], - 'new-library': [library, 'generates a native library bridge'], - 'android': [generateWrapper, 'generates an Android project for your app'], - 'run-android': [runAndroid, 'builds your app and starts it on a connected Android emulator or device'], - 'log-android': [logAndroid, 'print Android logs'], - 'run-ios': [runIOS, 'builds your app and starts it on iOS simulator'], - 'log-ios': [logIOS, 'print iOS logs'], - 'upgrade': [upgrade, 'upgrade your app\'s template files to the latest version; run this after ' + - 'updating the react-native version in your package.json and running npm install'], - 'link': [linkWrapper, 'link a library'], -}; +commander.version(pkg.version); -const exportedCommands = {dependencies: dependencies}; -Object.keys(documentedCommands).forEach(function(command) { - exportedCommands[command] = documentedCommands[command][0]; -}); +const defaultOptParser = (val) => val; -const undocumentedCommands = { - '--version': [version, ''], - 'init': [printInitWarning, ''], +const handleError = (err) => { + console.error(); + console.error(err.message || err); + console.error(); + process.exit(1); }; -const commands = Object.assign({}, documentedCommands, undocumentedCommands); - -/** - * Parses the command line and runs a command of the CLI. - */ -function run() { - const args = process.argv.slice(2); - if (args.length === 0) { - printUsage(); +// Custom printHelpInformation command inspired by internal Commander.js +// one modified to suit our needs +function printHelpInformation() { + let cmdName = this._name; + if (this._alias) { + cmdName = cmdName + '|' + this._alias; } - const setupEnvScript = /^win/.test(process.platform) - ? 'setup_env.bat' - : 'setup_env.sh'; - childProcess.execFileSync(path.join(__dirname, setupEnvScript)); + let output = [ + '', + chalk.bold(chalk.cyan((` react-native ${cmdName} [options]`))), + ` ${this._description}`, + '', + ` ${chalk.bold('Options:')}`, + '', + this.optionHelp().replace(/^/gm, ' '), + '', + ]; - const command = commands[args[0]]; - if (!command) { - console.error('Command `%s` unrecognized', args[0]); - printUsage(); - return; - } + const usage = this.usage(); - command[0](args, Config.get(__dirname, defaultConfig)).done(); -} + if (usage !== '[options]') { + const formattedUsage = usage.map( + example => ` ${example.desc}: \n ${chalk.cyan(example.cmd)}`, + ).join('\n\n'); + + output = output.concat([ + chalk.bold(' Example usage:'), + '', + formattedUsage, + ]); + } -function generateWrapper(args, config) { - return generate([ - '--platform', 'android', - '--project-path', process.cwd(), - '--project-name', JSON.parse( - fs.readFileSync('package.json', 'utf8') - ).name - ], config); + return output.concat([ + '', + '', + ]).join('\n'); } -function printUsage() { +function printUnknownCommand(cmdName) { console.log([ - 'Usage: react-native ', '', - 'Commands:' - ].concat(Object.keys(documentedCommands).map(function(name) { - return ' - ' + name + ': ' + documentedCommands[name][1]; - })).join('\n')); - process.exit(1); + cmdName + ? chalk.red(` Unrecognized command '${cmdName}'`) + : chalk.red(' You didn\'t pass any command'), + ` Run ${chalk.cyan('react-native --help')} to see list of all available commands`, + '', + ].join('\n')); } -// The user should never get here because projects are inited by -// using `react-native-cli` from outside a project directory. -function printInitWarning() { - return Promise.resolve().then(function() { - console.log([ - 'Looks like React Native project already exists in the current', - 'folder. Run this command from a different folder or remove node_modules/react-native' - ].join('\n')); - process.exit(1); - }); -} +const addCommand = (command: Command, config: Config) => { + const options = command.options || []; + + const cmd = commander + .command(command.name, undefined, { + noHelp: !command.description, + }) + .usage(command.examples) + .description(command.description) + .action(function runAction() { + const passedOptions = this.opts(); + const argv: Array = Array.from(arguments).slice(0, -1); + + Promise.resolve() + .then(() => { + assertRequiredOptions(options, passedOptions); + return command.func(argv, config, passedOptions); + }) + .catch(handleError); + }); + + cmd.helpInformation = printHelpInformation.bind(cmd); + + options + .forEach(opt => cmd.option( + opt.command, + opt.description, + opt.parse || defaultOptParser, + typeof opt.default === 'function' ? opt.default(config) : opt.default, + )); +}; -class CreateSuppressingTerminalAdapter extends TerminalAdapter { - constructor() { - super(); - // suppress 'create' output generated by yeoman - this.log.create = function() {}; +function run() { + const config = Config.get(__dirname, defaultConfig); + const setupEnvScript = /^win/.test(process.platform) + ? 'setup_env.bat' + : 'setup_env.sh'; + + childProcess.execFileSync(path.join(__dirname, setupEnvScript)); + + commands.forEach(cmd => addCommand(cmd, config)); + + commander.parse(process.argv); + + const isValidCommand = commands.find(cmd => cmd.name === process.argv[2]); + + if (!isValidCommand) { + printUnknownCommand(process.argv[2]); + return; } -} -/** - * Creates the template for a React Native project given the provided - * parameters: - * - projectDir: templates will be copied here. - * - argsOrName: project name or full list of custom arguments to pass to the - * generator. - */ -function init(projectDir, argsOrName) { - console.log('Setting up new React Native app in ' + projectDir); - const env = yeoman.createEnv( - undefined, - undefined, - new CreateSuppressingTerminalAdapter() - ); - - env.register( - require.resolve(path.join(__dirname, 'generator')), - 'react:app' - ); - - // argv is for instance - // ['node', 'react-native', 'init', 'AwesomeApp', '--verbose'] - // args should be ['AwesomeApp', '--verbose'] - const args = Array.isArray(argsOrName) - ? argsOrName - : [argsOrName].concat(process.argv.slice(4)); - - const generator = env.create('react:app', {args: args}); - generator.destinationRoot(projectDir); - generator.run(); + if (!commander.args.length) { + commander.help(); + } } module.exports = { run: run, init: init, - commands: exportedCommands }; diff --git a/local-cli/commands.js b/local-cli/commands.js new file mode 100644 index 00000000000000..e696b35be1f4e5 --- /dev/null +++ b/local-cli/commands.js @@ -0,0 +1,70 @@ +/** + * 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. + * + * @flow + */ +'use strict'; + +const Config = require('./util/Config'); +const getUserCommands = require('./rnpm/core/src/getCommands'); + +export type Command = { + name: string, + description?: string, + usage?: string, + func: (argv: Array, config: Config, args: Object) => ?Promise, + options?: Array<{ + command: string, + description?: string, + parse?: (val: string) => any, + default?: (config: Config) => any | any, + }>, + examples?: Array<{ + desc: string, + cmd: string, + }>, +}; + +const documentedCommands = [ + require('./server/server'), + require('./runIOS/runIOS'), + require('./runAndroid/runAndroid'), + require('./library/library'), + require('./bundle/bundle'), + require('./bundle/unbundle'), + require('./rnpm/link/link'), + require('./rnpm/link/unlink'), + require('./rnpm/install/install'), + require('./rnpm/install/uninstall'), + require('./upgrade/upgrade'), + require('./logAndroid/logAndroid'), + require('./logIOS/logIOS'), + require('./dependencies/dependencies'), +]; + +// The user should never get here because projects are inited by +// using `react-native-cli` from outside a project directory. +const undocumentedCommands = [ + { + name: 'init', + func: () => { + console.log([ + 'Looks like React Native project already exists in the current', + 'folder. Run this command from a different folder or remove node_modules/react-native' + ].join('\n')); + }, + }, +]; + +const commands: Array = [ + ...documentedCommands, + ...undocumentedCommands, + ...getUserCommands(), +]; + +module.exports = commands; diff --git a/local-cli/default.config.js b/local-cli/default.config.js index 7b5b2fa00b9076..7a1e48a836f3c5 100644 --- a/local-cli/default.config.js +++ b/local-cli/default.config.js @@ -2,6 +2,7 @@ var blacklist = require('../packager/blacklist'); var path = require('path'); +var rnpmConfig = require('./rnpm/core/src/config'); /** * Default configuration for the CLI. @@ -15,6 +16,9 @@ var config = { return getRoots(); }, + getProjectConfig: rnpmConfig.getProjectConfig, + getDependencyConfig: rnpmConfig.getDependencyConfig, + /** * Specify where to look for assets that are referenced using * `image!`. Asset directories for images referenced using diff --git a/local-cli/dependencies/dependencies.js b/local-cli/dependencies/dependencies.js index 14fd27def20a66..633a5942601175 100644 --- a/local-cli/dependencies/dependencies.js +++ b/local-cli/dependencies/dependencies.js @@ -8,50 +8,14 @@ */ const fs = require('fs'); -const parseCommandLine = require('../util/parseCommandLine'); const path = require('path'); const Promise = require('promise'); const ReactPackager = require('../../packager/react-packager'); -/** - * Returns the dependencies an entry path has. - */ -function dependencies(argv, config, packagerInstance) { - return new Promise((resolve, reject) => { - _dependencies(argv, config, resolve, reject, packagerInstance); - }); -} - -function _dependencies(argv, config, resolve, reject, packagerInstance) { - const args = parseCommandLine([ - { - command: 'entry-file', - description: 'Absolute path to the root JS file', - type: 'string', - required: true, - }, { - command: 'output', - description: 'File name where to store the output, ex. /tmp/dependencies.txt', - type: 'string', - }, { - command: 'platform', - description: 'The platform extension used for selecting modules', - type: 'string', - }, { - command: 'transformer', - type: 'string', - default: null, - description: 'Specify a custom transformer to be used' - }, { - command: 'verbose', - description: 'Enables logging', - default: false, - } - ], argv); - - const rootModuleAbsolutePath = args['entry-file']; +function dependencies(argv, config, args, packagerInstance) { + const rootModuleAbsolutePath = args.entryFile; if (!fs.existsSync(rootModuleAbsolutePath)) { - reject(`File ${rootModuleAbsolutePath} does not exist`); + return Promise.reject(`File ${rootModuleAbsolutePath} does not exist`); } const transformModulePath = @@ -86,7 +50,7 @@ function _dependencies(argv, config, resolve, reject, packagerInstance) { ? fs.createWriteStream(args.output) : process.stdout; - resolve((packagerInstance ? + return Promise.resolve((packagerInstance ? packagerInstance.getOrderedDependencyPaths(options) : ReactPackager.getOrderedDependencyPaths(packageOpts, options)).then( deps => { @@ -110,4 +74,27 @@ function _dependencies(argv, config, resolve, reject, packagerInstance) { )); } -module.exports = dependencies; +module.exports = { + name: 'dependencies', + func: dependencies, + options: [ + { + command: '--entry-file ', + description: 'Absolute path to the root JS file', + }, { + command: '--output [path]', + description: 'File name where to store the output, ex. /tmp/dependencies.txt', + }, { + command: '--platform [extension]', + description: 'The platform extension used for selecting modules', + }, { + command: '--transformer [path]', + default: null, + description: 'Specify a custom transformer to be used' + }, { + command: '--verbose', + description: 'Enables logging', + default: false, + }, + ], +}; diff --git a/local-cli/init/init.js b/local-cli/init/init.js new file mode 100644 index 00000000000000..92df12bc5e33e5 --- /dev/null +++ b/local-cli/init/init.js @@ -0,0 +1,55 @@ +/** + * 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 path = require('path'); +const TerminalAdapter = require('yeoman-environment/lib/adapter.js'); +const yeoman = require('yeoman-environment'); + +class CreateSuppressingTerminalAdapter extends TerminalAdapter { + constructor() { + super(); + // suppress 'create' output generated by yeoman + this.log.create = function() {}; + } +} + +/** + * Creates the template for a React Native project given the provided + * parameters: + * - projectDir: templates will be copied here. + * - argsOrName: project name or full list of custom arguments to pass to the + * generator. + */ +function init(projectDir, argsOrName) { + console.log('Setting up new React Native app in ' + projectDir); + const env = yeoman.createEnv( + undefined, + undefined, + new CreateSuppressingTerminalAdapter() + ); + + env.register( + require.resolve(path.join(__dirname, '../generator')), + 'react:app' + ); + + // argv is for instance + // ['node', 'react-native', 'init', 'AwesomeApp', '--verbose'] + // args should be ['AwesomeApp', '--verbose'] + const args = Array.isArray(argsOrName) + ? argsOrName + : [argsOrName].concat(process.argv.slice(4)); + + const generator = env.create('react:app', {args: args}); + generator.destinationRoot(projectDir); + generator.run(); +} + +module.exports = init; diff --git a/local-cli/library/library.js b/local-cli/library/library.js index 107c4455bcbeae..318b56830f0df7 100644 --- a/local-cli/library/library.js +++ b/local-cli/library/library.js @@ -11,7 +11,6 @@ const copyAndReplace = require('../util/copyAndReplace'); const fs = require('fs'); const isValidPackageName = require('../util/isValidPackageName'); -const parseCommandLine = require('../util/parseCommandLine'); const path = require('path'); const Promise = require('promise'); const walk = require('../util/walk'); @@ -19,22 +18,9 @@ const walk = require('../util/walk'); /** * Creates a new native library with the given name */ -function library(argv, config) { - return new Promise((resolve, reject) => { - _library(argv, config, resolve, reject); - }); -} - -function _library(argv, config, resolve, reject) { - const args = parseCommandLine([{ - command: 'name', - description: 'Library name', - type: 'string', - required: true, - }], argv); - +function library(argv, config, args) { if (!isValidPackageName(args.name)) { - reject( + return Promise.reject( args.name + ' is not a valid name for a project. Please use a valid ' + 'identifier name (alphanumeric).' ); @@ -50,7 +36,7 @@ function _library(argv, config, resolve, reject) { } if (fs.existsSync(libraryDest)) { - reject('Library already exists in', libraryDest); + return Promise.reject(`Library already exists in ${libraryDest}`); } walk(source).forEach(f => { @@ -71,7 +57,15 @@ function _library(argv, config, resolve, reject) { console.log('Next Steps:'); console.log(' Link your library in Xcode:'); console.log(' https://facebook.github.io/react-native/docs/linking-libraries-ios.html#content\n'); - resolve(); } -module.exports = library; +module.exports = { + name: 'new-library', + func: library, + description: 'generates a native library bridge', + options: [{ + command: '--name ', + description: 'name of the library to generate', + default: null, + }], +}; diff --git a/local-cli/logAndroid/logAndroid.js b/local-cli/logAndroid/logAndroid.js index ddb60fca0d365d..6005d24d936bbb 100644 --- a/local-cli/logAndroid/logAndroid.js +++ b/local-cli/logAndroid/logAndroid.js @@ -21,7 +21,7 @@ function logAndroid() { }); } -function _logAndroid(resolve, reject) { +function _logAndroid() { try { const adbPath = process.env.ANDROID_HOME ? process.env.ANDROID_HOME + '/platform-tools/adb' @@ -43,9 +43,12 @@ function _logAndroid(resolve, reject) { console.log(chalk.red( 'adb invocation failed. Do you have adb in your PATH?' )); - reject(); - return; + return Promise.reject(); } } -module.exports = logAndroid; +module.exports = { + name: 'log-android', + description: 'starts adb logcat', + func: logAndroid, +}; diff --git a/local-cli/logIOS/logIOS.js b/local-cli/logIOS/logIOS.js index 728a060b2492ca..4a898ad5e88a29 100644 --- a/local-cli/logIOS/logIOS.js +++ b/local-cli/logIOS/logIOS.js @@ -15,7 +15,7 @@ function logIOS() { }); } -function _logIOS(resolve, reject) { +function _logIOS() { let rawDevices; try { @@ -26,8 +26,7 @@ function _logIOS(resolve, reject) { console.log(chalk.red( 'xcrun invocation failed. Please check that Xcode is installed.' )); - reject(e); - return; + return Promise.reject(e); } const { devices } = JSON.parse(rawDevices); @@ -37,10 +36,10 @@ function _logIOS(resolve, reject) { console.log(chalk.red( 'No active iOS device found' )); - reject(); + return Promise.reject(); } - tailDeviceLogs(device.udid, reject); + return tailDeviceLogs(device.udid); } function _findAvailableDevice(devices) { @@ -53,7 +52,7 @@ function _findAvailableDevice(devices) { } } -function tailDeviceLogs(udid, reject) { +function tailDeviceLogs(udid) { const logDir = path.join( os.homedir(), 'Library', @@ -70,8 +69,12 @@ function tailDeviceLogs(udid, reject) { console.log(chalk.red( 'syslog invocation failed.' )); - reject(log.error); + return Promise.reject(log.error); } } -module.exports = logIOS; +module.exports = { + name: 'log-ios', + description: 'starts iOS device syslog tail', + func: logIOS, +}; diff --git a/local-cli/rnpm/core/test/fixtures/android.js b/local-cli/rnpm/core/__fixtures__/android.js similarity index 100% rename from local-cli/rnpm/core/test/fixtures/android.js rename to local-cli/rnpm/core/__fixtures__/android.js diff --git a/local-cli/rnpm/core/test/fixtures/commands.js b/local-cli/rnpm/core/__fixtures__/commands.js similarity index 100% rename from local-cli/rnpm/core/test/fixtures/commands.js rename to local-cli/rnpm/core/__fixtures__/commands.js diff --git a/local-cli/rnpm/core/test/fixtures/dependencies.js b/local-cli/rnpm/core/__fixtures__/dependencies.js similarity index 100% rename from local-cli/rnpm/core/test/fixtures/dependencies.js rename to local-cli/rnpm/core/__fixtures__/dependencies.js diff --git a/local-cli/rnpm/core/test/fixtures/files/AndroidManifest.xml b/local-cli/rnpm/core/__fixtures__/files/AndroidManifest.xml similarity index 100% rename from local-cli/rnpm/core/test/fixtures/files/AndroidManifest.xml rename to local-cli/rnpm/core/__fixtures__/files/AndroidManifest.xml diff --git a/local-cli/rnpm/core/test/fixtures/files/Main.java b/local-cli/rnpm/core/__fixtures__/files/Main.java similarity index 100% rename from local-cli/rnpm/core/test/fixtures/files/Main.java rename to local-cli/rnpm/core/__fixtures__/files/Main.java diff --git a/local-cli/rnpm/core/test/fixtures/files/ReactPackage.java b/local-cli/rnpm/core/__fixtures__/files/ReactPackage.java similarity index 100% rename from local-cli/rnpm/core/test/fixtures/files/ReactPackage.java rename to local-cli/rnpm/core/__fixtures__/files/ReactPackage.java diff --git a/local-cli/rnpm/core/test/fixtures/files/package.json b/local-cli/rnpm/core/__fixtures__/files/package.json similarity index 100% rename from local-cli/rnpm/core/test/fixtures/files/package.json rename to local-cli/rnpm/core/__fixtures__/files/package.json diff --git a/local-cli/rnpm/core/test/fixtures/files/project.pbxproj b/local-cli/rnpm/core/__fixtures__/files/project.pbxproj similarity index 100% rename from local-cli/rnpm/core/test/fixtures/files/project.pbxproj rename to local-cli/rnpm/core/__fixtures__/files/project.pbxproj diff --git a/local-cli/rnpm/core/test/fixtures/ios.js b/local-cli/rnpm/core/__fixtures__/ios.js similarity index 100% rename from local-cli/rnpm/core/test/fixtures/ios.js rename to local-cli/rnpm/core/__fixtures__/ios.js diff --git a/local-cli/rnpm/core/test/fixtures/projects.js b/local-cli/rnpm/core/__fixtures__/projects.js similarity index 100% rename from local-cli/rnpm/core/test/fixtures/projects.js rename to local-cli/rnpm/core/__fixtures__/projects.js diff --git a/local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js b/local-cli/rnpm/core/__tests__/android/findAndroidAppFolder.spec.js similarity index 93% rename from local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js rename to local-cli/rnpm/core/__tests__/android/findAndroidAppFolder.spec.js index fe6223aadda65b..3aa2806e684049 100644 --- a/local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js +++ b/local-cli/rnpm/core/__tests__/android/findAndroidAppFolder.spec.js @@ -2,7 +2,7 @@ jest.autoMockOff(); const findAndroidAppFolder = require('../../src/config/android/findAndroidAppFolder'); const mockFs = require('mock-fs'); -const mocks = require('../fixtures/android'); +const mocks = require('../../__fixtures__/android'); describe('android::findAndroidAppFolder', () => { beforeAll(() => mockFs({ diff --git a/local-cli/rnpm/core/test/android/findManifest.spec.js b/local-cli/rnpm/core/__tests__/android/findManifest.spec.js similarity index 91% rename from local-cli/rnpm/core/test/android/findManifest.spec.js rename to local-cli/rnpm/core/__tests__/android/findManifest.spec.js index 27a81fd6d4d954..ca388fa399f627 100644 --- a/local-cli/rnpm/core/test/android/findManifest.spec.js +++ b/local-cli/rnpm/core/__tests__/android/findManifest.spec.js @@ -2,7 +2,7 @@ jest.autoMockOff(); const findManifest = require('../../src/config/android/findManifest'); const mockFs = require('mock-fs'); -const mocks = require('../fixtures/android'); +const mocks = require('../../__fixtures__/android'); describe('android::findManifest', () => { diff --git a/local-cli/rnpm/core/test/android/findPackageClassName.spec.js b/local-cli/rnpm/core/__tests__/android/findPackageClassName.spec.js similarity index 91% rename from local-cli/rnpm/core/test/android/findPackageClassName.spec.js rename to local-cli/rnpm/core/__tests__/android/findPackageClassName.spec.js index fbdd614a16acdb..3ee3dd2d5bf1ae 100644 --- a/local-cli/rnpm/core/test/android/findPackageClassName.spec.js +++ b/local-cli/rnpm/core/__tests__/android/findPackageClassName.spec.js @@ -2,7 +2,7 @@ jest.autoMockOff(); const findPackageClassName = require('../../src/config/android/findPackageClassName'); const mockFs = require('mock-fs'); -const mocks = require('../fixtures/android'); +const mocks = require('../../__fixtures__/android'); describe('android::findPackageClassName', () => { diff --git a/local-cli/rnpm/core/test/android/getDependencyConfig.spec.js b/local-cli/rnpm/core/__tests__/android/getDependencyConfig.spec.js similarity index 96% rename from local-cli/rnpm/core/test/android/getDependencyConfig.spec.js rename to local-cli/rnpm/core/__tests__/android/getDependencyConfig.spec.js index 15126a2de97166..67065a002aad28 100644 --- a/local-cli/rnpm/core/test/android/getDependencyConfig.spec.js +++ b/local-cli/rnpm/core/__tests__/android/getDependencyConfig.spec.js @@ -2,7 +2,7 @@ jest.autoMockOff(); const getDependencyConfig = require('../../src/config/android').dependencyConfig; const mockFs = require('mock-fs'); -const mocks = require('../fixtures/android'); +const mocks = require('../../__fixtures__/android'); const userConfig = {}; describe('android::getDependencyConfig', () => { diff --git a/local-cli/rnpm/core/test/android/getProjectConfig.spec.js b/local-cli/rnpm/core/__tests__/android/getProjectConfig.spec.js similarity index 96% rename from local-cli/rnpm/core/test/android/getProjectConfig.spec.js rename to local-cli/rnpm/core/__tests__/android/getProjectConfig.spec.js index 1e62dbeca0b5bc..f445f5061b641b 100644 --- a/local-cli/rnpm/core/test/android/getProjectConfig.spec.js +++ b/local-cli/rnpm/core/__tests__/android/getProjectConfig.spec.js @@ -2,7 +2,7 @@ jest.autoMockOff(); const getProjectConfig = require('../../src/config/android').projectConfig; const mockFs = require('mock-fs'); -const mocks = require('../fixtures/android'); +const mocks = require('../../__fixtures__/android'); describe('android::getProjectConfig', () => { beforeAll(() => mockFs({ diff --git a/local-cli/rnpm/core/test/android/readManifest.spec.js b/local-cli/rnpm/core/__tests__/android/readManifest.spec.js similarity index 94% rename from local-cli/rnpm/core/test/android/readManifest.spec.js rename to local-cli/rnpm/core/__tests__/android/readManifest.spec.js index 571e183415361e..0c96a4cb152d43 100644 --- a/local-cli/rnpm/core/test/android/readManifest.spec.js +++ b/local-cli/rnpm/core/__tests__/android/readManifest.spec.js @@ -3,7 +3,7 @@ jest.autoMockOff(); const findManifest = require('../../src/config/android/findManifest'); const readManifest = require('../../src/config/android/readManifest'); const mockFs = require('mock-fs'); -const mocks = require('../fixtures/android'); +const mocks = require('../../__fixtures__/android'); describe('android::readManifest', () => { diff --git a/local-cli/rnpm/core/test/findAssets.spec.js b/local-cli/rnpm/core/__tests__/findAssets.spec.js similarity index 93% rename from local-cli/rnpm/core/test/findAssets.spec.js rename to local-cli/rnpm/core/__tests__/findAssets.spec.js index b31d5fac30cb61..bb712aa32aa1a9 100644 --- a/local-cli/rnpm/core/test/findAssets.spec.js +++ b/local-cli/rnpm/core/__tests__/findAssets.spec.js @@ -2,7 +2,7 @@ jest.autoMockOff(); const findAssets = require('../src/config/findAssets'); const mockFs = require('mock-fs'); -const dependencies = require('./fixtures/dependencies'); +const dependencies = require('../__fixtures__/dependencies'); const isArray = (arg) => Object.prototype.toString.call(arg) === '[object Array]'; diff --git a/local-cli/rnpm/core/test/findPlugins.js b/local-cli/rnpm/core/__tests__/findPlugins.spec.js similarity index 100% rename from local-cli/rnpm/core/test/findPlugins.js rename to local-cli/rnpm/core/__tests__/findPlugins.spec.js diff --git a/local-cli/rnpm/core/test/ios/findProject.spec.js b/local-cli/rnpm/core/__tests__/ios/findProject.spec.js similarity index 95% rename from local-cli/rnpm/core/test/ios/findProject.spec.js rename to local-cli/rnpm/core/__tests__/ios/findProject.spec.js index 6626921ba14731..a1c0801d2541e4 100644 --- a/local-cli/rnpm/core/test/ios/findProject.spec.js +++ b/local-cli/rnpm/core/__tests__/ios/findProject.spec.js @@ -2,8 +2,8 @@ jest.autoMockOff(); const findProject = require('../../src/config/ios/findProject'); const mockFs = require('mock-fs'); -const projects = require('../fixtures/projects'); -const ios = require('../fixtures/ios'); +const projects = require('../../__fixtures__/projects'); +const ios = require('../../__fixtures__/ios'); const userConfig = {}; describe('ios::findProject', () => { diff --git a/local-cli/rnpm/core/test/ios/getProjectConfig.spec.js b/local-cli/rnpm/core/__tests__/ios/getProjectConfig.spec.js similarity index 63% rename from local-cli/rnpm/core/test/ios/getProjectConfig.spec.js rename to local-cli/rnpm/core/__tests__/ios/getProjectConfig.spec.js index bba3bedb13071b..5536de3287b4dc 100644 --- a/local-cli/rnpm/core/test/ios/getProjectConfig.spec.js +++ b/local-cli/rnpm/core/__tests__/ios/getProjectConfig.spec.js @@ -2,7 +2,7 @@ jest.autoMockOff(); const getProjectConfig = require('../../src/config/ios').projectConfig; const mockFs = require('mock-fs'); -const projects = require('../fixtures/projects'); +const projects = require('../../__fixtures__/projects'); describe('ios::getProjectConfig', () => { const userConfig = {}; @@ -22,5 +22,15 @@ describe('ios::getProjectConfig', () => { expect(getProjectConfig(folder, userConfig)).toBe(null); }); + it('should return normalized shared library names', () => { + const projectConfig = getProjectConfig('testDir/nested', { + sharedLibraries: ['libc++', 'libz.tbd', 'HealthKit', 'HomeKit.framework'], + }); + + expect(projectConfig.sharedLibraries).toEqual( + ['libc++.tbd', 'libz.tbd', 'HealthKit.framework', 'HomeKit.framework'] + ); + }); + afterEach(mockFs.restore); }); diff --git a/local-cli/rnpm/core/test/makeCommand.spec.js b/local-cli/rnpm/core/__tests__/makeCommand.spec.js similarity index 100% rename from local-cli/rnpm/core/test/makeCommand.spec.js rename to local-cli/rnpm/core/__tests__/makeCommand.spec.js diff --git a/local-cli/rnpm/core/src/config/ios/index.js b/local-cli/rnpm/core/src/config/ios/index.js index 04ada20bfc84f8..5e1f2c0917328d 100644 --- a/local-cli/rnpm/core/src/config/ios/index.js +++ b/local-cli/rnpm/core/src/config/ios/index.js @@ -1,6 +1,19 @@ const path = require('path'); const findProject = require('./findProject'); +/** + * For libraries specified without an extension, add '.tbd' for those that + * start with 'lib' and '.framework' to the rest. + */ +const mapSharedLibaries = (libraries) => { + return libraries.map(name => { + if (path.extname(name)) { + return name; + } + return name + (name.indexOf('lib') === 0 ? '.tbd' : '.framework'); + }); +}; + /** * Returns project config by analyzing given folder and applying some user defaults * when constructing final object @@ -24,6 +37,7 @@ exports.projectConfig = function projectConfigIOS(folder, userConfig) { projectPath: projectPath, projectName: path.basename(projectPath), libraryFolder: userConfig.libraryFolder || 'Libraries', + sharedLibraries: mapSharedLibaries(userConfig.sharedLibraries || []), plist: userConfig.plist || [], }; }; diff --git a/local-cli/rnpm/core/src/findPlugins.js b/local-cli/rnpm/core/src/findPlugins.js index 58d38559ee9e86..95bb2b20d6307b 100644 --- a/local-cli/rnpm/core/src/findPlugins.js +++ b/local-cli/rnpm/core/src/findPlugins.js @@ -1,5 +1,4 @@ const path = require('path'); -const fs = require('fs'); const union = require('lodash').union; const uniq = require('lodash').uniq; const flatten = require('lodash').flatten; @@ -9,7 +8,7 @@ const flatten = require('lodash').flatten; * @param {String} dependency Name of the dependency * @return {Boolean} If dependency is a rnpm plugin */ -const isPlugin = (dependency) => !!~dependency.indexOf('rnpm-plugin-'); +const isPlugin = (dependency) => dependency.indexOf('rnpm-plugin-') === 0; const findPluginInFolder = (folder) => { var pjson; diff --git a/local-cli/rnpm/core/src/getCommands.js b/local-cli/rnpm/core/src/getCommands.js index 0de2cf7feadbaa..0a86b64140f0a6 100644 --- a/local-cli/rnpm/core/src/getCommands.js +++ b/local-cli/rnpm/core/src/getCommands.js @@ -1,20 +1,11 @@ const path = require('path'); -const fs = require('fs'); -const uniq = require('lodash').uniq; -const flattenDeep = require('lodash').flattenDeep; const findPlugins = require('./findPlugins'); /** * @return {Array} Array of commands */ module.exports = function getCommands() { - const rnpmRoot = path.join(__dirname, '..'); const appRoot = process.cwd(); - return uniq( - flattenDeep([ - findPlugins([rnpmRoot]).map(require), - findPlugins([appRoot]).map(name => require(path.join(appRoot, 'node_modules', name))), - ]) - , 'name'); + return findPlugins([appRoot]).map(name => require(path.join(appRoot, 'node_modules', name))); }; diff --git a/local-cli/rnpm/core/test/getCommands.spec.js b/local-cli/rnpm/core/test/getCommands.spec.js deleted file mode 100644 index 4389b064da5969..00000000000000 --- a/local-cli/rnpm/core/test/getCommands.spec.js +++ /dev/null @@ -1,171 +0,0 @@ -jest.autoMockOff(); - -const path = require('path'); -const mock = require('mock-require'); -const rewire = require('rewire'); - -const commands = require('./fixtures/commands'); -const isArray = (arg) => - Object.prototype.toString.call(arg) === '[object Array]'; - -/** - * Paths to two possible `node_modules` locations `rnpm` can be installed - */ -const LOCAL_NODE_MODULES = path.join(process.cwd(), 'node_modules'); -const GLOBAL_NODE_MODULES = '/usr/local/lib/node_modules'; - -/** - * Paths to `package.json` of project, and rnpm - in two installation locations - */ -const APP_JSON = path.join(process.cwd(), 'package.json'); -const GLOBAL_RNPM_PJSON = path.join(GLOBAL_NODE_MODULES, '/rnpm/package.json'); -const LOCAL_RNPM_PJSON = path.join(LOCAL_NODE_MODULES, 'rnpm/package.json'); - -/** - * Sample `rnpm` plugin used in test cases - */ -const SAMPLE_RNPM_PLUGIN = 'rnpm-plugin-test'; - -/** - * Sample `package.json` of RNPM that will be used in test cases - */ -const SAMPLE_RNPM_JSON = { - dependencies: { - [SAMPLE_RNPM_PLUGIN]: '*', - }, -}; - -/** - * Project without `rnpm` plugins defined - */ -const NO_PLUGINS_JSON = { - dependencies: {}, -}; - -const getCommands = rewire('../src/getCommands'); -var revert; - -describe('getCommands', () => { - - afterEach(mock.stopAll); - - describe('in all installations', () => { - - beforeEach(() => { - revert = getCommands.__set__({ - __dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'), - }); - mock(APP_JSON, NO_PLUGINS_JSON); - }); - - afterEach(() => revert()); - - it('list of the commands should be a non-empty array', () => { - mock(APP_JSON, NO_PLUGINS_JSON); - mock(LOCAL_RNPM_PJSON, SAMPLE_RNPM_JSON); - mock(SAMPLE_RNPM_PLUGIN, commands.single); - - expect(getCommands().length).not.toBe(0); - expect(isArray(getCommands())).toBeTruthy(); - }); - - it('should export one command', () => { - mock(LOCAL_RNPM_PJSON, SAMPLE_RNPM_JSON); - mock(SAMPLE_RNPM_PLUGIN, commands.single); - - expect(getCommands().length).toEqual(1); - }); - - it('should export multiple commands', () => { - mock(LOCAL_RNPM_PJSON, SAMPLE_RNPM_JSON); - mock(SAMPLE_RNPM_PLUGIN, commands.multiple); - - expect(getCommands().length).toEqual(2); - }); - - it('should export unique list of commands by name', () => { - mock(LOCAL_RNPM_PJSON, { - dependencies: { - [SAMPLE_RNPM_PLUGIN]: '*', - [`${SAMPLE_RNPM_PLUGIN}-2`]: '*', - }, - }); - - mock(SAMPLE_RNPM_PLUGIN, commands.single); - mock(`${SAMPLE_RNPM_PLUGIN}-2`, commands.single); - - expect(getCommands().length).toEqual(1); - }); - - }); - - describe('project plugins', () => { - /** - * In this test suite we only test project plugins thus we make sure - * `rnpm` package.json is properly mocked - */ - beforeEach(() => { - mock(LOCAL_RNPM_PJSON, NO_PLUGINS_JSON); - mock(GLOBAL_RNPM_PJSON, NO_PLUGINS_JSON); - }); - - afterEach(() => revert()); - - it('should load when installed locally', () => { - revert = getCommands.__set__({ - __dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'), - }); - - mock(APP_JSON, SAMPLE_RNPM_JSON); - mock( - path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN), - commands.single - ); - - expect(getCommands()[0]).toEqual(commands.single); - }); - - it('should load when installed globally', () => { - revert = getCommands.__set__({ - __dirname: path.join(GLOBAL_NODE_MODULES, 'rnpm/src'), - }); - - mock(APP_JSON, SAMPLE_RNPM_JSON); - mock( - path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN), - commands.single - ); - - expect(getCommands()[0]).toEqual(commands.single); - }); - - }); - - describe('rnpm and project plugins', () => { - - beforeEach(() => { - revert = getCommands.__set__({ - __dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'), - }); - }); - - afterEach(() => revert()); - - it('should load concatenated list of plugins', () => { - mock(APP_JSON, SAMPLE_RNPM_JSON); - mock(LOCAL_RNPM_PJSON, { - dependencies: { - [`${SAMPLE_RNPM_PLUGIN}-2`]: '*', - }, - }); - - mock( - path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN), - commands.multiple - ); - mock(`${SAMPLE_RNPM_PLUGIN}-2`, commands.single); - - expect(getCommands().length).toEqual(3); - }); - }); -}); diff --git a/local-cli/rnpm/install/index.js b/local-cli/rnpm/install/index.js deleted file mode 100644 index 66891919c75adc..00000000000000 --- a/local-cli/rnpm/install/index.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = [ - { - func: require('./src/install'), - description: 'Install and link native dependencies', - name: 'install [packageName]', - }, { - func: require('./src/uninstall'), - description: 'Uninstall and unlink native dependencies', - name: 'uninstall [packageName]', - }, -]; diff --git a/local-cli/rnpm/install/install.js b/local-cli/rnpm/install/install.js new file mode 100644 index 00000000000000..bb43a2787d9d53 --- /dev/null +++ b/local-cli/rnpm/install/install.js @@ -0,0 +1,5 @@ +module.exports = { + func: require('./src/install'), + description: 'install and link native dependencies', + name: 'install ', +}; diff --git a/local-cli/rnpm/install/src/install.js b/local-cli/rnpm/install/src/install.js index d9eed2db433ac1..77d6bd427687b9 100644 --- a/local-cli/rnpm/install/src/install.js +++ b/local-cli/rnpm/install/src/install.js @@ -7,7 +7,7 @@ const spawnOpts = { log.heading = 'rnpm-install'; -module.exports = function install(config, args, callback) { +module.exports = function install(args, config) { const name = args[0]; var res = spawnSync('npm', ['install', name, '--save'], spawnOpts); diff --git a/local-cli/rnpm/install/src/uninstall.js b/local-cli/rnpm/install/src/uninstall.js index a1d7d9fc2d6b49..ff6b41f1c94200 100644 --- a/local-cli/rnpm/install/src/uninstall.js +++ b/local-cli/rnpm/install/src/uninstall.js @@ -7,7 +7,7 @@ const spawnOpts = { log.heading = 'rnpm-install'; -module.exports = function install(config, args, callback) { +module.exports = function install(args, config) { const name = args[0]; var res = spawnSync('rnpm', ['unlink', name], spawnOpts); diff --git a/local-cli/rnpm/install/uninstall.js b/local-cli/rnpm/install/uninstall.js new file mode 100644 index 00000000000000..258cae1b06404e --- /dev/null +++ b/local-cli/rnpm/install/uninstall.js @@ -0,0 +1,5 @@ +module.exports = { + func: require('./src/uninstall'), + description: 'uninstall and unlink native dependencies', + name: 'uninstall ', +}; diff --git a/local-cli/rnpm/link/test/fixtures/android/0.17/MainActivity.java b/local-cli/rnpm/link/__fixtures__/android/0.17/MainActivity.java similarity index 100% rename from local-cli/rnpm/link/test/fixtures/android/0.17/MainActivity.java rename to local-cli/rnpm/link/__fixtures__/android/0.17/MainActivity.java diff --git a/local-cli/rnpm/link/test/fixtures/android/0.17/patchedMainActivity.java b/local-cli/rnpm/link/__fixtures__/android/0.17/patchedMainActivity.java similarity index 100% rename from local-cli/rnpm/link/test/fixtures/android/0.17/patchedMainActivity.java rename to local-cli/rnpm/link/__fixtures__/android/0.17/patchedMainActivity.java diff --git a/local-cli/rnpm/link/test/fixtures/android/0.18/MainActivity.java b/local-cli/rnpm/link/__fixtures__/android/0.18/MainActivity.java similarity index 100% rename from local-cli/rnpm/link/test/fixtures/android/0.18/MainActivity.java rename to local-cli/rnpm/link/__fixtures__/android/0.18/MainActivity.java diff --git a/local-cli/rnpm/link/test/fixtures/android/0.18/patchedMainActivity.java b/local-cli/rnpm/link/__fixtures__/android/0.18/patchedMainActivity.java similarity index 100% rename from local-cli/rnpm/link/test/fixtures/android/0.18/patchedMainActivity.java rename to local-cli/rnpm/link/__fixtures__/android/0.18/patchedMainActivity.java diff --git a/local-cli/rnpm/link/test/fixtures/android/0.20/MainActivity.java b/local-cli/rnpm/link/__fixtures__/android/0.20/MainActivity.java similarity index 100% rename from local-cli/rnpm/link/test/fixtures/android/0.20/MainActivity.java rename to local-cli/rnpm/link/__fixtures__/android/0.20/MainActivity.java diff --git a/local-cli/rnpm/link/test/fixtures/android/build.gradle b/local-cli/rnpm/link/__fixtures__/android/build.gradle similarity index 100% rename from local-cli/rnpm/link/test/fixtures/android/build.gradle rename to local-cli/rnpm/link/__fixtures__/android/build.gradle diff --git a/local-cli/rnpm/link/test/fixtures/android/patchedBuild.gradle b/local-cli/rnpm/link/__fixtures__/android/patchedBuild.gradle similarity index 100% rename from local-cli/rnpm/link/test/fixtures/android/patchedBuild.gradle rename to local-cli/rnpm/link/__fixtures__/android/patchedBuild.gradle diff --git a/local-cli/rnpm/link/test/fixtures/android/patchedSettings.gradle b/local-cli/rnpm/link/__fixtures__/android/patchedSettings.gradle similarity index 100% rename from local-cli/rnpm/link/test/fixtures/android/patchedSettings.gradle rename to local-cli/rnpm/link/__fixtures__/android/patchedSettings.gradle diff --git a/local-cli/rnpm/link/test/fixtures/android/settings.gradle b/local-cli/rnpm/link/__fixtures__/android/settings.gradle similarity index 100% rename from local-cli/rnpm/link/test/fixtures/android/settings.gradle rename to local-cli/rnpm/link/__fixtures__/android/settings.gradle diff --git a/local-cli/rnpm/link/test/fixtures/linearGradient.pbxproj b/local-cli/rnpm/link/__fixtures__/linearGradient.pbxproj similarity index 100% rename from local-cli/rnpm/link/test/fixtures/linearGradient.pbxproj rename to local-cli/rnpm/link/__fixtures__/linearGradient.pbxproj diff --git a/local-cli/rnpm/link/test/fixtures/project.pbxproj b/local-cli/rnpm/link/__fixtures__/project.pbxproj similarity index 100% rename from local-cli/rnpm/link/test/fixtures/project.pbxproj rename to local-cli/rnpm/link/__fixtures__/project.pbxproj diff --git a/local-cli/rnpm/link/test/android/patches/applyPatch.js b/local-cli/rnpm/link/__tests__/android/applyPatch.spec.js similarity index 53% rename from local-cli/rnpm/link/test/android/patches/applyPatch.js rename to local-cli/rnpm/link/__tests__/android/applyPatch.spec.js index cc5ba34f1d7092..35ff810574be62 100644 --- a/local-cli/rnpm/link/test/android/patches/applyPatch.js +++ b/local-cli/rnpm/link/__tests__/android/applyPatch.spec.js @@ -1,17 +1,19 @@ -const chai = require('chai'); -const expect = chai.expect; -const applyParams = require('../../../src/android/patches/applyParams'); +'use strict'; + +jest.autoMockOff(); + +const applyParams = require('../../src/android/patches/applyParams'); describe('applyParams', () => { it('apply params to the string', () => { expect( applyParams('${foo}', {foo: 'foo'}, 'react-native') - ).to.be.equal('this.getResources().getString(R.strings.reactNative_foo)'); + ).toEqual('this.getResources().getString(R.strings.reactNative_foo)'); }); it('use null if no params provided', () => { expect( applyParams('${foo}', {}, 'react-native') - ).to.be.equal('null'); + ).toEqual('null'); }); }); diff --git a/local-cli/rnpm/link/__tests__/android/isInstalled.spec.js b/local-cli/rnpm/link/__tests__/android/isInstalled.spec.js new file mode 100644 index 00000000000000..56befa52bf836b --- /dev/null +++ b/local-cli/rnpm/link/__tests__/android/isInstalled.spec.js @@ -0,0 +1,20 @@ +'use strict'; + +jest.autoMockOff(); + +const path = require('path'); +const isInstalled = require('../../src/android/isInstalled'); + +const projectConfig = { + buildGradlePath: path.join(__dirname, '../../__fixtures__/android/patchedBuild.gradle'), +}; + +describe('android::isInstalled', () => { + it('should return true when project is already in build.gradle', () => + expect(isInstalled(projectConfig, 'test')).toBeTruthy() + ); + + it('should return false when project is not in build.gradle', () => + expect(isInstalled(projectConfig, 'test2')).toBeFalsy() + ); +}); diff --git a/local-cli/rnpm/link/__tests__/android/makeBuildPatch.spec.js b/local-cli/rnpm/link/__tests__/android/makeBuildPatch.spec.js new file mode 100644 index 00000000000000..e20e2b59296497 --- /dev/null +++ b/local-cli/rnpm/link/__tests__/android/makeBuildPatch.spec.js @@ -0,0 +1,18 @@ +'use strict'; + +jest.autoMockOff(); + +const makeBuildPatch = require('../../src/android/patches/makeBuildPatch'); +const name = 'test'; + +describe('makeBuildPatch', () => { + it('should build a patch function', () => { + expect(Object.prototype.toString(makeBuildPatch(name))) + .toBe('[object Object]'); + }); + + it('should make a correct patch', () => { + const {patch} = makeBuildPatch(name); + expect(patch).toBe(` compile project(':${name}')\n`); + }); +}); diff --git a/local-cli/rnpm/link/__tests__/android/makeImportPatch.spec.js b/local-cli/rnpm/link/__tests__/android/makeImportPatch.spec.js new file mode 100644 index 00000000000000..6ee21406855f79 --- /dev/null +++ b/local-cli/rnpm/link/__tests__/android/makeImportPatch.spec.js @@ -0,0 +1,20 @@ +'use strict'; + +jest.autoMockOff(); + +const makeImportPatch = require('../../src/android/patches/makeImportPatch'); + +const packageImportPath = 'import some.example.project'; + +describe('makeImportPatch', () => { + it('should build a patch', () => { + expect(Object.prototype.toString(makeImportPatch(packageImportPath))) + .toBe('[object Object]'); + }); + + it('MainActivity contains a correct import patch', () => { + const {patch} = makeImportPatch(packageImportPath); + + expect(patch).toBe('\n' + packageImportPath); + }); +}); diff --git a/local-cli/rnpm/link/__tests__/android/makePackagePatch.spec.js b/local-cli/rnpm/link/__tests__/android/makePackagePatch.spec.js new file mode 100644 index 00000000000000..2a87848ff5874f --- /dev/null +++ b/local-cli/rnpm/link/__tests__/android/makePackagePatch.spec.js @@ -0,0 +1,28 @@ +'use strict'; + +jest.autoMockOff(); + +const makePackagePatch = require('../../src/android/patches/makePackagePatch'); +const applyParams = require('../../src/android/patches/applyParams'); + +const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; +const name = 'some-library'; +const params = { + foo: 'foo', + bar: 'bar', +}; + +describe('makePackagePatch@0.20', () => { + it('should build a patch', () => { + const packagePatch = makePackagePatch(packageInstance, params, name); + expect(Object.prototype.toString(packagePatch)) + .toBe('[object Object]'); + }); + + it('MainActivity contains a correct 0.20 import patch', () => { + const {patch} = makePackagePatch(packageInstance, params, name); + const processedInstance = applyParams(packageInstance, params, name); + + expect(patch).toBe(',\n ' + processedInstance); + }); +}); diff --git a/local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js b/local-cli/rnpm/link/__tests__/android/makeSettingsPatch.spec.js similarity index 65% rename from local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js rename to local-cli/rnpm/link/__tests__/android/makeSettingsPatch.spec.js index 2dfe97c6c49f66..ca3b303e968fce 100644 --- a/local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js +++ b/local-cli/rnpm/link/__tests__/android/makeSettingsPatch.spec.js @@ -1,8 +1,9 @@ -const fs = require('fs'); +'use strict'; + +jest.autoMockOff(); + const path = require('path'); -const chai = require('chai'); -const expect = chai.expect; -const makeSettingsPatch = require('../../../src/android/patches/makeSettingsPatch'); +const makeSettingsPatch = require('../../src/android/patches/makeSettingsPatch'); const name = 'test'; const projectConfig = { @@ -15,9 +16,9 @@ const dependencyConfig = { describe('makeSettingsPatch', () => { it('should build a patch function', () => { - expect( - makeSettingsPatch(name, dependencyConfig, {}, projectConfig) - ).to.be.an('object'); + expect(Object.prototype.toString( + makeSettingsPatch(name, dependencyConfig, projectConfig) + )).toBe('[object Object]'); }); it('should make a correct patch', () => { @@ -26,8 +27,10 @@ describe('makeSettingsPatch', () => { dependencyConfig.sourceDir ); - expect(makeSettingsPatch(name, dependencyConfig, projectConfig).patch) - .to.be.equal( + const {patch} = makeSettingsPatch(name, dependencyConfig, projectConfig); + + expect(patch) + .toBe( `include ':${name}'\n` + `project(':${name}').projectDir = ` + `new File(rootProject.projectDir, '${projectDir}')\n` diff --git a/local-cli/rnpm/link/test/getDependencyConfig.spec.js b/local-cli/rnpm/link/__tests__/getDependencyConfig.spec.js similarity index 64% rename from local-cli/rnpm/link/test/getDependencyConfig.spec.js rename to local-cli/rnpm/link/__tests__/getDependencyConfig.spec.js index 7fba3b65bccd99..eeb2bfa437bdef 100644 --- a/local-cli/rnpm/link/test/getDependencyConfig.spec.js +++ b/local-cli/rnpm/link/__tests__/getDependencyConfig.spec.js @@ -1,5 +1,7 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const getDependencyConfig = require('../src/getDependencyConfig'); const sinon = require('sinon'); @@ -9,8 +11,8 @@ describe('getDependencyConfig', () => { getDependencyConfig: sinon.stub(), }; - expect(getDependencyConfig(config, ['abcd'])).to.be.an.array; - expect(config.getDependencyConfig.callCount).to.equals(1); + expect(Array.isArray(getDependencyConfig(config, ['abcd']))).toBeTruthy(); + expect(config.getDependencyConfig.callCount).toEqual(1); }); it('should filter out invalid react-native projects', () => { @@ -18,6 +20,6 @@ describe('getDependencyConfig', () => { getDependencyConfig: sinon.stub().throws(new Error('Cannot require')), }; - expect(getDependencyConfig(config, ['abcd'])).to.deep.equal([]); + expect(getDependencyConfig(config, ['abcd'])).toEqual([]); }); }); diff --git a/local-cli/rnpm/link/test/getProjectDependencies.spec.js b/local-cli/rnpm/link/__tests__/getProjectDependencies.spec.js similarity index 55% rename from local-cli/rnpm/link/test/getProjectDependencies.spec.js rename to local-cli/rnpm/link/__tests__/getProjectDependencies.spec.js index 21abb764828b07..2edd173619ee21 100644 --- a/local-cli/rnpm/link/test/getProjectDependencies.spec.js +++ b/local-cli/rnpm/link/__tests__/getProjectDependencies.spec.js @@ -1,27 +1,23 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const getProjectDependencies = require('../src/getProjectDependencies'); -const mock = require('mock-require'); const path = require('path'); describe('getProjectDependencies', () => { it('should return an array of project dependencies', () => { - mock( + jest.setMock( path.join(process.cwd(), './package.json'), - { dependencies: { lodash: '^6.0.0', 'react-native': '^16.0.0' } } + { dependencies: { lodash: '^6.0.0', 'react-native': '^16.0.0' }} ); - expect(getProjectDependencies()).to.deep.equals(['lodash']); + expect(getProjectDependencies()).toEqual(['lodash']); }); it('should return an empty array when no dependencies set', () => { - mock(path.join(process.cwd(), './package.json'), {}); - expect(getProjectDependencies()).to.deep.equals([]); + jest.setMock(path.join(process.cwd(), './package.json'), {}); + expect(getProjectDependencies()).toEqual([]); }); - - afterEach(() => { - mock.stopAll(); - }); - }); diff --git a/local-cli/rnpm/link/test/groupFilesByType.spec.js b/local-cli/rnpm/link/__tests__/groupFilesByType.spec.js similarity index 69% rename from local-cli/rnpm/link/test/groupFilesByType.spec.js rename to local-cli/rnpm/link/__tests__/groupFilesByType.spec.js index ac5ecfc691a582..805851da4079de 100644 --- a/local-cli/rnpm/link/test/groupFilesByType.spec.js +++ b/local-cli/rnpm/link/__tests__/groupFilesByType.spec.js @@ -1,5 +1,7 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const groupFilesByType = require('../src/groupFilesByType'); describe('groupFilesByType', () => { @@ -16,8 +18,8 @@ describe('groupFilesByType', () => { const groupedFiles = groupFilesByType(fonts.concat(images)); - expect(groupedFiles.font).to.deep.equal(fonts); - expect(groupedFiles.image).to.deep.equal(images); + expect(groupedFiles.font).toEqual(fonts); + expect(groupedFiles.image).toEqual(images); }); }); diff --git a/local-cli/rnpm/link/__tests__/ios/addFileToProject.spec.js b/local-cli/rnpm/link/__tests__/ios/addFileToProject.spec.js new file mode 100644 index 00000000000000..a591394b31721b --- /dev/null +++ b/local-cli/rnpm/link/__tests__/ios/addFileToProject.spec.js @@ -0,0 +1,27 @@ +'use strict'; + +jest.autoMockOff(); + +const xcode = require('xcode'); +const path = require('path'); +const addFileToProject = require('../../src/ios/addFileToProject'); +const _ = require('lodash'); + +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); + +describe('ios::addFileToProject', () => { + beforeEach(() => { + project.parseSync(); + }); + + xit('should add file to a project', () => { + expect( + _.includes( + Object.keys(project.pbxFileReferenceSection()), + addFileToProject(project, '../../__fixtures__/linearGradient.pbxproj').fileRef + ) + ).toBeTruthy(); + }); +}); diff --git a/local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js b/local-cli/rnpm/link/__tests__/ios/addProjectToLibraries.spec.js similarity index 67% rename from local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js rename to local-cli/rnpm/link/__tests__/ios/addProjectToLibraries.spec.js index 696cce114c4218..40673e4438c3a3 100644 --- a/local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/addProjectToLibraries.spec.js @@ -1,14 +1,18 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); +const path = require('path'); const PbxFile = require('xcode/lib/pbxFile'); const addProjectToLibraries = require('../../src/ios/addProjectToLibraries'); const last = require('lodash').last; -const project = xcode.project('test/fixtures/project.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); describe('ios::addProjectToLibraries', () => { - beforeEach(() => { project.parseSync(); }); @@ -21,8 +25,7 @@ describe('ios::addProjectToLibraries', () => { const child = last(libraries.children); - expect(child).to.have.keys(['value', 'comment']); - expect(child.comment).to.equals(file.basename); + expect((['value', 'comment']), child).toBeTruthy(); + expect(child.comment).toBe(file.basename); }); - }); diff --git a/local-cli/rnpm/link/__tests__/ios/addSharedLibraries.spec.js b/local-cli/rnpm/link/__tests__/ios/addSharedLibraries.spec.js new file mode 100644 index 00000000000000..f407fd980229c1 --- /dev/null +++ b/local-cli/rnpm/link/__tests__/ios/addSharedLibraries.spec.js @@ -0,0 +1,45 @@ +'use strict'; + +jest.autoMockOff(); + +const xcode = require('xcode'); +const path = require('path'); +const addSharedLibraries = require('../../src/ios/addSharedLibraries'); +const getGroup = require('../../src/ios/getGroup'); + +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); + +describe('ios::addSharedLibraries', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should automatically create Frameworks group', () => { + expect(getGroup(project, 'Frameworks')).toBeNull(); + addSharedLibraries(project, ['libz.tbd']); + expect(getGroup(project, 'Frameworks')).not.toBeNull(); + }); + + it('should add shared libraries to project', () => { + addSharedLibraries(project, ['libz.tbd']); + + const frameworksGroup = getGroup(project, 'Frameworks'); + expect(frameworksGroup.children.length).toEqual(1); + expect(frameworksGroup.children[0].comment).toEqual('libz.tbd'); + + addSharedLibraries(project, ['MessageUI.framework']); + expect(frameworksGroup.children.length).toEqual(2); + }); + + it('should not add duplicate libraries to project', () => { + addSharedLibraries(project, ['libz.tbd']); + addSharedLibraries(project, ['libz.tbd']); + + const frameworksGroup = getGroup(project, 'Frameworks'); + expect(frameworksGroup.children.length).toEqual(1); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/createGroup.spec.js b/local-cli/rnpm/link/__tests__/ios/createGroup.spec.js similarity index 70% rename from local-cli/rnpm/link/test/ios/createGroup.spec.js rename to local-cli/rnpm/link/__tests__/ios/createGroup.spec.js index f4653ea612c08c..2e95c8f1865209 100644 --- a/local-cli/rnpm/link/test/ios/createGroup.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/createGroup.spec.js @@ -1,21 +1,25 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); +const path = require('path'); const createGroup = require('../../src/ios/createGroup'); const getGroup = require('../../src/ios/getGroup'); const last = require('lodash').last; -const project = xcode.project('test/fixtures/project.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); describe('ios::createGroup', () => { - beforeEach(() => { project.parseSync(); }); it('should create a group with given name', () => { const createdGroup = createGroup(project, 'Resources'); - expect(createdGroup.name).to.equals('Resources'); + expect(createdGroup.name).toBe('Resources'); }); it('should attach group to main project group', () => { @@ -24,7 +28,7 @@ describe('ios::createGroup', () => { expect( last(mainGroup.children).comment - ).to.equals(createdGroup.name); + ).toBe(createdGroup.name); }); it('should create a nested group with given path', () => { @@ -33,7 +37,7 @@ describe('ios::createGroup', () => { expect( last(outerGroup.children).comment - ).to.equals(createdGroup.name); + ).toBe(createdGroup.name); }); it('should-not create already created groups', () => { @@ -42,8 +46,11 @@ describe('ios::createGroup', () => { const mainGroup = getGroup(project); expect( - mainGroup.children.filter(group => group.comment === 'Libraries').length - ).to.equals(1); - expect(last(outerGroup.children).comment).to.equals(createdGroup.name); + mainGroup + .children + .filter(group => group.comment === 'Libraries') + .length + ).toBe(1); + expect(last(outerGroup.children).comment).toBe(createdGroup.name); }); }); diff --git a/local-cli/rnpm/link/test/ios/getBuildProperty.spec.js b/local-cli/rnpm/link/__tests__/ios/getBuildProperty.spec.js similarity index 61% rename from local-cli/rnpm/link/test/ios/getBuildProperty.spec.js rename to local-cli/rnpm/link/__tests__/ios/getBuildProperty.spec.js index 1ca3f30a605ed7..e913758312daee 100644 --- a/local-cli/rnpm/link/test/ios/getBuildProperty.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/getBuildProperty.spec.js @@ -1,19 +1,22 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); +const path = require('path'); const getBuildProperty = require('../../src/ios/getBuildProperty'); -const project = xcode.project('test/fixtures/project.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); describe('ios::getBuildProperty', () => { - beforeEach(() => { project.parseSync(); }); it('should return build property from main target', () => { const plistPath = getBuildProperty(project, 'INFOPLIST_FILE'); - expect(plistPath).to.equals('"Basic/Info.plist"'); + expect(plistPath).toEqual('"Basic/Info.plist"'); }); - }); diff --git a/local-cli/rnpm/link/test/ios/getGroup.spec.js b/local-cli/rnpm/link/__tests__/ios/getGroup.spec.js similarity index 62% rename from local-cli/rnpm/link/test/ios/getGroup.spec.js rename to local-cli/rnpm/link/__tests__/ios/getGroup.spec.js index 5df45f7f000f78..6d1b770dc0e863 100644 --- a/local-cli/rnpm/link/test/ios/getGroup.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/getGroup.spec.js @@ -1,9 +1,14 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); const getGroup = require('../../src/ios/getGroup'); +const path = require('path'); -const project = xcode.project('test/fixtures/project.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); describe('ios::getGroup', () => { beforeEach(() => { @@ -12,25 +17,25 @@ describe('ios::getGroup', () => { it('should return a top-level group', () => { const group = getGroup(project, 'Libraries'); - expect(group.children.length > 0).to.be.true; // our test top-level Libraries has children - expect(group.name).to.equals('Libraries'); + expect(group.children.length > 0).toBeTruthy(); + expect(group.name).toBe('Libraries'); }); it('should return nested group when specified', () => { const group = getGroup(project, 'NestedGroup/Libraries'); - expect(group.children.length).to.equals(0); // our test nested Libraries is empty - expect(group.name).to.equals('Libraries'); + expect(group.children.length).toBe(0); // our test nested Libraries is empty + expect(group.name).toBe('Libraries'); }); it('should return null when no group found', () => { const group = getGroup(project, 'I-Dont-Exist'); - expect(group).to.be.null; + expect(group).toBeNull(); }); it('should return top-level group when name not specified', () => { const mainGroupId = project.getFirstProject().firstProject.mainGroup; const mainGroup = project.getPBXGroupByKey(mainGroupId); const group = getGroup(project); - expect(group).to.equals(mainGroup); + expect(group).toEqual(mainGroup); }); }); diff --git a/local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js b/local-cli/rnpm/link/__tests__/ios/getHeaderSearchPath.spec.js similarity index 92% rename from local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js rename to local-cli/rnpm/link/__tests__/ios/getHeaderSearchPath.spec.js index 216979ca41273c..bcf3b8e4a0dc31 100644 --- a/local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/getHeaderSearchPath.spec.js @@ -1,12 +1,13 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const getHeaderSearchPath = require('../../src/ios/getHeaderSearchPath'); const path = require('path'); const SRC_DIR = path.join('react-native-project', 'ios'); describe('ios::getHeaderSearchPath', () => { - /** * See https://github.com/Microsoft/react-native-code-push */ @@ -18,7 +19,7 @@ describe('ios::getHeaderSearchPath', () => { const searchPath = getHeaderSearchPath(SRC_DIR, files); - expect(searchPath).to.equal( + expect(searchPath).toBe( `"${['$(SRCROOT)', '..', 'node_modules', 'package'].join(path.sep)}"` ); }); @@ -34,7 +35,7 @@ describe('ios::getHeaderSearchPath', () => { const searchPath = getHeaderSearchPath(SRC_DIR, files); - expect(searchPath).to.equal( + expect(searchPath).toBe( `"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"` ); }); @@ -51,7 +52,7 @@ describe('ios::getHeaderSearchPath', () => { const searchPath = getHeaderSearchPath(SRC_DIR, files); - expect(searchPath).to.equal( + expect(searchPath).toBe( `"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"` ); }); diff --git a/local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js b/local-cli/rnpm/link/__tests__/ios/getHeadersInFolder.spec.js similarity index 64% rename from local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js rename to local-cli/rnpm/link/__tests__/ios/getHeadersInFolder.spec.js index 54f4c0f6e914fa..206f9f50cc9b39 100644 --- a/local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/getHeadersInFolder.spec.js @@ -1,27 +1,27 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const getHeadersInFolder = require('../../src/ios/getHeadersInFolder'); -const mock = require('mock-fs'); describe('ios::getHeadersInFolder', () => { - - it('should return an array of all headers in given folder', () => { - mock({ + xit('should return an array of all headers in given folder', () => { + jest.setMock({ 'FileA.h': '', 'FileB.h': '', }); const foundHeaders = getHeadersInFolder(process.cwd()); - expect(foundHeaders.length).to.equals(2); + expect(foundHeaders.length).toBe(2); getHeadersInFolder(process.cwd()).forEach(headerPath => { expect(headerPath).to.contain(process.cwd()); }); }); - it('should ignore all headers in Pods, Examples & node_modules', () => { - mock({ + xit('should ignore all headers in Pods, Examples & node_modules', () => { + jest.setMock({ 'FileA.h': '', 'FileB.h': '', Pods: { @@ -37,9 +37,4 @@ describe('ios::getHeadersInFolder', () => { expect(getHeadersInFolder(process.cwd()).length).to.equals(2); }); - - afterEach(() => { - mock.restore(); - }); - }); diff --git a/local-cli/rnpm/link/test/ios/getPlist.spec.js b/local-cli/rnpm/link/__tests__/ios/getPlist.spec.js similarity index 55% rename from local-cli/rnpm/link/test/ios/getPlist.spec.js rename to local-cli/rnpm/link/__tests__/ios/getPlist.spec.js index 76e072755c2661..d222b3f475711a 100644 --- a/local-cli/rnpm/link/test/ios/getPlist.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/getPlist.spec.js @@ -1,23 +1,24 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); const getPlist = require('../../src/ios/getPlist'); +const path = require('path'); -const project = xcode.project('test/fixtures/project.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); describe('ios::getPlist', () => { - beforeEach(() => { project.parseSync(); }); it('should return null when `.plist` file missing', () => { const plistPath = getPlist(project, process.cwd()); - expect(plistPath).to.equals(null); - }); - - it.skip('should return parsed `plist`', () => { - // @todo mock fs here + expect(plistPath).toBeNull(); }); + // @todo - Happy scenario }); diff --git a/local-cli/rnpm/link/test/ios/getPlistPath.spec.js b/local-cli/rnpm/link/__tests__/ios/getPlistPath.spec.js similarity index 59% rename from local-cli/rnpm/link/test/ios/getPlistPath.spec.js rename to local-cli/rnpm/link/__tests__/ios/getPlistPath.spec.js index c8c758ac04e3b1..4b21b484e7c615 100644 --- a/local-cli/rnpm/link/test/ios/getPlistPath.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/getPlistPath.spec.js @@ -1,19 +1,22 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); const getPlistPath = require('../../src/ios/getPlistPath'); +const path = require('path'); -const project = xcode.project('test/fixtures/project.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); describe('ios::getPlistPath', () => { - beforeEach(() => { project.parseSync(); }); it('should return path without Xcode $(SRCROOT)', () => { const plistPath = getPlistPath(project, '/'); - expect(plistPath).to.equals('/Basic/Info.plist'); + expect(plistPath).toBe('/Basic/Info.plist'); }); - }); diff --git a/local-cli/rnpm/link/test/ios/getProducts.spec.js b/local-cli/rnpm/link/__tests__/ios/getProducts.spec.js similarity index 55% rename from local-cli/rnpm/link/test/ios/getProducts.spec.js rename to local-cli/rnpm/link/__tests__/ios/getProducts.spec.js index bf483f1b2e69ad..6ec8daba695205 100644 --- a/local-cli/rnpm/link/test/ios/getProducts.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/getProducts.spec.js @@ -1,20 +1,23 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); const getProducts = require('../../src/ios/getProducts'); +const path = require('path'); -const project = xcode.project('test/fixtures/linearGradient.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); describe('ios::getProducts', () => { - beforeEach(() => { project.parseSync(); }); it('should return an array of static libraries project exports', () => { const products = getProducts(project); - expect(products.length).to.equals(1); - expect(products).to.contains('libBVLinearGradient.a'); + expect(products.length).toBe(1); + expect(products).toContain('libRCTActionSheet.a'); }); - }); diff --git a/local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js b/local-cli/rnpm/link/__tests__/ios/hasLibraryImported.spec.js similarity index 60% rename from local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js rename to local-cli/rnpm/link/__tests__/ios/hasLibraryImported.spec.js index c321418799eafa..77f1657cf03209 100644 --- a/local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/hasLibraryImported.spec.js @@ -1,24 +1,27 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); const hasLibraryImported = require('../../src/ios/hasLibraryImported'); +const path = require('path'); -const project = xcode.project('test/fixtures/project.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); describe('ios::hasLibraryImported', () => { - beforeEach(() => { project.parseSync(); }); it('should return true if project has been already imported', () => { const libraries = project.pbxGroupByName('Libraries'); - expect(hasLibraryImported(libraries, 'React.xcodeproj')).to.be.true; + expect(hasLibraryImported(libraries, 'React.xcodeproj')).toBeTruthy(); }); it('should return false if project is not imported', () => { const libraries = project.pbxGroupByName('Libraries'); - expect(hasLibraryImported(libraries, 'ACME.xcodeproj')).to.be.false; + expect(hasLibraryImported(libraries, 'ACME.xcodeproj')).toBeFalsy(); }); - }); diff --git a/local-cli/rnpm/link/test/ios/isInstalled.spec.js b/local-cli/rnpm/link/__tests__/ios/isInstalled.spec.js similarity index 57% rename from local-cli/rnpm/link/test/ios/isInstalled.spec.js rename to local-cli/rnpm/link/__tests__/ios/isInstalled.spec.js index 65983c6f9922fd..ae3651f02564e1 100644 --- a/local-cli/rnpm/link/test/ios/isInstalled.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/isInstalled.spec.js @@ -1,39 +1,29 @@ -const chai = require('chai'); -const expect = chai.expect; -const mock = require('mock-fs'); -const fs = require('fs'); +'use strict'; + +jest.autoMockOff(); + const path = require('path'); const isInstalled = require('../../src/ios/isInstalled'); const baseProjectConfig = { - pbxprojPath: 'project.pbxproj', + pbxprojPath: path.join(__dirname, '../../__fixtures__/project.pbxproj'), libraryFolder: 'Libraries', }; describe('ios::isInstalled', () => { - - before(() => { - mock({ - 'project.pbxproj': fs.readFileSync(path.join(__dirname, '../fixtures/project.pbxproj')), - }); - }); - it('should return true when .xcodeproj in Libraries', () => { const dependencyConfig = { projectName: 'React.xcodeproj' }; - expect(isInstalled(baseProjectConfig, dependencyConfig)).to.be.true; + expect(isInstalled(baseProjectConfig, dependencyConfig)).toBeTruthy(); }); it('should return false when .xcodeproj not in Libraries', () => { const dependencyConfig = { projectName: 'Missing.xcodeproj' }; - expect(isInstalled(baseProjectConfig, dependencyConfig)).to.be.false; + expect(isInstalled(baseProjectConfig, dependencyConfig)).toBeFalsy(); }); it('should return false when `LibraryFolder` is missing', () => { const dependencyConfig = { projectName: 'React.xcodeproj' }; const projectConfig = Object.assign({}, baseProjectConfig, { libraryFolder: 'Missing' }); - expect(isInstalled(projectConfig, dependencyConfig)).to.be.false; + expect(isInstalled(projectConfig, dependencyConfig)).toBeFalsy(); }); - - after(mock.restore); - }); diff --git a/local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js b/local-cli/rnpm/link/__tests__/ios/mapHeaderSearchPaths.spec.js similarity index 61% rename from local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js rename to local-cli/rnpm/link/__tests__/ios/mapHeaderSearchPaths.spec.js index f885fbd8f39164..4bc1a7d24d623f 100644 --- a/local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/mapHeaderSearchPaths.spec.js @@ -1,23 +1,24 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); const mapHeaderSearchPaths = require('../../src/ios/mapHeaderSearchPaths'); +const path = require('path'); -const project = xcode.project('test/fixtures/project.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); const reactPath = '"$(SRCROOT)/../node_modules/react-native/React/**"'; describe('ios::mapHeaderSearchPaths', () => { - beforeEach(() => { project.parseSync(); }); it('should iterate over headers with `react` added only', () => { - const path = '../../node_modules/path-to-module/**'; - mapHeaderSearchPaths(project, paths => { - expect(paths.find(path => path.indexOf(reactPath))).to.be.not.empty; + expect(paths.find(p => p.indexOf(reactPath))).toBeDefined(); }); }); - }); diff --git a/local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js b/local-cli/rnpm/link/__tests__/ios/removeProjectFromLibraries.js similarity index 78% rename from local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js rename to local-cli/rnpm/link/__tests__/ios/removeProjectFromLibraries.js index 49a4c42b463061..8c0d728ebeeee2 100644 --- a/local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js +++ b/local-cli/rnpm/link/__tests__/ios/removeProjectFromLibraries.js @@ -1,15 +1,19 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); const PbxFile = require('xcode/lib/pbxFile'); const addProjectToLibraries = require('../../src/ios/addProjectToLibraries'); const removeProjectFromLibraries = require('../../src/ios/removeProjectFromLibraries'); const last = require('lodash').last; +const path = require('path'); -const project = xcode.project('test/fixtures/project.pbxproj'); +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); describe('ios::removeProjectFromLibraries', () => { - beforeEach(() => { project.parseSync(); @@ -27,7 +31,6 @@ describe('ios::removeProjectFromLibraries', () => { const child = last(libraries.children); - expect(child.comment).to.not.equals(file.basename); + expect(child.comment).not.toBe(file.basename); }); - }); diff --git a/local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js b/local-cli/rnpm/link/__tests__/ios/removeProjectFromProject.spec.js similarity index 59% rename from local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js rename to local-cli/rnpm/link/__tests__/ios/removeProjectFromProject.spec.js index 8220028aeafbec..0201d8824ef719 100644 --- a/local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js +++ b/local-cli/rnpm/link/__tests__/ios/removeProjectFromProject.spec.js @@ -1,32 +1,36 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const xcode = require('xcode'); const pbxFile = require('xcode/lib/pbxFile'); const addFileToProject = require('../../src/ios/addFileToProject'); const removeProjectFromProject = require('../../src/ios/removeProjectFromProject'); +const path = require('path'); -const project = xcode.project('test/fixtures/project.pbxproj'); -const filePath = '../fixtures/linearGradient.pbxproj'; +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); +const filePath = '../../__fixtures__/linearGradient.pbxproj'; describe('ios::addFileToProject', () => { - beforeEach(() => { project.parseSync(); addFileToProject(project, filePath); }); it('should return removed file', () => { - expect(removeProjectFromProject(project, filePath)).to.be.instanceof(pbxFile); + expect(removeProjectFromProject(project, filePath) instanceof pbxFile) + .toBeTruthy(); }); it('should remove file from a project', () => { const file = removeProjectFromProject(project, filePath); - expect(project.pbxFileReferenceSection()).to.not.include.keys(file.fileRef); + expect(project.pbxFileReferenceSection()[file.fileRef]).not.toBeDefined(); }); - it.skip('should remove file from PBXContainerProxy', () => { + xit('should remove file from PBXContainerProxy', () => { // todo(mike): add in .xcodeproj after Xcode modifications so we can test extra // removals later. }); - }); diff --git a/local-cli/rnpm/link/__tests__/ios/removeSharedLibrary.spec.js b/local-cli/rnpm/link/__tests__/ios/removeSharedLibrary.spec.js new file mode 100644 index 00000000000000..53aeb9be12d611 --- /dev/null +++ b/local-cli/rnpm/link/__tests__/ios/removeSharedLibrary.spec.js @@ -0,0 +1,37 @@ +'use strict'; + +jest.autoMockOff(); + +const xcode = require('xcode'); +const path = require('path'); +const addSharedLibraries = require('../../src/ios/addSharedLibraries'); +const removeSharedLibraries = require('../../src/ios/removeSharedLibraries'); +const getGroup = require('../../src/ios/getGroup'); + +const project = xcode.project( + path.join(__dirname, '../../__fixtures__/project.pbxproj') +); + +describe('ios::removeSharedLibraries', () => { + + beforeEach(() => { + project.parseSync(); + addSharedLibraries(project, ['libc++.tbd', 'libz.tbd']); + }); + + it('should remove only the specified shared library', () => { + removeSharedLibraries(project, ['libc++.tbd']); + + const frameworksGroup = getGroup(project, 'Frameworks'); + expect(frameworksGroup.children.length).toEqual(1); + expect(frameworksGroup.children[0].comment).toEqual('libz.tbd'); + }); + + it('should ignore missing shared libraries', () => { + removeSharedLibraries(project, ['libxml2.tbd']); + + const frameworksGroup = getGroup(project, 'Frameworks'); + expect(frameworksGroup.children.length).toEqual(2); + }); + +}); diff --git a/local-cli/rnpm/link/test/link.spec.js b/local-cli/rnpm/link/__tests__/link.spec.js similarity index 77% rename from local-cli/rnpm/link/test/link.spec.js rename to local-cli/rnpm/link/__tests__/link.spec.js index 9cce9988da09df..e6f5754a746fbf 100644 --- a/local-cli/rnpm/link/test/link.spec.js +++ b/local-cli/rnpm/link/__tests__/link.spec.js @@ -1,18 +1,15 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const sinon = require('sinon'); -const mock = require('mock-require'); const log = require('npmlog'); const path = require('path'); -const link = require('../src/link'); - -log.level = 'silent'; - describe('link', () => { - beforeEach(() => { delete require.cache[require.resolve('../src/link')]; + log.level = 'silent'; }); it('should reject when run in a folder without package.json', (done) => { @@ -22,7 +19,8 @@ describe('link', () => { }, }; - link(config).catch(() => done()); + const link = require('../src/link'); + link([], config).catch(() => done()); }); it('should accept a name of a dependency to link', (done) => { @@ -31,10 +29,11 @@ describe('link', () => { getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), }; - link(config, ['react-native-gradient']).then(() => { + const link = require('../src/link'); + link(['react-native-gradient'], config).then(() => { expect( config.getDependencyConfig.calledWith('react-native-gradient') - ).to.be.true; + ).toBeTruthy(); done(); }); }); @@ -45,7 +44,7 @@ describe('link', () => { getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), }; - mock( + jest.setMock( path.join(process.cwd(), 'package.json'), { dependencies: { @@ -54,10 +53,11 @@ describe('link', () => { } ); - link(config, []).then(() => { + const link = require('../src/link'); + link([], config).then(() => { expect( config.getDependencyConfig.calledWith('react-native-test') - ).to.be.true; + ).toBeTruthy(); done(); }); }); @@ -70,30 +70,30 @@ describe('link', () => { getDependencyConfig: sinon.stub().returns(dependencyConfig), }; - mock( + jest.setMock( '../src/android/isInstalled.js', sinon.stub().returns(false) ); - mock( + jest.setMock( '../src/android/registerNativeModule.js', registerNativeModule ); - mock( + jest.setMock( '../src/ios/isInstalled.js', sinon.stub().returns(false) ); - mock( + jest.setMock( '../src/ios/registerNativeModule.js', registerNativeModule ); const link = require('../src/link'); - link(config, ['react-native-blur']).then(() => { - expect(registerNativeModule.calledTwice).to.be.true; + link(['react-native-blur'], config).then(() => { + expect(registerNativeModule.calledTwice).toBeTruthy(); done(); }); }); @@ -106,30 +106,30 @@ describe('link', () => { getDependencyConfig: sinon.stub().returns(dependencyConfig), }; - mock( + jest.setMock( '../src/ios/isInstalled.js', sinon.stub().returns(true) ); - mock( + jest.setMock( '../src/android/isInstalled.js', sinon.stub().returns(true) ); - mock( + jest.setMock( '../src/ios/registerNativeModule.js', registerNativeModule ); - mock( + jest.setMock( '../src/android/registerNativeModule.js', registerNativeModule ); const link = require('../src/link'); - link(config, ['react-native-blur']).then(() => { - expect(registerNativeModule.callCount).to.equal(0); + link(['react-native-blur'], config).then(() => { + expect(registerNativeModule.callCount).toEqual(0); done(); }); }); @@ -139,12 +139,12 @@ describe('link', () => { const prelink = sinon.stub().yieldsAsync(); const postlink = sinon.stub().yieldsAsync(); - mock( + jest.setMock( '../src/ios/registerNativeModule.js', registerNativeModule ); - mock( + jest.setMock( '../src/ios/isInstalled.js', sinon.stub().returns(false) ); @@ -158,9 +158,9 @@ describe('link', () => { const link = require('../src/link'); - link(config, ['react-native-blur']).then(() => { - expect(prelink.calledBefore(registerNativeModule)).to.be.true; - expect(postlink.calledAfter(registerNativeModule)).to.be.true; + link(['react-native-blur'], config).then(() => { + expect(prelink.calledBefore(registerNativeModule)).toBeTruthy(); + expect(postlink.calledAfter(registerNativeModule)).toBeTruthy(); done(); }); }); @@ -171,7 +171,7 @@ describe('link', () => { const projectAssets = ['Fonts/FontC.ttf']; const copyAssets = sinon.stub(); - mock( + jest.setMock( '../src/ios/copyAssets.js', copyAssets ); @@ -183,17 +183,12 @@ describe('link', () => { const link = require('../src/link'); - link(config, ['react-native-blur']).then(() => { - expect(copyAssets.calledOnce).to.be.true; - expect(copyAssets.getCall(0).args[0]).to.deep.equals( + link(['react-native-blur'], config).then(() => { + expect(copyAssets.calledOnce).toBeTruthy(); + expect(copyAssets.getCall(0).args[0]).toEqual( projectAssets.concat(dependencyAssets) ); done(); }); }); - - afterEach(() => { - mock.stopAll(); - }); - }); diff --git a/local-cli/rnpm/link/test/promiseWaterfall.spec.js b/local-cli/rnpm/link/__tests__/promiseWaterfall.spec.js similarity index 76% rename from local-cli/rnpm/link/test/promiseWaterfall.spec.js rename to local-cli/rnpm/link/__tests__/promiseWaterfall.spec.js index 55d215038f03cb..e327b780eecc98 100644 --- a/local-cli/rnpm/link/test/promiseWaterfall.spec.js +++ b/local-cli/rnpm/link/__tests__/promiseWaterfall.spec.js @@ -1,5 +1,7 @@ -const chai = require('chai'); -const expect = chai.expect; +'use strict'; + +jest.autoMockOff(); + const sinon = require('sinon'); const promiseWaterfall = require('../src/promiseWaterfall'); @@ -9,7 +11,7 @@ describe('promiseWaterfall', () => { const tasks = [sinon.stub(), sinon.stub()]; promiseWaterfall(tasks).then(() => { - expect(tasks[0].calledBefore(tasks[1])).to.be.true; + expect(tasks[0].calledBefore(tasks[1])).toBeTruthy(); done(); }); }); @@ -18,7 +20,7 @@ describe('promiseWaterfall', () => { const tasks = [sinon.stub().returns(1), sinon.stub().returns(2)]; promiseWaterfall(tasks).then(value => { - expect(value).to.equal(2); + expect(value).toEqual(2); done(); }); }); @@ -28,8 +30,8 @@ describe('promiseWaterfall', () => { const tasks = [sinon.stub().throws(error), sinon.stub().returns(2)]; promiseWaterfall(tasks).catch(err => { - expect(err).to.equal(error); - expect(tasks[1].callCount).to.equal(0); + expect(err).toEqual(error); + expect(tasks[1].callCount).toEqual(0); done(); }); }); diff --git a/local-cli/rnpm/link/index.js b/local-cli/rnpm/link/index.js deleted file mode 100644 index 13b39b59956315..00000000000000 --- a/local-cli/rnpm/link/index.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = [{ - func: require('./src/link'), - description: 'Links all native dependencies', - name: 'link [packageName]', -}, { - func: require('./src/unlink'), - description: 'Unlink native dependency', - name: 'unlink ', -}]; diff --git a/local-cli/rnpm/link/link.js b/local-cli/rnpm/link/link.js new file mode 100644 index 00000000000000..e6ab05c8d485e9 --- /dev/null +++ b/local-cli/rnpm/link/link.js @@ -0,0 +1,5 @@ +module.exports = { + func: require('./src/link'), + description: 'links all native dependencies', + name: 'link [packageName]', +}; diff --git a/local-cli/rnpm/link/src/android/getPrefix.js b/local-cli/rnpm/link/src/android/getPrefix.js deleted file mode 100644 index 1c40029e9e0c3f..00000000000000 --- a/local-cli/rnpm/link/src/android/getPrefix.js +++ /dev/null @@ -1,16 +0,0 @@ -const semver = require('semver'); -const versions = ['0.20', '0.18', '0.17']; - -module.exports = function getPrefix(rnVersion) { - const version = rnVersion.replace(/-.*/, ''); - var prefix = 'patches/0.20'; - - versions.forEach((item, i) => { - const nextVersion = versions[i + 1]; - if (semver.lt(version, item + '.0') && nextVersion) { - prefix = `patches/${nextVersion}`; - } - }); - - return prefix; -}; diff --git a/local-cli/rnpm/link/src/android/isInstalled.js b/local-cli/rnpm/link/src/android/isInstalled.js index 3389a383fbbd7c..fda849cf59a33d 100644 --- a/local-cli/rnpm/link/src/android/isInstalled.js +++ b/local-cli/rnpm/link/src/android/isInstalled.js @@ -1,5 +1,5 @@ const fs = require('fs'); -const makeBuildPatch = require(`./patches/makeBuildPatch`); +const makeBuildPatch = require('./patches/makeBuildPatch'); module.exports = function isInstalled(config, name) { return fs diff --git a/local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js deleted file mode 100644 index c7bcf3d794e8b1..00000000000000 --- a/local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function makeImportPatch(packageImportPath) { - return { - pattern: 'import android.app.Activity;', - patch: '\n' + packageImportPath, - }; -}; diff --git a/local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js b/local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js deleted file mode 100644 index ea4552c198bbe7..00000000000000 --- a/local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js +++ /dev/null @@ -1,10 +0,0 @@ -const applyParams = require('../applyParams'); - -module.exports = function makePackagePatch(packageInstance, params, prefix) { - const processedInstance = applyParams(packageInstance, params, prefix); - - return { - pattern: '.addPackage(new MainReactPackage())', - patch: `\n .addPackage(${processedInstance})`, - }; -}; diff --git a/local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js deleted file mode 100644 index 687d61bd4fd6c4..00000000000000 --- a/local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = function makeImportPatch(packageImportPath) { - return { - pattern: 'import com.facebook.react.ReactActivity;', - patch: '\n' + packageImportPath, - }; -}; diff --git a/local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js b/local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js deleted file mode 100644 index 6609303902de3e..00000000000000 --- a/local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js +++ /dev/null @@ -1,10 +0,0 @@ -const applyParams = require('../applyParams'); - -module.exports = function makePackagePatch(packageInstance, params, prefix) { - const processedInstance = applyParams(packageInstance, params, prefix); - - return { - pattern: 'new MainReactPackage()', - patch: ',\n ' + processedInstance, - }; -}; diff --git a/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/makeImportPatch.js similarity index 100% rename from local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js rename to local-cli/rnpm/link/src/android/patches/makeImportPatch.js diff --git a/local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js b/local-cli/rnpm/link/src/android/patches/makePackagePatch.js similarity index 84% rename from local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js rename to local-cli/rnpm/link/src/android/patches/makePackagePatch.js index 17120b14272211..e1a5bec5df3e49 100644 --- a/local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js +++ b/local-cli/rnpm/link/src/android/patches/makePackagePatch.js @@ -1,4 +1,4 @@ -const applyParams = require('../applyParams'); +const applyParams = require('./applyParams'); module.exports = function makePackagePatch(packageInstance, params, prefix) { const processedInstance = applyParams(packageInstance, params, prefix); diff --git a/local-cli/rnpm/link/src/android/registerNativeModule.js b/local-cli/rnpm/link/src/android/registerNativeModule.js index a5fee0dc002769..db31bfdcedf23b 100644 --- a/local-cli/rnpm/link/src/android/registerNativeModule.js +++ b/local-cli/rnpm/link/src/android/registerNativeModule.js @@ -1,11 +1,9 @@ -const fs = require('fs'); -const getReactVersion = require('../getReactNativeVersion'); -const getPrefix = require('./getPrefix'); - const applyPatch = require('./patches/applyPatch'); const makeStringsPatch = require('./patches/makeStringsPatch'); -const makeSettingsPatch = require(`./patches/makeSettingsPatch`); -const makeBuildPatch = require(`./patches/makeBuildPatch`); +const makeSettingsPatch = require('./patches/makeSettingsPatch'); +const makeBuildPatch = require('./patches/makeBuildPatch'); +const makeImportPatch = require('./patches/makeImportPatch'); +const makePackagePatch = require('./patches/makePackagePatch'); module.exports = function registerNativeAndroidModule( name, @@ -14,9 +12,6 @@ module.exports = function registerNativeAndroidModule( projectConfig ) { const buildPatch = makeBuildPatch(name); - const prefix = getPrefix(getReactVersion(projectConfig.folder)); - const makeImportPatch = require(`./${prefix}/makeImportPatch`); - const makePackagePatch = require(`./${prefix}/makePackagePatch`); applyPatch( projectConfig.settingsGradlePath, diff --git a/local-cli/rnpm/link/src/android/unregisterNativeModule.js b/local-cli/rnpm/link/src/android/unregisterNativeModule.js index 9b88628330c67e..f3b84c81178f69 100644 --- a/local-cli/rnpm/link/src/android/unregisterNativeModule.js +++ b/local-cli/rnpm/link/src/android/unregisterNativeModule.js @@ -1,12 +1,12 @@ const fs = require('fs'); -const getReactVersion = require('../getReactNativeVersion'); -const getPrefix = require('./getPrefix'); const toCamelCase = require('lodash').camelCase; const revokePatch = require('./patches/revokePatch'); const makeSettingsPatch = require('./patches/makeSettingsPatch'); const makeBuildPatch = require('./patches/makeBuildPatch'); const makeStringsPatch = require('./patches/makeStringsPatch'); +const makeImportPatch = require('./patches/makeImportPatch'); +const makePackagePatch = require('./patches/makePackagePatch'); module.exports = function unregisterNativeAndroidModule( name, @@ -14,9 +14,6 @@ module.exports = function unregisterNativeAndroidModule( projectConfig ) { const buildPatch = makeBuildPatch(name); - const prefix = getPrefix(getReactVersion(projectConfig.folder)); - const makeImportPatch = require(`./${prefix}/makeImportPatch`); - const makePackagePatch = require(`./${prefix}/makePackagePatch`); const strings = fs.readFileSync(projectConfig.stringsPath, 'utf8'); var params = {}; diff --git a/local-cli/rnpm/link/src/getReactNativeVersion.js b/local-cli/rnpm/link/src/getReactNativeVersion.js deleted file mode 100644 index a1756e9e0bb3dc..00000000000000 --- a/local-cli/rnpm/link/src/getReactNativeVersion.js +++ /dev/null @@ -1,5 +0,0 @@ -const path = require('path'); - -module.exports = (folder) => require( - path.join(folder, 'node_modules', 'react-native', 'package.json') -).version; diff --git a/local-cli/rnpm/link/src/ios/addSharedLibraries.js b/local-cli/rnpm/link/src/ios/addSharedLibraries.js index f84603abb30eeb..82cf58ba873137 100644 --- a/local-cli/rnpm/link/src/ios/addSharedLibraries.js +++ b/local-cli/rnpm/link/src/ios/addSharedLibraries.js @@ -1,3 +1,16 @@ +const createGroupWithMessage = require('./createGroupWithMessage'); + module.exports = function addSharedLibraries(project, libraries) { + if (!libraries.length) { + return; + } + + // Create a Frameworks group if necessary. + createGroupWithMessage(project, 'Frameworks'); + + const target = project.getFirstTarget().uuid; + for (var name of libraries) { + project.addFramework(name, { target }); + } }; diff --git a/local-cli/rnpm/link/src/ios/copyAssets.js b/local-cli/rnpm/link/src/ios/copyAssets.js index 538381a035f8ca..78db9fc94f0666 100644 --- a/local-cli/rnpm/link/src/ios/copyAssets.js +++ b/local-cli/rnpm/link/src/ios/copyAssets.js @@ -4,7 +4,7 @@ const xcode = require('xcode'); const log = require('npmlog'); const plistParser = require('plist'); const groupFilesByType = require('../groupFilesByType'); -const createGroup = require('./createGroup'); +const createGroupWithMessage = require('./createGroupWithMessage'); const getPlist = require('./getPlist'); const getPlistPath = require('./getPlistPath'); @@ -17,21 +17,7 @@ module.exports = function linkAssetsIOS(files, projectConfig) { const assets = groupFilesByType(files); const plist = getPlist(project, projectConfig.sourceDir); - if (!plist) { - return log.error( - 'ERRPLIST', - `Could not locate Info.plist. Check if your project has 'INFOPLIST_FILE' set properly` - ); - } - - if (!project.pbxGroupByName('Resources')) { - createGroup(project, 'Resources'); - - log.warn( - 'ERRGROUP', - `Group 'Resources' does not exist in your XCode project. We have created it automatically for you.` - ); - } + createGroupWithMessage(project, 'Resources'); const fonts = (assets.font || []) .map(asset => diff --git a/local-cli/rnpm/link/src/ios/createGroupWithMessage.js b/local-cli/rnpm/link/src/ios/createGroupWithMessage.js new file mode 100644 index 00000000000000..aeeb3dd2afc34c --- /dev/null +++ b/local-cli/rnpm/link/src/ios/createGroupWithMessage.js @@ -0,0 +1,25 @@ +const log = require('npmlog'); + +const createGroup = require('./createGroup'); +const getGroup = require('./getGroup'); + +/** + * Given project and path of the group, it checks if a group exists at that path, + * and deeply creates a group for that path if its does not already exist. + * + * Returns the existing or newly created group + */ +module.exports = function createGroupWithMessage(project, path) { + var group = getGroup(project, path); + + if (!group) { + group = createGroup(project, path); + + log.warn( + 'ERRGROUP', + `Group '${path}' does not exist in your XCode project. We have created it automatically for you.` + ); + } + + return group; +}; diff --git a/local-cli/rnpm/link/src/ios/registerNativeModule.js b/local-cli/rnpm/link/src/ios/registerNativeModule.js index 6d8bce262e761e..8391a46b2fac12 100644 --- a/local-cli/rnpm/link/src/ios/registerNativeModule.js +++ b/local-cli/rnpm/link/src/ios/registerNativeModule.js @@ -7,8 +7,7 @@ const addToHeaderSearchPaths = require('./addToHeaderSearchPaths'); const getHeadersInFolder = require('./getHeadersInFolder'); const getHeaderSearchPath = require('./getHeaderSearchPath'); const getProducts = require('./getProducts'); -const createGroup = require('./createGroup'); -const hasLibraryImported = require('./hasLibraryImported'); +const createGroupWithMessage = require('./createGroupWithMessage'); const addFileToProject = require('./addFileToProject'); const addProjectToLibraries = require('./addProjectToLibraries'); const addSharedLibraries = require('./addSharedLibraries'); @@ -26,17 +25,7 @@ module.exports = function registerNativeModuleIOS(dependencyConfig, projectConfi const project = xcode.project(projectConfig.pbxprojPath).parseSync(); const dependencyProject = xcode.project(dependencyConfig.pbxprojPath).parseSync(); - var libraries = getGroup(project, projectConfig.libraryFolder); - - if (!libraries) { - libraries = createGroup(project, projectConfig.libraryFolder); - - log.warn( - 'ERRGROUP', - `Group ${projectConfig.libraryFolder} does not exist in your XCode project. We have created it automatically for you.` - ); - } - + const libraries = createGroupWithMessage(project, projectConfig.libraryFolder); const file = addFileToProject( project, path.relative(projectConfig.sourceDir, dependencyConfig.projectPath) diff --git a/local-cli/rnpm/link/src/ios/removeSharedLibraries.js b/local-cli/rnpm/link/src/ios/removeSharedLibraries.js index 82e611f5fa63b0..1150e2383cc806 100644 --- a/local-cli/rnpm/link/src/ios/removeSharedLibraries.js +++ b/local-cli/rnpm/link/src/ios/removeSharedLibraries.js @@ -1,3 +1,11 @@ module.exports = function removeSharedLibraries(project, libraries) { + if (!libraries.length) { + return; + } + const target = project.getFirstTarget().uuid; + + for (var name of libraries) { + project.removeFramework(name, { target }); + } }; diff --git a/local-cli/rnpm/link/src/ios/unregisterNativeModule.js b/local-cli/rnpm/link/src/ios/unregisterNativeModule.js index 509848a5552897..5034be0edec140 100644 --- a/local-cli/rnpm/link/src/ios/unregisterNativeModule.js +++ b/local-cli/rnpm/link/src/ios/unregisterNativeModule.js @@ -1,24 +1,25 @@ const xcode = require('xcode'); const path = require('path'); const fs = require('fs'); +const difference = require('lodash').difference; +const isEmpty = require('lodash').isEmpty; +const getGroup = require('./getGroup'); const getProducts = require('./getProducts'); const getHeadersInFolder = require('./getHeadersInFolder'); -const isEmpty = require('lodash').isEmpty; const getHeaderSearchPath = require('./getHeaderSearchPath'); const removeProjectFromProject = require('./removeProjectFromProject'); const removeProjectFromLibraries = require('./removeProjectFromLibraries'); const removeFromStaticLibraries = require('./removeFromStaticLibraries'); const removeFromHeaderSearchPaths = require('./removeFromHeaderSearchPaths'); -const removeSharedLibraries = require('./addSharedLibraries'); -const getGroup = require('./getGroup'); +const removeSharedLibraries = require('./removeSharedLibraries'); /** * Unregister native module IOS * * If library is already unlinked, this action is a no-op. */ -module.exports = function unregisterNativeModule(dependencyConfig, projectConfig) { +module.exports = function unregisterNativeModule(dependencyConfig, projectConfig, iOSDependencies) { const project = xcode.project(projectConfig.pbxprojPath).parseSync(); const dependencyProject = xcode.project(dependencyConfig.pbxprojPath).parseSync(); @@ -37,7 +38,15 @@ module.exports = function unregisterNativeModule(dependencyConfig, projectConfig }); }); - removeSharedLibraries(project, dependencyConfig.sharedLibraries); + const sharedLibraries = difference( + dependencyConfig.sharedLibraries, + iOSDependencies.reduce( + (libs, dependency) => libs.concat(dependency.sharedLibraries), + projectConfig.sharedLibraries + ) + ); + + removeSharedLibraries(project, sharedLibraries); const headers = getHeadersInFolder(dependencyConfig.folder); if (!isEmpty(headers)) { diff --git a/local-cli/rnpm/link/src/link.js b/local-cli/rnpm/link/src/link.js index 51261aa30f741c..5362c7416f8abc 100644 --- a/local-cli/rnpm/link/src/link.js +++ b/local-cli/rnpm/link/src/link.js @@ -93,7 +93,7 @@ const linkAssets = (project, assets) => { * * If optional argument [packageName] is provided, it's the only one that's checked */ -module.exports = function link(config, args) { +module.exports = function link(args, config) { var project; try { project = config.getProjectConfig(); diff --git a/local-cli/rnpm/link/src/unlink.js b/local-cli/rnpm/link/src/unlink.js index e40b091e9737bd..cf5ab922405b91 100644 --- a/local-cli/rnpm/link/src/unlink.js +++ b/local-cli/rnpm/link/src/unlink.js @@ -1,4 +1,3 @@ -const path = require('path'); const log = require('npmlog'); const getProjectDependencies = require('./getProjectDependencies'); @@ -9,7 +8,9 @@ const isInstalledIOS = require('./ios/isInstalled'); const unlinkAssetsAndroid = require('./android/unlinkAssets'); const unlinkAssetsIOS = require('./ios/unlinkAssets'); const getDependencyConfig = require('./getDependencyConfig'); +const compact = require('lodash').compact; const difference = require('lodash').difference; +const filter = require('lodash').filter; const isEmpty = require('lodash').isEmpty; const flatten = require('lodash').flatten; @@ -34,7 +35,7 @@ const unlinkDependencyAndroid = (androidProject, dependency, packageName) => { log.info(`Android module ${packageName} has been successfully unlinked`); }; -const unlinkDependencyIOS = (iOSProject, dependency, packageName) => { +const unlinkDependencyIOS = (iOSProject, dependency, packageName, iOSDependencies) => { if (!iOSProject || !dependency.ios) { return; } @@ -48,7 +49,7 @@ const unlinkDependencyIOS = (iOSProject, dependency, packageName) => { log.info(`Unlinking ${packageName} ios dependency`); - unregisterDependencyIOS(dependency.ios, iOSProject); + unregisterDependencyIOS(dependency.ios, iOSProject, iOSDependencies); log.info(`iOS module ${packageName} has been successfully unlinked`); }; @@ -59,7 +60,7 @@ const unlinkDependencyIOS = (iOSProject, dependency, packageName) => { * If optional argument [packageName] is provided, it's the only one * that's checked */ -module.exports = function unlink(config, args) { +module.exports = function unlink(args, config) { const packageName = args[0]; var project; @@ -85,10 +86,12 @@ module.exports = function unlink(config, args) { return Promise.reject(err); } - unlinkDependencyAndroid(project.android, dependency, packageName); - unlinkDependencyIOS(project.ios, dependency, packageName); - const allDependencies = getDependencyConfig(config, getProjectDependencies()); + const otherDependencies = filter(allDependencies, d => d.name !== packageName); + const iOSDependencies = compact(otherDependencies.map(d => d.config.ios)); + + unlinkDependencyAndroid(project.android, dependency, packageName); + unlinkDependencyIOS(project.ios, dependency, packageName, iOSDependencies); const assets = difference( dependency.assets, diff --git a/local-cli/rnpm/link/test/android/isInstalled.spec.js b/local-cli/rnpm/link/test/android/isInstalled.spec.js deleted file mode 100644 index bde18c09e43dd4..00000000000000 --- a/local-cli/rnpm/link/test/android/isInstalled.spec.js +++ /dev/null @@ -1,28 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const mock = require('mock-fs'); -const fs = require('fs'); -const path = require('path'); -const isInstalled = require('../../src/android/isInstalled'); - -const projectConfig = { - buildGradlePath: 'build.gradle', -}; - -describe('android::isInstalled', () => { - before(() => mock({ - 'build.gradle': fs.readFileSync( - path.join(__dirname, '../fixtures/android/patchedBuild.gradle') - ), - })); - - it('should return true when project is already in build.gradle', () => - expect(isInstalled(projectConfig, 'test')).to.be.true - ); - - it('should return false when project is not in build.gradle', () => - expect(isInstalled(projectConfig, 'test2')).to.be.false - ); - - after(mock.restore); -}); diff --git a/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js deleted file mode 100644 index ed729defc2fc25..00000000000000 --- a/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js +++ /dev/null @@ -1,31 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const mock = require('mock-fs'); -const fs = require('fs'); -const path = require('path'); -const makeImportPatch = require('../../../../src/android/patches/0.17/makeImportPatch'); -const applyPatch = require('../../../../src/android/patches/applyPatch'); - -const projectConfig = { - mainFilePath: 'MainActivity.java', -}; - -const packageImportPath = 'import some.example.project'; - -describe('makeImportPatch@0.17', () => { - before(() => mock({ - 'MainActivity.java': fs.readFileSync( - path.join(__dirname, '../../../fixtures/android/0.17/MainActivity.java') - ), - })); - - it('MainActivity contains a correct 0.17 import patch', () => { - const importPatch = makeImportPatch(packageImportPath); - - applyPatch('MainActivity.java', importPatch); - expect(fs.readFileSync('MainActivity.java', 'utf8')) - .to.have.string(importPatch.patch); - }); - - after(mock.restore); -}); diff --git a/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js deleted file mode 100644 index cbbb3f517c9c16..00000000000000 --- a/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js +++ /dev/null @@ -1,36 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const mock = require('mock-fs'); -const fs = require('fs'); -const path = require('path'); -const makePackagePatch = require('../../../../src/android/patches/0.17/makePackagePatch'); -const applyPatch = require('../../../../src/android/patches/applyPatch'); - -const projectConfig = { - mainFilePath: 'MainActivity.java', -}; - -const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; -const name = 'some-library'; -const params = { - foo: 'foo', - bar: 'bar', -}; - -describe('makePackagePatch@0.17', () => { - before(() => mock({ - 'MainActivity.java': fs.readFileSync( - path.join(__dirname, '../../../fixtures/android/0.17/MainActivity.java') - ), - })); - - it('MainActivity contains a correct 0.17 package patch', () => { - const packagePatch = makePackagePatch(packageInstance, params, name); - - applyPatch('MainActivity.java', packagePatch); - expect(fs.readFileSync('MainActivity.java', 'utf8')) - .to.have.string(packagePatch.patch); - }); - - after(mock.restore); -}); diff --git a/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js deleted file mode 100644 index 0dd15c74890573..00000000000000 --- a/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js +++ /dev/null @@ -1,31 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const mock = require('mock-fs'); -const fs = require('fs'); -const path = require('path'); -const makeImportPatch = require('../../../../src/android/patches/0.18/makeImportPatch'); -const applyPatch = require('../../../../src/android/patches/applyPatch'); - -const projectConfig = { - mainFilePath: 'MainActivity.java', -}; - -const packageImportPath = 'import some.example.project'; - -describe('makeImportPatch@0.18', () => { - before(() => mock({ - 'MainActivity.java': fs.readFileSync( - path.join(__dirname, '../../../fixtures/android/0.18/MainActivity.java') - ), - })); - - it('MainActivity contains a correct 0.18 import patch', () => { - const importPatch = makeImportPatch(packageImportPath); - - applyPatch('MainActivity.java', importPatch); - expect(fs.readFileSync('MainActivity.java', 'utf8')) - .to.have.string(importPatch.patch); - }); - - after(mock.restore); -}); diff --git a/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js deleted file mode 100644 index b3f46bf5fe829c..00000000000000 --- a/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js +++ /dev/null @@ -1,36 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const mock = require('mock-fs'); -const fs = require('fs'); -const path = require('path'); -const makePackagePatch = require('../../../../src/android/patches/0.18/makePackagePatch'); -const applyPatch = require('../../../../src/android/patches/applyPatch'); - -const projectConfig = { - mainFilePath: 'MainActivity.java', -}; - -const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; -const name = 'some-library'; -const params = { - foo: 'foo', - bar: 'bar', -}; - -describe('makePackagePatch@0.18', () => { - before(() => mock({ - 'MainActivity.java': fs.readFileSync( - path.join(__dirname, '../../../fixtures/android/0.18/MainActivity.java') - ), - })); - - it('MainActivity contains a correct 0.18 package patch', () => { - const packagePatch = makePackagePatch(packageInstance, params, name); - - applyPatch('MainActivity.java', packagePatch); - expect(fs.readFileSync('MainActivity.java', 'utf8')) - .to.have.string(packagePatch.patch); - }); - - after(mock.restore); -}); diff --git a/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js deleted file mode 100644 index 31b328261158e7..00000000000000 --- a/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js +++ /dev/null @@ -1,31 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const mock = require('mock-fs'); -const fs = require('fs'); -const path = require('path'); -const makeImportPatch = require('../../../../src/android/patches/0.20/makeImportPatch'); -const applyPatch = require('../../../../src/android/patches/applyPatch'); - -const projectConfig = { - mainFilePath: 'MainActivity.java', -}; - -const packageImportPath = 'import some.example.project'; - -describe('makeImportPatch@0.20', () => { - before(() => mock({ - 'MainActivity.java': fs.readFileSync( - path.join(__dirname, '../../../fixtures/android/0.20/MainActivity.java') - ), - })); - - it('MainActivity contains a correct 0.20 import patch', () => { - const importPatch = makeImportPatch(packageImportPath); - - applyPatch('MainActivity.java', importPatch); - expect(fs.readFileSync('MainActivity.java', 'utf8')) - .to.have.string(importPatch.patch); - }); - - after(mock.restore); -}); diff --git a/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js deleted file mode 100644 index fe8086ce8b3262..00000000000000 --- a/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js +++ /dev/null @@ -1,36 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const mock = require('mock-fs'); -const fs = require('fs'); -const path = require('path'); -const makePackagePatch = require('../../../../src/android/patches/0.20/makePackagePatch'); -const applyPatch = require('../../../../src/android/patches/applyPatch'); - -const projectConfig = { - mainFilePath: 'MainActivity.java', -}; - -const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; -const name = 'some-library'; -const params = { - foo: 'foo', - bar: 'bar', -}; - -describe('makePackagePatch@0.20', () => { - before(() => mock({ - 'MainActivity.java': fs.readFileSync( - path.join(__dirname, '../../../fixtures/android/0.20/MainActivity.java') - ), - })); - - it('MainActivity contains a correct 0.20 package patch', () => { - const packagePatch = makePackagePatch(packageInstance, params, name); - - applyPatch('MainActivity.java', packagePatch); - expect(fs.readFileSync('MainActivity.java', 'utf8')) - .to.have.string(packagePatch.patch); - }); - - after(mock.restore); -}); diff --git a/local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js b/local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js deleted file mode 100644 index 51136a72ebf241..00000000000000 --- a/local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js +++ /dev/null @@ -1,17 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const makeBuildPatch = require('../../../src/android/patches/makeBuildPatch'); -const applyPatch = require('../../../src/android/patches/applyPatch'); - -const name = 'test'; - -describe('makeBuildPatch', () => { - it('should build a patch function', () => { - expect(makeBuildPatch(name)).to.be.an('object'); - }); - - it('should make a correct patch', () => { - expect(makeBuildPatch(name).patch) - .to.be.equal(` compile project(':${name}')\n`); - }); -}); diff --git a/local-cli/rnpm/link/test/getPrefix.spec.js b/local-cli/rnpm/link/test/getPrefix.spec.js deleted file mode 100644 index 1b5e7262732d7e..00000000000000 --- a/local-cli/rnpm/link/test/getPrefix.spec.js +++ /dev/null @@ -1,20 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const getMainActivityPatch = require('../src/android/getPrefix'); -const newPrefix = 'patches/0.18'; -const oldPrefix = 'patches/0.17'; - -describe('getPrefix', () => { - it('require a specific patch for react-native < 0.18', () => { - expect(getMainActivityPatch('0.17.0-rc')).to.equals(oldPrefix); - expect(getMainActivityPatch('0.17.1-rc2')).to.equals(oldPrefix); - expect(getMainActivityPatch('0.17.2')).to.equals(oldPrefix); - }); - - it('require a specific patch for react-native > 0.18', () => { - expect(getMainActivityPatch('0.19.0')).to.equals(newPrefix); - expect(getMainActivityPatch('0.19.0-rc')).to.equals(newPrefix); - expect(getMainActivityPatch('0.18.0-rc1')).to.equals(newPrefix); - expect(getMainActivityPatch('0.18.2')).to.equals(newPrefix); - }); -}); diff --git a/local-cli/rnpm/link/test/ios/addFileToProject.spec.js b/local-cli/rnpm/link/test/ios/addFileToProject.spec.js deleted file mode 100644 index 141d2ba7130f45..00000000000000 --- a/local-cli/rnpm/link/test/ios/addFileToProject.spec.js +++ /dev/null @@ -1,22 +0,0 @@ -const chai = require('chai'); -const expect = chai.expect; -const xcode = require('xcode'); -const addFileToProject = require('../../src/ios/addFileToProject'); - -const project = xcode.project('test/fixtures/project.pbxproj'); - -describe('ios::addFileToProject', () => { - - beforeEach(() => { - project.parseSync(); - }); - - it('should add file to a project', () => { - const file = addFileToProject(project, '../fixtures/linearGradient.pbxproj'); - - expect( - project.pbxFileReferenceSection() - ).to.include.keys(file.fileRef); - }); - -}); diff --git a/local-cli/rnpm/link/unlink.js b/local-cli/rnpm/link/unlink.js new file mode 100644 index 00000000000000..56ca0ec674b4b1 --- /dev/null +++ b/local-cli/rnpm/link/unlink.js @@ -0,0 +1,5 @@ +module.exports = { + func: require('./src/unlink'), + description: 'unlink native dependency', + name: 'unlink ', +}; diff --git a/local-cli/runAndroid/runAndroid.js b/local-cli/runAndroid/runAndroid.js index 26a49cff2480c4..dd7ada16c4efbd 100644 --- a/local-cli/runAndroid/runAndroid.js +++ b/local-cli/runAndroid/runAndroid.js @@ -12,63 +12,36 @@ const chalk = require('chalk'); const child_process = require('child_process'); const fs = require('fs'); const path = require('path'); -const parseCommandLine = require('../util/parseCommandLine'); const isPackagerRunning = require('../util/isPackagerRunning'); const Promise = require('promise'); const adb = require('./adb'); +// Verifies this is an Android project +function checkAndroid(root) { + return fs.existsSync(path.join(root, 'android/gradlew')); +} + /** * Starts the app on a connected Android emulator or device. */ -function runAndroid(argv, config) { - return new Promise((resolve, reject) => { - _runAndroid(argv, config, resolve, reject); - }); -} - -function _runAndroid(argv, config, resolve, reject) { - const args = parseCommandLine([{ - command: 'install-debug', - type: 'string', - required: false, - }, { - command: 'root', - type: 'string', - description: 'Override the root directory for the android build (which contains the android directory)', - }, { - command: 'flavor', - type: 'string', - required: false, - }, { - command: 'variant', - type: 'string', - required: false, - }], argv); - - args.root = args.root || ''; - - if (!checkAndroid(args)) { +function runAndroid(argv, config, args) { + if (!checkAndroid(args.root)) { console.log(chalk.red('Android project not found. Maybe run react-native android first?')); return; } - resolve(isPackagerRunning().then(result => { + return isPackagerRunning().then(result => { if (result === 'running') { - console.log(chalk.bold(`JS server already running.`)); + console.log(chalk.bold('JS server already running.')); } else if (result === 'unrecognized') { - console.warn(chalk.yellow(`JS server not recognized, continuing with build...`)); + console.warn(chalk.yellow('JS server not recognized, continuing with build...')); } else { // result == 'not_running' - console.log(chalk.bold(`Starting JS server...`)); + console.log(chalk.bold('Starting JS server...')); startServerInNewWindow(); } - run(args, reject); - })); -} - -// Verifies this is an Android project -function checkAndroid(args) { - return fs.existsSync(path.join(args.root, 'android/gradlew')); + return buildAndRun(args); + }); } function getAdbPath() { @@ -98,7 +71,7 @@ function tryRunAdbReverse() { } // Builds the app and runs it on a connected emulator / device. -function run(args, reject) { +function buildAndRun(args) { process.chdir(path.join(args.root, 'android')); try { tryRunAdbReverse(); @@ -108,23 +81,23 @@ function run(args, reject) { : './gradlew'; const gradleArgs = []; - if (args['variant']) { - gradleArgs.push('install' + - args['variant'][0].toUpperCase() + args['variant'].slice(1) - ); - } else if (args['flavor']) { - console.warn(chalk.yellow( - `--flavor has been deprecated. Use --variant instead` - )); - gradleArgs.push('install' + - args['flavor'][0].toUpperCase() + args['flavor'].slice(1) - ); + if (args.variant) { + gradleArgs.push('install' + + args.variant[0].toUpperCase() + args.variant.slice(1) + ); + } else if (args.flavor) { + console.warn(chalk.yellow( + '--flavor has been deprecated. Use --variant instead' + )); + gradleArgs.push('install' + + args.flavor[0].toUpperCase() + args.flavor.slice(1) + ); } else { - gradleArgs.push('installDebug'); + gradleArgs.push('installDebug'); } - if (args['install-debug']) { - gradleArgs.push(args['install-debug']); + if (args.installDebug) { + gradleArgs.push(args.installDebug); } console.log(chalk.bold( @@ -144,8 +117,7 @@ function run(args, reject) { // stderr is automatically piped from the gradle process, so the user // should see the error already, there is no need to do // `console.log(e.stderr)` - reject(); - return; + return Promise.reject(); } try { @@ -183,13 +155,12 @@ function run(args, reject) { } catch (e) { console.log(chalk.red( - `adb invocation failed. Do you have adb in your PATH?` + 'adb invocation failed. Do you have adb in your PATH?' )); // stderr is automatically piped from the gradle process, so the user // should see the error already, there is no need to do // `console.log(e.stderr)` - reject(); - return; + return Promise.reject(); } } @@ -224,4 +195,20 @@ function startServerInNewWindow() { } } -module.exports = runAndroid; +module.exports = { + name: 'run-android', + description: 'builds your app and starts it on a connected Android emulator or device', + func: runAndroid, + options: [{ + command: '--install-debug', + }, { + command: '--root [string]', + description: 'Override the root directory for the android build (which contains the android directory)', + default: '', + }, { + command: '--flavor [string]', + description: '--flavor has been deprecated. Use --variant instead', + }, { + command: '--variant [string]', + }], +}; diff --git a/local-cli/runIOS/runIOS.js b/local-cli/runIOS/runIOS.js index eb3378abb6ca81..8fdc83e82db848 100644 --- a/local-cli/runIOS/runIOS.js +++ b/local-cli/runIOS/runIOS.js @@ -11,46 +11,14 @@ const child_process = require('child_process'); const fs = require('fs'); const path = require('path'); -const parseCommandLine = require('../util/parseCommandLine'); const findXcodeProject = require('./findXcodeProject'); const parseIOSSimulatorsList = require('./parseIOSSimulatorsList'); -const Promise = require('promise'); -/** - * Starts the app on iOS simulator - */ -function runIOS(argv, config) { - return new Promise((resolve, reject) => { - _runIOS(argv, config, resolve, reject); - }); -} - -function _runIOS(argv, config, resolve, reject) { - const args = parseCommandLine([ - { - command: 'simulator', - description: 'Explicitly set simulator to use', - type: 'string', - required: false, - default: 'iPhone 6', - }, { - command: 'scheme', - description: 'Explicitly set Xcode scheme to use', - type: 'string', - required: false, - }, { - command: 'project-path', - description: 'Path relative to project root where the Xcode project (.xcodeproj) lives. The default is \'ios\'.', - type: 'string', - required: false, - default: 'ios', - } - ], argv); - - process.chdir(args['project-path']); +function runIOS(argv, config, args) { + process.chdir(args.projectPath); const xcodeProject = findXcodeProject(fs.readdirSync('.')); if (!xcodeProject) { - reject(new Error(`Could not find Xcode project files in ios folder`)); + throw new Error('Could not find Xcode project files in ios folder'); } const inferredSchemeName = path.basename(xcodeProject.name, path.extname(xcodeProject.name)); @@ -62,14 +30,14 @@ function _runIOS(argv, config, resolve, reject) { ); const selectedSimulator = matchingSimulator(simulators, args.simulator); if (!selectedSimulator) { - reject(new Error(`Cound't find ${args.simulator} simulator`)); + throw new Error(`Cound't find ${args.simulator} simulator`); } - const simulatorFullName = formattedSimulatorName(selectedSimulator) + const simulatorFullName = formattedSimulatorName(selectedSimulator); console.log(`Launching ${simulatorFullName}...`); try { child_process.spawnSync('xcrun', ['instruments', '-w', simulatorFullName]); - } catch(e) { + } catch (e) { // instruments always fail with 255 because it expects more arguments, // but we want it to only launch the simulator } @@ -81,42 +49,20 @@ function _runIOS(argv, config, resolve, reject) { '-derivedDataPath', 'build', ]; console.log(`Building using "xcodebuild ${xcodebuildArgs.join(' ')}"`); + child_process.spawnSync('xcodebuild', xcodebuildArgs, {stdio: 'inherit'}); - let appPath = `build/Build/Products/Debug-iphonesimulator/${inferredSchemeName}.app`; - const xcodeBuildProcess = child_process.spawn('xcodebuild', xcodebuildArgs, { - stdio: [process.stdin, 'pipe', process.stderr] - }); - - xcodeBuildProcess.stdout.on('data', (data) => { - process.stdout.write(data); + const appPath = `build/Build/Products/Debug-iphonesimulator/${inferredSchemeName}.app`; + console.log(`Installing ${appPath}`); + child_process.spawnSync('xcrun', ['simctl', 'install', 'booted', appPath], {stdio: 'inherit'}); - // search this part of the process output for a path to the generated app and replace default - const appPathFromLog = data.toString().match(/Touch (build\/Build\/Products\/.*\/.*\.app)/); - if (appPathFromLog) { - appPath = appPathFromLog[1]; - } - }); - - xcodeBuildProcess.on('close', (code) => { - if (code !== 0) { - reject(new Error(`xcodebuild process exited with code ${code}`)); - return; - } - - console.log(`Installing ${appPath}`); - child_process.spawnSync('xcrun', ['simctl', 'install', 'booted', appPath], {stdio: 'inherit'}); + const bundleID = child_process.execFileSync( + '/usr/libexec/PlistBuddy', + ['-c', 'Print:CFBundleIdentifier', path.join(appPath, 'Info.plist')], + {encoding: 'utf8'} + ).trim(); - const bundleID = child_process.execFileSync( - '/usr/libexec/PlistBuddy', - ['-c', 'Print:CFBundleIdentifier', path.join(appPath, 'Info.plist')], - {encoding: 'utf8'} - ).trim(); - - console.log(`Launching ${bundleID}`); - child_process.spawnSync('xcrun', ['simctl', 'launch', 'booted', bundleID], {stdio: 'inherit'}); - - resolve(); - }); + console.log(`Launching ${bundleID}`); + child_process.spawnSync('xcrun', ['simctl', 'launch', 'booted', bundleID], {stdio: 'inherit'}); } function matchingSimulator(simulators, simulatorName) { @@ -131,4 +77,31 @@ function formattedSimulatorName(simulator) { return `${simulator.name} (${simulator.version})`; } -module.exports = runIOS; +module.exports = { + name: 'run-ios', + description: 'builds your app and starts it on iOS simulator', + func: runIOS, + examples: [ + { + desc: 'Run on a different simulator, e.g. iPhone 5', + cmd: 'react-native run-ios --simulator "iPhone 5"', + }, + { + desc: 'Pass a non-standard location of iOS directory', + cmd: 'react-native run-ios --project-path "./app/ios"', + }, + ], + options: [{ + command: '--simulator [string]', + description: 'Explicitly set simulator to use', + default: 'iPhone 6', + }, { + command: '--scheme [string]', + description: 'Explicitly set Xcode scheme to use', + }, { + command: '--project-path [string]', + description: 'Path relative to project root where the Xcode project ' + + '(.xcodeproj) lives. The default is \'ios\'.', + default: 'ios', + }] +}; diff --git a/local-cli/server/runServer.js b/local-cli/server/runServer.js index 10e39b9df3e866..fb3cec33f30ed1 100644 --- a/local-cli/server/runServer.js +++ b/local-cli/server/runServer.js @@ -89,7 +89,7 @@ function getPackagerServer(args, config) { 'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', // Audio formats 'html', 'pdf', // Document formats ], - resetCache: args.resetCache || args['reset-cache'], + resetCache: args.resetCache, verbose: args.verbose, }); } diff --git a/local-cli/server/server.js b/local-cli/server/server.js index 7d1d6fc48d3213..945accf85f8f04 100644 --- a/local-cli/server/server.js +++ b/local-cli/server/server.js @@ -10,82 +10,14 @@ const chalk = require('chalk'); const formatBanner = require('./formatBanner'); -const parseCommandLine = require('../util/parseCommandLine'); const path = require('path'); -const Promise = require('promise'); const runServer = require('./runServer'); /** * Starts the React Native Packager Server. */ -function server(argv, config) { - return new Promise((resolve, reject) => { - _server(argv, config, resolve, reject); - }); -} - -function _server(argv, config, resolve, reject) { - const args = parseCommandLine([{ - command: 'port', - default: 8081, - type: 'string', - }, { - command: 'host', - default: '', - type: 'string', - }, { - command: 'root', - type: 'string', - description: 'add another root(s) to be used by the packager in this project', - }, { - command: 'projectRoots', - type: 'string', - description: 'override the root(s) to be used by the packager', - },{ - command: 'assetRoots', - type: 'string', - description: 'specify the root directories of app assets' - }, { - command: 'skipflow', - description: 'Disable flow checks' - }, { - command: 'nonPersistent', - description: 'Disable file watcher' - }, { - command: 'transformer', - type: 'string', - default: null, - description: 'Specify a custom transformer to be used' - }, { - command: 'resetCache', - description: 'Removes cached files', - default: false, - }, { - command: 'reset-cache', - description: 'Removes cached files', - default: false, - }, { - command: 'verbose', - description: 'Enables logging', - default: false, - }]); - - args.projectRoots = args.projectRoots - ? argToArray(args.projectRoots) - : config.getProjectRoots(); - - if (args.root) { - const additionalRoots = argToArray(args.root); - additionalRoots.forEach(root => { - args.projectRoots.push(path.resolve(root)); - }); - } - - args.assetRoots = args.assetRoots - ? argToArray(args.assetRoots).map(dir => - path.resolve(process.cwd(), dir) - ) - : config.getAssetRoots(); +function server(argv, config, args) { + args.projectRoots = args.projectRoots.concat(args.root); console.log(formatBanner( 'Running packager on port ' + args.port + '.\n\n' + @@ -130,25 +62,50 @@ function _server(argv, config, resolve, reject) { process.exit(1); }); - // TODO: remove once we deprecate this arg - if (args.resetCache) { - console.log( - 'Please start using `--reset-cache` instead. ' + - 'We\'ll deprecate this argument soon.' - ); - } - - startServer(args, config); -} - -function startServer(args, config) { - runServer(args, config, () => - console.log('\nReact packager ready.\n') - ); -} - -function argToArray(arg) { - return Array.isArray(arg) ? arg : arg.split(','); + runServer(args, config, () => console.log('\nReact packager ready.\n')); } -module.exports = server; +module.exports = { + name: 'start', + func: server, + description: 'starts the webserver', + options: [{ + command: '--port [number]', + default: 8081, + parse: (val) => Number(val), + }, { + command: '--host [string]', + default: '', + }, { + command: '--root [list]', + description: 'add another root(s) to be used by the packager in this project', + parse: (val) => val.split(',').map(root => path.resolve(root)), + default: [], + }, { + command: '--projectRoots [list]', + description: 'override the root(s) to be used by the packager', + parse: (val) => val.split(','), + default: (config) => config.getProjectRoots(), + }, { + command: '--assetRoots [list]', + description: 'specify the root directories of app assets', + parse: (val) => val.split(',').map(dir => path.resolve(process.cwd(), dir)), + default: (config) => config.getAssetRoots(), + }, { + command: '--skipflow', + description: 'Disable flow checks' + }, { + command: '--nonPersistent', + description: 'Disable file watcher' + }, { + command: '--transformer [string]', + default: require.resolve('../../packager/transformer'), + description: 'Specify a custom transformer to be used (absolute path)' + }, { + command: '--reset-cache, --resetCache', + description: 'Removes cached files', + }, { + command: '--verbose', + description: 'Enables logging', + }], +}; diff --git a/local-cli/upgrade/upgrade.js b/local-cli/upgrade/upgrade.js index aaf6c01ce2b905..82b3a83f9d4fe1 100644 --- a/local-cli/upgrade/upgrade.js +++ b/local-cli/upgrade/upgrade.js @@ -15,7 +15,7 @@ const Promise = require('promise'); const yeoman = require('yeoman-environment'); const semver = require('semver'); -module.exports = function upgrade(args, config) { +function upgrade(args, config) { args = args || process.argv; const env = yeoman.createEnv(); const pak = JSON.parse(fs.readFileSync('package.json', 'utf8')); @@ -46,7 +46,7 @@ module.exports = function upgrade(args, config) { ); // >= v0.21.0, we require react to be a peer dependency - if (semver.gte(v, '0.21.0') && !pak.dependencies['react']) { + if (semver.gte(v, '0.21.0') && !pak.dependencies.react) { console.log( chalk.yellow( '\nYour \'package.json\' file doesn\'t seem to have \'react\' as a dependency.\n' + @@ -55,9 +55,9 @@ module.exports = function upgrade(args, config) { 'Just run \'npm install --save react\', then re-run \'react-native upgrade\'.\n' ) ); - return Promise.resolve(); + return; } - + if (semver.satisfies(v, '~0.26.0')) { console.log( chalk.yellow( @@ -96,4 +96,11 @@ module.exports = function upgrade(args, config) { env.register(generatorPath, 'react:app'); const generatorArgs = ['react:app', pak.name].concat(args); return new Promise((resolve) => env.run(generatorArgs, {upgrade: true}, resolve)); +} + +module.exports = { + name: 'upgrade', + description: 'upgrade your app\'s template files to the latest version; run this after ' + + 'updating the react-native version in your package.json and running npm install', + func: upgrade, }; diff --git a/local-cli/util/assertRequiredOptions.js b/local-cli/util/assertRequiredOptions.js new file mode 100644 index 00000000000000..28bbc9b34e2ce2 --- /dev/null +++ b/local-cli/util/assertRequiredOptions.js @@ -0,0 +1,32 @@ +/** + * 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 { Option } = require('commander'); +const { camelCase } = require('lodash'); + +// Commander.js has a 2 years old open issue to support <...> syntax +// for options. Until that gets merged, we run the checks manually +// https://github.com/tj/commander.js/issues/230 +module.exports = function assertRequiredOptions(options, passedOptions) { + options.forEach(opt => { + const option = new Option(opt.command); + + if (!option.required) { + return; + } + + const name = camelCase(option.long); + + if (!passedOptions[name]) { + // Provide commander.js like error message + throw new Error(`error: option '${option.long}' missing`); + } + }); +}; diff --git a/local-cli/util/parseCommandLine.js b/local-cli/util/parseCommandLine.js index b373ddad428b70..10b8d3537aaf5b 100644 --- a/local-cli/util/parseCommandLine.js +++ b/local-cli/util/parseCommandLine.js @@ -15,6 +15,9 @@ * description: 'Run in a web browser instead of iOS', * default: true * }]) + * + * NOTE: This file is used internally at Facebook and not in `local-cli` itself. + * No changes should be made to this file without prior discussion with FB team. */ 'use strict'; diff --git a/local-cli/version/version.js b/local-cli/version/version.js deleted file mode 100644 index 523172e5e79139..00000000000000 --- a/local-cli/version/version.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * 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'; - -var pkg = require('../../package'); -var Promise = require('promise'); - -/** - * Prints the version of react-native and exits. - */ -function version(argv, config) { - console.log(pkg.version); - return Promise.resolve(); -} - -module.exports = version; diff --git a/package.json b/package.json index 19fd4e10fbb8c9..933ee19899e903 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,7 @@ "^[./a-zA-Z0-9$_-]+\\.png$": "RelativeImageStub" }, "testPathIgnorePatterns": [ - "/node_modules/", - "/local-cli/rnpm/" + "/node_modules/" ], "haste": { "defaultPlatform": "ios", @@ -145,6 +144,7 @@ "base64-js": "^0.0.8", "bser": "^1.0.2", "chalk": "^1.1.1", + "commander": "^2.9.0", "connect": "^2.8.3", "core-js": "^2.2.2", "debug": "^2.2.0", @@ -186,7 +186,7 @@ "wordwrap": "^1.0.0", "worker-farm": "^1.3.1", "ws": "^1.1.0", - "xcode": "^0.8.2", + "xcode": "^0.8.9", "xmldoc": "^0.4.0", "yargs": "^3.24.0", "yeoman-environment": "1.5.3", @@ -205,6 +205,7 @@ "jest": "latest", "jest-repl": "latest", "jest-runtime": "latest", + "mock-fs": "^3.11.0", "portfinder": "0.4.0", "react": "~15.3.0-rc.2", "shelljs": "0.6.0"