diff --git a/packages/local-cli/bundle/buildBundle.js b/packages/local-cli/bundle/buildBundle.js index 356cb7d0b..909dfe687 100644 --- a/packages/local-cli/bundle/buildBundle.js +++ b/packages/local-cli/bundle/buildBundle.js @@ -16,28 +16,20 @@ const outputBundle = require('metro/src/shared/output/bundle'); const path = require('path'); const saveAssets = require('./saveAssets'); -import type {RequestOptions, OutputOptions} from './types.flow'; -import type {ConfigT} from 'metro-config/src/configTypes.flow'; +const loadMetroConfig = require('../util/loadMetroConfig'); + +import type { ContextT } from '../core/types.flow'; +import type { CommandLineArgs } from './bundleCommandLineArgs'; + +async function buildBundle(args: CommandLineArgs, ctx: ContextT, output = outputBundle) { + const config = await loadMetroConfig(ctx.root, { + resetCache: args.resetCache, + config: args.config + }); -async function buildBundle( - args: OutputOptions & { - assetsDest: mixed, - entryFile: string, - maxWorkers: number, - resetCache: boolean, - transformer: string, - minify: boolean, - }, - configPromise: Promise, - /* $FlowFixMe(>=0.85.0 site=react_native_fb) This comment suppresses an error - * found when Flow v0.85 was deployed. To see the error, delete this comment - * and run Flow. */ - output = outputBundle, -) { // 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'; - const config = await configPromise; let sourceMapUrl = args.sourcemapOutput; if (sourceMapUrl && !args.sourcemapUseAbsolutePath) { @@ -52,7 +44,7 @@ async function buildBundle( platform: args.platform, }; - const server = new Server({...config, resetCache: args.resetCache}); + const server = new Server(config); try { const bundle = await output.build(server, requestOpts); diff --git a/packages/local-cli/bundle/bundle.js b/packages/local-cli/bundle/bundle.js index 8c42982b4..2eb039044 100644 --- a/packages/local-cli/bundle/bundle.js +++ b/packages/local-cli/bundle/bundle.js @@ -16,11 +16,8 @@ const outputBundle = require('metro/src/shared/output/bundle'); /** * Builds the bundle starting to look for dependencies at the given entry path. */ -function bundleWithOutput(argv, configPromise, args, output) { - if (!output) { - output = outputBundle; - } - return buildBundle(args, configPromise, output); +function bundleWithOutput(_, config, args, output) { + return buildBundle(args, config, output); } module.exports = { @@ -28,7 +25,6 @@ module.exports = { description: 'builds the javascript bundle for offline use', func: bundleWithOutput, options: bundleCommandLineArgs, - - // not used by the CLI itself + // Used by `ramBundle.js` withOutput: bundleWithOutput, }; diff --git a/packages/local-cli/bundle/bundleCommandLineArgs.js b/packages/local-cli/bundle/bundleCommandLineArgs.js index ef95e2a9f..3d70b2e11 100644 --- a/packages/local-cli/bundle/bundleCommandLineArgs.js +++ b/packages/local-cli/bundle/bundleCommandLineArgs.js @@ -4,11 +4,32 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @format + * @flow */ 'use strict'; +const path = require('path'); + +export type CommandLineArgs = { + assetsDest?: string, + entryFile: string, + resetCache: boolean, + resetGlobalCache: boolean, + transformer?: string, + minify?: boolean, + config?: string, + platform?: string, + dev: boolean, + bundleOutput: string, + bundleEncoding?: string, + maxWorkers?: number, + sourcemapOutput?: string, + sourcemapSourcesRoot?: string, + sourcemapUseAbsolutePath: boolean, + verbose: boolean, +}; + module.exports = [ { command: '--entry-file ', @@ -27,7 +48,7 @@ module.exports = [ { command: '--dev [boolean]', description: 'If false, warnings are disabled and the bundle is minified', - parse: val => (val === 'false' ? false : true), + parse: (val: string) => (val === 'false' ? false : true), default: true, }, { @@ -36,7 +57,7 @@ module.exports = [ 'Allows overriding whether bundle is minified. This defaults to ' + 'false if dev is true, and true if dev is false. Disabling minification ' + 'can be useful for speeding up production builds for testing purposes.', - parse: val => (val === 'false' ? false : true), + parse: (val: string) => (val === 'false' ? false : true), }, { command: '--bundle-output ', @@ -93,4 +114,9 @@ module.exports = [ 'Try to fetch transformed JS code from the global cache, if configured.', default: false, }, + { + command: '--config [string]', + description: 'Path to the CLI configuration file', + parse: (val: string) => path.resolve(val), + }, ]; diff --git a/packages/local-cli/bundle/types.flow.js b/packages/local-cli/bundle/types.flow.js deleted file mode 100644 index 04bc1c385..000000000 --- a/packages/local-cli/bundle/types.flow.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -'use strict'; - -export type {OutputOptions, RequestOptions} from 'metro/src/shared/types.flow'; diff --git a/packages/local-cli/cliEntry.js b/packages/local-cli/cliEntry.js index 94d32633c..a0992550b 100644 --- a/packages/local-cli/cliEntry.js +++ b/packages/local-cli/cliEntry.js @@ -10,25 +10,18 @@ 'use strict'; -const {configPromise} = require('./core'); - const assertRequiredOptions = require('./util/assertRequiredOptions'); -/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error - * found when Flow v0.54 was deployed. To see the error delete this comment and - * run Flow. */ const chalk = require('chalk'); const childProcess = require('child_process'); -/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error - * found when Flow v0.54 was deployed. To see the error delete this comment and - * run Flow. */ const commander = require('commander'); -const commands = require('./commands'); +const getCommands = require('./core/getCommands'); +const getLegacyConfig = require('./core/getLegacyConfig'); +const minimist = require('minimist'); const init = require('./init/init'); const path = require('path'); const pkg = require('./package.json'); -import type {CommandT} from './commands'; -import type {RNConfig} from './core'; +import type { CommandT, ContextT } from './core/types.flow'; commander.version(pkg.version); @@ -99,7 +92,7 @@ function printUnknownCommand(cmdName) { ); } -const addCommand = (command: CommandT, cfg: RNConfig) => { +const addCommand = (command: CommandT, ctx: ContextT) => { const options = command.options || []; const cmd = commander @@ -114,13 +107,14 @@ const addCommand = (command: CommandT, cfg: RNConfig) => { Promise.resolve() .then(() => { assertRequiredOptions(options, passedOptions); - return command.func(argv, cfg, passedOptions); + return command.func(argv, ctx, passedOptions); }) .catch(handleError); }); cmd.helpInformation = printHelpInformation.bind(cmd); cmd.examples = command.examples; + // $FlowFixMe: This is either null or not cmd.pkg = command.pkg; options.forEach(opt => @@ -128,24 +122,41 @@ const addCommand = (command: CommandT, cfg: RNConfig) => { opt.command, opt.description, opt.parse || defaultOptParser, - typeof opt.default === 'function' ? opt.default(cfg) : opt.default, + typeof opt.default === 'function' ? opt.default(ctx) : opt.default, ), ); - - // Placeholder option for --config, which is parsed before any other option, - // but needs to be here to avoid "unknown option" errors when specified - cmd.option('--config [string]', 'Path to the CLI configuration file'); + + // This is needed to avoid `unknown option` error by Commander.js + cmd.option('--projectRoot [string]', 'Path to the root of the project'); }; async function run() { - const config = await configPromise; const setupEnvScript = /^win/.test(process.platform) ? 'setup_env.bat' : 'setup_env.sh'; childProcess.execFileSync(path.join(__dirname, setupEnvScript)); - commands.forEach(cmd => addCommand(cmd, config)); + /** + * Read passed `options` and take the "global" settings + * + * @todo(grabbou): Consider unifying this by removing either `commander` + * or `minimist` + */ + const options = minimist(process.argv.slice(2)); + + const root = options.projectRoot + ? path.resolve(options.projectRoot) + : process.cwd(); + + const ctx = { + ...getLegacyConfig(root), + root, + }; + + const commands = getCommands(ctx.root); + + commands.forEach(command => addCommand(command, ctx)); commander.parse(process.argv); diff --git a/packages/local-cli/commands.js b/packages/local-cli/commands.js deleted file mode 100644 index 35a7f74de..000000000 --- a/packages/local-cli/commands.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -'use strict'; - -const {getProjectCommands} = require('./core'); - -import type {RNConfig} from './core'; - -export type CommandT = { - name: string, - description?: string, - usage?: string, - func: (argv: Array, config: RNConfig, args: Object) => ?Promise, - options?: Array<{ - command: string, - description?: string, - parse?: (val: string) => any, - default?: ((config: RNConfig) => mixed) | mixed, - }>, - examples?: Array<{ - desc: string, - cmd: string, - }>, - pkg?: { - version: string, - name: string, - }, -}; - -const documentedCommands = [ - require('./server/server'), - require('./runIOS/runIOS'), - require('./runAndroid/runAndroid'), - require('./library/library'), - require('./bundle/bundle'), - require('./bundle/ramBundle'), - require('./eject/eject'), - require('./link/link'), - require('./link/unlink'), - require('./install/install'), - require('./install/uninstall'), - require('./upgrade/upgrade'), - require('./logAndroid/logAndroid'), - require('./logIOS/logIOS'), - require('./dependencies/dependencies'), - require('./info/info'), -]; - -// 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, - ...getProjectCommands(), -]; - -module.exports = commands; diff --git a/packages/local-cli/core/Constants.js b/packages/local-cli/core/Constants.js deleted file mode 100644 index edf9cd517..000000000 --- a/packages/local-cli/core/Constants.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow strict - * @format - */ - -'use strict'; - -const ASSET_REGISTRY_PATH = 'react-native/Libraries/Image/AssetRegistry'; -const ASSET_SOURCE_RESOLVER_PATH = - 'react-native/Libraries/Image/AssetSourceResolver'; - -module.exports = { - ASSET_REGISTRY_PATH, - ASSET_SOURCE_RESOLVER_PATH, -}; diff --git a/packages/local-cli/core/__tests__/findAssets-test.js b/packages/local-cli/core/__tests__/findAssets-test.js index cf880b23d..bb3a000cc 100644 --- a/packages/local-cli/core/__tests__/findAssets-test.js +++ b/packages/local-cli/core/__tests__/findAssets-test.js @@ -13,7 +13,7 @@ jest.mock('path'); jest.mock('fs'); -const findAssets = require('../findAssets'); +const { findAssets } = require('../getAssets'); const dependencies = require('../__fixtures__/dependencies'); const fs = require('fs'); diff --git a/packages/local-cli/core/__tests__/findPlugins-test.js b/packages/local-cli/core/__tests__/findPlugins-test.js index ff1f80985..bf04ddecb 100644 --- a/packages/local-cli/core/__tests__/findPlugins-test.js +++ b/packages/local-cli/core/__tests__/findPlugins-test.js @@ -26,26 +26,26 @@ describe('findPlugins', () => { dependencies: {'rnpm-plugin-test': '*'}, })); - expect(findPlugins([ROOT])).toHaveProperty('commands'); - expect(findPlugins([ROOT])).toHaveProperty('platforms'); - expect(findPlugins([ROOT]).commands).toHaveLength(1); - expect(findPlugins([ROOT]).commands[0]).toBe('rnpm-plugin-test'); - expect(findPlugins([ROOT]).platforms).toHaveLength(0); + expect(findPlugins(ROOT)).toHaveProperty('commands'); + expect(findPlugins(ROOT)).toHaveProperty('platforms'); + expect(findPlugins(ROOT).commands).toHaveLength(1); + expect(findPlugins(ROOT).commands[0]).toBe('rnpm-plugin-test'); + expect(findPlugins(ROOT).platforms).toHaveLength(0); }); it('returns an empty array if there are no plugins in this folder', () => { jest.mock(pjsonPath, () => ({})); - expect(findPlugins([ROOT])).toHaveProperty('commands'); - expect(findPlugins([ROOT])).toHaveProperty('platforms'); - expect(findPlugins([ROOT]).commands).toHaveLength(0); - expect(findPlugins([ROOT]).platforms).toHaveLength(0); + expect(findPlugins(ROOT)).toHaveProperty('commands'); + expect(findPlugins(ROOT)).toHaveProperty('platforms'); + expect(findPlugins(ROOT).commands).toHaveLength(0); + expect(findPlugins(ROOT).platforms).toHaveLength(0); }); it('returns an object with empty arrays if there is no package.json in the supplied folder', () => { - expect(findPlugins(['fake-path'])).toHaveProperty('commands'); - expect(findPlugins(['fake-path'])).toHaveProperty('platforms'); - expect(findPlugins(['fake-path']).commands).toHaveLength(0); - expect(findPlugins(['fake-path']).platforms).toHaveLength(0); + expect(findPlugins('fake-path')).toHaveProperty('commands'); + expect(findPlugins('fake-path')).toHaveProperty('platforms'); + expect(findPlugins('fake-path').commands).toHaveLength(0); + expect(findPlugins('fake-path').platforms).toHaveLength(0); }); it('returns plugins from both dependencies and dev dependencies', () => { @@ -53,10 +53,10 @@ describe('findPlugins', () => { dependencies: {'rnpm-plugin-test': '*'}, devDependencies: {'rnpm-plugin-test-2': '*'}, })); - expect(findPlugins([ROOT])).toHaveProperty('commands'); - expect(findPlugins([ROOT])).toHaveProperty('platforms'); - expect(findPlugins([ROOT]).commands).toHaveLength(2); - expect(findPlugins([ROOT]).platforms).toHaveLength(0); + expect(findPlugins(ROOT)).toHaveProperty('commands'); + expect(findPlugins(ROOT)).toHaveProperty('platforms'); + expect(findPlugins(ROOT).commands).toHaveLength(2); + expect(findPlugins(ROOT).platforms).toHaveLength(0); }); it('returns unique list of plugins', () => { @@ -64,7 +64,7 @@ describe('findPlugins', () => { dependencies: {'rnpm-plugin-test': '*'}, devDependencies: {'rnpm-plugin-test': '*'}, })); - expect(findPlugins([ROOT]).commands).toHaveLength(1); + expect(findPlugins(ROOT).commands).toHaveLength(1); }); it('returns plugins in scoped modules', () => { @@ -77,8 +77,8 @@ describe('findPlugins', () => { }, })); - expect(findPlugins([ROOT])).toHaveProperty('commands'); - expect(findPlugins([ROOT])).toHaveProperty('platforms'); - expect(findPlugins([ROOT]).commands[0]).toBe('@org/rnpm-plugin-test'); + expect(findPlugins(ROOT)).toHaveProperty('commands'); + expect(findPlugins(ROOT)).toHaveProperty('platforms'); + expect(findPlugins(ROOT).commands[0]).toBe('@org/rnpm-plugin-test'); }); }); diff --git a/packages/local-cli/core/__tests__/makeCommand-test.js b/packages/local-cli/core/__tests__/makeCommand-test.js index c9842efa8..34a55e6f2 100644 --- a/packages/local-cli/core/__tests__/makeCommand-test.js +++ b/packages/local-cli/core/__tests__/makeCommand-test.js @@ -18,7 +18,7 @@ jest.setMock('child_process', { }), }); -const makeCommand = require('../makeCommand'); +const { makeCommand } = require('../getHooks'); describe('makeCommand', () => { const command = makeCommand('echo'); diff --git a/packages/local-cli/core/findAssets.js b/packages/local-cli/core/findAssets.js deleted file mode 100644 index a06596d10..000000000 --- a/packages/local-cli/core/findAssets.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -'use strict'; - -const glob = require('glob'); -const path = require('path'); - -const findAssetsInFolder = folder => - glob.sync(path.join(folder, '**'), {nodir: true}); - -/** - * Given an array of assets folders, e.g. ['Fonts', 'Images'], - * it globs in them to find all files that can be copied. - * - * It returns an array of absolute paths to files found. - */ -module.exports = function findAssets(folder, assets) { - return (assets || []) - .map(assetsFolder => path.join(folder, assetsFolder)) - .reduce( - (_assets, assetsFolder) => - _assets.concat(findAssetsInFolder(assetsFolder)), - [], - ); -}; diff --git a/packages/local-cli/core/findPlugins.js b/packages/local-cli/core/findPlugins.js index 7d76b5b6f..3ff90bfc4 100644 --- a/packages/local-cli/core/findPlugins.js +++ b/packages/local-cli/core/findPlugins.js @@ -82,7 +82,7 @@ const findHasteConfigInPackageAndConcat = (pjson, haste) => { } }; -const findPluginInFolder = folder => { +const findPluginsInFolder = folder => { const pjson = readPackage(folder); if (!pjson) { @@ -116,17 +116,16 @@ const findPluginInFolder = folder => { /** * Find plugins in package.json of the given folder * @param {String} folder Path to the folder to get the package.json from - * @type {Object} Object of commands and platform plugins */ -module.exports = function findPlugins(folders) { - const plugins = folders.map(findPluginInFolder); +module.exports = function findPlugins(folder: string) { + const plugin = findPluginsInFolder(folder); return { - commands: uniq(flatten(plugins.map(p => p.commands))), - platforms: uniq(flatten(plugins.map(p => p.platforms))), + commands: uniq(flatten(plugin.commands)), + platforms: uniq(flatten(plugin.platforms)), haste: { - platforms: uniq(flatten(plugins.map(p => p.haste.platforms))), + platforms: uniq(flatten(plugin.haste.platforms)), providesModuleNodeModules: uniq( - flatten(plugins.map(p => p.haste.providesModuleNodeModules)), + flatten(plugin.haste.providesModuleNodeModules), ), }, }; diff --git a/packages/local-cli/core/getAssets.js b/packages/local-cli/core/getAssets.js new file mode 100644 index 000000000..f25ce57df --- /dev/null +++ b/packages/local-cli/core/getAssets.js @@ -0,0 +1,37 @@ +/** + * @flow + */ + +'use strict'; + +const glob = require('glob'); +const path = require('path'); +const getPackageConfiguration = require('./getPackageConfiguration'); + +const findAssetsInFolder = folder => + glob.sync(path.join(folder, '**'), {nodir: true}); + +/** + * Given an array of assets folders, e.g. ['Fonts', 'Images'], + * it globs in them to find all files that can be copied. + * + * It returns an array of absolute paths to files found. + */ +function findAssets(folder, assets) { + return (assets || []) + .map(asset => path.join(folder, asset)) + .reduce( + (acc, assetPath) => (acc.concat(findAssetsInFolder(assetPath)): Array), + [], + ); +}; + +/** + * Returns a project configuration in a given folder + */ +module.exports = function getAssets(root: string) { + const config = getPackageConfiguration(root); + return findAssets(root, config.assets); +}; + +module.exports.findAssets = findAssets; diff --git a/packages/local-cli/core/getCommands.js b/packages/local-cli/core/getCommands.js new file mode 100644 index 000000000..325f678ae --- /dev/null +++ b/packages/local-cli/core/getCommands.js @@ -0,0 +1,93 @@ +/** + * @flow + */ + +'use strict'; + +const findPlugins = require('./findPlugins'); +const path = require('path'); +const flatten = require('lodash').flatten; + +import type { CommandT, ProjectCommandT } from './types.flow'; + +/** + * List of built-in commands + */ +const loadLocalCommands = () => [ + require('../server/server'), + require('../runIOS/runIOS'), + require('../runAndroid/runAndroid'), + require('../library/library'), + require('../bundle/bundle'), + require('../bundle/ramBundle'), + require('../eject/eject'), + require('../link/link'), + require('../link/unlink'), + require('../install/install'), + require('../install/uninstall'), + require('../upgrade/upgrade'), + require('../logAndroid/logAndroid'), + require('../logIOS/logIOS'), + require('../dependencies/dependencies'), + require('../info/info'), +]; + +/** + * Returns an array of commands that are defined in the project. + * + * This checks all CLI plugins for presence of 3rd party packages that define commands + * and loads them + */ +const loadProjectCommands = (root: string): Array => { + const plugins = findPlugins(root); + + return plugins.commands.reduce((acc: Array, pathToCommands) => { + /** + * `pathToCommand` is a path to a file where commands are defined, relative to `node_modules` + * folder. + * + * Following code gets the name of the package name out of the path, taking scope + * into consideration. + */ + const name = + pathToCommands[0] === '@' + ? pathToCommands + .split(path.sep) + .slice(0, 2) + .join(path.sep) + : pathToCommands.split(path.sep)[0]; + + // $FlowFixMe: Non-literal require + const pkg = require(path.join(root, 'node_modules', name, 'package.json')); + + // $FlowFixMe: Non-literal require + let requiredCommands: (ProjectCommandT | Array) = require( + path.join(root, 'node_modules', pathToCommands) + ); + + if (Array.isArray(requiredCommands)) { + return acc.concat(requiredCommands.map(requiredCommand => ({ ...requiredCommand, pkg }))); + } + + return acc.concat({ ...requiredCommands }); + }, []); +}; + +/** + * Loads all the commands inside a given `root` folder + */ +module.exports = (root: string): Array => [ + ...loadLocalCommands(), + { + 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'), + ); + }, + }, + ...loadProjectCommands(root), +]; diff --git a/packages/local-cli/core/makeCommand.js b/packages/local-cli/core/getHooks.js similarity index 57% rename from packages/local-cli/core/makeCommand.js rename to packages/local-cli/core/getHooks.js index d1d0fe819..dafb34de9 100644 --- a/packages/local-cli/core/makeCommand.js +++ b/packages/local-cli/core/getHooks.js @@ -1,17 +1,13 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format + * @flow */ 'use strict'; const spawn = require('child_process').spawn; +const getPackageConfiguration = require('./getPackageConfiguration'); -module.exports = function makeCommand(command) { +function makeCommand(command) { return cb => { if (!cb) { throw new Error( @@ -36,3 +32,18 @@ module.exports = function makeCommand(command) { }); }; }; + +module.exports = function getHooks(root: string) { + const commands = getPackageConfiguration(root).commands || {}; + + const acc = {}; + + Object.keys(commands) + .forEach(command => { + acc[command] = makeCommand(commands[command]); + }); + + return acc; +}; + +module.exports.makeCommand = makeCommand; diff --git a/packages/local-cli/core/getLegacyConfig.js b/packages/local-cli/core/getLegacyConfig.js new file mode 100644 index 000000000..a252eaa09 --- /dev/null +++ b/packages/local-cli/core/getLegacyConfig.js @@ -0,0 +1,69 @@ +/** + * @flow + */ +const path = require('path'); +const util = require('util'); + +const getPlatforms = require('./getPlatforms'); +const getPackageConfiguration = require('./getPackageConfiguration'); +const getHooks = require('./getHooks'); +const getAssets = require('./getAssets'); +const getParams = require('./getParams'); + +const generateDeprecationMessage = (api) => { + return `${api} is deprecated and will be removed soon. Please check release notes on how to upgrade` +}; + +/** + * Gets legacy configuration to support existing plugins while they migrate + * to the new API + * + * This file will be removed from the next version. + */ +module.exports = (root: string) => ({ + getPlatformConfig: util.deprecate( + () => getPlatforms(root), + generateDeprecationMessage("getPlatformConfig()"), + ), + getProjectConfig: util.deprecate( + () => { + const platforms = getPlatforms(root); + + const rnpm = getPackageConfiguration(root); + + const config = { + ...rnpm, + assets: getAssets(root), + }; + + Object.keys(platforms).forEach(key => { + config[key] = platforms[key].projectConfig(root, rnpm[key] || {}); + }); + + return config; + }, + generateDeprecationMessage("getProjectConfig()"), + ), + getDependencyConfig: util.deprecate( + (packageName: string) => { + const platforms = getPlatforms(root); + const folder = path.join(process.cwd(), 'node_modules', packageName); + + const rnpm = getPackageConfiguration(folder); + + const config = { + ...rnpm, + assets: getAssets(folder), + commands: getHooks(folder), + params: getParams(folder), + }; + + Object.keys(platforms).forEach(key => { + config[key] = platforms[key].dependencyConfig(folder, rnpm[key] || {}); + }); + + return config; + }, + generateDeprecationMessage("getDependencyConfig()"), + ), +}); diff --git a/packages/local-cli/core/getPackageConfiguration.js b/packages/local-cli/core/getPackageConfiguration.js new file mode 100644 index 000000000..656e0f818 --- /dev/null +++ b/packages/local-cli/core/getPackageConfiguration.js @@ -0,0 +1,14 @@ +/** + * @flow + */ +const path = require('path'); + +import type { PackageConfigurationT } from './types.flow'; + +/** + * Returns configuration of the CLI from `package.json`. + */ +module.exports = function getPackageConfiguration(folder: string): PackageConfigurationT { + // $FlowFixMe: Non-literal require + return require(path.join(folder, './package.json')).rnpm || {}; +}; diff --git a/packages/local-cli/core/getParams.js b/packages/local-cli/core/getParams.js new file mode 100644 index 000000000..52c985e9d --- /dev/null +++ b/packages/local-cli/core/getParams.js @@ -0,0 +1,12 @@ +/** + * @flow + */ + +'use strict'; + +const getPackageConfiguration = require('./getPackageConfiguration'); + +module.exports = function getParams(root: string) { + const config = getPackageConfiguration(root); + return config.params || []; +}; diff --git a/packages/local-cli/core/getPlatforms.js b/packages/local-cli/core/getPlatforms.js new file mode 100644 index 000000000..2d3f22248 --- /dev/null +++ b/packages/local-cli/core/getPlatforms.js @@ -0,0 +1,45 @@ +/** + * @flow + */ + +const findPlugins = require('./findPlugins'); + +/** + * Support for `ios` and `android` platforms is built-in + * + * @todo(grabbou): Move this out of the core to increase "interoperability" + * with other platforms + */ +const builtInPlatforms = { + ios: require('./ios'), + android: require('./android'), +}; + +import type { PlatformsT } from './types.flow'; + +/** + * Returns an object with available platforms + */ +module.exports = function getPlatforms(root: string): PlatformsT { + const plugins = findPlugins(root); + + /** + * Each `platfom` is a file that should define an object with platforms available + * and the config required. + * + * @todo(grabbou): We should validate if the config loaded is correct, warn and skip + * using it if it's invalid. + */ + const projectPlatforms = plugins.platforms.reduce((acc, pathToPlatform) => { + return Object.assign( + acc, + // $FlowFixMe non-literal require + require(path.join(root, 'node_modules', pathToPlatform)), + ); + }, {}); + + return { + ...builtInPlatforms, + ...projectPlatforms, + }; +} diff --git a/packages/local-cli/core/index.js b/packages/local-cli/core/index.js deleted file mode 100644 index 3f201c61e..000000000 --- a/packages/local-cli/core/index.js +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ - -'use strict'; - -const android = require('./android'); -const Config = require('../util/Config'); -const findPlugins = require('./findPlugins'); -const findAssets = require('./findAssets'); -const ios = require('./ios'); -const wrapCommands = require('./wrapCommands'); -const {ASSET_REGISTRY_PATH} = require('./Constants'); -const findReactNativePath = require('../util/findReactNativePath'); - -const flatten = require('lodash').flatten; -const minimist = require('minimist'); -const path = require('path'); - -import type {CommandT} from '../commands'; -import type {ConfigT} from 'metro-config/src/configTypes.flow'; - -export type RNConfig = { - ...ConfigT, - /** - * Returns an object with all platform configurations. - */ - getPlatformConfig(): Object, - /** - * Returns project config from the current working directory - */ - getProjectConfig(): Object, - /** - * Returns dependency config from /packageName - */ - getDependencyConfig(pkgName: string): Object, -}; - -const getRNPMConfig = folder => - // $FlowFixMe non-literal require - require(path.join(folder, './package.json')).rnpm || {}; - -const attachPackage = (command, pkg) => - Array.isArray(command) - ? command.map(cmd => attachPackage(cmd, pkg)) - : {...command, pkg}; - -const appRoot = process.cwd(); -const plugins = findPlugins([appRoot]); -const pluginPlatforms = plugins.platforms.reduce((acc, pathToPlatforms) => { - return Object.assign( - acc, - // $FlowFixMe non-literal require - require(path.join(appRoot, 'node_modules', pathToPlatforms)), - ); -}, {}); - -const defaultConfig = { - getHasteImplModulePath(): ?string { - try { - return require.resolve(findReactNativePath('jest/hasteImpl')); - } catch(_ignored) { - return; - } - }, - - getPlatforms(): Array { - return ['ios', 'android', 'native', ...plugins.haste.platforms]; - }, - - getProvidesModuleNodeModules(): Array { - return ['react-native', ...plugins.haste.providesModuleNodeModules]; - }, -}; - -const defaultRNConfig = { - getPlatformConfig(): Object { - return { - ios, - android, - ...pluginPlatforms, - }; - }, - - getProjectConfig(): Object { - const platforms = this.getPlatformConfig(); - const folder = process.cwd(); - const rnpm = getRNPMConfig(folder); - - let config = Object.assign({}, rnpm, { - assets: findAssets(folder, rnpm.assets), - }); - - Object.keys(platforms).forEach(key => { - config[key] = platforms[key].projectConfig(folder, rnpm[key] || {}); - }); - - return config; - }, - - getDependencyConfig(packageName: string) { - const platforms = this.getPlatformConfig(); - const folder = path.join(process.cwd(), 'node_modules', packageName); - const rnpm = getRNPMConfig(folder); - - let config = Object.assign({}, rnpm, { - assets: findAssets(folder, rnpm.assets), - commands: wrapCommands(rnpm.commands), - params: rnpm.params || [], - }); - - Object.keys(platforms).forEach(key => { - config[key] = platforms[key].dependencyConfig(folder, rnpm[key] || {}); - }); - - return config; - }, -}; - -/** - * Loads the CLI configuration - */ -async function getCliConfig(): Promise { - const cliArgs = minimist(process.argv.slice(2)); - const config = await Config.load( - typeof cliArgs.config === 'string' - ? path.resolve(__dirname, cliArgs.config) - : null, - ); - - // $FlowFixMe Metro configuration is immutable. - config.transformer.assetRegistryPath = ASSET_REGISTRY_PATH; - config.resolver.hasteImplModulePath = - config.resolver.hasteImplModulePath || defaultConfig.getHasteImplModulePath(); - config.resolver.platforms = config.resolver.platforms - ? config.resolver.platforms.concat(defaultConfig.getPlatforms()) - : defaultConfig.getPlatforms(); - config.resolver.providesModuleNodeModules = config.resolver - .providesModuleNodeModules - ? config.resolver.providesModuleNodeModules.concat( - defaultConfig.getProvidesModuleNodeModules(), - ) - : defaultConfig.getProvidesModuleNodeModules(); - - return {...defaultRNConfig, ...config}; -} - -/** - * Returns an array of project commands used by the CLI to load - */ -function getProjectCommands(): Array { - const commands = plugins.commands.map(pathToCommands => { - const name = - pathToCommands[0] === '@' - ? pathToCommands - .split(path.sep) - .slice(0, 2) - .join(path.sep) - : pathToCommands.split(path.sep)[0]; - - return attachPackage( - // $FlowFixMe: Non-literal require - require(path.join(appRoot, 'node_modules', pathToCommands)), - // $FlowFixMe: Non-literal require - require(path.join(appRoot, 'node_modules', name, 'package.json')), - ); - }); - return flatten(commands); -} - -module.exports.configPromise = getCliConfig(); -module.exports.getProjectCommands = getProjectCommands; diff --git a/packages/local-cli/core/types.flow.js b/packages/local-cli/core/types.flow.js new file mode 100644 index 000000000..9434e700c --- /dev/null +++ b/packages/local-cli/core/types.flow.js @@ -0,0 +1,122 @@ +/** + * @flow + */ + +export type ContextT = { + root: string, +}; + +export type LocalCommandT = { + name: string, + description?: string, + usage?: string, + func: (argv: Array, ctx: ContextT, args: Object) =>?Promise, + options?: Array<{ + command: string, + description?: string, + parse?: (val: string) => any, + default?: ((ctx: ContextT) => mixed) | mixed, + }>, + examples?: Array<{ + desc: string, + cmd: string, + }>, +}; + +type Package = { + version: string, + name: string, +}; + +/** + * User can define command either as an object (RequiredCommandT) or + * as an array of commands (Array). + */ +export type ProjectCommandT = LocalCommandT & { + pkg: Package +}; + +/** + * Main type. Can be either local or a project command. + */ +export type CommandT = LocalCommandT | ProjectCommandT; + +/** + * Config of a single platform + */ +export type PlatformConfigT = { + projectConfig: (string, ParamsT) => ?ProjectConfigT, + dependencyConfig: (string, ParamsT) => ?DependencyConfigT, + /** + * @todo(grabbou): This should not be part of the "core". It should be + * specific to `link` and `unlink`. Remove it from here soon. + */ + linkConfig: () => { + /** + * @todo(grabbou): Revert the arguments order to align with the rest + */ + isInstalled: (ProjectConfigT, string, DependencyConfigT) => boolean, + register: (string, DependencyConfigT, Object, ProjectConfigT) => void, + unregister: (string, DependencyConfigT, ProjectConfigT, Array) => void, + copyAssets: (string[], ProjectConfigT) => void, + unlinkAssets: (string[], ProjectConfigT) => void + }, +}; + +/** + * The following types will be useful when we type `link` itself. For now, + * they can be treated as aliases. + */ +export type AndroidConfigParamsT = {}; + +export type IOSConfigParamsT = {}; + +export type ProjectConfigIOST = {}; + +export type DependencyConfigIOST = ProjectConfigIOST; + +export type ProjectConfigAndroidT = {}; + +export type DependencyConfigAndroidT = {}; + +/** + * Config of a project. + * + * When one of the projects is `null`, that means given platform + * is not available in the current project. + */ +export type ProjectConfigT = { + android: ?ProjectConfigAndroidT, + ios: ?ProjectConfigIOST +}; + +/** + * Config of a dependency. Just like above, when one of the values is `null`, + * given platform is not available. + */ +export type DependencyConfigT = { + android: ?DependencyConfigAndroidT, + ios: ?DependencyConfigIOST +}; + +/** + * Available platforms. Additional plugins should assert the type on their own. + */ +export type PlatformsT = { + ios: PlatformConfigT, + android: PlatformConfigT, + [name: string]: PlatformConfigT +}; + +export type InquirerPromptT = any; + +/** + * Configuration of the CLI as set by a package in the package.json + */ +export type PackageConfigurationT = { + assets?: string[], + commands?: { [name: string]: string }, + params?: InquirerPromptT[], + android: AndroidConfigParamsT, + ios: IOSConfigParamsT +}; diff --git a/packages/local-cli/core/wrapCommands.js b/packages/local-cli/core/wrapCommands.js deleted file mode 100644 index 1f3ce34ab..000000000 --- a/packages/local-cli/core/wrapCommands.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -'use strict'; - -const makeCommand = require('./makeCommand'); - -module.exports = function wrapCommands(commands) { - const mappedCommands = {}; - Object.keys(commands || []).forEach(k => { - mappedCommands[k] = makeCommand(commands[k]); - }); - return mappedCommands; -}; diff --git a/packages/local-cli/init/init.js b/packages/local-cli/init/init.js index 9368389f4..fc0f81d71 100644 --- a/packages/local-cli/init/init.js +++ b/packages/local-cli/init/init.js @@ -17,7 +17,6 @@ const path = require('path'); const printRunInstructions = require('../generator/printRunInstructions'); const process = require('process'); const yarn = require('../util/yarn'); -const findReactNativePath = require('../util/findReactNativePath'); /** * Creates the template for a React Native project given the provided @@ -52,7 +51,7 @@ function init(projectDir, argsOrName) { * @param options Command line arguments parsed by minimist. */ function generateProject(destinationRoot, newProjectName, options) { - var reactNativePackageJson = require(findReactNativePath('package.json')); + var reactNativePackageJson = require.resolve('react-native/package.json'); var {peerDependencies} = reactNativePackageJson; if (!peerDependencies) { console.error( diff --git a/packages/local-cli/link/__tests__/__snapshots__/getDependencyConfig-test.js.snap b/packages/local-cli/link/__tests__/__snapshots__/getDependencyConfig-test.js.snap new file mode 100644 index 000000000..51a975b31 --- /dev/null +++ b/packages/local-cli/link/__tests__/__snapshots__/getDependencyConfig-test.js.snap @@ -0,0 +1,41 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getDependencyConfig should filter out invalid react-native projects 1`] = ` +Array [ + Object { + "assets": Array [], + "commands": Object {}, + "config": Object { + "android": Object { + "sampleAndroidKey": "", + }, + "ios": Object { + "sampleiOSKey": "", + }, + }, + "name": "react-native-windows", + "params": Array [], + "path": "/root/node_modules/react-native-windows", + }, +] +`; + +exports[`getDependencyConfig should return an array of dependencies' config 1`] = ` +Array [ + Object { + "assets": Array [], + "commands": Object {}, + "config": Object { + "android": Object { + "sampleAndroidKey": "", + }, + "ios": Object { + "sampleiOSKey": "", + }, + }, + "name": "react-native-windows", + "params": Array [], + "path": "/root/node_modules/react-native-windows", + }, +] +`; diff --git a/packages/local-cli/link/__tests__/getDependencyConfig-test.js b/packages/local-cli/link/__tests__/getDependencyConfig-test.js index 1e3813d06..78a4bfdbe 100644 --- a/packages/local-cli/link/__tests__/getDependencyConfig-test.js +++ b/packages/local-cli/link/__tests__/getDependencyConfig-test.js @@ -10,25 +10,38 @@ 'use strict'; +const platforms = { + ios: { + dependencyConfig: () => ({ sampleiOSKey: '' }), + }, + android: { + dependencyConfig: () => ({ sampleAndroidKey: '' }), + }, +}; + +jest.setMock('../../core/getPackageConfiguration', (folder) => { + if (folder === '/root/node_modules/abcd') { + throw new Error('Cannot require'); + } + return {}; +}); + const getDependencyConfig = require('../getDependencyConfig'); describe('getDependencyConfig', () => { - it("should return an array of dependencies' rnpm config", () => { - const config = { - getDependencyConfig: jest.fn(), - }; + it("should return an array of dependencies' config", () => { + const dependencies = getDependencyConfig({ root: '/root' }, platforms, ['react-native-windows']); - expect(Array.isArray(getDependencyConfig(config, ['abcd']))).toBeTruthy(); - expect(config.getDependencyConfig.mock.calls.length).toEqual(1); + expect(dependencies).toMatchSnapshot(); }); it('should filter out invalid react-native projects', () => { - const config = { - getDependencyConfig: jest.fn().mockImplementation(() => { - throw new Error('Cannot require'); - }), - }; + const dependencies = getDependencyConfig( + { root: '/root' }, + platforms, + ['react-native-windows', 'abcd'] + ); - expect(getDependencyConfig(config, ['abcd'])).toEqual([]); + expect(dependencies).toMatchSnapshot(); }); }); diff --git a/packages/local-cli/link/__tests__/getProjectDependencies-test.js b/packages/local-cli/link/__tests__/getProjectDependencies-test.js index de9a443a7..82a5e0109 100644 --- a/packages/local-cli/link/__tests__/getProjectDependencies-test.js +++ b/packages/local-cli/link/__tests__/getProjectDependencies-test.js @@ -22,7 +22,7 @@ describe('getProjectDependencies', () => { }); it('should return an array of project dependencies', () => { jest.setMock(path.join(CWD, './package.json'), { - dependencies: {lodash: '^6.0.0', 'react-native': '^16.0.0'}, + dependencies: {lodash: '^6.0.0', 'react-native': '^16.0.0', 'react-native-local-cli': '*'}, }); expect(getProjectDependencies(CWD)).toEqual(['lodash']); diff --git a/packages/local-cli/link/__tests__/link-test.js b/packages/local-cli/link/__tests__/link-test.js index cab9a24c4..6e9d71141 100644 --- a/packages/local-cli/link/__tests__/link-test.js +++ b/packages/local-cli/link/__tests__/link-test.js @@ -11,8 +11,13 @@ 'use strict'; const log = require('npmlog'); + jest.setMock('chalk', {grey: str => str}); +const context = { + root: process.cwd(), +}; + describe('link', () => { beforeEach(() => { jest.resetModules(); @@ -21,28 +26,25 @@ describe('link', () => { }); it('should reject when run in a folder without package.json', done => { - const config = { - getProjectConfig: () => { - throw new Error('No package.json found'); - }, - }; - const link = require('../link').func; - link([], config).catch(() => done()); + link([], { root: '/' }).catch(() => done()); }); it('should accept a name of a dependency to link', done => { - const config = { - getPlatformConfig: () => ({ios: {}, android: {}}), - getProjectConfig: () => ({assets: []}), - getDependencyConfig: jest - .fn() - .mockReturnValue({assets: [], commands: {}}), - }; + const getDependencyConfig = jest.fn(() => [{ + config: { + ios: null, + android: null, + }, + assets: [], + commands: {} + }]); + + jest.setMock('../getDependencyConfig', getDependencyConfig); const link = require('../link').func; - link(['react-native-gradient'], config).then(() => { - expect(config.getDependencyConfig.mock.calls[0]).toEqual([ + link(['react-native-gradient'], context).then(() => { + expect(getDependencyConfig.mock.calls[0][2]).toEqual([ 'react-native-gradient', ]); done(); @@ -50,199 +52,188 @@ describe('link', () => { }); it('should accept the name of a dependency with a scope / tag', async () => { - const config = { - getPlatformConfig: () => ({ios: {}, android: {}}), - getProjectConfig: () => ({assets: []}), - getDependencyConfig: jest - .fn() - .mockReturnValue({assets: [], commands: {}}), - }; + const getDependencyConfig = jest.fn(() => [{ + config: { + ios: null, + android: null, + }, + assets: [], + commands: {} + }]); + + jest.setMock('../getDependencyConfig', getDependencyConfig); const link = require('../link').func; - await link(['@scope/something@latest'], config); - expect(config.getDependencyConfig.mock.calls[0]).toEqual([ + await link(['@scope/something@latest'], context); + + expect(getDependencyConfig.mock.calls[0][2]).toEqual([ '@scope/something', ]); }); it('should register native module when android/ios projects are present', done => { + const prelink = jest.fn().mockImplementation(cb => cb()); + const postlink = jest.fn().mockImplementation(cb => cb()); + + jest.setMock('../getProjectConfig', () => ({ + ios: {}, + android: {}, + })); + + const getDependencyConfig = jest.fn(() => [{ + config: { + ios: {}, + android: {}, + }, + assets: [], + commands: {prelink, postlink} + }]); + + jest.setMock('../getDependencyConfig', getDependencyConfig); + const registerNativeModule = jest.fn(); - const dependencyConfig = {android: {}, ios: {}, assets: [], commands: {}}; - const androidLinkConfig = require('../android'); - const iosLinkConfig = require('../ios'); - const config = { - getPlatformConfig: () => ({ - ios: {linkConfig: iosLinkConfig}, - android: {linkConfig: androidLinkConfig}, - }), - getProjectConfig: () => ({android: {}, ios: {}, assets: []}), - getDependencyConfig: jest.fn().mockReturnValue(dependencyConfig), - }; jest.setMock('../android/isInstalled.js', jest.fn().mockReturnValue(false)); - jest.setMock('../android/registerNativeModule.js', registerNativeModule); - + jest.setMock('../ios/isInstalled.js', jest.fn().mockReturnValue(false)); - jest.setMock('../ios/registerNativeModule.js', registerNativeModule); const link = require('../link').func; - link(['react-native-blur'], config).then(() => { + link(['react-native-blur'], context).then(() => { expect(registerNativeModule.mock.calls.length).toBe(2); - done(); - }); - }); - - it('should not register modules when they are already installed', done => { - const registerNativeModule = jest.fn(); - const dependencyConfig = {ios: {}, android: {}, assets: [], commands: {}}; - const config = { - getPlatformConfig: () => ({ios: {}, android: {}}), - getProjectConfig: () => ({ios: {}, android: {}, assets: []}), - getDependencyConfig: jest.fn().mockReturnValue(dependencyConfig), - }; - - jest.setMock('../ios/isInstalled.js', jest.fn().mockReturnValue(true)); - - jest.setMock('../android/isInstalled.js', jest.fn().mockReturnValue(true)); - - jest.setMock('../ios/registerNativeModule.js', registerNativeModule); - jest.setMock('../android/registerNativeModule.js', registerNativeModule); + expect(prelink.mock.invocationCallOrder[0]).toBeLessThan( + registerNativeModule.mock.invocationCallOrder[0], + ); - const link = require('../link').func; + expect(postlink.mock.invocationCallOrder[0]).toBeGreaterThan( + registerNativeModule.mock.invocationCallOrder[0], + ); - link(['react-native-blur'], config).then(() => { - expect(registerNativeModule.mock.calls.length).toEqual(0); done(); }); }); - it('should register native modules for plugins', done => { - const registerNativeModule = jest.fn(); - const dependencyConfig = { + it('should copy assets from both project and dependencies projects', done => { + const dependencyAssets = ['Fonts/Font.ttf']; + const projectAssets = ['Fonts/FontC.ttf']; + + jest.setMock('../getProjectConfig', () => ({ ios: {}, android: {}, - test: {}, - assets: [], - commands: {}, - }; - const linkPluginConfig = { - isInstalled: () => false, - register: registerNativeModule, - }; - const config = { - getPlatformConfig: () => ({ + })); + + jest.setMock('../getDependencyConfig', () => [{ + config: { ios: {}, android: {}, - test: {linkConfig: () => linkPluginConfig}, - }), - getProjectConfig: () => ({ios: {}, android: {}, test: {}, assets: []}), - getDependencyConfig: jest.fn().mockReturnValue(dependencyConfig), - }; + }, + assets: dependencyAssets, + commands: {} + }]); - jest.setMock('../ios/isInstalled.js', jest.fn().mockReturnValue(true)); + jest.setMock('../android/isInstalled.js', jest.fn().mockReturnValue(false)); + jest.setMock('../android/registerNativeModule.js', jest.fn()); + + jest.setMock('../ios/isInstalled.js', jest.fn().mockReturnValue(false)); + jest.setMock('../ios/registerNativeModule.js', jest.fn()); - jest.setMock('../android/isInstalled.js', jest.fn().mockReturnValue(true)); + jest.setMock('../../core/getAssets', () => projectAssets); + + const copyAssets = jest.fn(); + + jest.setMock('../ios/copyAssets.js', copyAssets); + jest.setMock('../android/copyAssets.js', copyAssets); const link = require('../link').func; - link(['react-native-blur'], config).then(() => { - expect(registerNativeModule.mock.calls.length).toBe(1); + link(['react-native-blur'], context).then(() => { + expect(copyAssets.mock.calls.length).toBe(2); + expect(copyAssets.mock.calls[0][0]).toEqual( + projectAssets.concat(dependencyAssets), + ); + jest.unmock('../../core/getAssets'); done(); }); }); - it('should not register native modules for plugins when already installed', done => { - const registerNativeModule = jest.fn(); - const dependencyConfig = { + it('should not register modules when they are already installed', done => { + jest.setMock('../getProjectConfig', () => ({ ios: {}, android: {}, - test: {}, - assets: [], - commands: {}, - }; - const linkPluginConfig = { - isInstalled: () => true, - register: registerNativeModule, - }; - const config = { - getPlatformConfig: () => ({ + })); + + const getDependencyConfig = jest.fn(() => [{ + config: { ios: {}, android: {}, - test: {linkConfig: () => linkPluginConfig}, - }), - getProjectConfig: () => ({ios: {}, android: {}, test: {}, assets: []}), - getDependencyConfig: jest.fn().mockReturnValue(dependencyConfig), - }; + }, + assets: [], + commands: {} + }]); - jest.setMock('../ios/isInstalled.js', jest.fn().mockReturnValue(true)); + jest.setMock('../getDependencyConfig', getDependencyConfig); + + const registerNativeModule = jest.fn(); jest.setMock('../android/isInstalled.js', jest.fn().mockReturnValue(true)); + jest.setMock('../android/registerNativeModule.js', registerNativeModule); + + jest.setMock('../ios/isInstalled.js', jest.fn().mockReturnValue(true)); + jest.setMock('../ios/registerNativeModule.js', registerNativeModule); const link = require('../link').func; - link(['react-native-blur'], config).then(() => { + link(['react-native-blur'], context).then(() => { expect(registerNativeModule.mock.calls.length).toEqual(0); done(); }); }); - it('should run prelink and postlink commands at the appropriate times', async () => { - const registerNativeModule = jest.fn(); - const prelink = jest.fn().mockImplementation(cb => cb()); - const postlink = jest.fn().mockImplementation(cb => cb()); - - jest.setMock('../ios/registerNativeModule.js', registerNativeModule); + it('should register native modules for additional platforms', done => { + jest.setMock('../getProjectConfig', () => ({ + ios: {}, + android: {}, + windows: {}, + })); - jest.setMock('../ios/isInstalled.js', jest.fn().mockReturnValue(false)); + const registerNativeModule = jest.fn(); - const linkConfig = require('../ios'); - const config = { - getPlatformConfig: () => ({ios: {linkConfig: linkConfig}}), - getProjectConfig: () => ({ios: {}, assets: []}), - getDependencyConfig: jest.fn().mockReturnValue({ + const genericLinkConfig = () => ({ + isInstalled: () => false, + register: registerNativeModule, + }); + + const getDependencyConfig = jest.fn(() => [{ + config: { ios: {}, - assets: [], - commands: {prelink, postlink}, - }), - }; - - const link = require('../link').func; - await link(['react-native-blur'], config); - - expect(prelink.mock.invocationCallOrder[0]).toBeLessThan( - registerNativeModule.mock.invocationCallOrder[0], - ); - expect(postlink.mock.invocationCallOrder[0]).toBeGreaterThan( - registerNativeModule.mock.invocationCallOrder[0], - ); - }); + android: {}, + windows: {} + }, + assets: [], + commands: {} + }]); - it('should copy assets from both project and dependencies projects', done => { - const dependencyAssets = ['Fonts/Font.ttf']; - const dependencyConfig = {assets: dependencyAssets, ios: {}, commands: {}}; - const projectAssets = ['Fonts/FontC.ttf']; - const copyAssets = jest.fn(); + jest.setMock('../../core/getPlatforms', () => ({ + ios: {linkConfig: require('../ios')}, + android: {linkConfig: require('../android')}, + windows: {linkConfig: genericLinkConfig}, + })); - jest.setMock('../ios/copyAssets.js', copyAssets); + jest.setMock('../getDependencyConfig', getDependencyConfig); - const linkConfig = require('../ios'); - const config = { - getPlatformConfig: () => ({ios: {linkConfig: linkConfig}}), - getProjectConfig: () => ({ios: {}, assets: projectAssets}), - getDependencyConfig: jest.fn().mockReturnValue(dependencyConfig), - }; + jest.setMock('../android/isInstalled.js', jest.fn().mockReturnValue(true)); + jest.setMock('../android/registerNativeModule.js', registerNativeModule); + + jest.setMock('../ios/isInstalled.js', jest.fn().mockReturnValue(true)); + jest.setMock('../ios/registerNativeModule.js', registerNativeModule); const link = require('../link').func; - link(['react-native-blur'], config).then(() => { - expect(copyAssets.mock.calls.length).toBe(1); - expect(copyAssets.mock.calls[0][0]).toEqual( - projectAssets.concat(dependencyAssets), - ); + link(['react-native-blur'], context).then(() => { + expect(registerNativeModule.mock.calls.length).toBe(1); done(); }); }); diff --git a/packages/local-cli/link/getDependencyConfig.js b/packages/local-cli/link/getDependencyConfig.js index fcecae2ef..e241be317 100644 --- a/packages/local-cli/link/getDependencyConfig.js +++ b/packages/local-cli/link/getDependencyConfig.js @@ -1,25 +1,54 @@ /** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format + * @flow */ -/** - * Given an array of dependencies - it returns their RNPM config - * if they were valid. - */ -module.exports = function getDependencyConfig(config, deps) { - return deps.reduce((acc, name) => { +'use strict'; + +const path = require('path'); + +import type { PlatformsT, ContextT, InquirerPromptT, DependencyConfigT } from '../core/types.flow'; + +const getPackageConfiguration = require('../core/getPackageConfiguration'); +const getParams = require('../core/getParams'); +const getHooks = require('../core/getHooks'); +const getAssets = require('../core/getAssets'); + +type DependenciesConfig = Array<{ + config: DependencyConfigT, + name: string, + path: string, + assets: string[], + commands: { [name: string]: string }, + params: InquirerPromptT[], +}>; + +module.exports = function getDependencyConfig( + ctx: ContextT, + availablePlatforms: PlatformsT, + dependencies: string[] +): DependenciesConfig { + return dependencies.reduce((acc, packageName) => { try { + const folder = path.join(ctx.root, 'node_modules', packageName); + const config = getPackageConfiguration(folder); + + let platformConfigs = {ios: undefined, android: undefined}; + + Object.keys(availablePlatforms) + .forEach(platform => { + platformConfigs[platform] = availablePlatforms[platform] + .dependencyConfig(folder, config[platform] || {}); + }); + return acc.concat({ - config: config.getDependencyConfig(name), - name, + config: platformConfigs, + name: packageName, + path: folder, + commands: getHooks(folder), + assets: getAssets(folder), + params: getParams(folder) }); - } catch (err) { - console.log(err); + } catch (e) { return acc; } }, []); diff --git a/packages/local-cli/link/getProjectConfig.js b/packages/local-cli/link/getProjectConfig.js new file mode 100644 index 000000000..690507cd7 --- /dev/null +++ b/packages/local-cli/link/getProjectConfig.js @@ -0,0 +1,28 @@ +/** + * @flow + */ + +'use strict'; + +import type { PlatformsT, ContextT, ProjectConfigT } from '../core/types.flow'; + +const getPackageConfiguration = require('../core/getPackageConfiguration'); + +module.exports = function getProjectConfig( + ctx: ContextT, + availablePlatforms: PlatformsT +): ProjectConfigT { + const config = getPackageConfiguration(ctx.root); + + let platformConfigs = {ios: undefined, android: undefined}; + + Object.keys(availablePlatforms) + .forEach(platform => { + platformConfigs[platform] = availablePlatforms[platform] + .projectConfig(ctx.root, config[platform] || {}); + }); + + return platformConfigs; +}; + + \ No newline at end of file diff --git a/packages/local-cli/link/getProjectDependencies.js b/packages/local-cli/link/getProjectDependencies.js index 0cb1165c0..94c0cf2c3 100644 --- a/packages/local-cli/link/getProjectDependencies.js +++ b/packages/local-cli/link/getProjectDependencies.js @@ -9,12 +9,19 @@ const path = require('path'); +/** + * List of projects that should not be treated as projects to be linked. + * + * That includes `react-native` itself and the CLI project (under its real and staging npm package). + */ +const EXCLUDED_PROJECTS = ['react-native', 'react-native-local-cli', 'react-native-local-cli-preview']; + /** * Returns an array of dependencies that should be linked/checked. */ module.exports = function getProjectDependencies(cwd) { const pjson = require(path.join(cwd || process.cwd(), './package.json')); return Object.keys(pjson.dependencies || {}).filter( - name => name !== 'react-native', + name => EXCLUDED_PROJECTS.includes(name) === false, ); }; diff --git a/packages/local-cli/link/link.js b/packages/local-cli/link/link.js index 752e75e22..4e9aaf6ed 100644 --- a/packages/local-cli/link/link.js +++ b/packages/local-cli/link/link.js @@ -8,21 +8,11 @@ * @flow */ -/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error - * found when Flow v0.54 was deployed. To see the error delete this comment and - * run Flow. */ const log = require('npmlog'); const path = require('path'); const uniqBy = require('lodash').uniqBy; const flatten = require('lodash').flatten; -/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error - * found when Flow v0.54 was deployed. To see the error delete this comment and - * run Flow. */ const chalk = require('chalk'); - -/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error - * found when Flow v0.54 was deployed. To see the error delete this comment and - * run Flow. */ const isEmpty = require('lodash').isEmpty; const promiseWaterfall = require('./promiseWaterfall'); const getProjectDependencies = require('./getProjectDependencies'); @@ -30,16 +20,23 @@ const getDependencyConfig = require('./getDependencyConfig'); const pollParams = require('./pollParams'); const commandStub = require('./commandStub'); const promisify = require('./promisify'); +const getProjectConfig = require('./getProjectConfig'); + const findReactNativeScripts = require('../util/findReactNativeScripts'); -import type {RNConfig} from '../core'; +const getHooks = require('../core/getHooks'); +const getParams = require('../core/getParams'); +const getAssets = require('../core/getAssets'); +const getPlatforms = require('../core/getPlatforms'); + +import type {ContextT} from '../core/types.flow'; log.heading = 'rnpm-link'; const dedupeAssets = assets => uniqBy(assets, asset => path.basename(asset)); const linkDependency = async (platforms, project, dependency) => { - const params = await pollParams(dependency.config.params); + const params = await pollParams(dependency.params); Object.keys(platforms || {}).forEach(platform => { if (!project[platform] || !dependency.config[platform]) { @@ -73,8 +70,10 @@ const linkDependency = async (platforms, project, dependency) => { linkConfig.register( dependency.name, + // $FlowFixMe: We check if dependency.config[platform] exists on line 42 dependency.config[platform], params, + // $FlowFixMe: We check if project[platform] exists on line 42 project[platform], ); @@ -96,11 +95,13 @@ const linkAssets = (platforms, project, assets) => { platforms[platform] && platforms[platform].linkConfig && platforms[platform].linkConfig(); - if (!linkConfig || !linkConfig.copyAssets) { + + if (!linkConfig || !linkConfig.copyAssets || !project[platform]) { return; } - + log.info(`Linking assets to ${platform} project`); + // $FlowFixMe: We check for existence of project[platform] on line 97. linkConfig.copyAssets(assets, project[platform]); }); @@ -112,14 +113,13 @@ const linkAssets = (platforms, project, assets) => { * * @param args If optional argument [packageName] is provided, * only that package is processed. - * @param config CLI config, see local-cli/core/index.js */ -function link(args: Array, config: RNConfig) { - let project; +function link(args: Array, ctx: ContextT) { let platforms; + let project; try { - project = config.getProjectConfig(); - platforms = config.getPlatformConfig(); + platforms = getPlatforms(ctx.root); + project = getProjectConfig(ctx, platforms); } catch (err) { log.error( 'ERRPACKAGEJSON', @@ -127,7 +127,6 @@ function link(args: Array, config: RNConfig) { ); return Promise.reject(err); } - const hasProjectConfig = Object.keys(platforms).reduce( (acc, key) => acc || key in project, false, @@ -148,23 +147,28 @@ function link(args: Array, config: RNConfig) { packageName = packageName.replace(/^(.+?)(@.+?)$/gi, '$1'); } + /* + * @todo(grabbou): Deprecate `getProjectDependencies()` soon. The implicit + * behaviour is causing us more harm. + */ const dependencies = getDependencyConfig( - config, - packageName ? [packageName] : getProjectDependencies(), + ctx, + platforms, + packageName ? [packageName] : getProjectDependencies(ctx.root), ); const assets = dedupeAssets( dependencies.reduce( - (acc, dependency) => acc.concat(dependency.config.assets), - project.assets, + (acc, dependency) => acc.concat(dependency.assets), + getAssets(ctx.root), ), ); const tasks = flatten( dependencies.map(dependency => [ - () => promisify(dependency.config.commands.prelink || commandStub), + () => promisify(dependency.commands.prelink || commandStub), () => linkDependency(platforms, project, dependency), - () => promisify(dependency.config.commands.postlink || commandStub), + () => promisify(dependency.commands.postlink || commandStub), ]), ); diff --git a/packages/local-cli/link/unlink.js b/packages/local-cli/link/unlink.js index 48ed5f192..fd8199fb4 100644 --- a/packages/local-cli/link/unlink.js +++ b/packages/local-cli/link/unlink.js @@ -4,13 +4,15 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @format + * @flow */ const log = require('npmlog'); const getProjectDependencies = require('./getProjectDependencies'); const getDependencyConfig = require('./getDependencyConfig'); +const getProjectConfig = require('./getProjectConfig'); + const difference = require('lodash').difference; const filter = require('lodash').filter; const flatten = require('lodash').flatten; @@ -19,6 +21,10 @@ const promiseWaterfall = require('./promiseWaterfall'); const commandStub = require('./commandStub'); const promisify = require('./promisify'); +const getPlatforms = require('../core/getPlatforms'); + +import type { ContextT } from '../core/types.flow'; + log.heading = 'rnpm-link'; const unlinkDependency = ( @@ -29,7 +35,7 @@ const unlinkDependency = ( otherDependencies, ) => { Object.keys(platforms || {}).forEach(platform => { - if (!project[platform] || !dependency[platform]) { + if (!project[platform] || !dependency.config[platform]) { return; } @@ -44,7 +50,7 @@ const unlinkDependency = ( const isInstalled = linkConfig.isInstalled( project[platform], packageName, - dependency[platform], + dependency.config[platform], ); if (!isInstalled) { @@ -56,7 +62,9 @@ const unlinkDependency = ( linkConfig.unregister( packageName, - dependency[platform], + // $FlowFixMe: We check for existence on line 38 + dependency.config[platform], + // $FlowFixMe: We check for existence on line 38 project[platform], otherDependencies, ); @@ -75,16 +83,13 @@ const unlinkDependency = ( * If optional argument [packageName] is provided, it's the only one * that's checked */ -function unlink(args, config) { +function unlink(args: Array, ctx: ContextT) { const packageName = args[0]; let platforms; - let project; - let dependency; try { - platforms = config.getPlatformConfig(); - project = config.getProjectConfig(); + platforms = getPlatforms(ctx.root); } catch (err) { log.error( 'ERRPACKAGEJSON', @@ -93,21 +98,25 @@ function unlink(args, config) { return Promise.reject(err); } + const allDependencies = getDependencyConfig(ctx, platforms, getProjectDependencies()); + let otherDependencies; + let dependency; + try { - dependency = config.getDependencyConfig(packageName); + const idx = allDependencies.findIndex(p => p.name === packageName); + + if (idx === -1) { + throw new Error(`Project ${packageName} is not a react-native library`); + } + + otherDependencies = [...allDependencies]; + dependency = otherDependencies.splice(idx, 1)[0]; } catch (err) { - log.warn( - 'ERRINVALIDPROJ', - `Project ${packageName} is not a react-native library`, - ); + log.warn('ERRINVALIDPROJ', err.message); return Promise.reject(err); } - const allDependencies = getDependencyConfig(config, getProjectDependencies()); - const otherDependencies = filter( - allDependencies, - d => d.name !== packageName, - ); + const project = getProjectConfig(ctx, platforms); const tasks = [ () => promisify(dependency.commands.preunlink || commandStub), @@ -140,11 +149,12 @@ function unlink(args, config) { platforms[platform] && platforms[platform].linkConfig && platforms[platform].linkConfig(); - if (!linkConfig || !linkConfig.unlinkAssets) { + if (!linkConfig || !linkConfig.unlinkAssets || !project[platform]) { return; } log.info(`Unlinking assets from ${platform} project`); + // $FlowFixMe: We check for platorm existence on line 150 linkConfig.unlinkAssets(assets, project[platform]); }); diff --git a/packages/local-cli/package.json b/packages/local-cli/package.json index e7c3f4fa1..212e459d8 100644 --- a/packages/local-cli/package.json +++ b/packages/local-cli/package.json @@ -67,9 +67,10 @@ "babel-jest": "24.0.0-alpha.6", "eslint": "^5.7.0", "flow-bin": "0.87.0", - "jest": "24.0.0-alpha.6" + "jest": "24.0.0-alpha.6", + "react-native": "^0.57.0" }, "peerDependencies": { "react-native": "^0.57.0" } -} \ No newline at end of file +} diff --git a/packages/local-cli/server/runServer.js b/packages/local-cli/server/runServer.js index 43f96e93d..5daaa5204 100644 --- a/packages/local-cli/server/runServer.js +++ b/packages/local-cli/server/runServer.js @@ -11,7 +11,7 @@ const Metro = require('metro'); -const {Terminal} = require('metro-core'); +const { Terminal } = require('metro-core'); const messageSocket = require('./util/messageSocket'); const morgan = require('morgan'); @@ -19,62 +19,58 @@ const path = require('path'); const webSocketProxy = require('./util/webSocketProxy'); const MiddlewareManager = require('./middleware/MiddlewareManager'); -import type {ConfigT} from 'metro-config/src/configTypes.flow'; +const loadMetroConfig = require('../util/loadMetroConfig'); + +import type { ContextT } from '../core/types.flow'; export type Args = {| - +assetExts: $ReadOnlyArray, - +cert: string, - +customLogReporterPath?: string, - +host: string, - +https: boolean, - +maxWorkers: number, - +key: string, - +nonPersistent: boolean, - +platforms: $ReadOnlyArray, - +port: number, - +projectRoot: string, - +providesModuleNodeModules: Array, - +resetCache: boolean, - +sourceExts: $ReadOnlyArray, - +transformer?: string, - +verbose: boolean, - +watchFolders: $ReadOnlyArray, + assetExts?: string[], + cert?: string, + customLogReporterPath?: string, + host?: string, + https?: boolean, + maxWorkers?: number, + key?: string, + nonPersistent?: boolean, + platforms?: string[], + port?: number, + providesModuleNodeModules?: string[], + resetCache?: boolean, + sourceExts?: string[], + transformer?: string, + verbose?: boolean, + watchFolders?: string[], + config?: string, |}; -async function runServer(args: Args, config: ConfigT) { +async function runServer(argv: *, ctx: ContextT, args: Args) { const terminal = new Terminal(process.stdout); const ReporterImpl = getReporterImpl(args.customLogReporterPath || null); const reporter = new ReporterImpl(terminal); - const middlewareManager = new MiddlewareManager(args); + + const metroConfig = await loadMetroConfig(ctx.root, { + config: args.config, + maxWorkers: args.maxWorkers, + port: args.port, + resetCache: args.resetCache, + watchFolders: args.watchFolders, + sourceExts: args.sourceExts, + reporter, + }); + + const middlewareManager = new MiddlewareManager({ + host: args.host, + watchFolders: metroConfig.watchFolders + }); middlewareManager.getConnectInstance().use(morgan('combined')); - args.watchFolders.forEach(middlewareManager.serveStatic.bind(middlewareManager)); - - // $FlowFixMe Metro configuration is immutable. - config.maxWorkers = args.maxWorkers; - // $FlowFixMe Metro configuration is immutable. - config.server.port = args.port; - // $FlowFixMe Metro configuration is immutable. - config.reporter = reporter; - // $FlowFixMe Metro configuration is immutable. - config.resetCache = args.resetCache; - // $FlowFixMe Metro configuration is immutable. - config.projectRoot = args.projectRoot; - // $FlowFixMe Metro configuration is immutable. - config.watchFolders = args.watchFolders.slice(0); - // $FlowFixMe Metro configuration is immutable. - config.server.enhanceMiddleware = middleware => - middlewareManager.getConnectInstance().use(middleware); + metroConfig.watchFolders.forEach(middlewareManager.serveStatic.bind(middlewareManager)); - if (args.sourceExts !== config.resolver.sourceExts) { - // $FlowFixMe Metro configuration is immutable. - config.resolver.sourceExts = args.sourceExts.concat( - config.resolver.sourceExts, - ); - } + metroConfig.server.enhanceMiddleware = middleware => + middlewareManager.getConnectInstance().use(middleware); - const serverInstance = await Metro.runServer(config, { + const serverInstance = await Metro.runServer(metroConfig, { host: args.host, secure: args.https, secureCert: args.cert, diff --git a/packages/local-cli/server/server.js b/packages/local-cli/server/server.js index 86b70e37f..8a4d950c0 100644 --- a/packages/local-cli/server/server.js +++ b/packages/local-cli/server/server.js @@ -10,74 +10,52 @@ 'use strict'; -const runServer = require('./runServer'); +const path = require('path'); -import type {RNConfig} from '../core'; -import type {ConfigT} from 'metro-config/src/configTypes.flow'; -import type {Args as RunServerArgs} from './runServer'; - -/** - * Starts the React Native Packager Server. - */ -function server(argv: mixed, config: RNConfig, args: RunServerArgs) { - /* $FlowFixMe(site=react_native_fb) ConfigT shouldn't be extendable. */ - const configT: ConfigT = config; - runServer(args, configT); -} +import type { ContextT } from '../core/types.flow'; module.exports = { name: 'start', - func: server, + func: require('./runServer'), description: 'starts the webserver', options: [ { command: '--port [number]', parse: (val: string) => Number(val), - default: (config: ConfigT) => config.server.port, }, { command: '--host [string]', default: 'localhost', }, - { - command: '--projectRoot [string]', - description: 'Specify the main project root', - default: (config: ConfigT) => config.projectRoot, - }, { command: '--watchFolders [list]', description: 'Specify any additional folders to be added to the watch list', parse: (val: string) => val.split(','), - default: (config: ConfigT) => config.watchFolders, }, { command: '--assetExts [list]', description: 'Specify any additional asset extensions to be used by the packager', parse: (val: string) => val.split(','), - default: (config: ConfigT) => config.resolver.assetExts, }, { command: '--sourceExts [list]', description: 'Specify any additional source extensions to be used by the packager', parse: (val: string) => val.split(','), - default: (config: ConfigT) => config.resolver.sourceExts, }, { command: '--platforms [list]', description: 'Specify any additional platforms to be used by the packager', parse: (val: string) => val.split(','), - default: (config: ConfigT) => config.resolver.platforms, }, { command: '--providesModuleNodeModules [list]', description: 'Specify any npm packages that import dependencies with providesModule', parse: (val: string) => val.split(','), - default: (config: ConfigT) => config.resolver.providesModuleNodeModules, }, { command: '--max-workers [number]', @@ -85,7 +63,6 @@ module.exports = { 'Specifies the maximum number of workers the worker-pool ' + 'will spawn for transforming files. This defaults to the number of the ' + 'cores available on your machine.', - default: (config: ConfigT) => config.maxWorkers, parse: (workers: string) => Number(workers), }, { @@ -125,5 +102,10 @@ module.exports = { command: '--cert [path]', description: 'Path to custom SSL cert', }, + { + command: '--config [string]', + description: 'Path to the CLI configuration file', + parse: (val: string) => path.resolve(val), + }, ], }; diff --git a/packages/local-cli/util/Config.js b/packages/local-cli/util/Config.js deleted file mode 100644 index 85eccb6a2..000000000 --- a/packages/local-cli/util/Config.js +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow - */ -'use strict'; - -const findSymlinkedModules = require('./findSymlinkedModules'); -const findReactNativePath = require('./findReactNativePath'); -const path = require('path'); - -const {createBlacklist} = require('metro'); -const {loadConfig} = require('metro-config'); - -/** - * Configuration file of the CLI. - */ -import type {ConfigT} from 'metro-config/src/configTypes.flow'; - -function getProjectRoot() { - // React Native was installed using CocoaPods. - if (__dirname.match(/Pods[\/\\]React[\/\\]packager$/)) { - // @todo check this - return path.resolve(__dirname, '../../../..'); - } - // Packager is running from node_modules. - // This is the default case for all projects created using 'react-native init'. - return path.resolve(__dirname, '../../'); -} - -const resolveSymlinksForRoots = roots => - roots.reduce( - /* $FlowFixMe(>=0.70.0 site=react_native_fb) This comment suppresses an - * error found when Flow v0.70 was deployed. To see the error delete this - * comment and run Flow. */ - (arr, rootPath) => arr.concat(findSymlinkedModules(rootPath, roots)), - [...roots], - ); - -const getWatchFolders = () => { - const root = process.env.REACT_NATIVE_APP_ROOT; - if (root) { - return resolveSymlinksForRoots([path.resolve(root)]); - } - return []; -}; - -const getBlacklistRE = () => { - return createBlacklist([/.*\/__fixtures__\/.*/]); -}; - -/** - * Module capable of getting the configuration out of a given file. - * - * The function will return all the default configuration, as specified by the - * `DEFAULT` param overriden by those found on `rn-cli.config.js` files, if any. If no - * default config is provided and no configuration can be found in the directory - * hierarchy, an error will be thrown. - */ -const Config = { - DEFAULT: { - resolver: { - resolverMainFields: ['react-native', 'browser', 'main'], - blacklistRE: getBlacklistRE(), - }, - serializer: { - getModulesRunBeforeMainModule: () => [ - require.resolve(findReactNativePath('Libraries/Core/InitializeCore')), - ], - // $FlowFixMe: Non-literal require - getPolyfills: (...args) => require(findReactNativePath('rn-get-polyfills'))(...args), - }, - server: { - port: process.env.RCT_METRO_PORT || 8081, - }, - transformer: { - babelTransformerPath: require.resolve('metro/src/reactNativeTransformer'), - }, - watchFolders: getWatchFolders(), - }, - - async load(configFile: ?string): Promise { - const argv = {cwd: getProjectRoot()}; - - return await loadConfig( - configFile ? {...argv, config: configFile} : argv, - this.DEFAULT, - ); - }, -}; - -module.exports = Config; diff --git a/packages/local-cli/util/findReactNativePath.js b/packages/local-cli/util/findReactNativePath.js deleted file mode 100644 index 0e94b3e1e..000000000 --- a/packages/local-cli/util/findReactNativePath.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - * @flow strict - */ - -'use strict'; - -const path = require('path'); -const fs = require('fs'); - -let reactNativePath = null; - -function findReactNativePath(): string { - // By default, CLI lives inside `node_modules` next to React Native as - // node dependencies are flattened - if (fs.existsSync(path.join(__dirname, '../../react-native'))) { - return '../../react-native'; - } - // Otherwise, we assume it's within React Native `node_modules` - return '../../../'; -} - -module.exports = (str: string) => { - if (reactNativePath === null) { - reactNativePath = findReactNativePath(); - } - return path.join(reactNativePath, str); -}; diff --git a/packages/local-cli/util/findSymlinkedModules.js b/packages/local-cli/util/findSymlinkedModules.js index d7e5d30f4..a88b41f08 100644 --- a/packages/local-cli/util/findSymlinkedModules.js +++ b/packages/local-cli/util/findSymlinkedModules.js @@ -34,19 +34,11 @@ module.exports = function findSymlinkedModules( projectRoot: string, ignoredRoots?: Array = [], ) { - const timeStart = Date.now(); const nodeModuleRoot = path.join(projectRoot, 'node_modules'); const resolvedSymlinks = findModuleSymlinks(nodeModuleRoot, [ ...ignoredRoots, projectRoot, ]); - const timeEnd = Date.now(); - - console.log( - `Scanning folders for symlinks in ${nodeModuleRoot} (${timeEnd - - timeStart}ms)`, - ); - return resolvedSymlinks; }; diff --git a/packages/local-cli/util/loadMetroConfig.js b/packages/local-cli/util/loadMetroConfig.js new file mode 100644 index 000000000..8bb68c843 --- /dev/null +++ b/packages/local-cli/util/loadMetroConfig.js @@ -0,0 +1,139 @@ +/** + * @flow + */ +'use strict'; + +const findSymlinkedModules = require('./findSymlinkedModules'); +const path = require('path'); + +const {createBlacklist} = require('metro'); +const {loadConfig} = require('metro-config'); + +const findPlugins = require('../core/findPlugins'); + +/** + * Configuration file of Metro. + */ +import type {ConfigT} from 'metro-config/src/configTypes.flow'; + +const resolveSymlinksForRoots = (roots) => + roots.reduce( + (arr, rootPath) => arr.concat(findSymlinkedModules(rootPath, roots)), + [...roots], + ); + +const getWatchFolders = () => { + const root = process.env.REACT_NATIVE_APP_ROOT; + if (root) { + return resolveSymlinksForRoots([path.resolve(root)]); + } + return []; +}; + +const getBlacklistRE = () => { + return createBlacklist([/.*\/__fixtures__\/.*/]); +}; + +/** + * Default configuration + * + * @todo(grabbou): As a separate PR, haste.platforms should be added before "native". + * Otherwise, a.native.js will not load on Windows or other platforms + */ +const getDefaultConfig = (root: string) => { + const plugins = findPlugins(root); + + return ({ + resolver: { + resolverMainFields: ['react-native', 'browser', 'main'], + blacklistRE: getBlacklistRE(), + platforms: ['ios', 'android', 'native', ...plugins.haste.platforms], + providesModuleNodeModules: ['react-native', ...plugins.haste.providesModuleNodeModules], + hasteImplModulePath: require.resolve('react-native/jest/hasteImpl') + }, + serializer: { + getModulesRunBeforeMainModule: () => [ + require.resolve('react-native/Libraries/Core/InitializeCore'), + ], + getPolyfills: require('react-native/rn-get-polyfills'), + }, + server: { + port: process.env.RCT_METRO_PORT || 8081, + }, + transformer: { + babelTransformerPath: require.resolve('metro/src/reactNativeTransformer'), + assetRegistryPath: require.resolve('react-native/Libraries/Image/AssetRegistry') + }, + watchFolders: getWatchFolders(), + }); +}; + +export type ConfigOptionsT = { + maxWorkers?: number, + port?: number, + resetCache?: boolean, + watchFolders?: string[], + sourceExts?: string[], + reporter?: any, + config?: string, +}; + +/** + * Overwrites Metro configuration with options. + * + * This ensures that options are always taking precedence over other + * configuration present. + */ +const overwriteWithOptions = (config: ConfigT, options: ConfigOptionsT) => { + if (options.maxWorkers) { + config.maxWorkers = options.maxWorkers; + } + + if (options.port) { + config.server.port = options.port; + } + + if (options.reporter) { + config.reporter = options.reporter; + } + + if (options.resetCache) { + config.resetCache = options.resetCache; + } + + if (options.watchFolders) { + config.watchFolders = options.watchFolders; + } + + if ( + options.sourceExts + && options.sourceExts !== config.resolver.sourceExts + ) { + config.resolver.sourceExts = options.sourceExts.concat( + config.resolver.sourceExts, + ); + } +}; + +/** + * Loads Metro Config and applies `options` on top of the resolved config. + * + * This allows the CLI to always overwrite the file settings. + */ +module.exports = async function load( + projectRoot: string, + options?: ConfigOptionsT = {} +): Promise { + const defaultConfig = getDefaultConfig(projectRoot); + + const config = await loadConfig({ + cwd: projectRoot, + config: options.config + }, defaultConfig); + + if (options) { + overwriteWithOptions(config, options); + } + + return config; +}; diff --git a/yarn.lock b/yarn.lock index 18efd459e..9fb21203d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -395,6 +395,15 @@ dependencies: "@babel/helper-plugin-utils" "^7.0.0" +"@babel/plugin-transform-async-to-generator@^7.0.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.2.0.tgz#68b8a438663e88519e65b776f8938f3445b1a2ff" + integrity sha512-CEHzg4g5UraReozI9D4fblBYABs7IM6UerAVG7EJVrTLC5keh00aEuLUT+O40+mJCEzaXkYfTCUKIyeDfMOFFQ== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/plugin-transform-async-to-generator@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.1.0.tgz#109e036496c51dd65857e16acab3bafdf3c57811" @@ -711,6 +720,13 @@ pirates "^4.0.0" source-map-support "^0.5.9" +"@babel/runtime@^7.0.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.2.0.tgz#b03e42eeddf5898e00646e4c840fa07ba8dcad7f" + integrity sha512-oouEibCbHMVdZSDlJBO6bZmID/zA/G/Qx3H1d3rSNPTD+L8UNKvCat7aKWSJ74zYbm5zWGh0GQN0hKj8zYFTCg== + dependencies: + regenerator-runtime "^0.12.0" + "@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" @@ -982,6 +998,11 @@ arrify@^1.0.0, arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" +art@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/art/-/art-0.10.3.tgz#b01d84a968ccce6208df55a733838c96caeeaea2" + integrity sha512-HXwbdofRTiJT6qZX/FnchtldzJjS3vkLJxQilc3Xj+ma2MXjY4UAyQ0ls1XZYVnDvVIBiFZbC6QsvtW86TD6tQ== + asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -1230,7 +1251,7 @@ base64-js@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.1.2.tgz#d6400cac1c4c660976d90d07a04351d89395f5e8" -base64-js@^1.2.3: +base64-js@^1.1.2, base64-js@^1.2.3: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" @@ -1576,7 +1597,12 @@ copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" -core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0, core-js@^2.5.7: +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= + +core-js@^2.2.2, core-js@^2.4.0, core-js@^2.4.1, core-js@^2.5.0, core-js@^2.5.7: version "2.5.7" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" @@ -1592,6 +1618,15 @@ cosmiconfig@^5.0.5: js-yaml "^3.9.0" parse-json "^4.0.0" +create-react-class@^15.6.3: + version "15.6.3" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" + integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + cross-spawn@^5.0.1, cross-spawn@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" @@ -1962,6 +1997,11 @@ etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" +event-target-shim@^1.0.5: + version "1.1.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-1.1.1.tgz#a86e5ee6bdaa16054475da797ccddf0c55698491" + integrity sha1-qG5e5r2qFgVEddp5fM3fDFVphJE= + eventemitter3@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.0.tgz#090b4d6cdbd645ed10bf750d4b5407942d7ba163" @@ -2152,7 +2192,7 @@ fbjs-css-vars@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/fbjs-css-vars/-/fbjs-css-vars-1.0.1.tgz#836d876e887d702f45610f5ebd2fbeef649527fc" -fbjs-scripts@^1.0.1: +fbjs-scripts@^1.0.0, fbjs-scripts@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fbjs-scripts/-/fbjs-scripts-1.0.1.tgz#7d8d09d76e83308bf3b1fc7b4c9c6fd081c5ef64" dependencies: @@ -2167,6 +2207,19 @@ fbjs-scripts@^1.0.1: semver "^5.1.0" through2 "^2.0.0" +fbjs@^0.8.9: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + fbjs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-1.0.0.tgz#52c215e0883a3c86af2a7a776ed51525ae8e0a5a" @@ -3037,7 +3090,7 @@ jest-diff@^24.0.0-alpha.6: jest-get-type "^24.0.0-alpha.6" pretty-format "^24.0.0-alpha.6" -jest-docblock@^24.0.0-alpha.6: +jest-docblock@^24.0.0-alpha.2, jest-docblock@^24.0.0-alpha.6: version "24.0.0-alpha.6" resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-24.0.0-alpha.6.tgz#abb38d04afd624cbfb34e13fa9e0c1053388a333" integrity sha512-veghPy2eBQ5r8XXd+VLK7AfCxJMTwqA8B2fknR24aibIkGW7dj4fq538HtwIvXkRpUO5f1b5x6IEsCb9g+e6qw== @@ -3075,6 +3128,20 @@ jest-get-type@^24.0.0-alpha.6: resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.0.0-alpha.6.tgz#23cc13e4e04c61f001356529675a6aec5a2ef53d" integrity sha512-U9hmkEfO5dtccZ96iQgbPARorzyVRYWiRnrm5GO4l5iJOpK86fuUsZLjETu+cOpd72RVh00aEm/tVOhZrLizbA== +jest-haste-map@24.0.0-alpha.2: + version "24.0.0-alpha.2" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.0.0-alpha.2.tgz#bc1d498536c395699a44b1e61a3e901c95a2e5a6" + integrity sha512-FTSIbJdmaddjgpcaXOq+sPGegcE28w7uOHonpsCuEwBQcB0REhkNYY9Wj/e1w+q3SBmEK1++ChgTMEXEzAf0yQ== + dependencies: + fb-watchman "^2.0.0" + graceful-fs "^4.1.11" + invariant "^2.2.4" + jest-docblock "^24.0.0-alpha.2" + jest-serializer "^24.0.0-alpha.2" + jest-worker "^24.0.0-alpha.2" + micromatch "^2.3.11" + sane "^3.0.0" + jest-haste-map@24.0.0-alpha.6, jest-haste-map@^24.0.0-alpha.6: version "24.0.0-alpha.6" resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-24.0.0-alpha.6.tgz#fb2c785080f391b923db51846b86840d0d773076" @@ -3207,7 +3274,12 @@ jest-runtime@^24.0.0-alpha.6: write-file-atomic "^2.1.0" yargs "^12.0.2" -jest-serializer@24.0.0-alpha.6, jest-serializer@^24.0.0-alpha.6: +jest-serializer@24.0.0-alpha.2: + version "24.0.0-alpha.2" + resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.0.0-alpha.2.tgz#adcaa73ef49e56377f7fada19921c300b576e7f9" + integrity sha512-jLHHT71gyYdgMH5sFWP/e8bZjq/TC5iz9DQZlLsRE/7Er//m8WqyiNJs022FEc18PLq3jyk/z06N0xS6YlbsUA== + +jest-serializer@24.0.0-alpha.6, jest-serializer@^24.0.0-alpha.2, jest-serializer@^24.0.0-alpha.6: version "24.0.0-alpha.6" resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-24.0.0-alpha.6.tgz#27d2fee4b1a85698717a30c3ec2ab80767312597" integrity sha512-IPA5T6/GhlE6dedSk7Cd7YfuORnYjN0VD5iJVFn1Q81RJjpj++Hen5kJbKcg547vXsQ1TddV15qOA/zeIfOCLw== @@ -3261,7 +3333,14 @@ jest-watcher@^24.0.0-alpha.6: chalk "^2.0.1" string-length "^2.0.0" -jest-worker@24.0.0-alpha.6, jest-worker@^24.0.0-alpha.6: +jest-worker@24.0.0-alpha.2: + version "24.0.0-alpha.2" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.0.0-alpha.2.tgz#d376b328094dd5f1e0c6156b4f41b308a99a35bd" + integrity sha512-77YRl8eI4rrtdJ4mzzo4LVABecQmmy7lXsXc00rIJ9oiXJYbz4R4eL6RXcxZcRbwwqYjFL0g9h6H9iQaWqC/Kg== + dependencies: + merge-stream "^1.0.1" + +jest-worker@24.0.0-alpha.6, jest-worker@^24.0.0-alpha.2, jest-worker@^24.0.0-alpha.6: version "24.0.0-alpha.6" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.0.0-alpha.6.tgz#463681b92c117c57107135c14b9b9d6cd51d80ce" integrity sha512-iXtH7MR9bjWlNnlnRBcrBRrb4cSVxML96La5vsnmBvDI+mJnkP5uEt6Fgpo5Y8f3z9y2Rd7wuPnKRxqQsiU/dA== @@ -3513,7 +3592,7 @@ lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.3.0, version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" -loose-envify@^1.0.0: +loose-envify@^1.0.0, loose-envify@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" dependencies: @@ -3582,6 +3661,31 @@ merge@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.1.tgz#38bebf80c3220a8a487b6fcfb3941bb11720c145" +metro-babel-register@^0.48.1: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-babel-register/-/metro-babel-register-0.48.3.tgz#459b9e5bd635775e342109b6acd70fd63c731f57" + integrity sha512-KCuapWsM9Nrwb2XHQ0NhiTGZ9PrQfai8drhDrU5+A/aFGQ33N2wOvKet5OVRRyEuUvJ79Hbq0xs4UpFM13tQig== + dependencies: + "@babel/core" "^7.0.0" + "@babel/plugin-proposal-class-properties" "^7.0.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" + "@babel/plugin-proposal-optional-chaining" "^7.0.0" + "@babel/plugin-transform-async-to-generator" "^7.0.0" + "@babel/plugin-transform-flow-strip-types" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/register" "^7.0.0" + core-js "^2.2.2" + escape-string-regexp "^1.0.5" + +metro-babel7-plugin-react-transform@0.48.3: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-babel7-plugin-react-transform/-/metro-babel7-plugin-react-transform-0.48.3.tgz#c3e43c99173c143537fb234b44cdd6e6b511d511" + integrity sha512-F3fjKig7KJl+5iqjWUStx/Sv3Oryw1cftIx0MhaXOgq4emdd1NYoC1sMYrGdDY8aLPDysH9J7sIeErH805oFUg== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + metro-babel7-plugin-react-transform@0.50.0: version "0.50.0" resolved "https://registry.yarnpkg.com/metro-babel7-plugin-react-transform/-/metro-babel7-plugin-react-transform-0.50.0.tgz#a2f8eb879cf79f2251a23751f09520f63f525167" @@ -3589,6 +3693,16 @@ metro-babel7-plugin-react-transform@0.50.0: dependencies: "@babel/helper-module-imports" "^7.0.0" +metro-cache@0.48.3: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.48.3.tgz#8c2818d3cd6b79570cd7750da4685e9c7c061577" + integrity sha512-q49ult2PW2BYTuGCS5RXmMB+ejdOWtNHWh9yZS5XvI/xlfLZjJL47yakcOz3C8UImdRhWV8X890/+rQU7nALCg== + dependencies: + jest-serializer "24.0.0-alpha.2" + metro-core "0.48.3" + mkdirp "^0.5.1" + rimraf "^2.5.4" + metro-cache@0.50.0: version "0.50.0" resolved "https://registry.yarnpkg.com/metro-cache/-/metro-cache-0.50.0.tgz#6800bda39ae90b249c3dd480c95f3d5a3ed7946f" @@ -3599,6 +3713,17 @@ metro-cache@0.50.0: mkdirp "^0.5.1" rimraf "^2.5.4" +metro-config@0.48.3: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.48.3.tgz#71f9f27911582e960a660ed2b08cb4ee5d58724d" + integrity sha512-rpO0Edy6H7N2RnEUPQufIG6DdU/lXZcMIRP3UHR93Q7IuZEwkSd3lS0trCMqdkj5Hx7eIx+gj/MQrNDSa0L0gw== + dependencies: + cosmiconfig "^5.0.5" + metro "0.48.3" + metro-cache "0.48.3" + metro-core "0.48.3" + pretty-format "^23.4.1" + metro-config@0.50.0: version "0.50.0" resolved "https://registry.yarnpkg.com/metro-config/-/metro-config-0.50.0.tgz#bed285a43f4ea21c929432cf1549d1c651d67681" @@ -3610,6 +3735,16 @@ metro-config@0.50.0: metro-core "0.50.0" pretty-format "24.0.0-alpha.6" +metro-core@0.48.3, metro-core@^0.48.1: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.48.3.tgz#be2d615eaec759c8d01559e8685554cbdf8e7c4f" + integrity sha512-bLIikpGBvdaZhwvmnpY5OwS2XhOWGX7YUvRN4IegfTOYo88TzL/SAB5Osr7lpYGvTmGdJFJ5Mqr3Xy4bVGFEvA== + dependencies: + jest-haste-map "24.0.0-alpha.2" + lodash.throttle "^4.1.1" + metro-resolver "0.48.3" + wordwrap "^1.0.0" + metro-core@0.50.0: version "0.50.0" resolved "https://registry.yarnpkg.com/metro-core/-/metro-core-0.50.0.tgz#4281ae03a8159471fceb8185ef9badd15d2fd55f" @@ -3625,6 +3760,18 @@ metro-memory-fs@0.50.0: resolved "https://registry.yarnpkg.com/metro-memory-fs/-/metro-memory-fs-0.50.0.tgz#205c173a02123d3b5c3e6894e0947928a047e411" integrity sha512-JYsSeJ57jAcRnP6aHmG/yZkzwxtDTKrPQ82tNprexn88p2HvPjvb5VJTCRteyDk7ybF+WOBGUgGnI1XFi4BLuQ== +metro-memory-fs@^0.48.1: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-memory-fs/-/metro-memory-fs-0.48.3.tgz#2d180a73992daf08e242ea49682f72e6f0f7f094" + integrity sha512-Ku6k0JHZZ/EIHlIJZZEAJ7ItDq/AdIOpmgt4ebkQ6WJRUOc0960K3BQdRFiwL8riQKWJMlKZcXc4/2ktoY3U9A== + +metro-minify-uglify@0.48.3: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.48.3.tgz#493baadb65f6a1d8cab9fd157ac80c5801b23149" + integrity sha512-EvzoqPMbm839T6AXSYWF76b/VNqoY3E1oAbZoyZbN/J7EHHgz2xEVt4M49fI6tEKPCPzpEpGnJKoxjnkV9XdrA== + dependencies: + uglify-es "^3.1.9" + metro-minify-uglify@0.50.0: version "0.50.0" resolved "https://registry.yarnpkg.com/metro-minify-uglify/-/metro-minify-uglify-0.50.0.tgz#b6d18f41f0249ab9bd09c70d8e8358c6cc883104" @@ -3632,6 +3779,47 @@ metro-minify-uglify@0.50.0: dependencies: uglify-es "^3.1.9" +metro-react-native-babel-preset@0.48.3: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.48.3.tgz#839dbd0d9e4012f550861d2295b998144a61bcc8" + integrity sha512-GDW4yWk8z5RGk8A67OTS05luxCHZFDHbEuzesyxG1SlE/W6ODfJOPQpIFlFuPUEvVDunG0lYn3dGBL2VcaVVpQ== + dependencies: + "@babel/plugin-proposal-class-properties" "^7.0.0" + "@babel/plugin-proposal-export-default-from" "^7.0.0" + "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0" + "@babel/plugin-proposal-object-rest-spread" "^7.0.0" + "@babel/plugin-proposal-optional-catch-binding" "^7.0.0" + "@babel/plugin-proposal-optional-chaining" "^7.0.0" + "@babel/plugin-syntax-dynamic-import" "^7.0.0" + "@babel/plugin-syntax-export-default-from" "^7.0.0" + "@babel/plugin-transform-arrow-functions" "^7.0.0" + "@babel/plugin-transform-block-scoping" "^7.0.0" + "@babel/plugin-transform-classes" "^7.0.0" + "@babel/plugin-transform-computed-properties" "^7.0.0" + "@babel/plugin-transform-destructuring" "^7.0.0" + "@babel/plugin-transform-exponentiation-operator" "^7.0.0" + "@babel/plugin-transform-flow-strip-types" "^7.0.0" + "@babel/plugin-transform-for-of" "^7.0.0" + "@babel/plugin-transform-function-name" "^7.0.0" + "@babel/plugin-transform-literals" "^7.0.0" + "@babel/plugin-transform-modules-commonjs" "^7.0.0" + "@babel/plugin-transform-object-assign" "^7.0.0" + "@babel/plugin-transform-parameters" "^7.0.0" + "@babel/plugin-transform-react-display-name" "^7.0.0" + "@babel/plugin-transform-react-jsx" "^7.0.0" + "@babel/plugin-transform-react-jsx-source" "^7.0.0" + "@babel/plugin-transform-regenerator" "^7.0.0" + "@babel/plugin-transform-runtime" "^7.0.0" + "@babel/plugin-transform-shorthand-properties" "^7.0.0" + "@babel/plugin-transform-spread" "^7.0.0" + "@babel/plugin-transform-sticky-regex" "^7.0.0" + "@babel/plugin-transform-template-literals" "^7.0.0" + "@babel/plugin-transform-typescript" "^7.0.0" + "@babel/plugin-transform-unicode-regex" "^7.0.0" + "@babel/template" "^7.0.0" + metro-babel7-plugin-react-transform "0.48.3" + react-transform-hmr "^1.0.4" + metro-react-native-babel-preset@0.50.0: version "0.50.0" resolved "https://registry.yarnpkg.com/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.50.0.tgz#591daebea81ecb3fba76c5ff1afa3cceb9ed4509" @@ -3673,6 +3861,13 @@ metro-react-native-babel-preset@0.50.0: metro-babel7-plugin-react-transform "0.50.0" react-transform-hmr "^1.0.4" +metro-resolver@0.48.3: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.48.3.tgz#3459c117f25a6d91d501eb1c81fdc98fcfea1cc0" + integrity sha512-aXZdd4SWVPnTlnJ55f918QLvGOE8zlrGt8y8/BS0EktzPORkkXnouzC0dtkq5lTpFSX7b1OD2z3ZtZLxM5sQVg== + dependencies: + absolute-path "^0.0.0" + metro-resolver@0.50.0: version "0.50.0" resolved "https://registry.yarnpkg.com/metro-resolver/-/metro-resolver-0.50.0.tgz#39dfc6e4c99f7392a7fe61a0521af62ba1f9ad05" @@ -3680,6 +3875,13 @@ metro-resolver@0.50.0: dependencies: absolute-path "^0.0.0" +metro-source-map@0.48.3: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.48.3.tgz#ab102bf71c83754e6d5a04c3faf612a88e7f5dcf" + integrity sha512-TAIn1c/G69PHa+p4CUl7myXw5gOT4J1xXXLKAjIOA6xjxttMEa1S5co6fsXzvTMR+psK9OAsgBW3VAaEcJGEcw== + dependencies: + source-map "^0.5.6" + metro-source-map@0.50.0: version "0.50.0" resolved "https://registry.yarnpkg.com/metro-source-map/-/metro-source-map-0.50.0.tgz#2fe7b0be477d8651d165001a9e1d667929e2b90c" @@ -3687,6 +3889,61 @@ metro-source-map@0.50.0: dependencies: source-map "^0.5.6" +metro@0.48.3, metro@^0.48.1: + version "0.48.3" + resolved "https://registry.yarnpkg.com/metro/-/metro-0.48.3.tgz#43639828dc22fd75e0d31ce75a6dc4615feaf5f7" + integrity sha512-sf4Q95Zk7cvqi537WbQTvaPbPXflAk1zGWA50uchkF+frJHZWop5kwtbtWwvDGlPHgptjjXc3yTvIo5Y0LTkqQ== + dependencies: + "@babel/core" "^7.0.0" + "@babel/generator" "^7.0.0" + "@babel/parser" "^7.0.0" + "@babel/plugin-external-helpers" "^7.0.0" + "@babel/template" "^7.0.0" + "@babel/traverse" "^7.0.0" + "@babel/types" "^7.0.0" + absolute-path "^0.0.0" + async "^2.4.0" + babel-preset-fbjs "^3.0.1" + buffer-crc32 "^0.2.13" + chalk "^1.1.1" + concat-stream "^1.6.0" + connect "^3.6.5" + debug "^2.2.0" + denodeify "^1.2.1" + eventemitter3 "^3.0.0" + fbjs "^1.0.0" + fs-extra "^1.0.0" + graceful-fs "^4.1.3" + image-size "^0.6.0" + jest-haste-map "24.0.0-alpha.2" + jest-worker "24.0.0-alpha.2" + json-stable-stringify "^1.0.1" + lodash.throttle "^4.1.1" + merge-stream "^1.0.1" + metro-cache "0.48.3" + metro-config "0.48.3" + metro-core "0.48.3" + metro-minify-uglify "0.48.3" + metro-react-native-babel-preset "0.48.3" + metro-resolver "0.48.3" + metro-source-map "0.48.3" + mime-types "2.1.11" + mkdirp "^0.5.1" + node-fetch "^2.2.0" + nullthrows "^1.1.0" + react-transform-hmr "^1.0.4" + resolve "^1.5.0" + rimraf "^2.5.4" + serialize-error "^2.1.0" + source-map "^0.5.6" + temp "0.8.3" + throat "^4.1.0" + wordwrap "^1.0.0" + write-file-atomic "^1.2.0" + ws "^1.1.0" + xpipe "^1.0.5" + yargs "^9.0.0" + metro@0.50.0: version "0.50.0" resolved "https://registry.yarnpkg.com/metro/-/metro-0.50.0.tgz#4b11f065de5bc5857de2a48021b80d3e0afcebd7" @@ -4047,7 +4304,7 @@ oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" -object-assign@^4.0.1, object-assign@^4.1.0: +object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -4394,6 +4651,19 @@ pretty-format@24.0.0-alpha.6, pretty-format@^24.0.0-alpha.6: ansi-regex "^4.0.0" ansi-styles "^3.2.0" +pretty-format@^23.4.1: + version "23.6.0" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-23.6.0.tgz#5eaac8eeb6b33b987b7fe6097ea6a8a146ab5760" + integrity sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw== + dependencies: + ansi-regex "^3.0.0" + ansi-styles "^3.2.0" + +pretty-format@^4.2.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-4.3.1.tgz#530be5c42b3c05b36414a7a2a4337aa80acd0e8d" + integrity sha1-UwvlxCs8BbNkFKeipDN6qArNDo0= + private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -4433,6 +4703,14 @@ prompts@^1.1.0: kleur "^2.0.1" sisteransi "^1.0.0" +prop-types@^15.5.8: + version "15.6.2" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" + integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ== + dependencies: + loose-envify "^1.3.1" + object-assign "^4.1.1" + pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -4481,10 +4759,81 @@ rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +react-clone-referenced-element@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/react-clone-referenced-element/-/react-clone-referenced-element-1.1.0.tgz#9cdda7f2aeb54fea791f3ab8c6ab96c7a77d0158" + integrity sha512-FKOsfKbBkPxYE8576EM6uAfHC4rnMpLyH6/TJUL4WcHUEB3EUn8AxPjnnV/IiwSSzsClvHYK+sDELKN/EJ0WYg== + react-deep-force-update@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/react-deep-force-update/-/react-deep-force-update-1.1.2.tgz#3d2ae45c2c9040cbb1772be52f8ea1ade6ca2ee1" +react-devtools-core@^3.4.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/react-devtools-core/-/react-devtools-core-3.4.3.tgz#1a06b7dc546d41ecf8dc4fbabea2d79d339353cf" + integrity sha512-t3f6cRH5YSKv8qjRl1Z+1e0OwBZwJSdOAhJ9QAJdVKML7SmqAKKv3DxF+Ue03pE1N2UipPsLmaNcPzzMjIdZQg== + dependencies: + shell-quote "^1.6.1" + ws "^3.3.1" + +react-native@^0.57.0: + version "0.57.7" + resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.57.7.tgz#5b3af1c43366c41d8d8d2540fea8ce590060bca1" + integrity sha512-mdNibV6NblH8gbbcWjLjH6lVOkrXuCiIi+RQ+6e2QlrOIJVsKC216VvBhHsvdDIRO94v1qD8LMnTYlBY09qzQQ== + dependencies: + "@babel/runtime" "^7.0.0" + absolute-path "^0.0.0" + art "^0.10.0" + base64-js "^1.1.2" + chalk "^1.1.1" + commander "^2.9.0" + compression "^1.7.1" + connect "^3.6.5" + create-react-class "^15.6.3" + debug "^2.2.0" + denodeify "^1.2.1" + envinfo "^5.7.0" + errorhandler "^1.5.0" + escape-string-regexp "^1.0.5" + event-target-shim "^1.0.5" + fbjs "^1.0.0" + fbjs-scripts "^1.0.0" + fs-extra "^1.0.0" + glob "^7.1.1" + graceful-fs "^4.1.3" + inquirer "^3.0.6" + lodash "^4.17.5" + metro "^0.48.1" + metro-babel-register "^0.48.1" + metro-core "^0.48.1" + metro-memory-fs "^0.48.1" + mime "^1.3.4" + minimist "^1.2.0" + mkdirp "^0.5.1" + morgan "^1.9.0" + node-fetch "^2.2.0" + node-notifier "^5.2.1" + npmlog "^2.0.4" + opn "^3.0.2" + optimist "^0.6.1" + plist "^3.0.0" + pretty-format "^4.2.1" + promise "^7.1.1" + prop-types "^15.5.8" + react-clone-referenced-element "^1.0.1" + react-devtools-core "^3.4.2" + react-timer-mixin "^0.13.2" + regenerator-runtime "^0.11.0" + rimraf "^2.5.4" + semver "^5.0.3" + serve-static "^1.13.1" + shell-quote "1.6.1" + stacktrace-parser "^0.1.3" + ws "^1.1.0" + xcode "^1.0.0" + xmldoc "^0.4.0" + yargs "^9.0.0" + react-proxy@^1.1.7: version "1.1.8" resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-1.1.8.tgz#9dbfd9d927528c3aa9f444e4558c37830ab8c26a" @@ -4492,6 +4841,11 @@ react-proxy@^1.1.7: lodash "^4.6.1" react-deep-force-update "^1.0.0" +react-timer-mixin@^0.13.2: + version "0.13.4" + resolved "https://registry.yarnpkg.com/react-timer-mixin/-/react-timer-mixin-0.13.4.tgz#75a00c3c94c13abe29b43d63b4c65a88fc8264d3" + integrity sha512-4+ow23tp/Tv7hBM5Az5/Be/eKKF7DIvJ09voz5LyHGQaqqz9WV8YMs31eFvcYQs7d451LSg7kDJV70XYN/Ug/Q== + react-transform-hmr@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/react-transform-hmr/-/react-transform-hmr-1.0.4.tgz#e1a40bd0aaefc72e8dfd7a7cda09af85066397bb" @@ -4567,6 +4921,11 @@ regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" +regenerator-runtime@^0.12.0: + version "0.12.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" + integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== + regenerator-transform@^0.13.3: version "0.13.3" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.3.tgz#264bd9ff38a8ce24b06e0636496b2c856b57bcbb" @@ -4875,7 +5234,7 @@ shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" -shell-quote@1.6.1: +shell-quote@1.6.1, shell-quote@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" dependencies: @@ -5038,6 +5397,11 @@ stack-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" +stacktrace-parser@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/stacktrace-parser/-/stacktrace-parser-0.1.4.tgz#01397922e5f62ecf30845522c95c4fe1d25e7d4e" + integrity sha1-ATl5IuX2Ls8whFUiyVxP4dJefU4= + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -5313,6 +5677,11 @@ ultron@1.0.x: version "1.0.2" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" +ultron@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" + integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== + unicode-canonical-property-names-ecmascript@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" @@ -5548,6 +5917,15 @@ ws@^1.1.0: options ">=0.0.5" ultron "1.0.x" +ws@^3.3.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" + integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== + dependencies: + async-limiter "~1.0.0" + safe-buffer "~5.1.0" + ultron "~1.1.0" + ws@^5.2.0: version "5.2.2" resolved "https://registry.yarnpkg.com/ws/-/ws-5.2.2.tgz#dffef14866b8e8dc9133582514d1befaf96e980f"