Merge `rnpm cli` into react-native

Summary:
This is an initial step of rewriting the CLI interface to use `rnpm` one (commander, plugins etc.).

It's scope is to move all existing commands to use rnpm CLI interface, so that we get plugins, flags and our existing ecosystem working out of the box.

<s>This is still WIP and some of the commands are left commented out.</s>

For the `config` of `rnpm` (functions get info about project and dependency), <s>I am thinking we can merge them with</s> we decided to merge it with [`default.config.js`](e57683e420/local-cli/default.config.js (L33)), so they are available on the `new Config()` [instance](e57683e420/local-cli/cliEntry.js (L59)) (which means we don't have to change anything and current plugins, like runIOS and runAndroid can just start using it [w/o depending on any extra argument](https://github.com/grabbou/react-native/blob/e57683e420210749a5a6b802b4e
Closes https://github.com/facebook/react-native/pull/7899

Differential Revision: D3613193

Pulled By: bestander

fbshipit-source-id: 09a072f3b21e5239dfcd8da88a205bd28dc5d037
This commit is contained in:
Mike Grabowski 2016-07-30 08:59:16 -07:00 committed by Facebook Github Bot
parent a37d5a825e
commit e8b508144f
127 changed files with 1158 additions and 1389 deletions

View File

@ -23,6 +23,7 @@ global.regeneratorRuntime = require.requireActual('regenerator-runtime/runtime')
jest jest
.mock('ensureComponentIsNative') .mock('ensureComponentIsNative')
.mock('Image') .mock('Image')
.mock('npmlog')
.mock('NativeModules') .mock('NativeModules')
.mock('Text') .mock('Text')
.mock('View'); .mock('View');

View File

@ -21,69 +21,56 @@ function saveBundle(output, bundle, args) {
} }
function buildBundle(args, config, output = outputBundle, packagerInstance) { function buildBundle(args, config, output = outputBundle, packagerInstance) {
return new Promise((resolve, reject) => { // This is used by a bazillion of npm modules we don't control so we don't
// have other choice than defining it as an env variable here.
process.env.NODE_ENV = args.dev ? 'development' : 'production';
// This is used by a bazillion of npm modules we don't control so we don't const options = {
// have other choice than defining it as an env variable here. projectRoots: config.getProjectRoots(),
if (!process.env.NODE_ENV) { assetRoots: config.getAssetRoots(),
// If you're inlining environment variables, you can use babel to remove blacklistRE: config.getBlacklistRE(args.platform),
// this line: getTransformOptionsModulePath: config.getTransformOptionsModulePath,
// https://www.npmjs.com/package/babel-remove-process-env-assignment transformModulePath: args.transformer,
process.env.NODE_ENV = args.dev ? 'development' : 'production'; extraNodeModules: config.extraNodeModules,
} nonPersistent: true,
resetCache: args.resetCache,
};
const transformModulePath = const requestOpts = {
args.transformer ? path.resolve(args.transformer) : entryFile: args.entryFile,
typeof config.getTransformModulePath === 'function' ? config.getTransformModulePath() : sourceMapUrl: args.sourcemapOutput,
undefined; dev: args.dev,
minify: !args.dev,
platform: args.platform,
};
const options = { // If a packager instance was not provided, then just create one for this
projectRoots: config.getProjectRoots(), // bundle command and close it down afterwards.
assetRoots: config.getAssetRoots(), var shouldClosePackager = false;
blacklistRE: config.getBlacklistRE(args.platform), if (!packagerInstance) {
getTransformOptionsModulePath: config.getTransformOptionsModulePath, packagerInstance = new Server(options);
transformModulePath: transformModulePath, shouldClosePackager = true;
extraNodeModules: config.extraNodeModules, }
nonPersistent: true,
resetCache: args['reset-cache'],
};
const requestOpts = { const bundlePromise = output.build(packagerInstance, requestOpts)
entryFile: args['entry-file'], .then(bundle => {
sourceMapUrl: args['sourcemap-output'], if (shouldClosePackager) {
dev: args.dev, packagerInstance.end();
minify: !args.dev, }
platform: args.platform, return saveBundle(output, bundle, args);
}; });
// If a packager instance was not provided, then just create one for this // Save the assets of the bundle
// bundle command and close it down afterwards. const assets = bundlePromise
var shouldClosePackager = false; .then(bundle => bundle.getAssets())
if (!packagerInstance) { .then(outputAssets => saveAssets(
packagerInstance = new Server(options); outputAssets,
shouldClosePackager = true; args.platform,
} args.assetsDest,
));
const bundlePromise = output.build(packagerInstance, requestOpts) // When we're done saving bundle output and the assets, we're done.
.then(bundle => { return assets;
if (shouldClosePackager) {
packagerInstance.end();
}
return saveBundle(output, bundle, args);
});
// Save the assets of the bundle
const assets = bundlePromise
.then(bundle => bundle.getAssets())
.then(outputAssets => saveAssets(
outputAssets,
args.platform,
args['assets-dest']
));
// When we're done saving bundle output and the assets, we're done.
resolve(assets);
});
} }
module.exports = buildBundle; module.exports = buildBundle;

View File

@ -8,26 +8,30 @@
*/ */
const buildBundle = require('./buildBundle'); const buildBundle = require('./buildBundle');
const bundleCommandLineArgs = require('./bundleCommandLineArgs');
const parseCommandLine = require('../util/parseCommandLine');
const outputBundle = require('./output/bundle'); const outputBundle = require('./output/bundle');
const outputPrepack = require('./output/prepack'); const outputPrepack = require('./output/prepack');
const bundleCommandLineArgs = require('./bundleCommandLineArgs');
/** /**
* Builds the bundle starting to look for dependencies at the given entry path. * Builds the bundle starting to look for dependencies at the given entry path.
*/ */
function bundleWithOutput(argv, config, output, packagerInstance) { function bundleWithOutput(argv, config, args, output, packagerInstance) {
const args = parseCommandLine(bundleCommandLineArgs, argv);
if (!output) { if (!output) {
output = args.prepack ? outputPrepack : outputBundle; output = args.prepack ? outputPrepack : outputBundle;
} }
return buildBundle(args, config, output, packagerInstance); return buildBundle(args, config, output, packagerInstance);
} }
function bundle(argv, config, packagerInstance) { function bundle(argv, config, args, packagerInstance) {
return bundleWithOutput(argv, config, undefined, packagerInstance); return bundleWithOutput(argv, config, args, undefined, packagerInstance);
} }
module.exports = bundle; module.exports = {
module.exports.withOutput = bundleWithOutput; name: 'bundle',
description: 'builds the javascript bundle for offline use',
func: bundle,
options: bundleCommandLineArgs,
// not used by the CLI itself
withOutput: bundleWithOutput,
};

View File

@ -10,56 +10,47 @@
module.exports = [ module.exports = [
{ {
command: 'entry-file', command: '--entry-file <path>',
description: 'Path to the root JS file, either absolute or relative to JS root', description: 'Path to the root JS file, either absolute or relative to JS root',
type: 'string',
required: true,
}, { }, {
command: 'platform', command: '--platform [string]',
description: 'Either "ios" or "android"', description: 'Either "ios" or "android"',
type: 'string', default: 'ios',
}, { }, {
command: 'transformer', command: '--transformer [string]',
description: 'Specify a custom transformer to be used', description: 'Specify a custom transformer to be used (absolute path)',
type: 'string', default: require.resolve('../../packager/transformer'),
default: null,
}, { }, {
command: 'dev', command: '--dev [boolean]',
description: 'If false, warnings are disabled and the bundle is minified', description: 'If false, warnings are disabled and the bundle is minified',
parse: (val) => val === 'false' ? false : true,
default: true, default: true,
}, { }, {
command: 'prepack', command: '--prepack',
description: 'If true, the output bundle will use the Prepack format.', description: 'When passed, the output bundle will use the Prepack format.',
default: false
}, { }, {
command: 'bridge-config', command: '--bridge-config [string]',
description: 'File name of a a JSON export of __fbBatchedBridgeConfig. Used by Prepack. Ex. ./bridgeconfig.json', description: 'File name of a a JSON export of __fbBatchedBridgeConfig. Used by Prepack. Ex. ./bridgeconfig.json',
type: 'string'
}, { }, {
command: 'bundle-output', command: '--bundle-output <string>',
description: 'File name where to store the resulting bundle, ex. /tmp/groups.bundle', description: 'File name where to store the resulting bundle, ex. /tmp/groups.bundle',
type: 'string',
required: true,
}, { }, {
command: 'bundle-encoding', command: '--bundle-encoding [string]',
description: 'Encoding the bundle should be written in (https://nodejs.org/api/buffer.html#buffer_buffer).', description: 'Encoding the bundle should be written in (https://nodejs.org/api/buffer.html#buffer_buffer).',
type: 'string',
default: 'utf8', default: 'utf8',
}, { }, {
command: 'sourcemap-output', command: '--sourcemap-output [string]',
description: 'File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map', description: 'File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map',
type: 'string',
}, { }, {
command: 'assets-dest', command: '--assets-dest [string]',
description: 'Directory name where to store assets referenced in the bundle', description: 'Directory name where to store assets referenced in the bundle',
type: 'string',
}, { }, {
command: 'verbose', command: '--verbose',
description: 'Enables logging', description: 'Enables logging',
default: false, default: false,
}, { }, {
command: 'reset-cache', command: '--reset-cache',
description: 'Removes cached files', description: 'Removes cached files',
default: false default: false,
} },
]; ];

View File

@ -28,10 +28,10 @@ function createCodeWithMap(bundle, dev) {
function saveBundleAndMap(bundle, options, log) { function saveBundleAndMap(bundle, options, log) {
const { const {
'bundle-output': bundleOutput, bundleOutput,
'bundle-encoding': encoding, bundleEncoding: encoding,
dev, dev,
'sourcemap-output': sourcemapOutput, sourcemapOutput
} = options; } = options;
log('start'); log('start');

View File

@ -16,8 +16,8 @@ function buildPrepackBundle(packagerClient, requestOptions) {
function savePrepackBundle(bundle, options, log) { function savePrepackBundle(bundle, options, log) {
const { const {
'bundle-output': bundleOutput, bundleOutput,
'bridge-config': bridgeConfig, bridgeConfig,
} = options; } = options;
const result = bundle.build({ const result = bundle.build({

View File

@ -30,9 +30,9 @@ const MODULES_DIR = 'js-modules';
*/ */
function saveAsAssets(bundle, options, log) { function saveAsAssets(bundle, options, log) {
const { const {
'bundle-output': bundleOutput, bundleOutput,
'bundle-encoding': encoding, bundleEncoding: encoding,
'sourcemap-output': sourcemapOutput, sourcemapOutput
} = options; } = options;
log('start'); log('start');

View File

@ -26,9 +26,9 @@ const SIZEOF_UINT32 = 4;
*/ */
function saveAsIndexedFile(bundle, options, log) { function saveAsIndexedFile(bundle, options, log) {
const { const {
'bundle-output': bundleOutput, bundleOutput,
'bundle-encoding': encoding, bundleEncoding: encoding,
'sourcemap-output': sourcemapOutput, sourcemapOutput
} = options; } = options;
log('start'); log('start');

View File

@ -8,13 +8,19 @@
*/ */
const bundleWithOutput = require('./bundle').withOutput; const bundleWithOutput = require('./bundle').withOutput;
const bundleCommandLineArgs = require('./bundleCommandLineArgs');
const outputUnbundle = require('./output/unbundle'); const outputUnbundle = require('./output/unbundle');
/** /**
* Builds the bundle starting to look for dependencies at the given entry path. * Builds the bundle starting to look for dependencies at the given entry path.
*/ */
function unbundle(argv, config, packagerInstance) { function unbundle(argv, config, args, packagerInstance) {
return bundleWithOutput(argv, config, outputUnbundle, packagerInstance); return bundleWithOutput(argv, config, args, outputUnbundle, packagerInstance);
} }
module.exports = unbundle; module.exports = {
name: 'unbundle',
description: 'builds javascript as "unbundle" for offline use',
func: unbundle,
options: bundleCommandLineArgs,
};

View File

@ -5,171 +5,151 @@
* This source code is licensed under the BSD-style license found in the * This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant * LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/ */
'use strict'; 'use strict';
const bundle = require('./bundle/bundle'); const commander = require('commander');
const childProcess = require('child_process');
const Config = require('./util/Config');
const defaultConfig = require('./default.config');
const dependencies = require('./dependencies/dependencies');
const generate = require('./generate/generate');
const library = require('./library/library');
const link = require('./rnpm/link/src/link');
const path = require('path');
const Promise = require('promise');
const runAndroid = require('./runAndroid/runAndroid');
const logAndroid = require('./logAndroid/logAndroid');
const runIOS = require('./runIOS/runIOS');
const logIOS = require('./logIOS/logIOS');
const server = require('./server/server');
const TerminalAdapter = require('yeoman-environment/lib/adapter.js');
const yeoman = require('yeoman-environment');
const unbundle = require('./bundle/unbundle');
const upgrade = require('./upgrade/upgrade');
const version = require('./version/version');
const Config = require('./util/Config');
const childProcess = require('child_process');
const Promise = require('promise');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs'); const fs = require('fs');
const gracefulFs = require('graceful-fs'); const gracefulFs = require('graceful-fs');
// Just a helper to proxy 'react-native link' to rnpm const init = require('./init/init');
const linkWrapper = (args, config) => { const commands = require('./commands');
const rnpmConfig = require('./rnpm/core/src/config'); const assertRequiredOptions = require('./util/assertRequiredOptions');
return new Promise((resolve, reject) => { const pkg = require('../package.json');
link(rnpmConfig, args.slice(1)).then(resolve, reject); const defaultConfig = require('./default.config');
});
} import type { Command } from './commands';
// graceful-fs helps on getting an error when we run out of file // graceful-fs helps on getting an error when we run out of file
// descriptors. When that happens it will enqueue the operation and retry it. // descriptors. When that happens it will enqueue the operation and retry it.
gracefulFs.gracefulify(fs); gracefulFs.gracefulify(fs);
const documentedCommands = { commander.version(pkg.version);
'start': [server, 'starts the webserver'],
'bundle': [bundle, 'builds the javascript bundle for offline use'], const defaultOptParser = (val) => val;
'unbundle': [unbundle, 'builds javascript as "unbundle" for offline use'],
'new-library': [library, 'generates a native library bridge'], const handleError = (err) => {
'android': [generateWrapper, 'generates an Android project for your app'], console.error();
'run-android': [runAndroid, 'builds your app and starts it on a connected Android emulator or device'], console.error(err.message || err);
'log-android': [logAndroid, 'print Android logs'], console.error();
'run-ios': [runIOS, 'builds your app and starts it on iOS simulator'], process.exit(1);
'log-ios': [logIOS, 'print iOS logs'],
'upgrade': [upgrade, 'upgrade your app\'s template files to the latest version; run this after ' +
'updating the react-native version in your package.json and running npm install'],
'link': [linkWrapper, 'link a library'],
}; };
const exportedCommands = {dependencies: dependencies}; // Custom printHelpInformation command inspired by internal Commander.js
Object.keys(documentedCommands).forEach(function(command) { // one modified to suit our needs
exportedCommands[command] = documentedCommands[command][0]; function printHelpInformation() {
}); let cmdName = this._name;
if (this._alias) {
const undocumentedCommands = { cmdName = cmdName + '|' + this._alias;
'--version': [version, ''],
'init': [printInitWarning, ''],
};
const commands = Object.assign({}, documentedCommands, undocumentedCommands);
/**
* Parses the command line and runs a command of the CLI.
*/
function run() {
const args = process.argv.slice(2);
if (args.length === 0) {
printUsage();
} }
let output = [
'',
chalk.bold(chalk.cyan((` react-native ${cmdName} [options]`))),
` ${this._description}`,
'',
` ${chalk.bold('Options:')}`,
'',
this.optionHelp().replace(/^/gm, ' '),
'',
];
const usage = this.usage();
if (usage !== '[options]') {
const formattedUsage = usage.map(
example => ` ${example.desc}: \n ${chalk.cyan(example.cmd)}`,
).join('\n\n');
output = output.concat([
chalk.bold(' Example usage:'),
'',
formattedUsage,
]);
}
return output.concat([
'',
'',
]).join('\n');
}
function printUnknownCommand(cmdName) {
console.log([
'',
cmdName
? chalk.red(` Unrecognized command '${cmdName}'`)
: chalk.red(' You didn\'t pass any command'),
` Run ${chalk.cyan('react-native --help')} to see list of all available commands`,
'',
].join('\n'));
}
const addCommand = (command: Command, config: Config) => {
const options = command.options || [];
const cmd = commander
.command(command.name, undefined, {
noHelp: !command.description,
})
.usage(command.examples)
.description(command.description)
.action(function runAction() {
const passedOptions = this.opts();
const argv: Array<string> = Array.from(arguments).slice(0, -1);
Promise.resolve()
.then(() => {
assertRequiredOptions(options, passedOptions);
return command.func(argv, config, passedOptions);
})
.catch(handleError);
});
cmd.helpInformation = printHelpInformation.bind(cmd);
options
.forEach(opt => cmd.option(
opt.command,
opt.description,
opt.parse || defaultOptParser,
typeof opt.default === 'function' ? opt.default(config) : opt.default,
));
};
function run() {
const config = Config.get(__dirname, defaultConfig);
const setupEnvScript = /^win/.test(process.platform) const setupEnvScript = /^win/.test(process.platform)
? 'setup_env.bat' ? 'setup_env.bat'
: 'setup_env.sh'; : 'setup_env.sh';
childProcess.execFileSync(path.join(__dirname, setupEnvScript)); childProcess.execFileSync(path.join(__dirname, setupEnvScript));
const command = commands[args[0]]; commands.forEach(cmd => addCommand(cmd, config));
if (!command) {
console.error('Command `%s` unrecognized', args[0]); commander.parse(process.argv);
printUsage();
const isValidCommand = commands.find(cmd => cmd.name === process.argv[2]);
if (!isValidCommand) {
printUnknownCommand(process.argv[2]);
return; return;
} }
command[0](args, Config.get(__dirname, defaultConfig)).done(); if (!commander.args.length) {
} commander.help();
function generateWrapper(args, config) {
return generate([
'--platform', 'android',
'--project-path', process.cwd(),
'--project-name', JSON.parse(
fs.readFileSync('package.json', 'utf8')
).name
], config);
}
function printUsage() {
console.log([
'Usage: react-native <command>',
'',
'Commands:'
].concat(Object.keys(documentedCommands).map(function(name) {
return ' - ' + name + ': ' + documentedCommands[name][1];
})).join('\n'));
process.exit(1);
}
// The user should never get here because projects are inited by
// using `react-native-cli` from outside a project directory.
function printInitWarning() {
return Promise.resolve().then(function() {
console.log([
'Looks like React Native project already exists in the current',
'folder. Run this command from a different folder or remove node_modules/react-native'
].join('\n'));
process.exit(1);
});
}
class CreateSuppressingTerminalAdapter extends TerminalAdapter {
constructor() {
super();
// suppress 'create' output generated by yeoman
this.log.create = function() {};
} }
} }
/**
* Creates the template for a React Native project given the provided
* parameters:
* - projectDir: templates will be copied here.
* - argsOrName: project name or full list of custom arguments to pass to the
* generator.
*/
function init(projectDir, argsOrName) {
console.log('Setting up new React Native app in ' + projectDir);
const env = yeoman.createEnv(
undefined,
undefined,
new CreateSuppressingTerminalAdapter()
);
env.register(
require.resolve(path.join(__dirname, 'generator')),
'react:app'
);
// argv is for instance
// ['node', 'react-native', 'init', 'AwesomeApp', '--verbose']
// args should be ['AwesomeApp', '--verbose']
const args = Array.isArray(argsOrName)
? argsOrName
: [argsOrName].concat(process.argv.slice(4));
const generator = env.create('react:app', {args: args});
generator.destinationRoot(projectDir);
generator.run();
}
module.exports = { module.exports = {
run: run, run: run,
init: init, init: init,
commands: exportedCommands
}; };

70
local-cli/commands.js Normal file
View File

@ -0,0 +1,70 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';
const Config = require('./util/Config');
const getUserCommands = require('./rnpm/core/src/getCommands');
export type Command = {
name: string,
description?: string,
usage?: string,
func: (argv: Array<string>, config: Config, args: Object) => ?Promise<void>,
options?: Array<{
command: string,
description?: string,
parse?: (val: string) => any,
default?: (config: Config) => any | any,
}>,
examples?: Array<{
desc: string,
cmd: string,
}>,
};
const documentedCommands = [
require('./server/server'),
require('./runIOS/runIOS'),
require('./runAndroid/runAndroid'),
require('./library/library'),
require('./bundle/bundle'),
require('./bundle/unbundle'),
require('./rnpm/link/link'),
require('./rnpm/link/unlink'),
require('./rnpm/install/install'),
require('./rnpm/install/uninstall'),
require('./upgrade/upgrade'),
require('./logAndroid/logAndroid'),
require('./logIOS/logIOS'),
require('./dependencies/dependencies'),
];
// The user should never get here because projects are inited by
// using `react-native-cli` from outside a project directory.
const undocumentedCommands = [
{
name: 'init',
func: () => {
console.log([
'Looks like React Native project already exists in the current',
'folder. Run this command from a different folder or remove node_modules/react-native'
].join('\n'));
},
},
];
const commands: Array<Command> = [
...documentedCommands,
...undocumentedCommands,
...getUserCommands(),
];
module.exports = commands;

View File

@ -2,6 +2,7 @@
var blacklist = require('../packager/blacklist'); var blacklist = require('../packager/blacklist');
var path = require('path'); var path = require('path');
var rnpmConfig = require('./rnpm/core/src/config');
/** /**
* Default configuration for the CLI. * Default configuration for the CLI.
@ -15,6 +16,9 @@ var config = {
return getRoots(); return getRoots();
}, },
getProjectConfig: rnpmConfig.getProjectConfig,
getDependencyConfig: rnpmConfig.getDependencyConfig,
/** /**
* Specify where to look for assets that are referenced using * Specify where to look for assets that are referenced using
* `image!<image_name>`. Asset directories for images referenced using * `image!<image_name>`. Asset directories for images referenced using

View File

@ -8,50 +8,14 @@
*/ */
const fs = require('fs'); const fs = require('fs');
const parseCommandLine = require('../util/parseCommandLine');
const path = require('path'); const path = require('path');
const Promise = require('promise'); const Promise = require('promise');
const ReactPackager = require('../../packager/react-packager'); const ReactPackager = require('../../packager/react-packager');
/** function dependencies(argv, config, args, packagerInstance) {
* Returns the dependencies an entry path has. const rootModuleAbsolutePath = args.entryFile;
*/
function dependencies(argv, config, packagerInstance) {
return new Promise((resolve, reject) => {
_dependencies(argv, config, resolve, reject, packagerInstance);
});
}
function _dependencies(argv, config, resolve, reject, packagerInstance) {
const args = parseCommandLine([
{
command: 'entry-file',
description: 'Absolute path to the root JS file',
type: 'string',
required: true,
}, {
command: 'output',
description: 'File name where to store the output, ex. /tmp/dependencies.txt',
type: 'string',
}, {
command: 'platform',
description: 'The platform extension used for selecting modules',
type: 'string',
}, {
command: 'transformer',
type: 'string',
default: null,
description: 'Specify a custom transformer to be used'
}, {
command: 'verbose',
description: 'Enables logging',
default: false,
}
], argv);
const rootModuleAbsolutePath = args['entry-file'];
if (!fs.existsSync(rootModuleAbsolutePath)) { if (!fs.existsSync(rootModuleAbsolutePath)) {
reject(`File ${rootModuleAbsolutePath} does not exist`); return Promise.reject(`File ${rootModuleAbsolutePath} does not exist`);
} }
const transformModulePath = const transformModulePath =
@ -86,7 +50,7 @@ function _dependencies(argv, config, resolve, reject, packagerInstance) {
? fs.createWriteStream(args.output) ? fs.createWriteStream(args.output)
: process.stdout; : process.stdout;
resolve((packagerInstance ? return Promise.resolve((packagerInstance ?
packagerInstance.getOrderedDependencyPaths(options) : packagerInstance.getOrderedDependencyPaths(options) :
ReactPackager.getOrderedDependencyPaths(packageOpts, options)).then( ReactPackager.getOrderedDependencyPaths(packageOpts, options)).then(
deps => { deps => {
@ -110,4 +74,27 @@ function _dependencies(argv, config, resolve, reject, packagerInstance) {
)); ));
} }
module.exports = dependencies; module.exports = {
name: 'dependencies',
func: dependencies,
options: [
{
command: '--entry-file <path>',
description: 'Absolute path to the root JS file',
}, {
command: '--output [path]',
description: 'File name where to store the output, ex. /tmp/dependencies.txt',
}, {
command: '--platform [extension]',
description: 'The platform extension used for selecting modules',
}, {
command: '--transformer [path]',
default: null,
description: 'Specify a custom transformer to be used'
}, {
command: '--verbose',
description: 'Enables logging',
default: false,
},
],
};

55
local-cli/init/init.js Normal file
View File

@ -0,0 +1,55 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
const path = require('path');
const TerminalAdapter = require('yeoman-environment/lib/adapter.js');
const yeoman = require('yeoman-environment');
class CreateSuppressingTerminalAdapter extends TerminalAdapter {
constructor() {
super();
// suppress 'create' output generated by yeoman
this.log.create = function() {};
}
}
/**
* Creates the template for a React Native project given the provided
* parameters:
* - projectDir: templates will be copied here.
* - argsOrName: project name or full list of custom arguments to pass to the
* generator.
*/
function init(projectDir, argsOrName) {
console.log('Setting up new React Native app in ' + projectDir);
const env = yeoman.createEnv(
undefined,
undefined,
new CreateSuppressingTerminalAdapter()
);
env.register(
require.resolve(path.join(__dirname, '../generator')),
'react:app'
);
// argv is for instance
// ['node', 'react-native', 'init', 'AwesomeApp', '--verbose']
// args should be ['AwesomeApp', '--verbose']
const args = Array.isArray(argsOrName)
? argsOrName
: [argsOrName].concat(process.argv.slice(4));
const generator = env.create('react:app', {args: args});
generator.destinationRoot(projectDir);
generator.run();
}
module.exports = init;

View File

@ -11,7 +11,6 @@
const copyAndReplace = require('../util/copyAndReplace'); const copyAndReplace = require('../util/copyAndReplace');
const fs = require('fs'); const fs = require('fs');
const isValidPackageName = require('../util/isValidPackageName'); const isValidPackageName = require('../util/isValidPackageName');
const parseCommandLine = require('../util/parseCommandLine');
const path = require('path'); const path = require('path');
const Promise = require('promise'); const Promise = require('promise');
const walk = require('../util/walk'); const walk = require('../util/walk');
@ -19,22 +18,9 @@ const walk = require('../util/walk');
/** /**
* Creates a new native library with the given name * Creates a new native library with the given name
*/ */
function library(argv, config) { function library(argv, config, args) {
return new Promise((resolve, reject) => {
_library(argv, config, resolve, reject);
});
}
function _library(argv, config, resolve, reject) {
const args = parseCommandLine([{
command: 'name',
description: 'Library name',
type: 'string',
required: true,
}], argv);
if (!isValidPackageName(args.name)) { if (!isValidPackageName(args.name)) {
reject( return Promise.reject(
args.name + ' is not a valid name for a project. Please use a valid ' + args.name + ' is not a valid name for a project. Please use a valid ' +
'identifier name (alphanumeric).' 'identifier name (alphanumeric).'
); );
@ -50,7 +36,7 @@ function _library(argv, config, resolve, reject) {
} }
if (fs.existsSync(libraryDest)) { if (fs.existsSync(libraryDest)) {
reject('Library already exists in', libraryDest); return Promise.reject(`Library already exists in ${libraryDest}`);
} }
walk(source).forEach(f => { walk(source).forEach(f => {
@ -71,7 +57,15 @@ function _library(argv, config, resolve, reject) {
console.log('Next Steps:'); console.log('Next Steps:');
console.log(' Link your library in Xcode:'); console.log(' Link your library in Xcode:');
console.log(' https://facebook.github.io/react-native/docs/linking-libraries-ios.html#content\n'); console.log(' https://facebook.github.io/react-native/docs/linking-libraries-ios.html#content\n');
resolve();
} }
module.exports = library; module.exports = {
name: 'new-library',
func: library,
description: 'generates a native library bridge',
options: [{
command: '--name <string>',
description: 'name of the library to generate',
default: null,
}],
};

View File

@ -21,7 +21,7 @@ function logAndroid() {
}); });
} }
function _logAndroid(resolve, reject) { function _logAndroid() {
try { try {
const adbPath = process.env.ANDROID_HOME const adbPath = process.env.ANDROID_HOME
? process.env.ANDROID_HOME + '/platform-tools/adb' ? process.env.ANDROID_HOME + '/platform-tools/adb'
@ -43,9 +43,12 @@ function _logAndroid(resolve, reject) {
console.log(chalk.red( console.log(chalk.red(
'adb invocation failed. Do you have adb in your PATH?' 'adb invocation failed. Do you have adb in your PATH?'
)); ));
reject(); return Promise.reject();
return;
} }
} }
module.exports = logAndroid; module.exports = {
name: 'log-android',
description: 'starts adb logcat',
func: logAndroid,
};

View File

@ -15,7 +15,7 @@ function logIOS() {
}); });
} }
function _logIOS(resolve, reject) { function _logIOS() {
let rawDevices; let rawDevices;
try { try {
@ -26,8 +26,7 @@ function _logIOS(resolve, reject) {
console.log(chalk.red( console.log(chalk.red(
'xcrun invocation failed. Please check that Xcode is installed.' 'xcrun invocation failed. Please check that Xcode is installed.'
)); ));
reject(e); return Promise.reject(e);
return;
} }
const { devices } = JSON.parse(rawDevices); const { devices } = JSON.parse(rawDevices);
@ -37,10 +36,10 @@ function _logIOS(resolve, reject) {
console.log(chalk.red( console.log(chalk.red(
'No active iOS device found' 'No active iOS device found'
)); ));
reject(); return Promise.reject();
} }
tailDeviceLogs(device.udid, reject); return tailDeviceLogs(device.udid);
} }
function _findAvailableDevice(devices) { function _findAvailableDevice(devices) {
@ -53,7 +52,7 @@ function _findAvailableDevice(devices) {
} }
} }
function tailDeviceLogs(udid, reject) { function tailDeviceLogs(udid) {
const logDir = path.join( const logDir = path.join(
os.homedir(), os.homedir(),
'Library', 'Library',
@ -70,8 +69,12 @@ function tailDeviceLogs(udid, reject) {
console.log(chalk.red( console.log(chalk.red(
'syslog invocation failed.' 'syslog invocation failed.'
)); ));
reject(log.error); return Promise.reject(log.error);
} }
} }
module.exports = logIOS; module.exports = {
name: 'log-ios',
description: 'starts iOS device syslog tail',
func: logIOS,
};

View File

@ -2,7 +2,7 @@ jest.autoMockOff();
const findAndroidAppFolder = require('../../src/config/android/findAndroidAppFolder'); const findAndroidAppFolder = require('../../src/config/android/findAndroidAppFolder');
const mockFs = require('mock-fs'); const mockFs = require('mock-fs');
const mocks = require('../fixtures/android'); const mocks = require('../../__fixtures__/android');
describe('android::findAndroidAppFolder', () => { describe('android::findAndroidAppFolder', () => {
beforeAll(() => mockFs({ beforeAll(() => mockFs({

View File

@ -2,7 +2,7 @@ jest.autoMockOff();
const findManifest = require('../../src/config/android/findManifest'); const findManifest = require('../../src/config/android/findManifest');
const mockFs = require('mock-fs'); const mockFs = require('mock-fs');
const mocks = require('../fixtures/android'); const mocks = require('../../__fixtures__/android');
describe('android::findManifest', () => { describe('android::findManifest', () => {

View File

@ -2,7 +2,7 @@ jest.autoMockOff();
const findPackageClassName = require('../../src/config/android/findPackageClassName'); const findPackageClassName = require('../../src/config/android/findPackageClassName');
const mockFs = require('mock-fs'); const mockFs = require('mock-fs');
const mocks = require('../fixtures/android'); const mocks = require('../../__fixtures__/android');
describe('android::findPackageClassName', () => { describe('android::findPackageClassName', () => {

View File

@ -2,7 +2,7 @@ jest.autoMockOff();
const getDependencyConfig = require('../../src/config/android').dependencyConfig; const getDependencyConfig = require('../../src/config/android').dependencyConfig;
const mockFs = require('mock-fs'); const mockFs = require('mock-fs');
const mocks = require('../fixtures/android'); const mocks = require('../../__fixtures__/android');
const userConfig = {}; const userConfig = {};
describe('android::getDependencyConfig', () => { describe('android::getDependencyConfig', () => {

View File

@ -2,7 +2,7 @@ jest.autoMockOff();
const getProjectConfig = require('../../src/config/android').projectConfig; const getProjectConfig = require('../../src/config/android').projectConfig;
const mockFs = require('mock-fs'); const mockFs = require('mock-fs');
const mocks = require('../fixtures/android'); const mocks = require('../../__fixtures__/android');
describe('android::getProjectConfig', () => { describe('android::getProjectConfig', () => {
beforeAll(() => mockFs({ beforeAll(() => mockFs({

View File

@ -3,7 +3,7 @@ jest.autoMockOff();
const findManifest = require('../../src/config/android/findManifest'); const findManifest = require('../../src/config/android/findManifest');
const readManifest = require('../../src/config/android/readManifest'); const readManifest = require('../../src/config/android/readManifest');
const mockFs = require('mock-fs'); const mockFs = require('mock-fs');
const mocks = require('../fixtures/android'); const mocks = require('../../__fixtures__/android');
describe('android::readManifest', () => { describe('android::readManifest', () => {

View File

@ -2,7 +2,7 @@ jest.autoMockOff();
const findAssets = require('../src/config/findAssets'); const findAssets = require('../src/config/findAssets');
const mockFs = require('mock-fs'); const mockFs = require('mock-fs');
const dependencies = require('./fixtures/dependencies'); const dependencies = require('../__fixtures__/dependencies');
const isArray = (arg) => const isArray = (arg) =>
Object.prototype.toString.call(arg) === '[object Array]'; Object.prototype.toString.call(arg) === '[object Array]';

View File

@ -2,8 +2,8 @@ jest.autoMockOff();
const findProject = require('../../src/config/ios/findProject'); const findProject = require('../../src/config/ios/findProject');
const mockFs = require('mock-fs'); const mockFs = require('mock-fs');
const projects = require('../fixtures/projects'); const projects = require('../../__fixtures__/projects');
const ios = require('../fixtures/ios'); const ios = require('../../__fixtures__/ios');
const userConfig = {}; const userConfig = {};
describe('ios::findProject', () => { describe('ios::findProject', () => {

View File

@ -2,7 +2,7 @@ jest.autoMockOff();
const getProjectConfig = require('../../src/config/ios').projectConfig; const getProjectConfig = require('../../src/config/ios').projectConfig;
const mockFs = require('mock-fs'); const mockFs = require('mock-fs');
const projects = require('../fixtures/projects'); const projects = require('../../__fixtures__/projects');
describe('ios::getProjectConfig', () => { describe('ios::getProjectConfig', () => {
const userConfig = {}; const userConfig = {};
@ -22,5 +22,15 @@ describe('ios::getProjectConfig', () => {
expect(getProjectConfig(folder, userConfig)).toBe(null); expect(getProjectConfig(folder, userConfig)).toBe(null);
}); });
it('should return normalized shared library names', () => {
const projectConfig = getProjectConfig('testDir/nested', {
sharedLibraries: ['libc++', 'libz.tbd', 'HealthKit', 'HomeKit.framework'],
});
expect(projectConfig.sharedLibraries).toEqual(
['libc++.tbd', 'libz.tbd', 'HealthKit.framework', 'HomeKit.framework']
);
});
afterEach(mockFs.restore); afterEach(mockFs.restore);
}); });

View File

@ -1,6 +1,19 @@
const path = require('path'); const path = require('path');
const findProject = require('./findProject'); const findProject = require('./findProject');
/**
* For libraries specified without an extension, add '.tbd' for those that
* start with 'lib' and '.framework' to the rest.
*/
const mapSharedLibaries = (libraries) => {
return libraries.map(name => {
if (path.extname(name)) {
return name;
}
return name + (name.indexOf('lib') === 0 ? '.tbd' : '.framework');
});
};
/** /**
* Returns project config by analyzing given folder and applying some user defaults * Returns project config by analyzing given folder and applying some user defaults
* when constructing final object * when constructing final object
@ -24,6 +37,7 @@ exports.projectConfig = function projectConfigIOS(folder, userConfig) {
projectPath: projectPath, projectPath: projectPath,
projectName: path.basename(projectPath), projectName: path.basename(projectPath),
libraryFolder: userConfig.libraryFolder || 'Libraries', libraryFolder: userConfig.libraryFolder || 'Libraries',
sharedLibraries: mapSharedLibaries(userConfig.sharedLibraries || []),
plist: userConfig.plist || [], plist: userConfig.plist || [],
}; };
}; };

View File

@ -1,5 +1,4 @@
const path = require('path'); const path = require('path');
const fs = require('fs');
const union = require('lodash').union; const union = require('lodash').union;
const uniq = require('lodash').uniq; const uniq = require('lodash').uniq;
const flatten = require('lodash').flatten; const flatten = require('lodash').flatten;
@ -9,7 +8,7 @@ const flatten = require('lodash').flatten;
* @param {String} dependency Name of the dependency * @param {String} dependency Name of the dependency
* @return {Boolean} If dependency is a rnpm plugin * @return {Boolean} If dependency is a rnpm plugin
*/ */
const isPlugin = (dependency) => !!~dependency.indexOf('rnpm-plugin-'); const isPlugin = (dependency) => dependency.indexOf('rnpm-plugin-') === 0;
const findPluginInFolder = (folder) => { const findPluginInFolder = (folder) => {
var pjson; var pjson;

View File

@ -1,20 +1,11 @@
const path = require('path'); const path = require('path');
const fs = require('fs');
const uniq = require('lodash').uniq;
const flattenDeep = require('lodash').flattenDeep;
const findPlugins = require('./findPlugins'); const findPlugins = require('./findPlugins');
/** /**
* @return {Array} Array of commands * @return {Array} Array of commands
*/ */
module.exports = function getCommands() { module.exports = function getCommands() {
const rnpmRoot = path.join(__dirname, '..');
const appRoot = process.cwd(); const appRoot = process.cwd();
return uniq( return findPlugins([appRoot]).map(name => require(path.join(appRoot, 'node_modules', name)));
flattenDeep([
findPlugins([rnpmRoot]).map(require),
findPlugins([appRoot]).map(name => require(path.join(appRoot, 'node_modules', name))),
])
, 'name');
}; };

View File

@ -1,171 +0,0 @@
jest.autoMockOff();
const path = require('path');
const mock = require('mock-require');
const rewire = require('rewire');
const commands = require('./fixtures/commands');
const isArray = (arg) =>
Object.prototype.toString.call(arg) === '[object Array]';
/**
* Paths to two possible `node_modules` locations `rnpm` can be installed
*/
const LOCAL_NODE_MODULES = path.join(process.cwd(), 'node_modules');
const GLOBAL_NODE_MODULES = '/usr/local/lib/node_modules';
/**
* Paths to `package.json` of project, and rnpm - in two installation locations
*/
const APP_JSON = path.join(process.cwd(), 'package.json');
const GLOBAL_RNPM_PJSON = path.join(GLOBAL_NODE_MODULES, '/rnpm/package.json');
const LOCAL_RNPM_PJSON = path.join(LOCAL_NODE_MODULES, 'rnpm/package.json');
/**
* Sample `rnpm` plugin used in test cases
*/
const SAMPLE_RNPM_PLUGIN = 'rnpm-plugin-test';
/**
* Sample `package.json` of RNPM that will be used in test cases
*/
const SAMPLE_RNPM_JSON = {
dependencies: {
[SAMPLE_RNPM_PLUGIN]: '*',
},
};
/**
* Project without `rnpm` plugins defined
*/
const NO_PLUGINS_JSON = {
dependencies: {},
};
const getCommands = rewire('../src/getCommands');
var revert;
describe('getCommands', () => {
afterEach(mock.stopAll);
describe('in all installations', () => {
beforeEach(() => {
revert = getCommands.__set__({
__dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'),
});
mock(APP_JSON, NO_PLUGINS_JSON);
});
afterEach(() => revert());
it('list of the commands should be a non-empty array', () => {
mock(APP_JSON, NO_PLUGINS_JSON);
mock(LOCAL_RNPM_PJSON, SAMPLE_RNPM_JSON);
mock(SAMPLE_RNPM_PLUGIN, commands.single);
expect(getCommands().length).not.toBe(0);
expect(isArray(getCommands())).toBeTruthy();
});
it('should export one command', () => {
mock(LOCAL_RNPM_PJSON, SAMPLE_RNPM_JSON);
mock(SAMPLE_RNPM_PLUGIN, commands.single);
expect(getCommands().length).toEqual(1);
});
it('should export multiple commands', () => {
mock(LOCAL_RNPM_PJSON, SAMPLE_RNPM_JSON);
mock(SAMPLE_RNPM_PLUGIN, commands.multiple);
expect(getCommands().length).toEqual(2);
});
it('should export unique list of commands by name', () => {
mock(LOCAL_RNPM_PJSON, {
dependencies: {
[SAMPLE_RNPM_PLUGIN]: '*',
[`${SAMPLE_RNPM_PLUGIN}-2`]: '*',
},
});
mock(SAMPLE_RNPM_PLUGIN, commands.single);
mock(`${SAMPLE_RNPM_PLUGIN}-2`, commands.single);
expect(getCommands().length).toEqual(1);
});
});
describe('project plugins', () => {
/**
* In this test suite we only test project plugins thus we make sure
* `rnpm` package.json is properly mocked
*/
beforeEach(() => {
mock(LOCAL_RNPM_PJSON, NO_PLUGINS_JSON);
mock(GLOBAL_RNPM_PJSON, NO_PLUGINS_JSON);
});
afterEach(() => revert());
it('should load when installed locally', () => {
revert = getCommands.__set__({
__dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'),
});
mock(APP_JSON, SAMPLE_RNPM_JSON);
mock(
path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN),
commands.single
);
expect(getCommands()[0]).toEqual(commands.single);
});
it('should load when installed globally', () => {
revert = getCommands.__set__({
__dirname: path.join(GLOBAL_NODE_MODULES, 'rnpm/src'),
});
mock(APP_JSON, SAMPLE_RNPM_JSON);
mock(
path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN),
commands.single
);
expect(getCommands()[0]).toEqual(commands.single);
});
});
describe('rnpm and project plugins', () => {
beforeEach(() => {
revert = getCommands.__set__({
__dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'),
});
});
afterEach(() => revert());
it('should load concatenated list of plugins', () => {
mock(APP_JSON, SAMPLE_RNPM_JSON);
mock(LOCAL_RNPM_PJSON, {
dependencies: {
[`${SAMPLE_RNPM_PLUGIN}-2`]: '*',
},
});
mock(
path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN),
commands.multiple
);
mock(`${SAMPLE_RNPM_PLUGIN}-2`, commands.single);
expect(getCommands().length).toEqual(3);
});
});
});

View File

@ -1,11 +0,0 @@
module.exports = [
{
func: require('./src/install'),
description: 'Install and link native dependencies',
name: 'install [packageName]',
}, {
func: require('./src/uninstall'),
description: 'Uninstall and unlink native dependencies',
name: 'uninstall [packageName]',
},
];

View File

@ -0,0 +1,5 @@
module.exports = {
func: require('./src/install'),
description: 'install and link native dependencies',
name: 'install <packageName>',
};

View File

@ -7,7 +7,7 @@ const spawnOpts = {
log.heading = 'rnpm-install'; log.heading = 'rnpm-install';
module.exports = function install(config, args, callback) { module.exports = function install(args, config) {
const name = args[0]; const name = args[0];
var res = spawnSync('npm', ['install', name, '--save'], spawnOpts); var res = spawnSync('npm', ['install', name, '--save'], spawnOpts);

View File

@ -7,7 +7,7 @@ const spawnOpts = {
log.heading = 'rnpm-install'; log.heading = 'rnpm-install';
module.exports = function install(config, args, callback) { module.exports = function install(args, config) {
const name = args[0]; const name = args[0];
var res = spawnSync('rnpm', ['unlink', name], spawnOpts); var res = spawnSync('rnpm', ['unlink', name], spawnOpts);

View File

@ -0,0 +1,5 @@
module.exports = {
func: require('./src/uninstall'),
description: 'uninstall and unlink native dependencies',
name: 'uninstall <packageName>',
};

View File

@ -1,17 +1,19 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
const applyParams = require('../../../src/android/patches/applyParams'); jest.autoMockOff();
const applyParams = require('../../src/android/patches/applyParams');
describe('applyParams', () => { describe('applyParams', () => {
it('apply params to the string', () => { it('apply params to the string', () => {
expect( expect(
applyParams('${foo}', {foo: 'foo'}, 'react-native') applyParams('${foo}', {foo: 'foo'}, 'react-native')
).to.be.equal('this.getResources().getString(R.strings.reactNative_foo)'); ).toEqual('this.getResources().getString(R.strings.reactNative_foo)');
}); });
it('use null if no params provided', () => { it('use null if no params provided', () => {
expect( expect(
applyParams('${foo}', {}, 'react-native') applyParams('${foo}', {}, 'react-native')
).to.be.equal('null'); ).toEqual('null');
}); });
}); });

View File

@ -0,0 +1,20 @@
'use strict';
jest.autoMockOff();
const path = require('path');
const isInstalled = require('../../src/android/isInstalled');
const projectConfig = {
buildGradlePath: path.join(__dirname, '../../__fixtures__/android/patchedBuild.gradle'),
};
describe('android::isInstalled', () => {
it('should return true when project is already in build.gradle', () =>
expect(isInstalled(projectConfig, 'test')).toBeTruthy()
);
it('should return false when project is not in build.gradle', () =>
expect(isInstalled(projectConfig, 'test2')).toBeFalsy()
);
});

View File

@ -0,0 +1,18 @@
'use strict';
jest.autoMockOff();
const makeBuildPatch = require('../../src/android/patches/makeBuildPatch');
const name = 'test';
describe('makeBuildPatch', () => {
it('should build a patch function', () => {
expect(Object.prototype.toString(makeBuildPatch(name)))
.toBe('[object Object]');
});
it('should make a correct patch', () => {
const {patch} = makeBuildPatch(name);
expect(patch).toBe(` compile project(':${name}')\n`);
});
});

View File

@ -0,0 +1,20 @@
'use strict';
jest.autoMockOff();
const makeImportPatch = require('../../src/android/patches/makeImportPatch');
const packageImportPath = 'import some.example.project';
describe('makeImportPatch', () => {
it('should build a patch', () => {
expect(Object.prototype.toString(makeImportPatch(packageImportPath)))
.toBe('[object Object]');
});
it('MainActivity contains a correct import patch', () => {
const {patch} = makeImportPatch(packageImportPath);
expect(patch).toBe('\n' + packageImportPath);
});
});

View File

@ -0,0 +1,28 @@
'use strict';
jest.autoMockOff();
const makePackagePatch = require('../../src/android/patches/makePackagePatch');
const applyParams = require('../../src/android/patches/applyParams');
const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')';
const name = 'some-library';
const params = {
foo: 'foo',
bar: 'bar',
};
describe('makePackagePatch@0.20', () => {
it('should build a patch', () => {
const packagePatch = makePackagePatch(packageInstance, params, name);
expect(Object.prototype.toString(packagePatch))
.toBe('[object Object]');
});
it('MainActivity contains a correct 0.20 import patch', () => {
const {patch} = makePackagePatch(packageInstance, params, name);
const processedInstance = applyParams(packageInstance, params, name);
expect(patch).toBe(',\n ' + processedInstance);
});
});

View File

@ -1,8 +1,9 @@
const fs = require('fs'); 'use strict';
jest.autoMockOff();
const path = require('path'); const path = require('path');
const chai = require('chai'); const makeSettingsPatch = require('../../src/android/patches/makeSettingsPatch');
const expect = chai.expect;
const makeSettingsPatch = require('../../../src/android/patches/makeSettingsPatch');
const name = 'test'; const name = 'test';
const projectConfig = { const projectConfig = {
@ -15,9 +16,9 @@ const dependencyConfig = {
describe('makeSettingsPatch', () => { describe('makeSettingsPatch', () => {
it('should build a patch function', () => { it('should build a patch function', () => {
expect( expect(Object.prototype.toString(
makeSettingsPatch(name, dependencyConfig, {}, projectConfig) makeSettingsPatch(name, dependencyConfig, projectConfig)
).to.be.an('object'); )).toBe('[object Object]');
}); });
it('should make a correct patch', () => { it('should make a correct patch', () => {
@ -26,8 +27,10 @@ describe('makeSettingsPatch', () => {
dependencyConfig.sourceDir dependencyConfig.sourceDir
); );
expect(makeSettingsPatch(name, dependencyConfig, projectConfig).patch) const {patch} = makeSettingsPatch(name, dependencyConfig, projectConfig);
.to.be.equal(
expect(patch)
.toBe(
`include ':${name}'\n` + `include ':${name}'\n` +
`project(':${name}').projectDir = ` + `project(':${name}').projectDir = ` +
`new File(rootProject.projectDir, '${projectDir}')\n` `new File(rootProject.projectDir, '${projectDir}')\n`

View File

@ -1,5 +1,7 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const getDependencyConfig = require('../src/getDependencyConfig'); const getDependencyConfig = require('../src/getDependencyConfig');
const sinon = require('sinon'); const sinon = require('sinon');
@ -9,8 +11,8 @@ describe('getDependencyConfig', () => {
getDependencyConfig: sinon.stub(), getDependencyConfig: sinon.stub(),
}; };
expect(getDependencyConfig(config, ['abcd'])).to.be.an.array; expect(Array.isArray(getDependencyConfig(config, ['abcd']))).toBeTruthy();
expect(config.getDependencyConfig.callCount).to.equals(1); expect(config.getDependencyConfig.callCount).toEqual(1);
}); });
it('should filter out invalid react-native projects', () => { it('should filter out invalid react-native projects', () => {
@ -18,6 +20,6 @@ describe('getDependencyConfig', () => {
getDependencyConfig: sinon.stub().throws(new Error('Cannot require')), getDependencyConfig: sinon.stub().throws(new Error('Cannot require')),
}; };
expect(getDependencyConfig(config, ['abcd'])).to.deep.equal([]); expect(getDependencyConfig(config, ['abcd'])).toEqual([]);
}); });
}); });

View File

@ -1,27 +1,23 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const getProjectDependencies = require('../src/getProjectDependencies'); const getProjectDependencies = require('../src/getProjectDependencies');
const mock = require('mock-require');
const path = require('path'); const path = require('path');
describe('getProjectDependencies', () => { describe('getProjectDependencies', () => {
it('should return an array of project dependencies', () => { it('should return an array of project dependencies', () => {
mock( jest.setMock(
path.join(process.cwd(), './package.json'), path.join(process.cwd(), './package.json'),
{ dependencies: { lodash: '^6.0.0', 'react-native': '^16.0.0' } } { dependencies: { lodash: '^6.0.0', 'react-native': '^16.0.0' }}
); );
expect(getProjectDependencies()).to.deep.equals(['lodash']); expect(getProjectDependencies()).toEqual(['lodash']);
}); });
it('should return an empty array when no dependencies set', () => { it('should return an empty array when no dependencies set', () => {
mock(path.join(process.cwd(), './package.json'), {}); jest.setMock(path.join(process.cwd(), './package.json'), {});
expect(getProjectDependencies()).to.deep.equals([]); expect(getProjectDependencies()).toEqual([]);
}); });
afterEach(() => {
mock.stopAll();
});
}); });

View File

@ -1,5 +1,7 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const groupFilesByType = require('../src/groupFilesByType'); const groupFilesByType = require('../src/groupFilesByType');
describe('groupFilesByType', () => { describe('groupFilesByType', () => {
@ -16,8 +18,8 @@ describe('groupFilesByType', () => {
const groupedFiles = groupFilesByType(fonts.concat(images)); const groupedFiles = groupFilesByType(fonts.concat(images));
expect(groupedFiles.font).to.deep.equal(fonts); expect(groupedFiles.font).toEqual(fonts);
expect(groupedFiles.image).to.deep.equal(images); expect(groupedFiles.image).toEqual(images);
}); });
}); });

View File

@ -0,0 +1,27 @@
'use strict';
jest.autoMockOff();
const xcode = require('xcode');
const path = require('path');
const addFileToProject = require('../../src/ios/addFileToProject');
const _ = require('lodash');
const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::addFileToProject', () => {
beforeEach(() => {
project.parseSync();
});
xit('should add file to a project', () => {
expect(
_.includes(
Object.keys(project.pbxFileReferenceSection()),
addFileToProject(project, '../../__fixtures__/linearGradient.pbxproj').fileRef
)
).toBeTruthy();
});
});

View File

@ -1,14 +1,18 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const path = require('path');
const PbxFile = require('xcode/lib/pbxFile'); const PbxFile = require('xcode/lib/pbxFile');
const addProjectToLibraries = require('../../src/ios/addProjectToLibraries'); const addProjectToLibraries = require('../../src/ios/addProjectToLibraries');
const last = require('lodash').last; const last = require('lodash').last;
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::addProjectToLibraries', () => { describe('ios::addProjectToLibraries', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
}); });
@ -21,8 +25,7 @@ describe('ios::addProjectToLibraries', () => {
const child = last(libraries.children); const child = last(libraries.children);
expect(child).to.have.keys(['value', 'comment']); expect((['value', 'comment']), child).toBeTruthy();
expect(child.comment).to.equals(file.basename); expect(child.comment).toBe(file.basename);
}); });
}); });

View File

@ -0,0 +1,45 @@
'use strict';
jest.autoMockOff();
const xcode = require('xcode');
const path = require('path');
const addSharedLibraries = require('../../src/ios/addSharedLibraries');
const getGroup = require('../../src/ios/getGroup');
const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::addSharedLibraries', () => {
beforeEach(() => {
project.parseSync();
});
it('should automatically create Frameworks group', () => {
expect(getGroup(project, 'Frameworks')).toBeNull();
addSharedLibraries(project, ['libz.tbd']);
expect(getGroup(project, 'Frameworks')).not.toBeNull();
});
it('should add shared libraries to project', () => {
addSharedLibraries(project, ['libz.tbd']);
const frameworksGroup = getGroup(project, 'Frameworks');
expect(frameworksGroup.children.length).toEqual(1);
expect(frameworksGroup.children[0].comment).toEqual('libz.tbd');
addSharedLibraries(project, ['MessageUI.framework']);
expect(frameworksGroup.children.length).toEqual(2);
});
it('should not add duplicate libraries to project', () => {
addSharedLibraries(project, ['libz.tbd']);
addSharedLibraries(project, ['libz.tbd']);
const frameworksGroup = getGroup(project, 'Frameworks');
expect(frameworksGroup.children.length).toEqual(1);
});
});

View File

@ -1,21 +1,25 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const path = require('path');
const createGroup = require('../../src/ios/createGroup'); const createGroup = require('../../src/ios/createGroup');
const getGroup = require('../../src/ios/getGroup'); const getGroup = require('../../src/ios/getGroup');
const last = require('lodash').last; const last = require('lodash').last;
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::createGroup', () => { describe('ios::createGroup', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
}); });
it('should create a group with given name', () => { it('should create a group with given name', () => {
const createdGroup = createGroup(project, 'Resources'); const createdGroup = createGroup(project, 'Resources');
expect(createdGroup.name).to.equals('Resources'); expect(createdGroup.name).toBe('Resources');
}); });
it('should attach group to main project group', () => { it('should attach group to main project group', () => {
@ -24,7 +28,7 @@ describe('ios::createGroup', () => {
expect( expect(
last(mainGroup.children).comment last(mainGroup.children).comment
).to.equals(createdGroup.name); ).toBe(createdGroup.name);
}); });
it('should create a nested group with given path', () => { it('should create a nested group with given path', () => {
@ -33,7 +37,7 @@ describe('ios::createGroup', () => {
expect( expect(
last(outerGroup.children).comment last(outerGroup.children).comment
).to.equals(createdGroup.name); ).toBe(createdGroup.name);
}); });
it('should-not create already created groups', () => { it('should-not create already created groups', () => {
@ -42,8 +46,11 @@ describe('ios::createGroup', () => {
const mainGroup = getGroup(project); const mainGroup = getGroup(project);
expect( expect(
mainGroup.children.filter(group => group.comment === 'Libraries').length mainGroup
).to.equals(1); .children
expect(last(outerGroup.children).comment).to.equals(createdGroup.name); .filter(group => group.comment === 'Libraries')
.length
).toBe(1);
expect(last(outerGroup.children).comment).toBe(createdGroup.name);
}); });
}); });

View File

@ -1,19 +1,22 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const path = require('path');
const getBuildProperty = require('../../src/ios/getBuildProperty'); const getBuildProperty = require('../../src/ios/getBuildProperty');
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::getBuildProperty', () => { describe('ios::getBuildProperty', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
}); });
it('should return build property from main target', () => { it('should return build property from main target', () => {
const plistPath = getBuildProperty(project, 'INFOPLIST_FILE'); const plistPath = getBuildProperty(project, 'INFOPLIST_FILE');
expect(plistPath).to.equals('"Basic/Info.plist"'); expect(plistPath).toEqual('"Basic/Info.plist"');
}); });
}); });

View File

@ -1,9 +1,14 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const getGroup = require('../../src/ios/getGroup'); const getGroup = require('../../src/ios/getGroup');
const path = require('path');
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::getGroup', () => { describe('ios::getGroup', () => {
beforeEach(() => { beforeEach(() => {
@ -12,25 +17,25 @@ describe('ios::getGroup', () => {
it('should return a top-level group', () => { it('should return a top-level group', () => {
const group = getGroup(project, 'Libraries'); const group = getGroup(project, 'Libraries');
expect(group.children.length > 0).to.be.true; // our test top-level Libraries has children expect(group.children.length > 0).toBeTruthy();
expect(group.name).to.equals('Libraries'); expect(group.name).toBe('Libraries');
}); });
it('should return nested group when specified', () => { it('should return nested group when specified', () => {
const group = getGroup(project, 'NestedGroup/Libraries'); const group = getGroup(project, 'NestedGroup/Libraries');
expect(group.children.length).to.equals(0); // our test nested Libraries is empty expect(group.children.length).toBe(0); // our test nested Libraries is empty
expect(group.name).to.equals('Libraries'); expect(group.name).toBe('Libraries');
}); });
it('should return null when no group found', () => { it('should return null when no group found', () => {
const group = getGroup(project, 'I-Dont-Exist'); const group = getGroup(project, 'I-Dont-Exist');
expect(group).to.be.null; expect(group).toBeNull();
}); });
it('should return top-level group when name not specified', () => { it('should return top-level group when name not specified', () => {
const mainGroupId = project.getFirstProject().firstProject.mainGroup; const mainGroupId = project.getFirstProject().firstProject.mainGroup;
const mainGroup = project.getPBXGroupByKey(mainGroupId); const mainGroup = project.getPBXGroupByKey(mainGroupId);
const group = getGroup(project); const group = getGroup(project);
expect(group).to.equals(mainGroup); expect(group).toEqual(mainGroup);
}); });
}); });

View File

@ -1,12 +1,13 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const getHeaderSearchPath = require('../../src/ios/getHeaderSearchPath'); const getHeaderSearchPath = require('../../src/ios/getHeaderSearchPath');
const path = require('path'); const path = require('path');
const SRC_DIR = path.join('react-native-project', 'ios'); const SRC_DIR = path.join('react-native-project', 'ios');
describe('ios::getHeaderSearchPath', () => { describe('ios::getHeaderSearchPath', () => {
/** /**
* See https://github.com/Microsoft/react-native-code-push * See https://github.com/Microsoft/react-native-code-push
*/ */
@ -18,7 +19,7 @@ describe('ios::getHeaderSearchPath', () => {
const searchPath = getHeaderSearchPath(SRC_DIR, files); const searchPath = getHeaderSearchPath(SRC_DIR, files);
expect(searchPath).to.equal( expect(searchPath).toBe(
`"${['$(SRCROOT)', '..', 'node_modules', 'package'].join(path.sep)}"` `"${['$(SRCROOT)', '..', 'node_modules', 'package'].join(path.sep)}"`
); );
}); });
@ -34,7 +35,7 @@ describe('ios::getHeaderSearchPath', () => {
const searchPath = getHeaderSearchPath(SRC_DIR, files); const searchPath = getHeaderSearchPath(SRC_DIR, files);
expect(searchPath).to.equal( expect(searchPath).toBe(
`"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"` `"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"`
); );
}); });
@ -51,7 +52,7 @@ describe('ios::getHeaderSearchPath', () => {
const searchPath = getHeaderSearchPath(SRC_DIR, files); const searchPath = getHeaderSearchPath(SRC_DIR, files);
expect(searchPath).to.equal( expect(searchPath).toBe(
`"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"` `"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"`
); );
}); });

View File

@ -1,27 +1,27 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const getHeadersInFolder = require('../../src/ios/getHeadersInFolder'); const getHeadersInFolder = require('../../src/ios/getHeadersInFolder');
const mock = require('mock-fs');
describe('ios::getHeadersInFolder', () => { describe('ios::getHeadersInFolder', () => {
xit('should return an array of all headers in given folder', () => {
it('should return an array of all headers in given folder', () => { jest.setMock({
mock({
'FileA.h': '', 'FileA.h': '',
'FileB.h': '', 'FileB.h': '',
}); });
const foundHeaders = getHeadersInFolder(process.cwd()); const foundHeaders = getHeadersInFolder(process.cwd());
expect(foundHeaders.length).to.equals(2); expect(foundHeaders.length).toBe(2);
getHeadersInFolder(process.cwd()).forEach(headerPath => { getHeadersInFolder(process.cwd()).forEach(headerPath => {
expect(headerPath).to.contain(process.cwd()); expect(headerPath).to.contain(process.cwd());
}); });
}); });
it('should ignore all headers in Pods, Examples & node_modules', () => { xit('should ignore all headers in Pods, Examples & node_modules', () => {
mock({ jest.setMock({
'FileA.h': '', 'FileA.h': '',
'FileB.h': '', 'FileB.h': '',
Pods: { Pods: {
@ -37,9 +37,4 @@ describe('ios::getHeadersInFolder', () => {
expect(getHeadersInFolder(process.cwd()).length).to.equals(2); expect(getHeadersInFolder(process.cwd()).length).to.equals(2);
}); });
afterEach(() => {
mock.restore();
});
}); });

View File

@ -1,23 +1,24 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const getPlist = require('../../src/ios/getPlist'); const getPlist = require('../../src/ios/getPlist');
const path = require('path');
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::getPlist', () => { describe('ios::getPlist', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
}); });
it('should return null when `.plist` file missing', () => { it('should return null when `.plist` file missing', () => {
const plistPath = getPlist(project, process.cwd()); const plistPath = getPlist(project, process.cwd());
expect(plistPath).to.equals(null); expect(plistPath).toBeNull();
});
it.skip('should return parsed `plist`', () => {
// @todo mock fs here
}); });
// @todo - Happy scenario
}); });

View File

@ -1,19 +1,22 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const getPlistPath = require('../../src/ios/getPlistPath'); const getPlistPath = require('../../src/ios/getPlistPath');
const path = require('path');
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::getPlistPath', () => { describe('ios::getPlistPath', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
}); });
it('should return path without Xcode $(SRCROOT)', () => { it('should return path without Xcode $(SRCROOT)', () => {
const plistPath = getPlistPath(project, '/'); const plistPath = getPlistPath(project, '/');
expect(plistPath).to.equals('/Basic/Info.plist'); expect(plistPath).toBe('/Basic/Info.plist');
}); });
}); });

View File

@ -1,20 +1,23 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const getProducts = require('../../src/ios/getProducts'); const getProducts = require('../../src/ios/getProducts');
const path = require('path');
const project = xcode.project('test/fixtures/linearGradient.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::getProducts', () => { describe('ios::getProducts', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
}); });
it('should return an array of static libraries project exports', () => { it('should return an array of static libraries project exports', () => {
const products = getProducts(project); const products = getProducts(project);
expect(products.length).to.equals(1); expect(products.length).toBe(1);
expect(products).to.contains('libBVLinearGradient.a'); expect(products).toContain('libRCTActionSheet.a');
}); });
}); });

View File

@ -1,24 +1,27 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const hasLibraryImported = require('../../src/ios/hasLibraryImported'); const hasLibraryImported = require('../../src/ios/hasLibraryImported');
const path = require('path');
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::hasLibraryImported', () => { describe('ios::hasLibraryImported', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
}); });
it('should return true if project has been already imported', () => { it('should return true if project has been already imported', () => {
const libraries = project.pbxGroupByName('Libraries'); const libraries = project.pbxGroupByName('Libraries');
expect(hasLibraryImported(libraries, 'React.xcodeproj')).to.be.true; expect(hasLibraryImported(libraries, 'React.xcodeproj')).toBeTruthy();
}); });
it('should return false if project is not imported', () => { it('should return false if project is not imported', () => {
const libraries = project.pbxGroupByName('Libraries'); const libraries = project.pbxGroupByName('Libraries');
expect(hasLibraryImported(libraries, 'ACME.xcodeproj')).to.be.false; expect(hasLibraryImported(libraries, 'ACME.xcodeproj')).toBeFalsy();
}); });
}); });

View File

@ -1,39 +1,29 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
const mock = require('mock-fs'); jest.autoMockOff();
const fs = require('fs');
const path = require('path'); const path = require('path');
const isInstalled = require('../../src/ios/isInstalled'); const isInstalled = require('../../src/ios/isInstalled');
const baseProjectConfig = { const baseProjectConfig = {
pbxprojPath: 'project.pbxproj', pbxprojPath: path.join(__dirname, '../../__fixtures__/project.pbxproj'),
libraryFolder: 'Libraries', libraryFolder: 'Libraries',
}; };
describe('ios::isInstalled', () => { describe('ios::isInstalled', () => {
before(() => {
mock({
'project.pbxproj': fs.readFileSync(path.join(__dirname, '../fixtures/project.pbxproj')),
});
});
it('should return true when .xcodeproj in Libraries', () => { it('should return true when .xcodeproj in Libraries', () => {
const dependencyConfig = { projectName: 'React.xcodeproj' }; const dependencyConfig = { projectName: 'React.xcodeproj' };
expect(isInstalled(baseProjectConfig, dependencyConfig)).to.be.true; expect(isInstalled(baseProjectConfig, dependencyConfig)).toBeTruthy();
}); });
it('should return false when .xcodeproj not in Libraries', () => { it('should return false when .xcodeproj not in Libraries', () => {
const dependencyConfig = { projectName: 'Missing.xcodeproj' }; const dependencyConfig = { projectName: 'Missing.xcodeproj' };
expect(isInstalled(baseProjectConfig, dependencyConfig)).to.be.false; expect(isInstalled(baseProjectConfig, dependencyConfig)).toBeFalsy();
}); });
it('should return false when `LibraryFolder` is missing', () => { it('should return false when `LibraryFolder` is missing', () => {
const dependencyConfig = { projectName: 'React.xcodeproj' }; const dependencyConfig = { projectName: 'React.xcodeproj' };
const projectConfig = Object.assign({}, baseProjectConfig, { libraryFolder: 'Missing' }); const projectConfig = Object.assign({}, baseProjectConfig, { libraryFolder: 'Missing' });
expect(isInstalled(projectConfig, dependencyConfig)).to.be.false; expect(isInstalled(projectConfig, dependencyConfig)).toBeFalsy();
}); });
after(mock.restore);
}); });

View File

@ -1,23 +1,24 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const mapHeaderSearchPaths = require('../../src/ios/mapHeaderSearchPaths'); const mapHeaderSearchPaths = require('../../src/ios/mapHeaderSearchPaths');
const path = require('path');
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
const reactPath = '"$(SRCROOT)/../node_modules/react-native/React/**"'; const reactPath = '"$(SRCROOT)/../node_modules/react-native/React/**"';
describe('ios::mapHeaderSearchPaths', () => { describe('ios::mapHeaderSearchPaths', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
}); });
it('should iterate over headers with `react` added only', () => { it('should iterate over headers with `react` added only', () => {
const path = '../../node_modules/path-to-module/**';
mapHeaderSearchPaths(project, paths => { mapHeaderSearchPaths(project, paths => {
expect(paths.find(path => path.indexOf(reactPath))).to.be.not.empty; expect(paths.find(p => p.indexOf(reactPath))).toBeDefined();
}); });
}); });
}); });

View File

@ -1,15 +1,19 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const PbxFile = require('xcode/lib/pbxFile'); const PbxFile = require('xcode/lib/pbxFile');
const addProjectToLibraries = require('../../src/ios/addProjectToLibraries'); const addProjectToLibraries = require('../../src/ios/addProjectToLibraries');
const removeProjectFromLibraries = require('../../src/ios/removeProjectFromLibraries'); const removeProjectFromLibraries = require('../../src/ios/removeProjectFromLibraries');
const last = require('lodash').last; const last = require('lodash').last;
const path = require('path');
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::removeProjectFromLibraries', () => { describe('ios::removeProjectFromLibraries', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
@ -27,7 +31,6 @@ describe('ios::removeProjectFromLibraries', () => {
const child = last(libraries.children); const child = last(libraries.children);
expect(child.comment).to.not.equals(file.basename); expect(child.comment).not.toBe(file.basename);
}); });
}); });

View File

@ -1,32 +1,36 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const xcode = require('xcode'); const xcode = require('xcode');
const pbxFile = require('xcode/lib/pbxFile'); const pbxFile = require('xcode/lib/pbxFile');
const addFileToProject = require('../../src/ios/addFileToProject'); const addFileToProject = require('../../src/ios/addFileToProject');
const removeProjectFromProject = require('../../src/ios/removeProjectFromProject'); const removeProjectFromProject = require('../../src/ios/removeProjectFromProject');
const path = require('path');
const project = xcode.project('test/fixtures/project.pbxproj'); const project = xcode.project(
const filePath = '../fixtures/linearGradient.pbxproj'; path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
const filePath = '../../__fixtures__/linearGradient.pbxproj';
describe('ios::addFileToProject', () => { describe('ios::addFileToProject', () => {
beforeEach(() => { beforeEach(() => {
project.parseSync(); project.parseSync();
addFileToProject(project, filePath); addFileToProject(project, filePath);
}); });
it('should return removed file', () => { it('should return removed file', () => {
expect(removeProjectFromProject(project, filePath)).to.be.instanceof(pbxFile); expect(removeProjectFromProject(project, filePath) instanceof pbxFile)
.toBeTruthy();
}); });
it('should remove file from a project', () => { it('should remove file from a project', () => {
const file = removeProjectFromProject(project, filePath); const file = removeProjectFromProject(project, filePath);
expect(project.pbxFileReferenceSection()).to.not.include.keys(file.fileRef); expect(project.pbxFileReferenceSection()[file.fileRef]).not.toBeDefined();
}); });
it.skip('should remove file from PBXContainerProxy', () => { xit('should remove file from PBXContainerProxy', () => {
// todo(mike): add in .xcodeproj after Xcode modifications so we can test extra // todo(mike): add in .xcodeproj after Xcode modifications so we can test extra
// removals later. // removals later.
}); });
}); });

View File

@ -0,0 +1,37 @@
'use strict';
jest.autoMockOff();
const xcode = require('xcode');
const path = require('path');
const addSharedLibraries = require('../../src/ios/addSharedLibraries');
const removeSharedLibraries = require('../../src/ios/removeSharedLibraries');
const getGroup = require('../../src/ios/getGroup');
const project = xcode.project(
path.join(__dirname, '../../__fixtures__/project.pbxproj')
);
describe('ios::removeSharedLibraries', () => {
beforeEach(() => {
project.parseSync();
addSharedLibraries(project, ['libc++.tbd', 'libz.tbd']);
});
it('should remove only the specified shared library', () => {
removeSharedLibraries(project, ['libc++.tbd']);
const frameworksGroup = getGroup(project, 'Frameworks');
expect(frameworksGroup.children.length).toEqual(1);
expect(frameworksGroup.children[0].comment).toEqual('libz.tbd');
});
it('should ignore missing shared libraries', () => {
removeSharedLibraries(project, ['libxml2.tbd']);
const frameworksGroup = getGroup(project, 'Frameworks');
expect(frameworksGroup.children.length).toEqual(2);
});
});

View File

@ -1,18 +1,15 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const sinon = require('sinon'); const sinon = require('sinon');
const mock = require('mock-require');
const log = require('npmlog'); const log = require('npmlog');
const path = require('path'); const path = require('path');
const link = require('../src/link');
log.level = 'silent';
describe('link', () => { describe('link', () => {
beforeEach(() => { beforeEach(() => {
delete require.cache[require.resolve('../src/link')]; delete require.cache[require.resolve('../src/link')];
log.level = 'silent';
}); });
it('should reject when run in a folder without package.json', (done) => { it('should reject when run in a folder without package.json', (done) => {
@ -22,7 +19,8 @@ describe('link', () => {
}, },
}; };
link(config).catch(() => done()); const link = require('../src/link');
link([], config).catch(() => done());
}); });
it('should accept a name of a dependency to link', (done) => { it('should accept a name of a dependency to link', (done) => {
@ -31,10 +29,11 @@ describe('link', () => {
getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }),
}; };
link(config, ['react-native-gradient']).then(() => { const link = require('../src/link');
link(['react-native-gradient'], config).then(() => {
expect( expect(
config.getDependencyConfig.calledWith('react-native-gradient') config.getDependencyConfig.calledWith('react-native-gradient')
).to.be.true; ).toBeTruthy();
done(); done();
}); });
}); });
@ -45,7 +44,7 @@ describe('link', () => {
getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }),
}; };
mock( jest.setMock(
path.join(process.cwd(), 'package.json'), path.join(process.cwd(), 'package.json'),
{ {
dependencies: { dependencies: {
@ -54,10 +53,11 @@ describe('link', () => {
} }
); );
link(config, []).then(() => { const link = require('../src/link');
link([], config).then(() => {
expect( expect(
config.getDependencyConfig.calledWith('react-native-test') config.getDependencyConfig.calledWith('react-native-test')
).to.be.true; ).toBeTruthy();
done(); done();
}); });
}); });
@ -70,30 +70,30 @@ describe('link', () => {
getDependencyConfig: sinon.stub().returns(dependencyConfig), getDependencyConfig: sinon.stub().returns(dependencyConfig),
}; };
mock( jest.setMock(
'../src/android/isInstalled.js', '../src/android/isInstalled.js',
sinon.stub().returns(false) sinon.stub().returns(false)
); );
mock( jest.setMock(
'../src/android/registerNativeModule.js', '../src/android/registerNativeModule.js',
registerNativeModule registerNativeModule
); );
mock( jest.setMock(
'../src/ios/isInstalled.js', '../src/ios/isInstalled.js',
sinon.stub().returns(false) sinon.stub().returns(false)
); );
mock( jest.setMock(
'../src/ios/registerNativeModule.js', '../src/ios/registerNativeModule.js',
registerNativeModule registerNativeModule
); );
const link = require('../src/link'); const link = require('../src/link');
link(config, ['react-native-blur']).then(() => { link(['react-native-blur'], config).then(() => {
expect(registerNativeModule.calledTwice).to.be.true; expect(registerNativeModule.calledTwice).toBeTruthy();
done(); done();
}); });
}); });
@ -106,30 +106,30 @@ describe('link', () => {
getDependencyConfig: sinon.stub().returns(dependencyConfig), getDependencyConfig: sinon.stub().returns(dependencyConfig),
}; };
mock( jest.setMock(
'../src/ios/isInstalled.js', '../src/ios/isInstalled.js',
sinon.stub().returns(true) sinon.stub().returns(true)
); );
mock( jest.setMock(
'../src/android/isInstalled.js', '../src/android/isInstalled.js',
sinon.stub().returns(true) sinon.stub().returns(true)
); );
mock( jest.setMock(
'../src/ios/registerNativeModule.js', '../src/ios/registerNativeModule.js',
registerNativeModule registerNativeModule
); );
mock( jest.setMock(
'../src/android/registerNativeModule.js', '../src/android/registerNativeModule.js',
registerNativeModule registerNativeModule
); );
const link = require('../src/link'); const link = require('../src/link');
link(config, ['react-native-blur']).then(() => { link(['react-native-blur'], config).then(() => {
expect(registerNativeModule.callCount).to.equal(0); expect(registerNativeModule.callCount).toEqual(0);
done(); done();
}); });
}); });
@ -139,12 +139,12 @@ describe('link', () => {
const prelink = sinon.stub().yieldsAsync(); const prelink = sinon.stub().yieldsAsync();
const postlink = sinon.stub().yieldsAsync(); const postlink = sinon.stub().yieldsAsync();
mock( jest.setMock(
'../src/ios/registerNativeModule.js', '../src/ios/registerNativeModule.js',
registerNativeModule registerNativeModule
); );
mock( jest.setMock(
'../src/ios/isInstalled.js', '../src/ios/isInstalled.js',
sinon.stub().returns(false) sinon.stub().returns(false)
); );
@ -158,9 +158,9 @@ describe('link', () => {
const link = require('../src/link'); const link = require('../src/link');
link(config, ['react-native-blur']).then(() => { link(['react-native-blur'], config).then(() => {
expect(prelink.calledBefore(registerNativeModule)).to.be.true; expect(prelink.calledBefore(registerNativeModule)).toBeTruthy();
expect(postlink.calledAfter(registerNativeModule)).to.be.true; expect(postlink.calledAfter(registerNativeModule)).toBeTruthy();
done(); done();
}); });
}); });
@ -171,7 +171,7 @@ describe('link', () => {
const projectAssets = ['Fonts/FontC.ttf']; const projectAssets = ['Fonts/FontC.ttf'];
const copyAssets = sinon.stub(); const copyAssets = sinon.stub();
mock( jest.setMock(
'../src/ios/copyAssets.js', '../src/ios/copyAssets.js',
copyAssets copyAssets
); );
@ -183,17 +183,12 @@ describe('link', () => {
const link = require('../src/link'); const link = require('../src/link');
link(config, ['react-native-blur']).then(() => { link(['react-native-blur'], config).then(() => {
expect(copyAssets.calledOnce).to.be.true; expect(copyAssets.calledOnce).toBeTruthy();
expect(copyAssets.getCall(0).args[0]).to.deep.equals( expect(copyAssets.getCall(0).args[0]).toEqual(
projectAssets.concat(dependencyAssets) projectAssets.concat(dependencyAssets)
); );
done(); done();
}); });
}); });
afterEach(() => {
mock.stopAll();
});
}); });

View File

@ -1,5 +1,7 @@
const chai = require('chai'); 'use strict';
const expect = chai.expect;
jest.autoMockOff();
const sinon = require('sinon'); const sinon = require('sinon');
const promiseWaterfall = require('../src/promiseWaterfall'); const promiseWaterfall = require('../src/promiseWaterfall');
@ -9,7 +11,7 @@ describe('promiseWaterfall', () => {
const tasks = [sinon.stub(), sinon.stub()]; const tasks = [sinon.stub(), sinon.stub()];
promiseWaterfall(tasks).then(() => { promiseWaterfall(tasks).then(() => {
expect(tasks[0].calledBefore(tasks[1])).to.be.true; expect(tasks[0].calledBefore(tasks[1])).toBeTruthy();
done(); done();
}); });
}); });
@ -18,7 +20,7 @@ describe('promiseWaterfall', () => {
const tasks = [sinon.stub().returns(1), sinon.stub().returns(2)]; const tasks = [sinon.stub().returns(1), sinon.stub().returns(2)];
promiseWaterfall(tasks).then(value => { promiseWaterfall(tasks).then(value => {
expect(value).to.equal(2); expect(value).toEqual(2);
done(); done();
}); });
}); });
@ -28,8 +30,8 @@ describe('promiseWaterfall', () => {
const tasks = [sinon.stub().throws(error), sinon.stub().returns(2)]; const tasks = [sinon.stub().throws(error), sinon.stub().returns(2)];
promiseWaterfall(tasks).catch(err => { promiseWaterfall(tasks).catch(err => {
expect(err).to.equal(error); expect(err).toEqual(error);
expect(tasks[1].callCount).to.equal(0); expect(tasks[1].callCount).toEqual(0);
done(); done();
}); });
}); });

View File

@ -1,9 +0,0 @@
module.exports = [{
func: require('./src/link'),
description: 'Links all native dependencies',
name: 'link [packageName]',
}, {
func: require('./src/unlink'),
description: 'Unlink native dependency',
name: 'unlink <packageName>',
}];

View File

@ -0,0 +1,5 @@
module.exports = {
func: require('./src/link'),
description: 'links all native dependencies',
name: 'link [packageName]',
};

View File

@ -1,16 +0,0 @@
const semver = require('semver');
const versions = ['0.20', '0.18', '0.17'];
module.exports = function getPrefix(rnVersion) {
const version = rnVersion.replace(/-.*/, '');
var prefix = 'patches/0.20';
versions.forEach((item, i) => {
const nextVersion = versions[i + 1];
if (semver.lt(version, item + '.0') && nextVersion) {
prefix = `patches/${nextVersion}`;
}
});
return prefix;
};

View File

@ -1,5 +1,5 @@
const fs = require('fs'); const fs = require('fs');
const makeBuildPatch = require(`./patches/makeBuildPatch`); const makeBuildPatch = require('./patches/makeBuildPatch');
module.exports = function isInstalled(config, name) { module.exports = function isInstalled(config, name) {
return fs return fs

View File

@ -1,6 +0,0 @@
module.exports = function makeImportPatch(packageImportPath) {
return {
pattern: 'import android.app.Activity;',
patch: '\n' + packageImportPath,
};
};

View File

@ -1,10 +0,0 @@
const applyParams = require('../applyParams');
module.exports = function makePackagePatch(packageInstance, params, prefix) {
const processedInstance = applyParams(packageInstance, params, prefix);
return {
pattern: '.addPackage(new MainReactPackage())',
patch: `\n .addPackage(${processedInstance})`,
};
};

View File

@ -1,6 +0,0 @@
module.exports = function makeImportPatch(packageImportPath) {
return {
pattern: 'import com.facebook.react.ReactActivity;',
patch: '\n' + packageImportPath,
};
};

View File

@ -1,10 +0,0 @@
const applyParams = require('../applyParams');
module.exports = function makePackagePatch(packageInstance, params, prefix) {
const processedInstance = applyParams(packageInstance, params, prefix);
return {
pattern: 'new MainReactPackage()',
patch: ',\n ' + processedInstance,
};
};

View File

@ -1,4 +1,4 @@
const applyParams = require('../applyParams'); const applyParams = require('./applyParams');
module.exports = function makePackagePatch(packageInstance, params, prefix) { module.exports = function makePackagePatch(packageInstance, params, prefix) {
const processedInstance = applyParams(packageInstance, params, prefix); const processedInstance = applyParams(packageInstance, params, prefix);

View File

@ -1,11 +1,9 @@
const fs = require('fs');
const getReactVersion = require('../getReactNativeVersion');
const getPrefix = require('./getPrefix');
const applyPatch = require('./patches/applyPatch'); const applyPatch = require('./patches/applyPatch');
const makeStringsPatch = require('./patches/makeStringsPatch'); const makeStringsPatch = require('./patches/makeStringsPatch');
const makeSettingsPatch = require(`./patches/makeSettingsPatch`); const makeSettingsPatch = require('./patches/makeSettingsPatch');
const makeBuildPatch = require(`./patches/makeBuildPatch`); const makeBuildPatch = require('./patches/makeBuildPatch');
const makeImportPatch = require('./patches/makeImportPatch');
const makePackagePatch = require('./patches/makePackagePatch');
module.exports = function registerNativeAndroidModule( module.exports = function registerNativeAndroidModule(
name, name,
@ -14,9 +12,6 @@ module.exports = function registerNativeAndroidModule(
projectConfig projectConfig
) { ) {
const buildPatch = makeBuildPatch(name); const buildPatch = makeBuildPatch(name);
const prefix = getPrefix(getReactVersion(projectConfig.folder));
const makeImportPatch = require(`./${prefix}/makeImportPatch`);
const makePackagePatch = require(`./${prefix}/makePackagePatch`);
applyPatch( applyPatch(
projectConfig.settingsGradlePath, projectConfig.settingsGradlePath,

View File

@ -1,12 +1,12 @@
const fs = require('fs'); const fs = require('fs');
const getReactVersion = require('../getReactNativeVersion');
const getPrefix = require('./getPrefix');
const toCamelCase = require('lodash').camelCase; const toCamelCase = require('lodash').camelCase;
const revokePatch = require('./patches/revokePatch'); const revokePatch = require('./patches/revokePatch');
const makeSettingsPatch = require('./patches/makeSettingsPatch'); const makeSettingsPatch = require('./patches/makeSettingsPatch');
const makeBuildPatch = require('./patches/makeBuildPatch'); const makeBuildPatch = require('./patches/makeBuildPatch');
const makeStringsPatch = require('./patches/makeStringsPatch'); const makeStringsPatch = require('./patches/makeStringsPatch');
const makeImportPatch = require('./patches/makeImportPatch');
const makePackagePatch = require('./patches/makePackagePatch');
module.exports = function unregisterNativeAndroidModule( module.exports = function unregisterNativeAndroidModule(
name, name,
@ -14,9 +14,6 @@ module.exports = function unregisterNativeAndroidModule(
projectConfig projectConfig
) { ) {
const buildPatch = makeBuildPatch(name); const buildPatch = makeBuildPatch(name);
const prefix = getPrefix(getReactVersion(projectConfig.folder));
const makeImportPatch = require(`./${prefix}/makeImportPatch`);
const makePackagePatch = require(`./${prefix}/makePackagePatch`);
const strings = fs.readFileSync(projectConfig.stringsPath, 'utf8'); const strings = fs.readFileSync(projectConfig.stringsPath, 'utf8');
var params = {}; var params = {};

View File

@ -1,5 +0,0 @@
const path = require('path');
module.exports = (folder) => require(
path.join(folder, 'node_modules', 'react-native', 'package.json')
).version;

View File

@ -1,3 +1,16 @@
module.exports = function addSharedLibraries(project, libraries) { const createGroupWithMessage = require('./createGroupWithMessage');
module.exports = function addSharedLibraries(project, libraries) {
if (!libraries.length) {
return;
}
// Create a Frameworks group if necessary.
createGroupWithMessage(project, 'Frameworks');
const target = project.getFirstTarget().uuid;
for (var name of libraries) {
project.addFramework(name, { target });
}
}; };

Some files were not shown because too many files have changed in this diff Show More