diff --git a/README.md b/README.md
index 36d51e2..4de3e7c 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,16 @@
+# Dear friends,
+
+Last November me ([@Kureev](https://github.com/Kureev)) and Mike ([@grabbou](https://github.com/grabbou)) started RNPM. We aimed to bring you a better developer experience and bridge the tooling gap we had back then. Now, as you may know, RNPM is merged into [React Native core](https://github.com/facebook/react-native). It means that from now on you don't need to install a third-party software to use your favorite linking functionality (just use a react-native cli). We'd like to say a big "Thank you!" to everybody who supported us, filed new issues, composed PRs and helped us to review them.
+
+Now, when RNPM is a part of React Native, we're going to seal this repository and keep working on React Native tooling inside the core. That said, I kindly ask you to file all new issues / prs in react-native repo and cc us. This repo (and other rnpm plugins) will be a available for a few more months in a read-only mode.
+
+With love,
+Alexey Kureev and MichaĆ Grabowski
+
+
+
+
+

  [](https://codeclimate.com/github/rnpm/rnpm) [](https://codeclimate.com/github/rnpm/rnpm/coverage) [](https://circleci.com/gh/rnpm/rnpm)
@@ -100,7 +113,7 @@ Command exported by installed plugin will be available straight away.
First of all, every plugin is [just a function](https://github.com/rnpm/rnpm-plugin-link/blob/master/src/link.js#L81) which accepts `config` and `args` parameters. Every plugin consists of [public interface](https://github.com/rnpm/rnpm-plugin-link/blob/master/index.js) for CLI and [implementation intself](https://github.com/rnpm/rnpm-plugin-link/blob/master/src/link.js).
We use **public interface** to make your plugins auto-pluggable and easy to use for end-users. Every public interface consists of `name`, `func` & `description` fields:
-- `name` - Name of the plugin. After plugin installation it'll be used as a command name. For instance plugin with following interface:
+- `name` - Name of the plugin. After plugin installation it'll be used as a command name. For instance a plugin with the following interface:
```javascript
module.exports = {
func: require('./src/link'),
@@ -115,6 +128,23 @@ We use **public interface** to make your plugins auto-pluggable and easy to use
- `func` - Plugin itself. This function will be used when you run a command above
- `description` - Command description. If user runs `$ rnpm --help`, this field will be displayed as a command description.
+- `options` - An array of flags user can specify for your plugin to run. When defined, your exported `func` will receive an object of options as a 3rd argument. For instance a plugin with the following:
+
+ ```js
+ options: [{
+ flags: '-L, --list [path]',
+ description: 'List flag',
+ parse: (val) => val.split(',').map(Number),
+ default: [1,2,3],
+ }],
+ ```
+ will receive the following object:
+ ```
+ { list: [1,2,3] }
+ ```
+ by default.
+
+ **Note**: `parse` and `default` are optional. You can check [commander.js](https://github.com/tj/commander.js#option-parsing) docs for more information on how to define `flags` value.
Also, in the case you want to expose multiple commands from the one plugin, you may use an array syntax:
```javascript
@@ -153,19 +183,35 @@ In this scenario we're using custom `prelink` and `postlink` hooks for `rnpm-plu
While making your own plugins for `rnpm` you may use any names for the commands, but we strongly recommend you to use a convention we suggest to avoid collisions: `when` + `plugin name`: `prelink` = `pre` + `link`.
-### Developers
+### Params
+On Android - you can specify a custom `packageInstance` to be used when linking your project. The reason for that is often that your package constructor simply requires extra user provided config (e.g. API token). `rnpm` allows you to define an array of additional arguments to get from user during linking process that you can then, reference in your `packageInstance`.
-The documentation is still in progress, but if you are interested in the concept and good practices, see sample implementation [here](https://github.com/rnpm/rnpm-plugin-link/blob/master/index.js)
+Simply include the following in your package.json:
+```json
+"rnpm": {
+ "params": [{
+ "type": "input",
+ "name": "gaToken",
+ "message": "What's your GA token"
+ }]
+}
+```
+and update your `packageInstance` with the new variable:
+```json
+"rnpm": {
+ "android": {
+ "packageInstance": "new SomeLibName(this, ${gaToken})"
+ }
+}
+```
-## Roadmap
+Starting from now on - users will be presented an interactive form powered by `inquirer` each time they run `rnpm link`.
-First priority: **core elements**
-- [ ] Test coverage
-- [X] Plugins support
+**Note**: We pass `params` array directly to inquirer which means you can also let users choose an answer from a list as well as provide a default value! See [API docs](https://github.com/SBoudrias/Inquirer.js/#question) for more details.
-Second priority: **new plugins**
-- [ ] rnpm ship
-- [ ] rnpm build
+### Developers
+
+The documentation is still in progress, but if you are interested in the concept and good practices, see sample implementation [here](https://github.com/rnpm/rnpm-plugin-link/blob/master/index.js)
We're open for community ideas!
If you know how to improve `rnpm` - please, [let us know](https://github.com/rnpm/rnpm/issues/new)!
@@ -216,7 +262,7 @@ Special thanks to [Sonny Lazuardi](https://github.com/sonnylazuardi) for the awe
This tool development and maintenance is sponsored by below companies:
-
+
## License
diff --git a/bin/cli b/bin/cli
index 774129a..06c8edb 100755
--- a/bin/cli
+++ b/bin/cli
@@ -12,15 +12,25 @@ updateNotifier({ pkg }).notify();
cli.version(pkg.version);
-const addCommand = (command) =>
- cli
+const defaultOptParser = (val) => val;
+
+const addCommand = (command) => {
+ const cmd = cli
.command(command.name)
.usage(command.usage)
.description(command.description)
.action(function runAction() {
- command.func(config, arguments);
+ command.func(config, arguments, this.opts());
});
+ (command.options || [])
+ .forEach(opt => cmd.option(
+ opt.flags,
+ opt.description,
+ opt.parse || defaultOptParser,
+ opt.default
+ ));
+};
flatten(commands).forEach(addCommand);
diff --git a/package.json b/package.json
index c23d1c4..da31a81 100644
--- a/package.json
+++ b/package.json
@@ -1,8 +1,8 @@
{
"name": "rnpm",
- "version": "1.5.2",
+ "version": "1.9.0",
"description": "React Native Package Manager",
- "main": "./src/getActions.js",
+ "main": "./src/getCommands.js",
"scripts": {
"test": "jest"
},
@@ -39,17 +39,17 @@
"homepage": "https://github.com/rnpm/rnpm#readme",
"dependencies": {
"commander": "^2.9.0",
- "glob": "^6.0.1",
+ "glob": "^7.0.1",
"lodash": "^3.10.1",
- "rnpm-plugin-install": "^1.0.0",
- "rnpm-plugin-link": "^1.6.0",
+ "rnpm-plugin-install": "^1.1.0",
+ "rnpm-plugin-link": "^1.8.0",
"update-notifier": "^0.6.0",
"xmldoc": "^0.4.0"
},
"devDependencies": {
"babel-eslint": "^4.1.5",
"eslint": "^1.9.0",
- "mock-fs": "^3.5.0",
+ "mock-fs": "^3.9.0",
"mock-require": "^1.2.1",
"rewire": "^2.5.1",
"jest-cli": "^0.9.0-fb2"
diff --git a/src/config/android/index.js b/src/config/android/index.js
index 7038524..f827730 100644
--- a/src/config/android/index.js
+++ b/src/config/android/index.js
@@ -38,6 +38,11 @@ exports.projectConfig = function projectConfigAndroid(folder, userConfig) {
userConfig.mainActivityPath || `src/main/java/${packageFolder}/MainActivity.java`
);
+ const stringsPath = path.join(
+ sourceDir,
+ userConfig.stringsPath || 'src/main/res/values/strings.xml'
+ );
+
const settingsGradlePath = path.join(
folder,
'android',
@@ -58,6 +63,7 @@ exports.projectConfig = function projectConfigAndroid(folder, userConfig) {
sourceDir,
isFlat,
folder,
+ stringsPath,
manifestPath,
buildGradlePath,
settingsGradlePath,
diff --git a/src/config/index.js b/src/config/index.js
index 7e64d41..318b0be 100644
--- a/src/config/index.js
+++ b/src/config/index.js
@@ -30,12 +30,20 @@ exports.getProjectConfig = function getProjectConfig() {
*/
exports.getDependencyConfig = function getDependencyConfig(packageName) {
const folder = path.join(process.cwd(), 'node_modules', packageName);
- const rnpm = getRNPMConfig(folder);
+ var rnpm;
+
+ // Will throw when package.json is not present in a nested folder
+ try {
+ rnpm = getRNPMConfig(folder);
+ } catch (err) {
+ rnpm = {};
+ }
return Object.assign({}, rnpm, {
ios: ios.dependencyConfig(folder, rnpm.ios || {}),
android: android.dependencyConfig(folder, rnpm.android || {}),
assets: findAssets(folder, rnpm.assets),
commands: wrapCommands(rnpm.commands),
+ params: rnpm.params || [],
});
};
diff --git a/src/config/ios/findProject.js b/src/config/ios/findProject.js
index 0923f0d..17c99cf 100644
--- a/src/config/ios/findProject.js
+++ b/src/config/ios/findProject.js
@@ -9,7 +9,7 @@ const GLOB_PATTERN = '**/*.xcodeproj';
/**
* Regexp matching all test projects
*/
-const TEST_PROJECTS = /(test|example)/;
+const TEST_PROJECTS = /test|example|sample/i;
/**
* Base iOS folder
@@ -19,7 +19,7 @@ const IOS_BASE = 'ios';
/**
* These folders will be excluded from search to speed it up
*/
-const GLOB_EXCLUDE_PATTERN = ['@(Pods|node_modules)/**'];
+const GLOB_EXCLUDE_PATTERN = ['**/@(Pods|node_modules)/**'];
/**
* Finds iOS project by looking for all .xcodeproj files
@@ -36,8 +36,7 @@ module.exports = function findProject(folder) {
ignore: GLOB_EXCLUDE_PATTERN,
})
.filter(project => {
- const xcPath = project.toLowerCase();
- return path.dirname(xcPath) === IOS_BASE || !xcPath.match(TEST_PROJECTS);
+ return path.dirname(project) === IOS_BASE || !TEST_PROJECTS.test(project);
});
if (projects.length === 0) {
diff --git a/src/config/ios/index.js b/src/config/ios/index.js
index 3171504..5e1f2c0 100644
--- a/src/config/ios/index.js
+++ b/src/config/ios/index.js
@@ -1,6 +1,19 @@
const path = require('path');
const findProject = require('./findProject');
+/**
+ * For libraries specified without an extension, add '.tbd' for those that
+ * start with 'lib' and '.framework' to the rest.
+ */
+const mapSharedLibaries = (libraries) => {
+ return libraries.map(name => {
+ if (path.extname(name)) {
+ return name;
+ }
+ return name + (name.indexOf('lib') === 0 ? '.tbd' : '.framework');
+ });
+};
+
/**
* Returns project config by analyzing given folder and applying some user defaults
* when constructing final object
@@ -24,6 +37,8 @@ exports.projectConfig = function projectConfigIOS(folder, userConfig) {
projectPath: projectPath,
projectName: path.basename(projectPath),
libraryFolder: userConfig.libraryFolder || 'Libraries',
+ sharedLibraries: mapSharedLibaries(userConfig.sharedLibraries || []),
+ plist: userConfig.plist || [],
};
};
diff --git a/src/findPlugins.js b/src/findPlugins.js
index 43e626d..8cb8818 100644
--- a/src/findPlugins.js
+++ b/src/findPlugins.js
@@ -9,12 +9,29 @@ const flatten = require('lodash').flatten;
* @param {String} dependency Name of the dependency
* @return {Boolean} If dependency is a rnpm plugin
*/
-const isPlugin = (dependency) => !!~dependency.indexOf('rnpm-plugin-');
+const isRNPMPlugin = (dependency) => !!~dependency.indexOf('rnpm-plugin-');
+const isReactNativePlugin = (dependency) => !!~dependency.indexOf('react-native-');
-const findPluginInFolder = (folder) => {
+const readPackage = (folder) => {
try {
- const pjson = require(path.join(folder, 'package.json'));
+ return require(path.join(folder, 'package.json'));
} catch (e) {
+ return null;
+ }
+};
+
+const findPluginsInReactNativePackage = (pjson) => {
+ if (!pjson.rnpm || !pjson.rnpm.plugin) {
+ return [];
+ }
+
+ return path.join(pjson.name, pjson.rnpm.plugin);
+};
+
+const findPluginInFolder = (folder) => {
+ const pjson = readPackage(folder);
+
+ if (!pjson) {
return [];
}
@@ -23,7 +40,22 @@ const findPluginInFolder = (folder) => {
Object.keys(pjson.devDependencies || {})
);
- return deps.filter(isPlugin);
+ return deps.reduce(
+ (acc, pkg) => {
+ if (isRNPMPlugin(pkg)) {
+ return acc.concat(pkg);
+ }
+ if (isReactNativePlugin(pkg)) {
+ const pjson = readPackage(path.join(folder, 'node_modules', pkg));
+ if (!pjson) {
+ return acc;
+ }
+ return acc.concat(findPluginsInReactNativePackage(pjson));
+ }
+ return acc;
+ },
+ []
+ );
};
/**
diff --git a/test/fixtures/ios.js b/test/fixtures/ios.js
index f3382db..5094355 100644
--- a/test/fixtures/ios.js
+++ b/test/fixtures/ios.js
@@ -2,7 +2,7 @@ const fs = require('fs');
const path = require('path');
exports.valid = {
- 'sampleProject.xcodeproj': {
+ 'demoProject.xcodeproj': {
'project.pbxproj': fs.readFileSync(path.join(__dirname, './files/project.pbxproj')),
},
};
diff --git a/test/ios/findProject.spec.js b/test/ios/findProject.spec.js
index f7a3593..6626921 100644
--- a/test/ios/findProject.spec.js
+++ b/test/ios/findProject.spec.js
@@ -32,6 +32,16 @@ describe('ios::findProject', () => {
expect(findProject('')).toBe(null);
});
+ it('should ignore Pods inside `ios` folder', () => {
+ mockFs({
+ ios: {
+ Pods: projects.flat,
+ DemoApp: projects.flat.ios,
+ },
+ });
+ expect(findProject('')).toBe('ios/DemoApp/demoProject.xcodeproj');
+ });
+
it('should ignore xcodeproj from example folders', () => {
mockFs({
examples: projects.flat,
@@ -44,6 +54,18 @@ describe('ios::findProject', () => {
expect(findProject('').toLowerCase()).not.toContain('example');
});
+ it('should ignore xcodeproj from sample folders', () => {
+ mockFs({
+ samples: projects.flat,
+ Samples: projects.flat,
+ sample: projects.flat,
+ KeychainSample: projects.flat,
+ Zpp: projects.flat,
+ });
+
+ expect(findProject('').toLowerCase()).not.toContain('sample');
+ });
+
it('should ignore xcodeproj from test folders at any level', () => {
mockFs({
test: projects.flat,
diff --git a/test/ios/getProjectConfig.spec.js b/test/ios/getProjectConfig.spec.js
index bba3bed..82a2245 100644
--- a/test/ios/getProjectConfig.spec.js
+++ b/test/ios/getProjectConfig.spec.js
@@ -22,5 +22,15 @@ describe('ios::getProjectConfig', () => {
expect(getProjectConfig(folder, userConfig)).toBe(null);
});
+ it('should return normalized shared library names', () => {
+ const projectConfig = getProjectConfig('testDir/nested', {
+ sharedLibraries: ['libc++', 'libz.tbd', 'HealthKit', 'HomeKit.framework'],
+ });
+
+ expect(projectConfig.sharedLibraries).toEqual(
+ ['libc++.tbd', 'libz.tbd', 'HealthKit.framework', 'HomeKit.framework']
+ );
+ });
+
afterEach(mockFs.restore);
});