From 149d0b91c2707323a140fa7fc6486cd705e434bc Mon Sep 17 00:00:00 2001 From: Mike Grabowski Date: Fri, 20 May 2016 04:52:08 -0700 Subject: [PATCH] Merge rnpm into react-native Summary: This is initial (first step) in the merging process. For now, we are just going to move our code as is into `local-cli` folder (first commit). There were other tweaks made in separate commits to make it easier to go through the code as the diff is expected to be rather large. The purpose of this is to make it easier to start working in small batches and improving the CLI incrementally on a daily basis. Current codebase will still leave in `rnpm` organisation on Github where we keep working on new features, bugs and ship releases to `npm` until we finish our integration and provide a nice interface for users to migrate (in case it changes at all) Flow, Jest and npm will ignore this folder for now until we integrate it properly. Tests are to be rewritten from mocha to jest in `rnpm/link`. We will hook them all up as soon as we start using them in local-cli. For now, there's no point in having them running and possibly breaking the builds. We will announce next steps with Kureev later this week Closes https://github.com/facebook/react-native/pull/7550 Differential Revision: D3327772 Pulled By: mkonicek fbshipit-source-id: 90faa4bd78476d93ed21b1253e0d95c755d28a30 --- .flowconfig | 3 + .npmignore | 2 + local-cli/rnpm/core/package.json | 50 ++ .../config/android/findAndroidAppFolder.js | 21 + .../core/src/config/android/findManifest.js | 17 + .../config/android/findPackageClassName.js | 20 + .../rnpm/core/src/config/android/index.js | 111 +++ .../core/src/config/android/readManifest.js | 10 + local-cli/rnpm/core/src/config/findAssets.js | 20 + local-cli/rnpm/core/src/config/index.js | 44 + .../rnpm/core/src/config/ios/findProject.js | 47 + local-cli/rnpm/core/src/config/ios/index.js | 31 + .../rnpm/core/src/config/wrapCommands.js | 9 + local-cli/rnpm/core/src/findPlugins.js | 37 + local-cli/rnpm/core/src/getCommands.js | 20 + local-cli/rnpm/core/src/makeCommand.js | 25 + .../test/android/findAndroidAppFolder.spec.js | 30 + .../core/test/android/findManifest.spec.js | 25 + .../test/android/findPackageClassName.spec.js | 25 + .../test/android/getDependencyConfig.spec.js | 49 ++ .../test/android/getProjectConfig.spec.js | 56 ++ .../core/test/android/readManifest.spec.js | 31 + local-cli/rnpm/core/test/findAssets.spec.js | 31 + local-cli/rnpm/core/test/findPlugins.js | 46 + local-cli/rnpm/core/test/fixtures/android.js | 49 ++ local-cli/rnpm/core/test/fixtures/commands.js | 15 + .../rnpm/core/test/fixtures/dependencies.js | 27 + .../test/fixtures/files/AndroidManifest.xml | 4 + .../rnpm/core/test/fixtures/files/Main.java | 8 + .../test/fixtures/files/ReactPackage.java | 35 + .../core/test/fixtures/files/package.json | 62 ++ .../core/test/fixtures/files/project.pbxproj | 804 ++++++++++++++++++ local-cli/rnpm/core/test/fixtures/ios.js | 14 + local-cli/rnpm/core/test/fixtures/projects.js | 24 + local-cli/rnpm/core/test/getCommands.spec.js | 171 ++++ .../rnpm/core/test/ios/findProject.spec.js | 84 ++ .../core/test/ios/getProjectConfig.spec.js | 26 + local-cli/rnpm/core/test/makeCommand.spec.js | 35 + local-cli/rnpm/install/index.js | 11 + local-cli/rnpm/install/package.json | 26 + local-cli/rnpm/install/src/install.js | 26 + local-cli/rnpm/install/src/uninstall.js | 26 + local-cli/rnpm/link/index.js | 9 + local-cli/rnpm/link/package.json | 52 ++ local-cli/rnpm/link/src/android/copyAssets.js | 17 + local-cli/rnpm/link/src/android/fs.js | 8 + local-cli/rnpm/link/src/android/getPrefix.js | 16 + .../rnpm/link/src/android/isInstalled.js | 8 + .../android/patches/0.17/makeImportPatch.js | 6 + .../android/patches/0.17/makePackagePatch.js | 10 + .../android/patches/0.18/makeImportPatch.js | 6 + .../android/patches/0.18/makePackagePatch.js | 10 + .../android/patches/0.20/makeImportPatch.js | 6 + .../android/patches/0.20/makePackagePatch.js | 10 + .../link/src/android/patches/applyParams.js | 14 + .../link/src/android/patches/applyPatch.js | 8 + .../src/android/patches/makeBuildPatch.js | 6 + .../src/android/patches/makeSettingsPatch.js | 26 + .../src/android/patches/makeStringsPatch.js | 14 + .../link/src/android/patches/revokePatch.js | 8 + .../link/src/android/registerNativeModule.js | 38 + .../rnpm/link/src/android/unlinkAssets.js | 20 + .../src/android/unregisterNativeModule.js | 47 + .../rnpm/link/src/getDependencyConfig.js | 17 + .../rnpm/link/src/getProjectDependencies.js | 9 + .../rnpm/link/src/getReactNativeVersion.js | 5 + local-cli/rnpm/link/src/groupFilesByType.js | 27 + .../rnpm/link/src/ios/addFileToProject.js | 14 + .../link/src/ios/addProjectToLibraries.js | 13 + .../rnpm/link/src/ios/addSharedLibraries.js | 3 + .../link/src/ios/addToHeaderSearchPaths.js | 5 + local-cli/rnpm/link/src/ios/copyAssets.js | 57 ++ local-cli/rnpm/link/src/ios/createGroup.js | 27 + .../rnpm/link/src/ios/getBuildProperty.js | 18 + local-cli/rnpm/link/src/ios/getGroup.js | 34 + .../rnpm/link/src/ios/getHeaderSearchPath.js | 52 ++ .../rnpm/link/src/ios/getHeadersInFolder.js | 18 + local-cli/rnpm/link/src/ios/getPlist.js | 20 + local-cli/rnpm/link/src/ios/getPlistPath.js | 15 + local-cli/rnpm/link/src/ios/getProducts.js | 12 + .../rnpm/link/src/ios/hasLibraryImported.js | 10 + local-cli/rnpm/link/src/ios/isInstalled.js | 18 + .../rnpm/link/src/ios/mapHeaderSearchPaths.js | 36 + .../rnpm/link/src/ios/registerNativeModule.js | 67 ++ .../src/ios/removeFromHeaderSearchPaths.js | 10 + .../removeFromPbxItemContainerProxySection.js | 16 + .../ios/removeFromPbxReferenceProxySection.js | 15 + .../src/ios/removeFromProjectReferences.js | 26 + .../link/src/ios/removeFromStaticLibraries.js | 21 + .../rnpm/link/src/ios/removeProductGroup.js | 11 + .../src/ios/removeProjectFromLibraries.js | 12 + .../link/src/ios/removeProjectFromProject.js | 26 + .../link/src/ios/removeSharedLibraries.js | 3 + local-cli/rnpm/link/src/ios/unlinkAssets.js | 54 ++ .../link/src/ios/unregisterNativeModule.js | 54 ++ local-cli/rnpm/link/src/link.js | 136 +++ local-cli/rnpm/link/src/pollParams.js | 9 + local-cli/rnpm/link/src/promiseWaterfall.js | 14 + local-cli/rnpm/link/src/unlink.js | 117 +++ .../link/test/android/isInstalled.spec.js | 28 + .../android/patches/0.17/makeImportPatch.js | 31 + .../android/patches/0.17/makePackagePatch.js | 36 + .../android/patches/0.18/makeImportPatch.js | 31 + .../android/patches/0.18/makePackagePatch.js | 36 + .../android/patches/0.20/makeImportPatch.js | 31 + .../android/patches/0.20/makePackagePatch.js | 36 + .../link/test/android/patches/applyPatch.js | 17 + .../android/patches/makeBuildPatch.spec.js | 17 + .../android/patches/makeSettingsPatch.spec.js | 36 + .../fixtures/android/0.17/MainActivity.java | 78 ++ .../android/0.17/patchedMainActivity.java | 80 ++ .../fixtures/android/0.18/MainActivity.java | 39 + .../android/0.18/patchedMainActivity.java | 41 + .../fixtures/android/0.20/MainActivity.java | 40 + .../link/test/fixtures/android/build.gradle | 5 + .../test/fixtures/android/patchedBuild.gradle | 6 + .../fixtures/android/patchedSettings.gradle | 5 + .../test/fixtures/android/settings.gradle | 3 + .../link/test/fixtures/linearGradient.pbxproj | 258 ++++++ .../rnpm/link/test/fixtures/project.pbxproj | 778 +++++++++++++++++ .../link/test/getDependencyConfig.spec.js | 23 + local-cli/rnpm/link/test/getPrefix.spec.js | 20 + .../link/test/getProjectDependencies.spec.js | 27 + .../rnpm/link/test/groupFilesByType.spec.js | 23 + .../link/test/ios/addFileToProject.spec.js | 22 + .../test/ios/addProjectToLibraries.spec.js | 28 + .../rnpm/link/test/ios/createGroup.spec.js | 49 ++ .../link/test/ios/getBuildProperty.spec.js | 19 + local-cli/rnpm/link/test/ios/getGroup.spec.js | 36 + .../link/test/ios/getHeaderSearchPath.spec.js | 58 ++ .../link/test/ios/getHeadersInFolder.spec.js | 45 + local-cli/rnpm/link/test/ios/getPlist.spec.js | 23 + .../rnpm/link/test/ios/getPlistPath.spec.js | 19 + .../rnpm/link/test/ios/getProducts.spec.js | 20 + .../link/test/ios/hasLibraryImported.spec.js | 24 + .../rnpm/link/test/ios/isInstalled.spec.js | 39 + .../test/ios/mapHeaderSearchPaths.spec.js | 23 + .../test/ios/removeProjectFromLibraries.js | 33 + .../test/ios/removeProjectFromProject.spec.js | 32 + local-cli/rnpm/link/test/link.spec.js | 199 +++++ .../rnpm/link/test/promiseWaterfall.spec.js | 37 + package.json | 3 +- 142 files changed, 5980 insertions(+), 1 deletion(-) create mode 100644 .npmignore create mode 100644 local-cli/rnpm/core/package.json create mode 100644 local-cli/rnpm/core/src/config/android/findAndroidAppFolder.js create mode 100644 local-cli/rnpm/core/src/config/android/findManifest.js create mode 100644 local-cli/rnpm/core/src/config/android/findPackageClassName.js create mode 100644 local-cli/rnpm/core/src/config/android/index.js create mode 100644 local-cli/rnpm/core/src/config/android/readManifest.js create mode 100644 local-cli/rnpm/core/src/config/findAssets.js create mode 100644 local-cli/rnpm/core/src/config/index.js create mode 100644 local-cli/rnpm/core/src/config/ios/findProject.js create mode 100644 local-cli/rnpm/core/src/config/ios/index.js create mode 100644 local-cli/rnpm/core/src/config/wrapCommands.js create mode 100644 local-cli/rnpm/core/src/findPlugins.js create mode 100644 local-cli/rnpm/core/src/getCommands.js create mode 100644 local-cli/rnpm/core/src/makeCommand.js create mode 100644 local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js create mode 100644 local-cli/rnpm/core/test/android/findManifest.spec.js create mode 100644 local-cli/rnpm/core/test/android/findPackageClassName.spec.js create mode 100644 local-cli/rnpm/core/test/android/getDependencyConfig.spec.js create mode 100644 local-cli/rnpm/core/test/android/getProjectConfig.spec.js create mode 100644 local-cli/rnpm/core/test/android/readManifest.spec.js create mode 100644 local-cli/rnpm/core/test/findAssets.spec.js create mode 100644 local-cli/rnpm/core/test/findPlugins.js create mode 100644 local-cli/rnpm/core/test/fixtures/android.js create mode 100644 local-cli/rnpm/core/test/fixtures/commands.js create mode 100644 local-cli/rnpm/core/test/fixtures/dependencies.js create mode 100644 local-cli/rnpm/core/test/fixtures/files/AndroidManifest.xml create mode 100644 local-cli/rnpm/core/test/fixtures/files/Main.java create mode 100644 local-cli/rnpm/core/test/fixtures/files/ReactPackage.java create mode 100644 local-cli/rnpm/core/test/fixtures/files/package.json create mode 100644 local-cli/rnpm/core/test/fixtures/files/project.pbxproj create mode 100644 local-cli/rnpm/core/test/fixtures/ios.js create mode 100644 local-cli/rnpm/core/test/fixtures/projects.js create mode 100644 local-cli/rnpm/core/test/getCommands.spec.js create mode 100644 local-cli/rnpm/core/test/ios/findProject.spec.js create mode 100644 local-cli/rnpm/core/test/ios/getProjectConfig.spec.js create mode 100644 local-cli/rnpm/core/test/makeCommand.spec.js create mode 100644 local-cli/rnpm/install/index.js create mode 100644 local-cli/rnpm/install/package.json create mode 100644 local-cli/rnpm/install/src/install.js create mode 100644 local-cli/rnpm/install/src/uninstall.js create mode 100644 local-cli/rnpm/link/index.js create mode 100644 local-cli/rnpm/link/package.json create mode 100644 local-cli/rnpm/link/src/android/copyAssets.js create mode 100644 local-cli/rnpm/link/src/android/fs.js create mode 100644 local-cli/rnpm/link/src/android/getPrefix.js create mode 100644 local-cli/rnpm/link/src/android/isInstalled.js create mode 100644 local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/applyParams.js create mode 100644 local-cli/rnpm/link/src/android/patches/applyPatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/makeBuildPatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/makeSettingsPatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/makeStringsPatch.js create mode 100644 local-cli/rnpm/link/src/android/patches/revokePatch.js create mode 100644 local-cli/rnpm/link/src/android/registerNativeModule.js create mode 100644 local-cli/rnpm/link/src/android/unlinkAssets.js create mode 100644 local-cli/rnpm/link/src/android/unregisterNativeModule.js create mode 100644 local-cli/rnpm/link/src/getDependencyConfig.js create mode 100644 local-cli/rnpm/link/src/getProjectDependencies.js create mode 100644 local-cli/rnpm/link/src/getReactNativeVersion.js create mode 100644 local-cli/rnpm/link/src/groupFilesByType.js create mode 100644 local-cli/rnpm/link/src/ios/addFileToProject.js create mode 100644 local-cli/rnpm/link/src/ios/addProjectToLibraries.js create mode 100644 local-cli/rnpm/link/src/ios/addSharedLibraries.js create mode 100644 local-cli/rnpm/link/src/ios/addToHeaderSearchPaths.js create mode 100644 local-cli/rnpm/link/src/ios/copyAssets.js create mode 100644 local-cli/rnpm/link/src/ios/createGroup.js create mode 100644 local-cli/rnpm/link/src/ios/getBuildProperty.js create mode 100644 local-cli/rnpm/link/src/ios/getGroup.js create mode 100644 local-cli/rnpm/link/src/ios/getHeaderSearchPath.js create mode 100644 local-cli/rnpm/link/src/ios/getHeadersInFolder.js create mode 100644 local-cli/rnpm/link/src/ios/getPlist.js create mode 100644 local-cli/rnpm/link/src/ios/getPlistPath.js create mode 100644 local-cli/rnpm/link/src/ios/getProducts.js create mode 100644 local-cli/rnpm/link/src/ios/hasLibraryImported.js create mode 100644 local-cli/rnpm/link/src/ios/isInstalled.js create mode 100644 local-cli/rnpm/link/src/ios/mapHeaderSearchPaths.js create mode 100644 local-cli/rnpm/link/src/ios/registerNativeModule.js create mode 100644 local-cli/rnpm/link/src/ios/removeFromHeaderSearchPaths.js create mode 100644 local-cli/rnpm/link/src/ios/removeFromPbxItemContainerProxySection.js create mode 100644 local-cli/rnpm/link/src/ios/removeFromPbxReferenceProxySection.js create mode 100644 local-cli/rnpm/link/src/ios/removeFromProjectReferences.js create mode 100644 local-cli/rnpm/link/src/ios/removeFromStaticLibraries.js create mode 100644 local-cli/rnpm/link/src/ios/removeProductGroup.js create mode 100644 local-cli/rnpm/link/src/ios/removeProjectFromLibraries.js create mode 100644 local-cli/rnpm/link/src/ios/removeProjectFromProject.js create mode 100644 local-cli/rnpm/link/src/ios/removeSharedLibraries.js create mode 100644 local-cli/rnpm/link/src/ios/unlinkAssets.js create mode 100644 local-cli/rnpm/link/src/ios/unregisterNativeModule.js create mode 100644 local-cli/rnpm/link/src/link.js create mode 100644 local-cli/rnpm/link/src/pollParams.js create mode 100644 local-cli/rnpm/link/src/promiseWaterfall.js create mode 100644 local-cli/rnpm/link/src/unlink.js create mode 100644 local-cli/rnpm/link/test/android/isInstalled.spec.js create mode 100644 local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js create mode 100644 local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js create mode 100644 local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js create mode 100644 local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js create mode 100644 local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js create mode 100644 local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js create mode 100644 local-cli/rnpm/link/test/android/patches/applyPatch.js create mode 100644 local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js create mode 100644 local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js create mode 100644 local-cli/rnpm/link/test/fixtures/android/0.17/MainActivity.java create mode 100644 local-cli/rnpm/link/test/fixtures/android/0.17/patchedMainActivity.java create mode 100644 local-cli/rnpm/link/test/fixtures/android/0.18/MainActivity.java create mode 100644 local-cli/rnpm/link/test/fixtures/android/0.18/patchedMainActivity.java create mode 100644 local-cli/rnpm/link/test/fixtures/android/0.20/MainActivity.java create mode 100644 local-cli/rnpm/link/test/fixtures/android/build.gradle create mode 100644 local-cli/rnpm/link/test/fixtures/android/patchedBuild.gradle create mode 100644 local-cli/rnpm/link/test/fixtures/android/patchedSettings.gradle create mode 100644 local-cli/rnpm/link/test/fixtures/android/settings.gradle create mode 100644 local-cli/rnpm/link/test/fixtures/linearGradient.pbxproj create mode 100644 local-cli/rnpm/link/test/fixtures/project.pbxproj create mode 100644 local-cli/rnpm/link/test/getDependencyConfig.spec.js create mode 100644 local-cli/rnpm/link/test/getPrefix.spec.js create mode 100644 local-cli/rnpm/link/test/getProjectDependencies.spec.js create mode 100644 local-cli/rnpm/link/test/groupFilesByType.spec.js create mode 100644 local-cli/rnpm/link/test/ios/addFileToProject.spec.js create mode 100644 local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js create mode 100644 local-cli/rnpm/link/test/ios/createGroup.spec.js create mode 100644 local-cli/rnpm/link/test/ios/getBuildProperty.spec.js create mode 100644 local-cli/rnpm/link/test/ios/getGroup.spec.js create mode 100644 local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js create mode 100644 local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js create mode 100644 local-cli/rnpm/link/test/ios/getPlist.spec.js create mode 100644 local-cli/rnpm/link/test/ios/getPlistPath.spec.js create mode 100644 local-cli/rnpm/link/test/ios/getProducts.spec.js create mode 100644 local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js create mode 100644 local-cli/rnpm/link/test/ios/isInstalled.spec.js create mode 100644 local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js create mode 100644 local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js create mode 100644 local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js create mode 100644 local-cli/rnpm/link/test/link.spec.js create mode 100644 local-cli/rnpm/link/test/promiseWaterfall.spec.js diff --git a/.flowconfig b/.flowconfig index 92991af156f8bb..dd29ffc7434af9 100644 --- a/.flowconfig +++ b/.flowconfig @@ -46,6 +46,9 @@ # Ignore BUCK generated folders .*\.buckd/ +# Ignore RNPM +.*/local-cli/rnpm/.* + .*/node_modules/is-my-json-valid/test/.*\.json .*/node_modules/iconv-lite/encodings/tables/.*\.json .*/node_modules/y18n/test/.*\.json diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000000000..65a8df8d40829e --- /dev/null +++ b/.npmignore @@ -0,0 +1,2 @@ +# rnpm +/local-cli/rnpm diff --git a/local-cli/rnpm/core/package.json b/local-cli/rnpm/core/package.json new file mode 100644 index 00000000000000..2ff57ffd09f8ac --- /dev/null +++ b/local-cli/rnpm/core/package.json @@ -0,0 +1,50 @@ +{ + "name": "rnpm", + "version": "1.7.0", + "description": "React Native Package Manager", + "main": "./src/getCommands.js", + "scripts": { + "test": "jest" + }, + "jest": { + "testDirectoryName": "test", + "collectCoverage": true, + "testRunner": "/node_modules/jest-cli/src/testRunners/jasmine/jasmine2.js" + }, + "author": "Amazing React Native Community (https://github.com/facebook/react-native)", + "contributors": [ + "Alexey Kureev (https://github.com/Kureev)", + "Mike Grabowski (https://github.com/grabbou)" + ], + "engines": { + "node": ">= 4.0.0" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/rnpm/rnpm/issues" + }, + "keywords": [ + "react-native", + "native-modules", + "packager", + "rnpm" + ], + "homepage": "https://github.com/rnpm/rnpm#readme", + "dependencies": { + "commander": "^2.9.0", + "glob": "^7.0.1", + "lodash": "^3.10.1", + "rnpm-plugin-install": "^1.1.0", + "rnpm-plugin-link": "^1.7.4", + "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-require": "^1.2.1", + "rewire": "^2.5.1", + "jest-cli": "^0.9.0-fb2" + } +} diff --git a/local-cli/rnpm/core/src/config/android/findAndroidAppFolder.js b/local-cli/rnpm/core/src/config/android/findAndroidAppFolder.js new file mode 100644 index 00000000000000..2f1ddb355fc1a1 --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/findAndroidAppFolder.js @@ -0,0 +1,21 @@ +const fs = require('fs'); +const path = require('path'); + +/** + * @param {String} folder Folder to seek in + * @return {String} + */ +module.exports = function findAndroidAppFolder(folder) { + const flat = 'android'; + const nested = path.join('android', 'app'); + + if (fs.existsSync(path.join(folder, nested))) { + return nested; + } + + if (fs.existsSync(path.join(folder, flat))) { + return flat; + } + + return null; +}; diff --git a/local-cli/rnpm/core/src/config/android/findManifest.js b/local-cli/rnpm/core/src/config/android/findManifest.js new file mode 100644 index 00000000000000..3827b4276e7ee5 --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/findManifest.js @@ -0,0 +1,17 @@ +const glob = require('glob'); +const path = require('path'); + +/** + * Find an android application path in the folder + * + * @param {String} folder Name of the folder where to seek + * @return {String} + */ +module.exports = function findManifest(folder) { + const manifestPath = glob.sync(path.join('**', 'AndroidManifest.xml'), { + cwd: folder, + ignore: ['node_modules/**', '**/build/**', 'Examples/**', 'examples/**'], + })[0]; + + return manifestPath ? path.join(folder, manifestPath) : null; +}; diff --git a/local-cli/rnpm/core/src/config/android/findPackageClassName.js b/local-cli/rnpm/core/src/config/android/findPackageClassName.js new file mode 100644 index 00000000000000..30a33b1337096d --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/findPackageClassName.js @@ -0,0 +1,20 @@ +const fs = require('fs'); +const path = require('path'); +const glob = require('glob'); + +/** + * Gets package's class name (class that implements ReactPackage) + * by searching for its declaration in all Java files present in the folder + * + * @param {String} folder Folder to find java files + */ +module.exports = function getPackageClassName(folder) { + const files = glob.sync('**/*.java', { cwd: folder }); + + const packages = files + .map(filePath => fs.readFileSync(path.join(folder, filePath), 'utf8')) + .map(file => file.match(/class (.*) implements ReactPackage/)) + .filter(match => match); + + return packages.length ? packages[0][1] : null; +}; diff --git a/local-cli/rnpm/core/src/config/android/index.js b/local-cli/rnpm/core/src/config/android/index.js new file mode 100644 index 00000000000000..f8277307f1844f --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/index.js @@ -0,0 +1,111 @@ +const path = require('path'); +const fs = require('fs'); +const glob = require('glob'); +const findAndroidAppFolder = require('./findAndroidAppFolder'); +const findManifest = require('./findManifest'); +const readManifest = require('./readManifest'); +const findPackageClassName = require('./findPackageClassName'); + +const getPackageName = (manifest) => manifest.attr.package; + +/** + * Gets android project config by analyzing given folder and taking some + * defaults specified by user into consideration + */ +exports.projectConfig = function projectConfigAndroid(folder, userConfig) { + const src = userConfig.sourceDir || findAndroidAppFolder(folder); + + if (!src) { + return null; + } + + const sourceDir = path.join(folder, src); + const isFlat = sourceDir.indexOf('app') === -1; + const manifestPath = findManifest(sourceDir); + + if (!manifestPath) { + return null; + } + + const manifest = readManifest(manifestPath); + + const packageName = userConfig.packageName || getPackageName(manifest); + const packageFolder = userConfig.packageFolder || + packageName.replace(/\./g, path.sep); + + const mainActivityPath = path.join( + sourceDir, + 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', + userConfig.settingsGradlePath || 'settings.gradle' + ); + + const assetsPath = path.join( + sourceDir, + userConfig.assetsPath || 'src/main/assets' + ); + + const buildGradlePath = path.join( + sourceDir, + userConfig.buildGradlePath || 'build.gradle' + ); + + return { + sourceDir, + isFlat, + folder, + stringsPath, + manifestPath, + buildGradlePath, + settingsGradlePath, + assetsPath, + mainActivityPath, + }; +}; + +/** + * Same as projectConfigAndroid except it returns + * different config that applies to packages only + */ +exports.dependencyConfig = function dependencyConfigAndroid(folder, userConfig) { + const src = userConfig.sourceDir || findAndroidAppFolder(folder); + + if (!src) { + return null; + } + + const sourceDir = path.join(folder, src); + const manifestPath = findManifest(sourceDir); + + if (!manifestPath) { + return null; + } + + const manifest = readManifest(manifestPath); + const packageName = userConfig.packageName || getPackageName(manifest); + const packageClassName = findPackageClassName(sourceDir); + + /** + * This module has no package to export + */ + if (!packageClassName) { + return null; + } + + const packageImportPath = userConfig.packageImportPath || + `import ${packageName}.${packageClassName};`; + + const packageInstance = userConfig.packageInstance || + `new ${packageClassName}()`; + + return { sourceDir, folder, manifest, packageImportPath, packageInstance }; +}; diff --git a/local-cli/rnpm/core/src/config/android/readManifest.js b/local-cli/rnpm/core/src/config/android/readManifest.js new file mode 100644 index 00000000000000..056447695c19d6 --- /dev/null +++ b/local-cli/rnpm/core/src/config/android/readManifest.js @@ -0,0 +1,10 @@ +const fs = require('fs'); +const xml = require('xmldoc'); + +/** + * @param {String} manifestPath + * @return {XMLDocument} Parsed manifest's content + */ +module.exports = function readManifest(manifestPath) { + return new xml.XmlDocument(fs.readFileSync(manifestPath, 'utf8')); +}; diff --git a/local-cli/rnpm/core/src/config/findAssets.js b/local-cli/rnpm/core/src/config/findAssets.js new file mode 100644 index 00000000000000..0cd2f0dd3b1dfa --- /dev/null +++ b/local-cli/rnpm/core/src/config/findAssets.js @@ -0,0 +1,20 @@ +const glob = require('glob'); +const path = require('path'); + +const findAssetsInFolder = (folder) => + glob.sync(path.join(folder, '**'), { nodir: true }); + +/** + * Given an array of assets folders, e.g. ['Fonts', 'Images'], + * it globs in them to find all files that can be copied. + * + * It returns an array of absolute paths to files found. + */ +module.exports = function findAssets(folder, assets) { + return (assets || []) + .map(assetsFolder => path.join(folder, assetsFolder)) + .reduce((assets, assetsFolder) => + assets.concat(findAssetsInFolder(assetsFolder)), + [] + ); +}; diff --git a/local-cli/rnpm/core/src/config/index.js b/local-cli/rnpm/core/src/config/index.js new file mode 100644 index 00000000000000..ac75735108204d --- /dev/null +++ b/local-cli/rnpm/core/src/config/index.js @@ -0,0 +1,44 @@ +const path = require('path'); + +const android = require('./android'); +const ios = require('./ios'); +const findAssets = require('./findAssets'); +const wrapCommands = require('./wrapCommands'); + +const getRNPMConfig = (folder) => + require(path.join(folder, './package.json')).rnpm || {}; + +/** + * Returns project config from the current working directory + * @return {Object} + */ +exports.getProjectConfig = function getProjectConfig() { + const folder = process.cwd(); + const rnpm = getRNPMConfig(folder); + + return Object.assign({}, rnpm, { + ios: ios.projectConfig(folder, rnpm.ios || {}), + android: android.projectConfig(folder, rnpm.android || {}), + assets: findAssets(folder, rnpm.assets), + }); +}; + +/** + * Returns a dependency config from node_modules/ + * @param {String} packageName Dependency name + * @return {Object} + */ +exports.getDependencyConfig = function getDependencyConfig(packageName) { + const folder = path.join(process.cwd(), 'node_modules', packageName); + const rnpm = getRNPMConfig( + path.join(process.cwd(), 'node_modules', packageName.split('/')[0]) + ); + + 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/local-cli/rnpm/core/src/config/ios/findProject.js b/local-cli/rnpm/core/src/config/ios/findProject.js new file mode 100644 index 00000000000000..17c99cf4880354 --- /dev/null +++ b/local-cli/rnpm/core/src/config/ios/findProject.js @@ -0,0 +1,47 @@ +const glob = require('glob'); +const path = require('path'); + +/** + * Glob pattern to look for xcodeproj + */ +const GLOB_PATTERN = '**/*.xcodeproj'; + +/** + * Regexp matching all test projects + */ +const TEST_PROJECTS = /test|example|sample/i; + +/** + * Base iOS folder + */ +const IOS_BASE = 'ios'; + +/** + * These folders will be excluded from search to speed it up + */ +const GLOB_EXCLUDE_PATTERN = ['**/@(Pods|node_modules)/**']; + +/** + * Finds iOS project by looking for all .xcodeproj files + * in given folder. + * + * Returns first match if files are found or null + * + * Note: `./ios/*.xcodeproj` are returned regardless of the name + */ +module.exports = function findProject(folder) { + const projects = glob + .sync(GLOB_PATTERN, { + cwd: folder, + ignore: GLOB_EXCLUDE_PATTERN, + }) + .filter(project => { + return path.dirname(project) === IOS_BASE || !TEST_PROJECTS.test(project); + }); + + if (projects.length === 0) { + return null; + } + + return projects[0]; +}; diff --git a/local-cli/rnpm/core/src/config/ios/index.js b/local-cli/rnpm/core/src/config/ios/index.js new file mode 100644 index 00000000000000..04ada20bfc84f8 --- /dev/null +++ b/local-cli/rnpm/core/src/config/ios/index.js @@ -0,0 +1,31 @@ +const path = require('path'); +const findProject = require('./findProject'); + +/** + * Returns project config by analyzing given folder and applying some user defaults + * when constructing final object + */ +exports.projectConfig = function projectConfigIOS(folder, userConfig) { + const project = userConfig.project || findProject(folder); + + /** + * No iOS config found here + */ + if (!project) { + return null; + } + + const projectPath = path.join(folder, project); + + return { + sourceDir: path.dirname(projectPath), + folder: folder, + pbxprojPath: path.join(projectPath, 'project.pbxproj'), + projectPath: projectPath, + projectName: path.basename(projectPath), + libraryFolder: userConfig.libraryFolder || 'Libraries', + plist: userConfig.plist || [], + }; +}; + +exports.dependencyConfig = exports.projectConfig; diff --git a/local-cli/rnpm/core/src/config/wrapCommands.js b/local-cli/rnpm/core/src/config/wrapCommands.js new file mode 100644 index 00000000000000..7be7f1cf36a95c --- /dev/null +++ b/local-cli/rnpm/core/src/config/wrapCommands.js @@ -0,0 +1,9 @@ +const makeCommand = require('../makeCommand'); + +module.exports = function wrapCommands(commands) { + const mappedCommands = {}; + Object.keys(commands || []).forEach((k) => + mappedCommands[k] = makeCommand(commands[k]) + ); + return mappedCommands; +}; diff --git a/local-cli/rnpm/core/src/findPlugins.js b/local-cli/rnpm/core/src/findPlugins.js new file mode 100644 index 00000000000000..58d38559ee9e86 --- /dev/null +++ b/local-cli/rnpm/core/src/findPlugins.js @@ -0,0 +1,37 @@ +const path = require('path'); +const fs = require('fs'); +const union = require('lodash').union; +const uniq = require('lodash').uniq; +const flatten = require('lodash').flatten; + +/** + * Filter dependencies by name pattern + * @param {String} dependency Name of the dependency + * @return {Boolean} If dependency is a rnpm plugin + */ +const isPlugin = (dependency) => !!~dependency.indexOf('rnpm-plugin-'); + +const findPluginInFolder = (folder) => { + var pjson; + try { + pjson = require(path.join(folder, 'package.json')); + } catch (e) { + return []; + } + + const deps = union( + Object.keys(pjson.dependencies || {}), + Object.keys(pjson.devDependencies || {}) + ); + + return deps.filter(isPlugin); +}; + +/** + * Find plugins in package.json of the given folder + * @param {String} folder Path to the folder to get the package.json from + * @type {Array} Array of plugins or an empty array if no package.json found + */ +module.exports = function findPlugins(folders) { + return uniq(flatten(folders.map(findPluginInFolder))); +}; diff --git a/local-cli/rnpm/core/src/getCommands.js b/local-cli/rnpm/core/src/getCommands.js new file mode 100644 index 00000000000000..0de2cf7feadbaa --- /dev/null +++ b/local-cli/rnpm/core/src/getCommands.js @@ -0,0 +1,20 @@ +const path = require('path'); +const fs = require('fs'); +const uniq = require('lodash').uniq; +const flattenDeep = require('lodash').flattenDeep; +const findPlugins = require('./findPlugins'); + +/** + * @return {Array} Array of commands + */ +module.exports = function getCommands() { + const rnpmRoot = path.join(__dirname, '..'); + const appRoot = process.cwd(); + + return uniq( + flattenDeep([ + findPlugins([rnpmRoot]).map(require), + findPlugins([appRoot]).map(name => require(path.join(appRoot, 'node_modules', name))), + ]) + , 'name'); +}; diff --git a/local-cli/rnpm/core/src/makeCommand.js b/local-cli/rnpm/core/src/makeCommand.js new file mode 100644 index 00000000000000..5175f0e6877b2b --- /dev/null +++ b/local-cli/rnpm/core/src/makeCommand.js @@ -0,0 +1,25 @@ +const spawn = require('child_process').spawn; + +module.exports = function makeCommand(command) { + return (cb) => { + if (!cb) { + throw new Error(`You missed a callback function for the ${command} command`); + } + + const args = command.split(' '); + const cmd = args.shift(); + + const commandProcess = spawn(cmd, args, { + stdio: 'inherit', + stdin: 'inherit', + }); + + commandProcess.on('close', function prelink(code) { + if (code) { + throw new Error(`Error occured during executing "${command}" command`); + } + + cb(); + }); + }; +}; diff --git a/local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js b/local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js new file mode 100644 index 00000000000000..fe6223aadda65b --- /dev/null +++ b/local-cli/rnpm/core/test/android/findAndroidAppFolder.spec.js @@ -0,0 +1,30 @@ +jest.autoMockOff(); + +const findAndroidAppFolder = require('../../src/config/android/findAndroidAppFolder'); +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::findAndroidAppFolder', () => { + beforeAll(() => mockFs({ + empty: {}, + nested: { + android: { + app: mocks.valid, + }, + }, + flat: { + android: mocks.valid, + }, + })); + + it('should return an android app folder if it exists in the given folder', () => { + expect(findAndroidAppFolder('flat')).toBe('android'); + expect(findAndroidAppFolder('nested')).toBe('android/app'); + }); + + it('should return `null` if there\'s no android app folder', () => { + expect(findAndroidAppFolder('empty')).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/findManifest.spec.js b/local-cli/rnpm/core/test/android/findManifest.spec.js new file mode 100644 index 00000000000000..27a81fd6d4d954 --- /dev/null +++ b/local-cli/rnpm/core/test/android/findManifest.spec.js @@ -0,0 +1,25 @@ +jest.autoMockOff(); + +const findManifest = require('../../src/config/android/findManifest'); +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::findManifest', () => { + + beforeAll(() => mockFs({ + empty: {}, + flat: { + android: mocks.valid, + }, + })); + + it('should return a manifest path if file exists in the folder', () => { + expect(typeof findManifest('flat')).toBe('string'); + }); + + it('should return `null` if there is no manifest in the folder', () => { + expect(findManifest('empty')).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/findPackageClassName.spec.js b/local-cli/rnpm/core/test/android/findPackageClassName.spec.js new file mode 100644 index 00000000000000..fbdd614a16acdb --- /dev/null +++ b/local-cli/rnpm/core/test/android/findPackageClassName.spec.js @@ -0,0 +1,25 @@ +jest.autoMockOff(); + +const findPackageClassName = require('../../src/config/android/findPackageClassName'); +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::findPackageClassName', () => { + + beforeAll(() => mockFs({ + empty: {}, + flat: { + android: mocks.valid, + }, + })); + + it('should return manifest content if file exists in the folder', () => { + expect(typeof findPackageClassName('flat')).toBe('string'); + }); + + it('should return `null` if there\'s no matches', () => { + expect(findPackageClassName('empty')).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/getDependencyConfig.spec.js b/local-cli/rnpm/core/test/android/getDependencyConfig.spec.js new file mode 100644 index 00000000000000..15126a2de97166 --- /dev/null +++ b/local-cli/rnpm/core/test/android/getDependencyConfig.spec.js @@ -0,0 +1,49 @@ +jest.autoMockOff(); + +const getDependencyConfig = require('../../src/config/android').dependencyConfig; +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); +const userConfig = {}; + +describe('android::getDependencyConfig', () => { + + beforeAll(() => mockFs({ + empty: {}, + nested: { + android: { + app: mocks.valid, + }, + }, + corrupted: { + android: { + app: mocks.corrupted, + }, + }, + noPackage: { + android: {}, + }, + })); + + it('should return an object with android project configuration', () => { + expect(getDependencyConfig('nested', userConfig)).not.toBe(null); + expect(typeof getDependencyConfig('nested', userConfig)).toBe('object'); + }); + + it('should return `null` if manifest file hasn\'t been found', () => { + expect(getDependencyConfig('empty', userConfig)).toBe(null); + }); + + it('should return `null` if android project was not found', () => { + expect(getDependencyConfig('empty', userConfig)).toBe(null); + }); + + it('should return `null` if android project does not contain ReactPackage', () => { + expect(getDependencyConfig('noPackage', userConfig)).toBe(null); + }); + + it('should return `null` if it can\'t find a packageClassName', () => { + expect(getDependencyConfig('corrupted', userConfig)).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/getProjectConfig.spec.js b/local-cli/rnpm/core/test/android/getProjectConfig.spec.js new file mode 100644 index 00000000000000..1e62dbeca0b5bc --- /dev/null +++ b/local-cli/rnpm/core/test/android/getProjectConfig.spec.js @@ -0,0 +1,56 @@ +jest.autoMockOff(); + +const getProjectConfig = require('../../src/config/android').projectConfig; +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::getProjectConfig', () => { + beforeAll(() => mockFs({ + empty: {}, + nested: { + android: { + app: mocks.valid, + }, + }, + flat: { + android: mocks.valid, + }, + noManifest: { + android: {}, + }, + })); + + it('should return `null` if manifest file hasn\'t been found', () => { + const userConfig = {}; + const folder = 'noManifest'; + + expect(getProjectConfig(folder, userConfig)).toBe(null); + }); + + describe('return an object with android project configuration for', () => { + it('nested structure', () => { + const userConfig = {}; + const folder = 'nested'; + + expect(getProjectConfig(folder, userConfig)).not.toBe(null); + expect(typeof getProjectConfig(folder, userConfig)).toBe('object'); + }); + + it('flat structure', () => { + const userConfig = {}; + const folder = 'flat'; + + expect(getProjectConfig(folder, userConfig)).not.toBe(null); + expect(typeof getProjectConfig(folder, userConfig)).toBe('object'); + }); + }); + + it('should return `null` if android project was not found', () => { + const userConfig = {}; + const folder = 'empty'; + + expect(getProjectConfig(folder, userConfig)).toBe(null); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/android/readManifest.spec.js b/local-cli/rnpm/core/test/android/readManifest.spec.js new file mode 100644 index 00000000000000..571e183415361e --- /dev/null +++ b/local-cli/rnpm/core/test/android/readManifest.spec.js @@ -0,0 +1,31 @@ +jest.autoMockOff(); + +const findManifest = require('../../src/config/android/findManifest'); +const readManifest = require('../../src/config/android/readManifest'); +const mockFs = require('mock-fs'); +const mocks = require('../fixtures/android'); + +describe('android::readManifest', () => { + + beforeAll(() => mockFs({ + empty: {}, + nested: { + android: { + app: mocks.valid, + }, + }, + })); + + it('should return manifest content if file exists in the folder', () => { + const manifestPath = findManifest('nested'); + expect(readManifest(manifestPath)).not.toBe(null); + expect(typeof readManifest(manifestPath)).toBe('object'); + }); + + it('should throw an error if there is no manifest in the folder', () => { + const fakeManifestPath = findManifest('empty'); + expect(() => readManifest(fakeManifestPath)).toThrow(); + }); + + afterAll(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/findAssets.spec.js b/local-cli/rnpm/core/test/findAssets.spec.js new file mode 100644 index 00000000000000..b31d5fac30cb61 --- /dev/null +++ b/local-cli/rnpm/core/test/findAssets.spec.js @@ -0,0 +1,31 @@ +jest.autoMockOff(); + +const findAssets = require('../src/config/findAssets'); +const mockFs = require('mock-fs'); +const dependencies = require('./fixtures/dependencies'); +const isArray = (arg) => + Object.prototype.toString.call(arg) === '[object Array]'; + +describe('findAssets', () => { + + beforeEach(() => mockFs({ testDir: dependencies.withAssets })); + + it('should return an array of all files in given folders', () => { + const assets = findAssets('testDir', ['fonts', 'images']); + + expect(isArray(assets)).toBeTruthy(); + expect(assets.length).toEqual(3); + }); + + it('should prepend assets paths with the folder path', () => { + const assets = findAssets('testDir', ['fonts', 'images']); + + assets.forEach(assetPath => expect(assetPath).toContain('testDir')); + }); + + it('should return an empty array if given assets are null', () => { + expect(findAssets('testDir', null).length).toEqual(0); + }); + + afterEach(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/findPlugins.js b/local-cli/rnpm/core/test/findPlugins.js new file mode 100644 index 00000000000000..c7285687357351 --- /dev/null +++ b/local-cli/rnpm/core/test/findPlugins.js @@ -0,0 +1,46 @@ +jest.autoMockOff(); + +const path = require('path'); +const findPlugins = require('../src/findPlugins'); + +const pjsonPath = path.join(process.cwd(), 'package.json'); +const isArray = (arg) => + Object.prototype.toString.call(arg) === '[object Array]'; + +describe('findPlugins', () => { + + it('should return an array of dependencies', () => { + jest.setMock(pjsonPath, { + dependencies: { 'rnpm-plugin-test': '*' }, + }); + expect(findPlugins([process.cwd()]).length).toBe(1); + expect(findPlugins([process.cwd()])[0]).toBe('rnpm-plugin-test'); + }); + + it('should return an empty array if there\'re no plugins in this folder', () => { + jest.setMock(pjsonPath, {}); + expect(findPlugins([process.cwd()]).length).toBe(0); + }); + + it('should return an empty array if there\'s no package.json in the supplied folder', () => { + expect(isArray(findPlugins(['fake-path']))).toBeTruthy(); + expect(findPlugins(['fake-path']).length).toBe(0); + }); + + it('should return plugins from both dependencies and dev dependencies', () => { + jest.setMock(pjsonPath, { + dependencies: { 'rnpm-plugin-test': '*' }, + devDependencies: { 'rnpm-plugin-test-2': '*' }, + }); + expect(findPlugins([process.cwd()]).length).toEqual(2); + }); + + it('should return unique list of plugins', () => { + jest.setMock(pjsonPath, { + dependencies: { 'rnpm-plugin-test': '*' }, + devDependencies: { 'rnpm-plugin-test': '*' }, + }); + expect(findPlugins([process.cwd()]).length).toEqual(1); + }); + +}); diff --git a/local-cli/rnpm/core/test/fixtures/android.js b/local-cli/rnpm/core/test/fixtures/android.js new file mode 100644 index 00000000000000..ebecf9b8c9029f --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/android.js @@ -0,0 +1,49 @@ +const fs = require('fs'); +const path = require('path'); + +const manifest = fs.readFileSync(path.join(__dirname, './files/AndroidManifest.xml')); +const mainJavaClass = fs.readFileSync(path.join(__dirname, './files/Main.java')); + +exports.valid = { + src: { + 'AndroidManifest.xml': manifest, + main: { + com: { + some: { + example: { + 'Main.java': mainJavaClass, + 'ReactPackage.java': fs.readFileSync(path.join(__dirname, './files/ReactPackage.java')), + }, + }, + }, + }, + }, +}; + +exports.corrupted = { + src: { + 'AndroidManifest.xml': manifest, + main: { + com: { + some: { + example: {}, + }, + }, + }, + }, +}; + +exports.noPackage = { + src: { + 'AndroidManifest.xml': manifest, + main: { + com: { + some: { + example: { + 'Main.java': mainJavaClass, + }, + }, + }, + }, + }, +}; diff --git a/local-cli/rnpm/core/test/fixtures/commands.js b/local-cli/rnpm/core/test/fixtures/commands.js new file mode 100644 index 00000000000000..cbe4193be29e78 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/commands.js @@ -0,0 +1,15 @@ +exports.single = { + func: () => {}, + description: 'Test action', + name: 'test', +}; + +exports.multiple = [{ + func: () => {}, + description: 'Test action #1', + name: 'test1', +}, { + func: () => {}, + description: 'Test action #2', + name: 'test2', +}]; diff --git a/local-cli/rnpm/core/test/fixtures/dependencies.js b/local-cli/rnpm/core/test/fixtures/dependencies.js new file mode 100644 index 00000000000000..ad0ce62529d81a --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/dependencies.js @@ -0,0 +1,27 @@ +const fs = require('fs'); +const path = require('path'); +const android = require('./android'); + +const pjson = fs.readFileSync(path.join(__dirname, 'files', 'package.json')); + +module.exports = { + valid: { + 'package.json': pjson, + android: android.valid, + }, + withAssets: { + 'package.json': pjson, + android: android.valid, + fonts: { + 'A.ttf': '', + 'B.ttf': '', + }, + images: { + 'C.jpg': '', + }, + }, + noPackage: { + 'package.json': pjson, + android: android.noPackage, + }, +}; diff --git a/local-cli/rnpm/core/test/fixtures/files/AndroidManifest.xml b/local-cli/rnpm/core/test/fixtures/files/AndroidManifest.xml new file mode 100644 index 00000000000000..83cf6c79916298 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/AndroidManifest.xml @@ -0,0 +1,4 @@ + + diff --git a/local-cli/rnpm/core/test/fixtures/files/Main.java b/local-cli/rnpm/core/test/fixtures/files/Main.java new file mode 100644 index 00000000000000..08dc35a58bfabf --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/Main.java @@ -0,0 +1,8 @@ +package com.some.example; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SomeExamplePackage {} diff --git a/local-cli/rnpm/core/test/fixtures/files/ReactPackage.java b/local-cli/rnpm/core/test/fixtures/files/ReactPackage.java new file mode 100644 index 00000000000000..b1ea146323fc0b --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/ReactPackage.java @@ -0,0 +1,35 @@ +package com.some.example; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.JavaScriptModule; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class SomeExamplePackage implements ReactPackage { + + public SomeExamplePackage() {} + + @Override + public List createNativeModules( + ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + modules.add(new SomeExampleModule(reactContext)); + return modules; + } + + @Override + public List> createJSModules() { + return Collections.emptyList(); + } + + @Override + public List createViewManagers(ReactApplicationContext reactContext) { + return Collections.emptyList(); + } +} diff --git a/local-cli/rnpm/core/test/fixtures/files/package.json b/local-cli/rnpm/core/test/fixtures/files/package.json new file mode 100644 index 00000000000000..cce13033d3a405 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/package.json @@ -0,0 +1,62 @@ +{ + "name": "react-native-vector-icons", + "version": "1.0.0", + "description": "Customizable Icons for React Native with support for NavBar/TabBar, image source and full styling. Choose from 3000+ bundled icons or use your own.", + "main": "index.js", + "bin": { + "generate-icon": "./generate-icon.js" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "rm -rf {Fonts,Entypo.js,EvilIcons.js,FontAwesome.js,Foundation.js,Ionicons.js,MaterialIcons.js,Octicons.js,Zocial.js} && mkdir Fonts && npm run build-entypo && npm run build-evilicons && npm run build-fontawesome && npm run build-foundation && npm run build-ionicons && npm run build-materialicons && npm run build-octicons && npm run build-zocial", + "build-entypo": "mkdir -p tmp/svg && curl https://dl.dropboxusercontent.com/u/4339492/entypo.zip > tmp/entypo.zip && unzip -j tmp/entypo.zip *.svg -x __MACOSX/* -d tmp/svg && fontcustom compile tmp/svg -o tmp -n Entypo -t css -h && node generate-icon tmp/Entypo.css --componentName=Entypo --fontFamily=Entypo > Entypo.js && cp tmp/Entypo.ttf Fonts && rm -rf {tmp,.fontcustom-manifest.json}", + "build-evilicons": "fontcustom compile node_modules/evil-icons/assets/icons -o tmp -n EvilIcons -t css -h && node generate-icon tmp/EvilIcons.css --prefix=.icon-ei- --componentName=EvilIcons --fontFamily=EvilIcons > EvilIcons.js && cp tmp/EvilIcons.ttf Fonts && rm -rf {tmp,.fontcustom-manifest.json}", + "build-fontawesome": "node generate-icon node_modules/font-awesome/css/font-awesome.css --prefix=.fa- --componentName=FontAwesome --fontFamily=FontAwesome > FontAwesome.js && cp node_modules/font-awesome/fonts/fontawesome-webfont.ttf Fonts/FontAwesome.ttf", + "build-foundation": "node generate-icon bower_components/foundation-icon-fonts/foundation-icons.css --prefix=.fi- --componentName=Foundation --fontFamily=fontcustom > Foundation.js && cp bower_components/foundation-icon-fonts/foundation-icons.ttf Fonts/Foundation.ttf", + "build-ionicons": "node generate-icon bower_components/ionicons/css/ionicons.css --prefix=.ion- --componentName=Ionicons --fontFamily=Ionicons > Ionicons.js && cp bower_components/ionicons/fonts/ionicons.ttf Fonts/Ionicons.ttf", + "build-materialicons": "mkdir -p tmp/svg && for f in ./node_modules/material-design-icons/*/svg/production/ic_*_48px.svg; do t=${f/*\\/ic_/}; t=${t/_48px/}; cp \"$f\" \"./tmp/svg/${t//_/-}\"; done && fontcustom compile tmp/svg -o tmp -n MaterialIcons -t css -h && node generate-icon tmp/MaterialIcons.css --componentName=MaterialIcons --fontFamily=MaterialIcons > MaterialIcons.js && cp tmp/MaterialIcons.ttf Fonts && rm -rf {tmp,.fontcustom-manifest.json}", + "build-octicons": "node generate-icon bower_components/octicons/octicons/octicons.css --prefix=.octicon- --componentName=Octicons --fontFamily=octicons > Octicons.js && cp bower_components/octicons/octicons/octicons.ttf Fonts/Octicons.ttf", + "build-zocial": "node generate-icon bower_components/css-social-buttons/css/zocial.css --prefix=.zocial. --componentName=Zocial --fontFamily=zocial > Zocial.js && cp bower_components/css-social-buttons/css/zocial.ttf Fonts/Zocial.ttf" + }, + "keywords": [ + "react-native", + "react-component", + "react-native-component", + "react", + "mobile", + "ios", + "android", + "ui", + "icon", + "icons", + "vector", + "retina", + "font" + ], + "author": { + "name": "Joel Arvidsson", + "email": "joel@oblador.se" + }, + "homepage": "https://github.com/oblador/react-native-vector-icons", + "bugs": { + "url": "https://github.com/oblador/react-native-vector-icons/issues" + }, + "repository": { + "type": "git", + "url": "git://github.com/oblador/react-native-vector-icons.git" + }, + "license": "MIT", + "peerDependencies": { + "react-native": ">=0.4.0 || 0.5.0-rc1 || 0.6.0-rc || 0.7.0-rc || 0.7.0-rc.2 || 0.8.0-rc || 0.8.0-rc.2 || 0.9.0-rc || 0.10.0-rc || 0.11.0-rc || 0.12.0-rc || 0.13.0-rc || 0.14.0-rc || 0.15.0-rc || 0.16.0-rc" + }, + "dependencies": { + "lodash": "^3.8.0", + "yargs": "^3.30.0", + "rnpm-plugin-test": "*" + }, + "devDependencies": { + "evil-icons": "^1.7.6", + "font-awesome": "^4.4.0", + "material-design-icons": "^2.1.1" + } +} diff --git a/local-cli/rnpm/core/test/fixtures/files/project.pbxproj b/local-cli/rnpm/core/test/fixtures/files/project.pbxproj new file mode 100644 index 00000000000000..e5ac7a27b54e32 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/files/project.pbxproj @@ -0,0 +1,804 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { +/* Begin PBXBuildFile section */ + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; + 00E356F31AD99517003FC87E /* androidTestTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* androidTestTests.m */; }; + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 672DE8B31B124B8088D0D29F /* libBVLinearGradient.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B5255B7628A54AC2A9B4B2A0 /* libBVLinearGradient.a */; }; + 68FEB18F24414EF981BD7940 /* libCodePush.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 53C67FE8F7294B7A83790610 /* libCodePush.a */; }; + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; + C6C437D070BA42D6BE39198B /* libRCTVideo.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A7396DFBAFA4CA092E367F5 /* libRCTVideo.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTActionSheet; + }; + 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTGeolocation; + }; + 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5115D1A9E6B3D00147676; + remoteInfo = RCTImage; + }; + 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B511DB1A9E6C8500147676; + remoteInfo = RCTNetwork; + }; + 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; + remoteInfo = RCTVibration; + }; + 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = androidTest; + }; + 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTSettings; + }; + 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; + 146834031AC3E56700842450 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; + remoteInfo = React; + }; + 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTLinking; + }; + 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5119B1A9E6C1200147676; + remoteInfo = RCTText; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; + 00E356EE1AD99517003FC87E /* androidTestTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = androidTestTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* androidTestTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = androidTestTests.m; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* androidTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = androidTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = androidTest/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = androidTest/AppDelegate.m; sourceTree = ""; }; + 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = androidTest/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = androidTest/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = androidTest/main.m; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 53C67FE8F7294B7A83790610 /* libCodePush.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libCodePush.a; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; + B5255B7628A54AC2A9B4B2A0 /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libBVLinearGradient.a; sourceTree = ""; }; + 467A6CBCB2164E7D9B673D4C /* CodePush.xcodeproj */ = {isa = PBXFileReference; name = "CodePush.xcodeproj"; path = "../node_modules/react-native-code-push/CodePush.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + FD7121847BA447D8B737F22A /* BVLinearGradient.xcodeproj */ = {isa = PBXFileReference; name = "BVLinearGradient.xcodeproj"; path = "../node_modules/react-native-linear-gradient/BVLinearGradient.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + 409DA945815C46DEB4F254DB /* RCTVideo.xcodeproj */ = {isa = PBXFileReference; name = "RCTVideo.xcodeproj"; path = "../node_modules/react-native-video/RCTVideo.xcodeproj"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; + 3A7396DFBAFA4CA092E367F5 /* libRCTVideo.a */ = {isa = PBXFileReference; name = "libRCTVideo.a"; path = "libRCTVideo.a"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00E356EB1AD99517003FC87E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 146834051AC3E58100842450 /* libReact.a in Frameworks */, + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, + 672DE8B31B124B8088D0D29F /* libBVLinearGradient.a in Frameworks */, + 68FEB18F24414EF981BD7940 /* libCodePush.a in Frameworks */, + C6C437D070BA42D6BE39198B /* libRCTVideo.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00C302A81ABCB8CE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302B61ABCB90400DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302BC1ABCB91800DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302D41ABCB9D200DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302E01ABCB9EE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */, + ); + name = Products; + sourceTree = ""; + }; + 00E356EF1AD99517003FC87E /* androidTestTests */ = { + isa = PBXGroup; + children = ( + 00E356F21AD99517003FC87E /* androidTestTests.m */, + 00E356F01AD99517003FC87E /* Supporting Files */, + ); + path = androidTestTests; + sourceTree = ""; + }; + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00E356F11AD99517003FC87E /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 139105B71AF99BAD00B5F7CC /* Products */ = { + isa = PBXGroup; + children = ( + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */, + ); + name = Products; + sourceTree = ""; + }; + 139FDEE71B06529A00C62182 /* Products */ = { + isa = PBXGroup; + children = ( + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */, + ); + name = Products; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* androidTest */ = { + isa = PBXGroup; + children = ( + 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.m */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 13B07FB71A68108700A75B9A /* main.m */, + ); + name = androidTest; + sourceTree = ""; + }; + 146834001AC3E56700842450 /* Products */ = { + isa = PBXGroup; + children = ( + 146834041AC3E56700842450 /* libReact.a */, + ); + name = Products; + sourceTree = ""; + }; + 78C398B11ACF4ADC00677621 /* Products */ = { + isa = PBXGroup; + children = ( + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */, + ); + name = Products; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + 146833FF1AC3E56700842450 /* React.xcodeproj */, + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */, + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */, + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */, + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, + 467A6CBCB2164E7D9B673D4C /* CodePush.xcodeproj */, + FD7121847BA447D8B737F22A /* BVLinearGradient.xcodeproj */, + 409DA945815C46DEB4F254DB /* RCTVideo.xcodeproj */, + ); + name = Libraries; + sourceTree = ""; + }; + 832341B11AAA6A8300B99B32 /* Products */ = { + isa = PBXGroup; + children = ( + 832341B51AAA6A8300B99B32 /* libRCTText.a */, + ); + name = Products; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 13B07FAE1A68108700A75B9A /* androidTest */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* androidTestTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* androidTest.app */, + 00E356EE1AD99517003FC87E /* androidTestTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 00E356ED1AD99517003FC87E /* androidTestTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "androidTestTests" */; + buildPhases = ( + 00E356EA1AD99517003FC87E /* Sources */, + 00E356EB1AD99517003FC87E /* Frameworks */, + 00E356EC1AD99517003FC87E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 00E356F51AD99517003FC87E /* PBXTargetDependency */, + ); + name = androidTestTests; + productName = androidTestTests; + productReference = 00E356EE1AD99517003FC87E /* androidTestTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13B07F861A680F5B00A75B9A /* androidTest */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "androidTest" */; + buildPhases = ( + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = androidTest; + productName = "Hello World"; + productReference = 13B07F961A680F5B00A75B9A /* androidTest.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 00E356ED1AD99517003FC87E = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "androidTest" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; + ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + }, + { + ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; + ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + }, + { + ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */; + ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + }, + { + ProductGroup = 78C398B11ACF4ADC00677621 /* Products */; + ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + }, + { + ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */; + ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + }, + { + ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */; + ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + }, + { + ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; + ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + }, + { + ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; + ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + }, + { + ProductGroup = 139FDEE71B06529A00C62182 /* Products */; + ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + }, + { + ProductGroup = 146834001AC3E56700842450 /* Products */; + ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* androidTest */, + 00E356ED1AD99517003FC87E /* androidTestTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTActionSheet.a; + remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTGeolocation.a; + remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTImage.a; + remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTNetwork.a; + remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTVibration.a; + remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTSettings.a; + remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 146834041AC3E56700842450 /* libReact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReact.a; + remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTLinking.a; + remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTText.a; + remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 00E356EC1AD99517003FC87E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "../node_modules/react-native/packager/react-native-xcode.sh"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00E356EA1AD99517003FC87E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 00E356F31AD99517003FC87E /* androidTestTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* androidTest */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 13B07FB21A68108700A75B9A /* Base */, + ); + name = LaunchScreen.xib; + path = androidTest; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 00E356F61AD99517003FC87E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = androidTestTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/androidTest.app/androidTest"; + }; + name = Debug; + }; + 00E356F71AD99517003FC87E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = androidTestTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + ); + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/androidTest.app/androidTest"; + }; + name = Release; + }; + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEAD_CODE_STRIPPING = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)", + "$(SRCROOT)/../node_modules/react-native-code-push", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/**", + "$(SRCROOT)/../node_modules/react-native-video", + ); + INFOPLIST_FILE = androidTest/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = androidTest; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)", + "$(SRCROOT)/../node_modules/react-native-code-push", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/**", + "$(SRCROOT)/../node_modules/react-native-video", + ); + INFOPLIST_FILE = androidTest/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = androidTest; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)", + "$(SRCROOT)/../node_modules/react-native-code-push", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/**", + "$(SRCROOT)/../node_modules/react-native-video", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)", + "$(SRCROOT)/../node_modules/react-native-code-push", + "$(SRCROOT)/../node_modules/react-native-linear-gradient/**", + "$(SRCROOT)/../node_modules/react-native-video", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "androidTestTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00E356F61AD99517003FC87E /* Debug */, + 00E356F71AD99517003FC87E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "androidTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "androidTest" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/local-cli/rnpm/core/test/fixtures/ios.js b/local-cli/rnpm/core/test/fixtures/ios.js new file mode 100644 index 00000000000000..50943559889555 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/ios.js @@ -0,0 +1,14 @@ +const fs = require('fs'); +const path = require('path'); + +exports.valid = { + 'demoProject.xcodeproj': { + 'project.pbxproj': fs.readFileSync(path.join(__dirname, './files/project.pbxproj')), + }, +}; + +exports.validTestName = { + 'MyTestProject.xcodeproj': { + 'project.pbxproj': fs.readFileSync(path.join(__dirname, './files/project.pbxproj')), + }, +}; diff --git a/local-cli/rnpm/core/test/fixtures/projects.js b/local-cli/rnpm/core/test/fixtures/projects.js new file mode 100644 index 00000000000000..673aec6483f575 --- /dev/null +++ b/local-cli/rnpm/core/test/fixtures/projects.js @@ -0,0 +1,24 @@ +const fs = require('fs'); +const path = require('path'); +const android = require('./android'); +const ios = require('./ios'); + +const flat = { + android: android.valid, + ios: ios.valid, +}; + +const nested = { + android: { + app: android.valid, + }, + ios: ios.valid, +}; + +const withExamples = { + Examples: flat, + ios: ios.valid, + android: android.valid, +}; + +module.exports = { flat, nested, withExamples }; diff --git a/local-cli/rnpm/core/test/getCommands.spec.js b/local-cli/rnpm/core/test/getCommands.spec.js new file mode 100644 index 00000000000000..58c25446fb9200 --- /dev/null +++ b/local-cli/rnpm/core/test/getCommands.spec.js @@ -0,0 +1,171 @@ +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('shoud load when installed locally', () => { + revert = getCommands.__set__({ + __dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'), + }); + + mock(APP_JSON, SAMPLE_RNPM_JSON); + mock( + path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN), + commands.single + ); + + expect(getCommands()[0]).toEqual(commands.single); + }); + + it('should load when installed globally', () => { + revert = getCommands.__set__({ + __dirname: path.join(GLOBAL_NODE_MODULES, 'rnpm/src'), + }); + + mock(APP_JSON, SAMPLE_RNPM_JSON); + mock( + path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN), + commands.single + ); + + expect(getCommands()[0]).toEqual(commands.single); + }); + + }); + + describe('rnpm and project plugins', () => { + + beforeEach(() => { + revert = getCommands.__set__({ + __dirname: path.join(LOCAL_NODE_MODULES, 'rnpm/src'), + }); + }); + + afterEach(() => revert()); + + it('should load concatenated list of plugins', () => { + mock(APP_JSON, SAMPLE_RNPM_JSON); + mock(LOCAL_RNPM_PJSON, { + dependencies: { + [`${SAMPLE_RNPM_PLUGIN}-2`]: '*', + }, + }); + + mock( + path.join(LOCAL_NODE_MODULES, SAMPLE_RNPM_PLUGIN), + commands.multiple + ); + mock(`${SAMPLE_RNPM_PLUGIN}-2`, commands.single); + + expect(getCommands().length).toEqual(3); + }); + }); +}); diff --git a/local-cli/rnpm/core/test/ios/findProject.spec.js b/local-cli/rnpm/core/test/ios/findProject.spec.js new file mode 100644 index 00000000000000..6626921ba14731 --- /dev/null +++ b/local-cli/rnpm/core/test/ios/findProject.spec.js @@ -0,0 +1,84 @@ +jest.autoMockOff(); + +const findProject = require('../../src/config/ios/findProject'); +const mockFs = require('mock-fs'); +const projects = require('../fixtures/projects'); +const ios = require('../fixtures/ios'); +const userConfig = {}; + +describe('ios::findProject', () => { + it('should return path to xcodeproj if found', () => { + mockFs(projects.flat); + expect(findProject('')).not.toBe(null); + }); + + it('should return null if there\'re no projects', () => { + mockFs({ testDir: projects }); + expect(findProject('')).toBe(null); + }); + + it('should return ios project regardless of its name', () => { + mockFs({ ios: ios.validTestName }); + expect(findProject('')).not.toBe(null); + }); + + it('should ignore node_modules', () => { + mockFs({ node_modules: projects.flat }); + expect(findProject('')).toBe(null); + }); + + it('should ignore Pods', () => { + mockFs({ Pods: projects.flat }); + 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, + Examples: projects.flat, + example: projects.flat, + KeychainExample: projects.flat, + Zpp: projects.flat, + }); + + 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, + IntegrationTests: projects.flat, + tests: projects.flat, + Zpp: { + tests: projects.flat, + src: projects.flat, + }, + }); + + expect(findProject('').toLowerCase()).not.toContain('test'); + }); + + afterEach(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/ios/getProjectConfig.spec.js b/local-cli/rnpm/core/test/ios/getProjectConfig.spec.js new file mode 100644 index 00000000000000..bba3bedb13071b --- /dev/null +++ b/local-cli/rnpm/core/test/ios/getProjectConfig.spec.js @@ -0,0 +1,26 @@ +jest.autoMockOff(); + +const getProjectConfig = require('../../src/config/ios').projectConfig; +const mockFs = require('mock-fs'); +const projects = require('../fixtures/projects'); + +describe('ios::getProjectConfig', () => { + const userConfig = {}; + + beforeEach(() => mockFs({ testDir: projects })); + + it('should return an object with ios project configuration', () => { + const folder = 'testDir/nested'; + + expect(getProjectConfig(folder, userConfig)).not.toBe(null); + expect(typeof getProjectConfig(folder, userConfig)).toBe('object'); + }); + + it('should return `null` if ios project was not found', () => { + const folder = 'testDir/empty'; + + expect(getProjectConfig(folder, userConfig)).toBe(null); + }); + + afterEach(mockFs.restore); +}); diff --git a/local-cli/rnpm/core/test/makeCommand.spec.js b/local-cli/rnpm/core/test/makeCommand.spec.js new file mode 100644 index 00000000000000..1fb50dfe6629a0 --- /dev/null +++ b/local-cli/rnpm/core/test/makeCommand.spec.js @@ -0,0 +1,35 @@ +var spawnError = false; + +jest.setMock('child_process', { + spawn: () => ({ + on: (ev, cb) => cb(spawnError), + }), +}); +jest.dontMock('../src/makeCommand'); + +const makeCommand = require('../src/makeCommand'); + +describe('makeCommand', () => { + const command = makeCommand('echo'); + + it('should generate a function around shell command', () => { + expect(typeof command).toBe('function'); + }); + + it('should throw an error if there\'s no callback provided', () => { + expect(command).toThrow(); + }); + + it('should invoke a callback after command execution', () => { + const spy = jest.genMockFunction(); + command(spy); + + expect(spy.mock.calls.length).toBe(1); + }); + + it('should throw an error if spawn ended up with error', () => { + spawnError = true; + const cb = jest.genMockFunction(); + expect(() => command(cb)).toThrow(); + }); +}); diff --git a/local-cli/rnpm/install/index.js b/local-cli/rnpm/install/index.js new file mode 100644 index 00000000000000..66891919c75adc --- /dev/null +++ b/local-cli/rnpm/install/index.js @@ -0,0 +1,11 @@ +module.exports = [ + { + func: require('./src/install'), + description: 'Install and link native dependencies', + name: 'install [packageName]', + }, { + func: require('./src/uninstall'), + description: 'Uninstall and unlink native dependencies', + name: 'uninstall [packageName]', + }, +]; diff --git a/local-cli/rnpm/install/package.json b/local-cli/rnpm/install/package.json new file mode 100644 index 00000000000000..e0f4987356749b --- /dev/null +++ b/local-cli/rnpm/install/package.json @@ -0,0 +1,26 @@ +{ + "name": "rnpm-plugin-install", + "version": "1.1.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/rnpm/rnpm-plugin-install.git" + }, + "author": "Alexey Kureev (https://github.com/Kureev)", + "contributors": [ + "Alexey Kureev (https://github.com/Kureev)", + "Mike Grabowski (https://github.com/grabbou)" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/rnpm/rnpm-plugin-install/issues" + }, + "homepage": "https://github.com/rnpm/rnpm-plugin-install#readme", + "dependencies": { + "npmlog": "^2.0.2" + } +} diff --git a/local-cli/rnpm/install/src/install.js b/local-cli/rnpm/install/src/install.js new file mode 100644 index 00000000000000..d9eed2db433ac1 --- /dev/null +++ b/local-cli/rnpm/install/src/install.js @@ -0,0 +1,26 @@ +const spawnSync = require('child_process').spawnSync; +const log = require('npmlog'); +const spawnOpts = { + stdio: 'inherit', + stdin: 'inherit', +}; + +log.heading = 'rnpm-install'; + +module.exports = function install(config, args, callback) { + const name = args[0]; + + var res = spawnSync('npm', ['install', name, '--save'], spawnOpts); + + if (res.status) { + process.exit(res.status); + } + + res = spawnSync('rnpm', ['link', name], spawnOpts); + + if (res.status) { + process.exit(res.status); + } + + log.info(`Module ${name} has been successfully installed & linked`); +}; diff --git a/local-cli/rnpm/install/src/uninstall.js b/local-cli/rnpm/install/src/uninstall.js new file mode 100644 index 00000000000000..a1d7d9fc2d6b49 --- /dev/null +++ b/local-cli/rnpm/install/src/uninstall.js @@ -0,0 +1,26 @@ +const spawnSync = require('child_process').spawnSync; +const log = require('npmlog'); +const spawnOpts = { + stdio: 'inherit', + stdin: 'inherit', +}; + +log.heading = 'rnpm-install'; + +module.exports = function install(config, args, callback) { + const name = args[0]; + + var res = spawnSync('rnpm', ['unlink', name], spawnOpts); + + if (res.status) { + process.exit(res.status); + } + + res = spawnSync('npm', ['uninstall', name], spawnOpts); + + if (res.status) { + process.exit(res.status); + } + + log.info(`Module ${name} has been successfully uninstalled & unlinked`); +}; diff --git a/local-cli/rnpm/link/index.js b/local-cli/rnpm/link/index.js new file mode 100644 index 00000000000000..13b39b59956315 --- /dev/null +++ b/local-cli/rnpm/link/index.js @@ -0,0 +1,9 @@ +module.exports = [{ + func: require('./src/link'), + description: 'Links all native dependencies', + name: 'link [packageName]', +}, { + func: require('./src/unlink'), + description: 'Unlink native dependency', + name: 'unlink ', +}]; diff --git a/local-cli/rnpm/link/package.json b/local-cli/rnpm/link/package.json new file mode 100644 index 00000000000000..74f49e1a3aac4e --- /dev/null +++ b/local-cli/rnpm/link/package.json @@ -0,0 +1,52 @@ +{ + "name": "rnpm-plugin-link", + "version": "1.7.4", + "description": "rnpm plugin that links native dependencies to your project", + "main": "index.js", + "scripts": { + "test": "eslint ./ && mocha ./test --recursive" + }, + "keywords": [ + "rnpm", + "react-native", + "react-native link" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/rnpm/rnpm.git" + }, + "engines": { + "node": ">= 4.0.0" + }, + "author": "Amazing React Native Community (https://github.com/facebook/react-native)", + "contributors": [ + "Alexey Kureev (https://github.com/Kureev)", + "Mike Grabowski (https://github.com/grabbou)" + ], + "bugs": { + "url": "https://github.com/rnpm/rnpm/issues" + }, + "homepage": "https://github.com/rnpm/rnpm#readme", + "license": "MIT", + "dependencies": { + "fs-extra": "^0.26.2", + "glob": "^7.0.0", + "inquirer": "^0.12.0", + "lodash": "^3.10.1", + "mime": "^1.3.4", + "npmlog": "^2.0.0", + "plist": "^1.2.0", + "semver": "^5.1.0", + "to-camel-case": "^1.0.0", + "xcode": "^0.8.2" + }, + "devDependencies": { + "babel-eslint": "^4.1.5", + "chai": "^3.4.1", + "eslint": "^1.9.0", + "mocha": "^2.3.4", + "mock-fs": "^3.5.0", + "mock-require": "^1.2.1", + "sinon": "^1.17.2" + } +} diff --git a/local-cli/rnpm/link/src/android/copyAssets.js b/local-cli/rnpm/link/src/android/copyAssets.js new file mode 100644 index 00000000000000..709bf0b95c770b --- /dev/null +++ b/local-cli/rnpm/link/src/android/copyAssets.js @@ -0,0 +1,17 @@ +const fs = require('fs-extra'); +const path = require('path'); +const groupFilesByType = require('../groupFilesByType'); + +/** + * Copies each file from an array of assets provided to targetPath directory + * + * For now, the only types of files that are handled are: + * - Fonts (otf, ttf) - copied to targetPath/fonts under original name + */ +module.exports = function copyAssetsAndroid(files, targetPath) { + const assets = groupFilesByType(files); + + (assets.font || []).forEach(asset => + fs.copySync(asset, path.join(targetPath, 'fonts', path.basename(asset))) + ); +}; diff --git a/local-cli/rnpm/link/src/android/fs.js b/local-cli/rnpm/link/src/android/fs.js new file mode 100644 index 00000000000000..faa31d671ddb12 --- /dev/null +++ b/local-cli/rnpm/link/src/android/fs.js @@ -0,0 +1,8 @@ +const fs = require('fs-extra'); + +exports.readFile = (file) => + () => fs.readFileSync(file, 'utf8'); + +exports.writeFile = (file, content) => content ? + fs.writeFileSync(file, content, 'utf8') : + (c) => fs.writeFileSync(file, c, 'utf8'); diff --git a/local-cli/rnpm/link/src/android/getPrefix.js b/local-cli/rnpm/link/src/android/getPrefix.js new file mode 100644 index 00000000000000..1c40029e9e0c3f --- /dev/null +++ b/local-cli/rnpm/link/src/android/getPrefix.js @@ -0,0 +1,16 @@ +const semver = require('semver'); +const versions = ['0.20', '0.18', '0.17']; + +module.exports = function getPrefix(rnVersion) { + const version = rnVersion.replace(/-.*/, ''); + var prefix = 'patches/0.20'; + + versions.forEach((item, i) => { + const nextVersion = versions[i + 1]; + if (semver.lt(version, item + '.0') && nextVersion) { + prefix = `patches/${nextVersion}`; + } + }); + + return prefix; +}; diff --git a/local-cli/rnpm/link/src/android/isInstalled.js b/local-cli/rnpm/link/src/android/isInstalled.js new file mode 100644 index 00000000000000..3389a383fbbd7c --- /dev/null +++ b/local-cli/rnpm/link/src/android/isInstalled.js @@ -0,0 +1,8 @@ +const fs = require('fs'); +const makeBuildPatch = require(`./patches/makeBuildPatch`); + +module.exports = function isInstalled(config, name) { + return fs + .readFileSync(config.buildGradlePath) + .indexOf(makeBuildPatch(name).patch) > -1; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js new file mode 100644 index 00000000000000..c7bcf3d794e8b1 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.17/makeImportPatch.js @@ -0,0 +1,6 @@ +module.exports = function makeImportPatch(packageImportPath) { + return { + pattern: 'import android.app.Activity;', + patch: '\n' + packageImportPath, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js b/local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js new file mode 100644 index 00000000000000..ea4552c198bbe7 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.17/makePackagePatch.js @@ -0,0 +1,10 @@ +const applyParams = require('../applyParams'); + +module.exports = function makePackagePatch(packageInstance, params, prefix) { + const processedInstance = applyParams(packageInstance, params, prefix); + + return { + pattern: '.addPackage(new MainReactPackage())', + patch: `\n .addPackage(${processedInstance})`, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js new file mode 100644 index 00000000000000..687d61bd4fd6c4 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.18/makeImportPatch.js @@ -0,0 +1,6 @@ +module.exports = function makeImportPatch(packageImportPath) { + return { + pattern: 'import com.facebook.react.ReactActivity;', + patch: '\n' + packageImportPath, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js b/local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js new file mode 100644 index 00000000000000..6609303902de3e --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.18/makePackagePatch.js @@ -0,0 +1,10 @@ +const applyParams = require('../applyParams'); + +module.exports = function makePackagePatch(packageInstance, params, prefix) { + const processedInstance = applyParams(packageInstance, params, prefix); + + return { + pattern: 'new MainReactPackage()', + patch: ',\n ' + processedInstance, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js b/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js new file mode 100644 index 00000000000000..687d61bd4fd6c4 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.20/makeImportPatch.js @@ -0,0 +1,6 @@ +module.exports = function makeImportPatch(packageImportPath) { + return { + pattern: 'import com.facebook.react.ReactActivity;', + patch: '\n' + packageImportPath, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js b/local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js new file mode 100644 index 00000000000000..17120b14272211 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/0.20/makePackagePatch.js @@ -0,0 +1,10 @@ +const applyParams = require('../applyParams'); + +module.exports = function makePackagePatch(packageInstance, params, prefix) { + const processedInstance = applyParams(packageInstance, params, prefix); + + return { + pattern: 'new MainReactPackage()', + patch: ',\n ' + processedInstance, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/applyParams.js b/local-cli/rnpm/link/src/android/patches/applyParams.js new file mode 100644 index 00000000000000..90973c2f589a91 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/applyParams.js @@ -0,0 +1,14 @@ +const toCamelCase = require('to-camel-case'); + +module.exports = function applyParams(str, params, prefix) { + return str.replace( + /\$\{(\w+)\}/g, + (pattern, param) => { + const name = toCamelCase(prefix) + '_' + param; + + return params[param] + ? `this.getResources().getString(R.strings.${name})` + : null; + } + ); +}; diff --git a/local-cli/rnpm/link/src/android/patches/applyPatch.js b/local-cli/rnpm/link/src/android/patches/applyPatch.js new file mode 100644 index 00000000000000..67337d41ac243f --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/applyPatch.js @@ -0,0 +1,8 @@ +const fs = require('fs'); + +module.exports = function applyPatch(file, patch) { + fs.writeFileSync(file, fs + .readFileSync(file, 'utf8') + .replace(patch.pattern, match => `${match}${patch.patch}`) + ); +}; diff --git a/local-cli/rnpm/link/src/android/patches/makeBuildPatch.js b/local-cli/rnpm/link/src/android/patches/makeBuildPatch.js new file mode 100644 index 00000000000000..879acbd059b2ef --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/makeBuildPatch.js @@ -0,0 +1,6 @@ +module.exports = function makeBuildPatch(name) { + return { + pattern: /[^ \t]dependencies {\n/, + patch: ` compile project(':${name}')\n`, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/makeSettingsPatch.js b/local-cli/rnpm/link/src/android/patches/makeSettingsPatch.js new file mode 100644 index 00000000000000..af93d058703f34 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/makeSettingsPatch.js @@ -0,0 +1,26 @@ +const path = require('path'); +const isWin = process.platform === 'win32'; + +module.exports = function makeSettingsPatch(name, androidConfig, projectConfig) { + var projectDir = path.relative( + path.dirname(projectConfig.settingsGradlePath), + androidConfig.sourceDir + ); + + /* + * Fix for Windows + * Backslashes is the escape character and will result in + * an invalid path in settings.gradle + * https://github.com/rnpm/rnpm/issues/113 + */ + if (isWin) { + projectDir = projectDir.replace(/\\/g, '/'); + } + + return { + pattern: 'include \':app\'\n', + patch: `include ':${name}'\n` + + `project(':${name}').projectDir = ` + + `new File(rootProject.projectDir, '${projectDir}')\n`, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/makeStringsPatch.js b/local-cli/rnpm/link/src/android/patches/makeStringsPatch.js new file mode 100644 index 00000000000000..06300085dad429 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/makeStringsPatch.js @@ -0,0 +1,14 @@ +const toCamelCase = require('to-camel-case'); + +module.exports = function makeStringsPatch(params, prefix) { + const patch = Object.keys(params).map(param => { + const name = toCamelCase(prefix) + '_' + param; + return ' ' + + `${params[param]}`; + }).join('\n') + '\n'; + + return { + pattern: '\n', + patch: patch, + }; +}; diff --git a/local-cli/rnpm/link/src/android/patches/revokePatch.js b/local-cli/rnpm/link/src/android/patches/revokePatch.js new file mode 100644 index 00000000000000..8905923b1b3413 --- /dev/null +++ b/local-cli/rnpm/link/src/android/patches/revokePatch.js @@ -0,0 +1,8 @@ +const fs = require('fs'); + +module.exports = function revokePatch(file, patch) { + fs.writeFileSync(file, fs + .readFileSync(file, 'utf8') + .replace(patch.patch, '') + ); +}; diff --git a/local-cli/rnpm/link/src/android/registerNativeModule.js b/local-cli/rnpm/link/src/android/registerNativeModule.js new file mode 100644 index 00000000000000..fa01c2081bd222 --- /dev/null +++ b/local-cli/rnpm/link/src/android/registerNativeModule.js @@ -0,0 +1,38 @@ +const fs = require('fs'); +const getReactVersion = require('../getReactNativeVersion'); +const getPrefix = require('./getPrefix'); + +const applyPatch = require('./patches/applyPatch'); +const makeStringsPatch = require('./patches/makeStringsPatch'); +const makeSettingsPatch = require(`./patches/makeSettingsPatch`); +const makeBuildPatch = require(`./patches/makeBuildPatch`); + +module.exports = function registerNativeAndroidModule( + name, + androidConfig, + params, + projectConfig +) { + const buildPatch = makeBuildPatch(name); + const prefix = getPrefix(getReactVersion(projectConfig.folder)); + const makeImportPatch = require(`./${prefix}/makeImportPatch`); + const makePackagePatch = require(`./${prefix}/makePackagePatch`); + + applyPatch( + projectConfig.settingsGradlePath, + makeSettingsPatch(name, androidConfig, projectConfig) + ); + + applyPatch(projectConfig.buildGradlePath, buildPatch); + applyPatch(projectConfig.stringsPath, makeStringsPatch(params, name)); + + applyPatch( + projectConfig.mainActivityPath, + makePackagePatch(androidConfig.packageInstance, params, name) + ); + + applyPatch( + projectConfig.mainActivityPath, + makeImportPatch(androidConfig.packageImportPath) + ); +}; diff --git a/local-cli/rnpm/link/src/android/unlinkAssets.js b/local-cli/rnpm/link/src/android/unlinkAssets.js new file mode 100644 index 00000000000000..79c51a1864515e --- /dev/null +++ b/local-cli/rnpm/link/src/android/unlinkAssets.js @@ -0,0 +1,20 @@ +const fs = require('fs-extra'); +const path = require('path'); +const groupFilesByType = require('../groupFilesByType'); + +/** + * Copies each file from an array of assets provided to targetPath directory + * + * For now, the only types of files that are handled are: + * - Fonts (otf, ttf) - copied to targetPath/fonts under original name + */ +module.exports = function unlinkAssetsAndroid(files, targetPath) { + const grouped = groupFilesByType(files); + + grouped.font.forEach((file) => { + const filename = path.basename(file); + if (fs.existsSync(filename)) { + fs.unlinkSync(path.join(targetPath, 'fonts', filename)); + } + }); +}; diff --git a/local-cli/rnpm/link/src/android/unregisterNativeModule.js b/local-cli/rnpm/link/src/android/unregisterNativeModule.js new file mode 100644 index 00000000000000..64db4a0fb6ce36 --- /dev/null +++ b/local-cli/rnpm/link/src/android/unregisterNativeModule.js @@ -0,0 +1,47 @@ +const fs = require('fs'); +const getReactVersion = require('../getReactNativeVersion'); +const getPrefix = require('./getPrefix'); +const toCamelCase = require('to-camel-case'); + +const revokePatch = require('./patches/revokePatch'); +const makeSettingsPatch = require('./patches/makeSettingsPatch'); +const makeBuildPatch = require('./patches/makeBuildPatch'); +const makeStringsPatch = require('./patches/makeStringsPatch'); + +module.exports = function unregisterNativeAndroidModule( + name, + androidConfig, + projectConfig +) { + const buildPatch = makeBuildPatch(name); + const prefix = getPrefix(getReactVersion(projectConfig.folder)); + const makeImportPatch = require(`./${prefix}/makeImportPatch`); + const makePackagePatch = require(`./${prefix}/makePackagePatch`); + const strings = fs.readFileSync(projectConfig.stringsPath, 'utf8'); + var params = {}; + + strings.replace( + /moduleConfig="true" name="(\w+)">(.*) { + params[param.slice(toCamelCase(name).length + 1)] = value; + } + ); + + revokePatch( + projectConfig.settingsGradlePath, + makeSettingsPatch(name, androidConfig, projectConfig) + ); + + revokePatch(projectConfig.buildGradlePath, buildPatch); + revokePatch(projectConfig.stringsPath, makeStringsPatch(params, name)); + + revokePatch( + projectConfig.mainActivityPath, + makePackagePatch(androidConfig.packageInstance, params, name) + ); + + revokePatch( + projectConfig.mainActivityPath, + makeImportPatch(androidConfig.packageImportPath) + ); +}; diff --git a/local-cli/rnpm/link/src/getDependencyConfig.js b/local-cli/rnpm/link/src/getDependencyConfig.js new file mode 100644 index 00000000000000..597be6aab5cb30 --- /dev/null +++ b/local-cli/rnpm/link/src/getDependencyConfig.js @@ -0,0 +1,17 @@ +/** + * Given an array of dependencies - it returns their RNPM config + * if they were valid. + */ +module.exports = function getDependencyConfig(config, deps) { + return deps.reduce((acc, name) => { + try { + return acc.concat({ + config: config.getDependencyConfig(name), + name, + }); + } catch (err) { + console.log(err); + return acc; + } + }, []); +}; diff --git a/local-cli/rnpm/link/src/getProjectDependencies.js b/local-cli/rnpm/link/src/getProjectDependencies.js new file mode 100644 index 00000000000000..1897395371f9fe --- /dev/null +++ b/local-cli/rnpm/link/src/getProjectDependencies.js @@ -0,0 +1,9 @@ +const path = require('path'); + +/** + * Returns an array of dependencies that should be linked/checked. + */ +module.exports = function getProjectDependencies() { + const pjson = require(path.join(process.cwd(), './package.json')); + return Object.keys(pjson.dependencies || {}).filter(name => name !== 'react-native'); +}; diff --git a/local-cli/rnpm/link/src/getReactNativeVersion.js b/local-cli/rnpm/link/src/getReactNativeVersion.js new file mode 100644 index 00000000000000..a1756e9e0bb3dc --- /dev/null +++ b/local-cli/rnpm/link/src/getReactNativeVersion.js @@ -0,0 +1,5 @@ +const path = require('path'); + +module.exports = (folder) => require( + path.join(folder, 'node_modules', 'react-native', 'package.json') +).version; diff --git a/local-cli/rnpm/link/src/groupFilesByType.js b/local-cli/rnpm/link/src/groupFilesByType.js new file mode 100644 index 00000000000000..ddf0df6dbe00fa --- /dev/null +++ b/local-cli/rnpm/link/src/groupFilesByType.js @@ -0,0 +1,27 @@ +const groupBy = require('lodash').groupBy; +const mime = require('mime'); + +/** + * Since there are no officialy registered MIME types + * for ttf/otf yet http://www.iana.org/assignments/media-types/media-types.xhtml, + * we define two non-standard ones for the sake of parsing + */ +mime.define({ + 'font/opentype': ['otf'], + 'font/truetype': ['ttf'], +}); + +/** + * Given an array of files, it groups it by it's type. + * Type of the file is inferred from it's mimetype based on the extension + * file ends up with. The returned value is an object with properties that + * correspond to the first part of the mimetype, e.g. images will be grouped + * under `image` key since the mimetype for them is `image/jpg` etc. + * + * Example: + * Given an array ['fonts/a.ttf', 'images/b.jpg'], + * the returned object will be: {font: ['fonts/a.ttf'], image: ['images/b.jpg']} + */ +module.exports = function groupFilesByType(assets) { + return groupBy(assets, type => mime.lookup(type).split('/')[0]); +}; diff --git a/local-cli/rnpm/link/src/ios/addFileToProject.js b/local-cli/rnpm/link/src/ios/addFileToProject.js new file mode 100644 index 00000000000000..eab1d12535ef75 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/addFileToProject.js @@ -0,0 +1,14 @@ +const PbxFile = require('xcode/lib/pbxFile'); + +/** + * Given xcodeproj and filePath, it creates new file + * from path provided, adds it to the project + * and returns newly created instance of a file + */ +module.exports = function addFileToProject(project, filePath) { + const file = new PbxFile(filePath); + file.uuid = project.generateUuid(); + file.fileRef = project.generateUuid(); + project.addToPbxFileReferenceSection(file); + return file; +}; diff --git a/local-cli/rnpm/link/src/ios/addProjectToLibraries.js b/local-cli/rnpm/link/src/ios/addProjectToLibraries.js new file mode 100644 index 00000000000000..4e65ef30f35241 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/addProjectToLibraries.js @@ -0,0 +1,13 @@ +/** + * Given an array of xcodeproj libraries and pbxFile, + * it appends it to that group + * + * Important: That function mutates `libraries` and it's not pure. + * It's mainly due to limitations of `xcode` library. + */ +module.exports = function addProjectToLibraries(libraries, file) { + return libraries.children.push({ + value: file.fileRef, + comment: file.basename, + }); +}; diff --git a/local-cli/rnpm/link/src/ios/addSharedLibraries.js b/local-cli/rnpm/link/src/ios/addSharedLibraries.js new file mode 100644 index 00000000000000..f84603abb30eeb --- /dev/null +++ b/local-cli/rnpm/link/src/ios/addSharedLibraries.js @@ -0,0 +1,3 @@ +module.exports = function addSharedLibraries(project, libraries) { + +}; diff --git a/local-cli/rnpm/link/src/ios/addToHeaderSearchPaths.js b/local-cli/rnpm/link/src/ios/addToHeaderSearchPaths.js new file mode 100644 index 00000000000000..1eff6055e4b6a7 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/addToHeaderSearchPaths.js @@ -0,0 +1,5 @@ +const mapHeaderSearchPaths = require('./mapHeaderSearchPaths'); + +module.exports = function addToHeaderSearchPaths(project, path) { + mapHeaderSearchPaths(project, searchPaths => searchPaths.concat(path)); +}; diff --git a/local-cli/rnpm/link/src/ios/copyAssets.js b/local-cli/rnpm/link/src/ios/copyAssets.js new file mode 100644 index 00000000000000..538381a035f8ca --- /dev/null +++ b/local-cli/rnpm/link/src/ios/copyAssets.js @@ -0,0 +1,57 @@ +const fs = require('fs-extra'); +const path = require('path'); +const xcode = require('xcode'); +const log = require('npmlog'); +const plistParser = require('plist'); +const groupFilesByType = require('../groupFilesByType'); +const createGroup = require('./createGroup'); +const getPlist = require('./getPlist'); +const getPlistPath = require('./getPlistPath'); + +/** + * This function works in a similar manner to its Android version, + * except it does not copy fonts but creates XCode Group references + */ +module.exports = function linkAssetsIOS(files, projectConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const assets = groupFilesByType(files); + const plist = getPlist(project, projectConfig.sourceDir); + + if (!plist) { + return log.error( + 'ERRPLIST', + `Could not locate Info.plist. Check if your project has 'INFOPLIST_FILE' set properly` + ); + } + + if (!project.pbxGroupByName('Resources')) { + createGroup(project, 'Resources'); + + log.warn( + 'ERRGROUP', + `Group 'Resources' does not exist in your XCode project. We have created it automatically for you.` + ); + } + + const fonts = (assets.font || []) + .map(asset => + project.addResourceFile( + path.relative(projectConfig.sourceDir, asset), + { target: project.getFirstTarget().uuid } + ) + ) + .filter(file => file) // xcode returns false if file is already there + .map(file => file.basename); + + plist.UIAppFonts = (plist.UIAppFonts || []).concat(fonts); + + fs.writeFileSync( + projectConfig.pbxprojPath, + project.writeSync() + ); + + fs.writeFileSync( + getPlistPath(project, projectConfig.sourceDir), + plistParser.build(plist) + ); +}; diff --git a/local-cli/rnpm/link/src/ios/createGroup.js b/local-cli/rnpm/link/src/ios/createGroup.js new file mode 100644 index 00000000000000..dac9ec66c5df40 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/createGroup.js @@ -0,0 +1,27 @@ +const getGroup = require('./getGroup'); + +const hasGroup = (pbxGroup, name) => pbxGroup.children.find(group => group.comment === name); + +/** + * Given project and path of the group, it deeply creates a given group + * making all outer groups if neccessary + * + * Returns newly created group + */ +module.exports = function createGroup(project, path) { + return path.split('/').reduce( + (group, name) => { + if (!hasGroup(group, name)) { + const uuid = project.pbxCreateGroup(name, '""'); + + group.children.push({ + value: uuid, + comment: name, + }); + } + + return project.pbxGroupByName(name); + }, + getGroup(project) + ); +}; diff --git a/local-cli/rnpm/link/src/ios/getBuildProperty.js b/local-cli/rnpm/link/src/ios/getBuildProperty.js new file mode 100644 index 00000000000000..181a74ec9302c3 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getBuildProperty.js @@ -0,0 +1,18 @@ +/** + * Gets build property from the main target build section + * + * It differs from the project.getBuildProperty exposed by xcode in the way that: + * - it only checks for build property in the main target `Debug` section + * - `xcode` library iterates over all build sections and because it misses + * an early return when property is found, it will return undefined/wrong value + * when there's another build section typically after the one you want to access + * without the property defined (e.g. CocoaPods sections appended to project + * miss INFOPLIST_FILE), see: https://github.com/alunny/node-xcode/blob/master/lib/pbxProject.js#L1765 + */ +module.exports = function getBuildProperty(project, prop) { + const target = project.getFirstTarget().firstTarget; + const config = project.pbxXCConfigurationList()[target.buildConfigurationList]; + const buildSection = project.pbxXCBuildConfigurationSection()[config.buildConfigurations[0].value]; + + return buildSection.buildSettings[prop]; +}; diff --git a/local-cli/rnpm/link/src/ios/getGroup.js b/local-cli/rnpm/link/src/ios/getGroup.js new file mode 100644 index 00000000000000..ab24df8d3d511d --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getGroup.js @@ -0,0 +1,34 @@ +const getFirstProject = (project) => project.getFirstProject().firstProject; + +const findGroup = (group, name) => group.children.find(group => group.comment === name); + +/** + * Returns group from .xcodeproj if one exists, null otherwise + * + * Unlike node-xcode `pbxGroupByName` - it does not return `first-matching` + * group if multiple groups with the same name exist + * + * If path is not provided, it returns top-level group + */ +module.exports = function getGroup(project, path) { + const firstProject = getFirstProject(project); + + var group = project.getPBXGroupByKey(firstProject.mainGroup); + + if (!path) { + return group; + } + + for (var name of path.split('/')) { + var foundGroup = findGroup(group, name); + + if (foundGroup) { + group = project.getPBXGroupByKey(foundGroup.value); + } else { + group = null; + break; + } + } + + return group; +}; diff --git a/local-cli/rnpm/link/src/ios/getHeaderSearchPath.js b/local-cli/rnpm/link/src/ios/getHeaderSearchPath.js new file mode 100644 index 00000000000000..750c060914acb9 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getHeaderSearchPath.js @@ -0,0 +1,52 @@ +const path = require('path'); +const union = require('lodash').union; +const last = require('lodash').last; + +/** + * Given an array of directories, it returns the one that contains + * all the other directories in a given array inside it. + * + * Example: + * Given an array of directories: ['/Users/Kureev/a', '/Users/Kureev/b'] + * the returned folder is `/Users/Kureev` + * + * Check `getHeaderSearchPath.spec.js` for more use-cases. + */ +const getOuterDirectory = (directories) => + directories.reduce((topDir, currentDir) => { + const currentFolders = currentDir.split(path.sep); + const topMostFolders = topDir.split(path.sep); + + if (currentFolders.length === topMostFolders.length + && last(currentFolders) !== last(topMostFolders)) { + return currentFolders.slice(0, -1).join(path.sep); + } + + return currentFolders.length < topMostFolders.length + ? currentDir + : topDir; + }); + +/** + * Given an array of headers it returns search path so Xcode can resolve + * headers when referenced like below: + * ``` + * #import "CodePush.h" + * ``` + * If all files are located in one directory (directories.length === 1), + * we simply return a relative path to that location. + * + * Otherwise, we loop through them all to find the outer one that contains + * all the headers inside. That location is then returned with /** appended at + * the end so Xcode marks that location as `recursive` and will look inside + * every folder of it to locate correct headers. + */ +module.exports = function getHeaderSearchPath(sourceDir, headers) { + const directories = union( + headers.map(path.dirname) + ); + + return directories.length === 1 + ? `"$(SRCROOT)${path.sep}${path.relative(sourceDir, directories[0])}"` + : `"$(SRCROOT)${path.sep}${path.relative(sourceDir, getOuterDirectory(directories))}/**"`; +}; diff --git a/local-cli/rnpm/link/src/ios/getHeadersInFolder.js b/local-cli/rnpm/link/src/ios/getHeadersInFolder.js new file mode 100644 index 00000000000000..7dc954db59a6c6 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getHeadersInFolder.js @@ -0,0 +1,18 @@ +const glob = require('glob'); +const path = require('path'); + +const GLOB_EXCLUDE_PATTERN = ['node_modules/**', 'Pods/**', 'Examples/**', 'examples/**']; + +/** + * Given folder, it returns an array of all header files + * inside it, ignoring node_modules and examples + */ +module.exports = function getHeadersInFolder(folder) { + return glob + .sync('**/*.h', { + cwd: folder, + nodir: true, + ignore: GLOB_EXCLUDE_PATTERN, + }) + .map(file => path.join(folder, file)); +}; diff --git a/local-cli/rnpm/link/src/ios/getPlist.js b/local-cli/rnpm/link/src/ios/getPlist.js new file mode 100644 index 00000000000000..9190aa3459d86e --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getPlist.js @@ -0,0 +1,20 @@ +const plistParser = require('plist'); +const getPlistPath = require('./getPlistPath'); +const fs = require('fs'); + +/** + * Returns Info.plist located in the iOS project + * + * Returns `null` if INFOPLIST_FILE is not specified. + */ +module.exports = function getPlist(project, sourceDir) { + const plistPath = getPlistPath(project, sourceDir); + + if (!plistPath || !fs.existsSync(plistPath)) { + return null; + } + + return plistParser.parse( + fs.readFileSync(plistPath, 'utf-8') + ); +}; diff --git a/local-cli/rnpm/link/src/ios/getPlistPath.js b/local-cli/rnpm/link/src/ios/getPlistPath.js new file mode 100644 index 00000000000000..44b810ab1e298b --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getPlistPath.js @@ -0,0 +1,15 @@ +const path = require('path'); +const getBuildProperty = require('./getBuildProperty'); + +module.exports = function getPlistPath(project, sourceDir) { + const plistFile = getBuildProperty(project, 'INFOPLIST_FILE'); + + if (!plistFile) { + return null; + } + + return path.join( + sourceDir, + plistFile.replace(/"/g, '').replace('$(SRCROOT)', '') + ); +}; diff --git a/local-cli/rnpm/link/src/ios/getProducts.js b/local-cli/rnpm/link/src/ios/getProducts.js new file mode 100644 index 00000000000000..b51492734c35b2 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/getProducts.js @@ -0,0 +1,12 @@ +/** + * Given xcodeproj it returns list of products ending with + * .a extension, so that we know what elements add to target + * project static library + */ +module.exports = function getProducts(project) { + return project + .pbxGroupByName('Products') + .children + .map(c => c.comment) + .filter(c => c.indexOf('.a') > -1); +}; diff --git a/local-cli/rnpm/link/src/ios/hasLibraryImported.js b/local-cli/rnpm/link/src/ios/hasLibraryImported.js new file mode 100644 index 00000000000000..b380309344e41e --- /dev/null +++ b/local-cli/rnpm/link/src/ios/hasLibraryImported.js @@ -0,0 +1,10 @@ +/** + * Given an array of libraries already imported and packageName that will be + * added, returns true or false depending on whether the library is already linked + * or not + */ +module.exports = function hasLibraryImported(libraries, packageName) { + return libraries.children + .filter(library => library.comment === packageName) + .length > 0; +}; diff --git a/local-cli/rnpm/link/src/ios/isInstalled.js b/local-cli/rnpm/link/src/ios/isInstalled.js new file mode 100644 index 00000000000000..81b1fff01fb9ff --- /dev/null +++ b/local-cli/rnpm/link/src/ios/isInstalled.js @@ -0,0 +1,18 @@ +const xcode = require('xcode'); +const getGroup = require('./getGroup'); +const hasLibraryImported = require('./hasLibraryImported'); + +/** + * Returns true if `xcodeproj` specified by dependencyConfig is present + * in a top level `libraryFolder` + */ +module.exports = function isInstalled(projectConfig, dependencyConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const libraries = getGroup(project, projectConfig.libraryFolder); + + if (!libraries) { + return false; + } + + return hasLibraryImported(libraries, dependencyConfig.projectName); +}; diff --git a/local-cli/rnpm/link/src/ios/mapHeaderSearchPaths.js b/local-cli/rnpm/link/src/ios/mapHeaderSearchPaths.js new file mode 100644 index 00000000000000..ea76aac832d3bc --- /dev/null +++ b/local-cli/rnpm/link/src/ios/mapHeaderSearchPaths.js @@ -0,0 +1,36 @@ +/** + * Given Xcode project and path, iterate over all build configurations + * and execute func with HEADER_SEARCH_PATHS from current section + * + * We cannot use builtin addToHeaderSearchPaths method since react-native init does not + * use $(TARGET_NAME) for PRODUCT_NAME, but sets it manually so that method will skip + * that target. + * + * To workaround that issue and make it more bullet-proof for different names, + * we iterate over all configurations and look if React is already there. If it is, + * we assume we want to modify that section either + * + * Important: That function mutates `buildSettings` and it's not pure thus you should + * not rely on its return value + */ +module.exports = function headerSearchPathIter(project, func) { + const config = project.pbxXCBuildConfigurationSection(); + + Object + .keys(config) + .filter(ref => ref.indexOf('_comment') === -1) + .forEach(ref => { + const buildSettings = config[ref].buildSettings; + const shouldVisitBuildSettings = ( + Array.isArray(buildSettings.HEADER_SEARCH_PATHS) ? + buildSettings.HEADER_SEARCH_PATHS : + [] + ) + .filter(path => path.indexOf('react-native/React/**')) + .length > 0; + + if (shouldVisitBuildSettings) { + buildSettings.HEADER_SEARCH_PATHS = func(buildSettings.HEADER_SEARCH_PATHS); + } + }); +}; diff --git a/local-cli/rnpm/link/src/ios/registerNativeModule.js b/local-cli/rnpm/link/src/ios/registerNativeModule.js new file mode 100644 index 00000000000000..6d8bce262e761e --- /dev/null +++ b/local-cli/rnpm/link/src/ios/registerNativeModule.js @@ -0,0 +1,67 @@ +const xcode = require('xcode'); +const fs = require('fs'); +const path = require('path'); +const log = require('npmlog'); + +const addToHeaderSearchPaths = require('./addToHeaderSearchPaths'); +const getHeadersInFolder = require('./getHeadersInFolder'); +const getHeaderSearchPath = require('./getHeaderSearchPath'); +const getProducts = require('./getProducts'); +const createGroup = require('./createGroup'); +const hasLibraryImported = require('./hasLibraryImported'); +const addFileToProject = require('./addFileToProject'); +const addProjectToLibraries = require('./addProjectToLibraries'); +const addSharedLibraries = require('./addSharedLibraries'); +const isEmpty = require('lodash').isEmpty; +const getGroup = require('./getGroup'); + +/** + * Register native module IOS adds given dependency to project by adding + * its xcodeproj to project libraries as well as attaching static library + * to the first target (the main one) + * + * If library is already linked, this action is a no-op. + */ +module.exports = function registerNativeModuleIOS(dependencyConfig, projectConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const dependencyProject = xcode.project(dependencyConfig.pbxprojPath).parseSync(); + + var libraries = getGroup(project, projectConfig.libraryFolder); + + if (!libraries) { + libraries = createGroup(project, projectConfig.libraryFolder); + + log.warn( + 'ERRGROUP', + `Group ${projectConfig.libraryFolder} does not exist in your XCode project. We have created it automatically for you.` + ); + } + + const file = addFileToProject( + project, + path.relative(projectConfig.sourceDir, dependencyConfig.projectPath) + ); + + addProjectToLibraries(libraries, file); + + getProducts(dependencyProject).forEach(product => { + project.addStaticLibrary(product, { + target: project.getFirstTarget().uuid, + }); + }); + + addSharedLibraries(project, dependencyConfig.sharedLibraries); + + const headers = getHeadersInFolder(dependencyConfig.folder); + if (!isEmpty(headers)) { + addToHeaderSearchPaths( + project, + getHeaderSearchPath(projectConfig.sourceDir, headers) + ); + } + + fs.writeFileSync( + projectConfig.pbxprojPath, + project.writeSync() + ); +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromHeaderSearchPaths.js b/local-cli/rnpm/link/src/ios/removeFromHeaderSearchPaths.js new file mode 100644 index 00000000000000..07259f1d223e0b --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromHeaderSearchPaths.js @@ -0,0 +1,10 @@ +const mapHeaderSearchPaths = require('./mapHeaderSearchPaths'); + +/** + * Given Xcode project and absolute path, it makes sure there are no headers referring to it + */ +module.exports = function addToHeaderSearchPaths(project, path) { + mapHeaderSearchPaths(project, + searchPaths => searchPaths.filter(searchPath => searchPath !== path) + ); +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromPbxItemContainerProxySection.js b/local-cli/rnpm/link/src/ios/removeFromPbxItemContainerProxySection.js new file mode 100644 index 00000000000000..e0a6a84a7a65dc --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromPbxItemContainerProxySection.js @@ -0,0 +1,16 @@ +/** + * For all files that are created and referenced from another `.xcodeproj` - + * a new PBXItemContainerProxy is created that contains `containerPortal` value + * which equals to xcodeproj file.uuid from PBXFileReference section. + */ +module.exports = function removeFromPbxItemContainerProxySection(project, file) { + const section = project.hash.project.objects.PBXContainerItemProxy; + + for (var key of Object.keys(section)) { + if (section[key].containerPortal === file.uuid) { + delete section[key]; + } + } + + return; +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromPbxReferenceProxySection.js b/local-cli/rnpm/link/src/ios/removeFromPbxReferenceProxySection.js new file mode 100644 index 00000000000000..b867964ff6ef0a --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromPbxReferenceProxySection.js @@ -0,0 +1,15 @@ +/** + * Every file added to the project from another project is attached to + * `PBXItemContainerProxy` through `PBXReferenceProxy`. + */ +module.exports = function removeFromPbxReferenceProxySection(project, file) { + const section = project.hash.project.objects.PBXReferenceProxy; + + for (var key of Object.keys(section)) { + if (section[key].path === file.basename) { + delete section[key]; + } + } + + return; +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromProjectReferences.js b/local-cli/rnpm/link/src/ios/removeFromProjectReferences.js new file mode 100644 index 00000000000000..652b189295c297 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromProjectReferences.js @@ -0,0 +1,26 @@ +/** + * For each file (.xcodeproj), there's an entry in `projectReferences` created + * that has two entries - `ProjectRef` - reference to a file.uuid and + * `ProductGroup` - uuid of a Products group. + * + * When projectReference is found - it's deleted and the removed value is returned + * so that ProductGroup in PBXGroup section can be removed as well. + * + * Otherwise returns null + */ +module.exports = function removeFromProjectReferences(project, file) { + const firstProject = project.getFirstProject().firstProject; + + const projectRef = firstProject.projectReferences.find(item => item.ProjectRef === file.uuid); + + if (!projectRef) { + return null; + } + + firstProject.projectReferences.splice( + firstProject.projectReferences.indexOf(projectRef), + 1 + ); + + return projectRef; +}; diff --git a/local-cli/rnpm/link/src/ios/removeFromStaticLibraries.js b/local-cli/rnpm/link/src/ios/removeFromStaticLibraries.js new file mode 100644 index 00000000000000..2a66fa44aac879 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeFromStaticLibraries.js @@ -0,0 +1,21 @@ +const PbxFile = require('xcode/lib/pbxFile'); +const removeFromPbxReferenceProxySection = require('./removeFromPbxReferenceProxySection'); + +/** + * Removes file from static libraries + * + * Similar to `node-xcode` addStaticLibrary + */ +module.exports = function removeFromStaticLibraries(project, path, opts) { + const file = new PbxFile(path); + + file.target = opts ? opts.target : undefined; + + project.removeFromPbxFileReferenceSection(file); + project.removeFromPbxBuildFileSection(file); + project.removeFromPbxFrameworksBuildPhase(file); + project.removeFromLibrarySearchPaths(file); + removeFromPbxReferenceProxySection(project, file); + + return file; +}; diff --git a/local-cli/rnpm/link/src/ios/removeProductGroup.js b/local-cli/rnpm/link/src/ios/removeProductGroup.js new file mode 100644 index 00000000000000..186aed7f7b17f8 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeProductGroup.js @@ -0,0 +1,11 @@ +module.exports = function removeProductGroup(project, productGroupId) { + const section = project.hash.project.objects.PBXGroup; + + for (var key of Object.keys(section)) { + if (key === productGroupId) { + delete section[key]; + } + } + + return; +}; diff --git a/local-cli/rnpm/link/src/ios/removeProjectFromLibraries.js b/local-cli/rnpm/link/src/ios/removeProjectFromLibraries.js new file mode 100644 index 00000000000000..20965a67e09292 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeProjectFromLibraries.js @@ -0,0 +1,12 @@ +/** + * Given an array of xcodeproj libraries and pbxFile, + * it removes it from that group by comparing basenames + * + * Important: That function mutates `libraries` and it's not pure. + * It's mainly due to limitations of `xcode` library. + */ +module.exports = function removeProjectFromLibraries(libraries, file) { + libraries.children = libraries.children.filter(library => + library.comment !== file.basename + ); +}; diff --git a/local-cli/rnpm/link/src/ios/removeProjectFromProject.js b/local-cli/rnpm/link/src/ios/removeProjectFromProject.js new file mode 100644 index 00000000000000..3a3f173dbe682d --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeProjectFromProject.js @@ -0,0 +1,26 @@ +const PbxFile = require('xcode/lib/pbxFile'); +const removeFromPbxItemContainerProxySection = require('./removeFromPbxItemContainerProxySection'); +const removeFromProjectReferences = require('./removeFromProjectReferences'); +const removeProductGroup = require('./removeProductGroup'); + +/** + * Given xcodeproj and filePath, it creates new file + * from path provided and removes it. That operation is required since + * underlying method requires PbxFile instance to be passed (it does not + * have to have uuid or fileRef defined since it will do equality check + * by path) + * + * Returns removed file (that one will have UUID) + */ +module.exports = function removeProjectFromProject(project, filePath) { + const file = project.removeFromPbxFileReferenceSection(new PbxFile(filePath)); + const projectRef = removeFromProjectReferences(project, file); + + if (projectRef) { + removeProductGroup(project, projectRef.ProductGroup); + } + + removeFromPbxItemContainerProxySection(project, file); + + return file; +}; diff --git a/local-cli/rnpm/link/src/ios/removeSharedLibraries.js b/local-cli/rnpm/link/src/ios/removeSharedLibraries.js new file mode 100644 index 00000000000000..82e611f5fa63b0 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/removeSharedLibraries.js @@ -0,0 +1,3 @@ +module.exports = function removeSharedLibraries(project, libraries) { + +}; diff --git a/local-cli/rnpm/link/src/ios/unlinkAssets.js b/local-cli/rnpm/link/src/ios/unlinkAssets.js new file mode 100644 index 00000000000000..44422592985cce --- /dev/null +++ b/local-cli/rnpm/link/src/ios/unlinkAssets.js @@ -0,0 +1,54 @@ +const fs = require('fs-extra'); +const path = require('path'); +const xcode = require('xcode'); +const log = require('npmlog'); +const plistParser = require('plist'); +const groupFilesByType = require('../groupFilesByType'); +const getPlist = require('./getPlist'); +const getPlistPath = require('./getPlistPath'); +const difference = require('lodash').difference; + +/** + * Unlinks assets from iOS project. Removes references for fonts from `Info.plist` + * fonts provided by application and from `Resources` group + */ +module.exports = function unlinkAssetsIOS(files, projectConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const assets = groupFilesByType(files); + const plist = getPlist(project, projectConfig.sourceDir); + + if (!plist) { + return log.error( + 'ERRPLIST', + `Could not locate Info.plist file. Check if your project has 'INFOPLIST_FILE' set properly` + ); + } + + if (!project.pbxGroupByName('Resources')) { + return log.error( + 'ERRGROUP', + `Group 'Resources' does not exist in your XCode project. There is nothing to unlink.` + ); + } + + const fonts = (assets.font || []) + .map(asset => + project.removeResourceFile( + path.relative(projectConfig.sourceDir, asset), + { target: project.getFirstTarget().uuid } + ) + ) + .map(file => file.basename); + + plist.UIAppFonts = difference(plist.UIAppFonts || [], fonts); + + fs.writeFileSync( + projectConfig.pbxprojPath, + project.writeSync() + ); + + fs.writeFileSync( + getPlistPath(project, projectConfig.sourceDir), + plistParser.build(plist) + ); +}; diff --git a/local-cli/rnpm/link/src/ios/unregisterNativeModule.js b/local-cli/rnpm/link/src/ios/unregisterNativeModule.js new file mode 100644 index 00000000000000..509848a5552897 --- /dev/null +++ b/local-cli/rnpm/link/src/ios/unregisterNativeModule.js @@ -0,0 +1,54 @@ +const xcode = require('xcode'); +const path = require('path'); +const fs = require('fs'); + +const getProducts = require('./getProducts'); +const getHeadersInFolder = require('./getHeadersInFolder'); +const isEmpty = require('lodash').isEmpty; +const getHeaderSearchPath = require('./getHeaderSearchPath'); +const removeProjectFromProject = require('./removeProjectFromProject'); +const removeProjectFromLibraries = require('./removeProjectFromLibraries'); +const removeFromStaticLibraries = require('./removeFromStaticLibraries'); +const removeFromHeaderSearchPaths = require('./removeFromHeaderSearchPaths'); +const removeSharedLibraries = require('./addSharedLibraries'); +const getGroup = require('./getGroup'); + +/** + * Unregister native module IOS + * + * If library is already unlinked, this action is a no-op. + */ +module.exports = function unregisterNativeModule(dependencyConfig, projectConfig) { + const project = xcode.project(projectConfig.pbxprojPath).parseSync(); + const dependencyProject = xcode.project(dependencyConfig.pbxprojPath).parseSync(); + + const libraries = getGroup(project, projectConfig.libraryFolder); + + const file = removeProjectFromProject( + project, + path.relative(projectConfig.sourceDir, dependencyConfig.projectPath) + ); + + removeProjectFromLibraries(libraries, file); + + getProducts(dependencyProject).forEach(product => { + removeFromStaticLibraries(project, product, { + target: project.getFirstTarget().uuid, + }); + }); + + removeSharedLibraries(project, dependencyConfig.sharedLibraries); + + const headers = getHeadersInFolder(dependencyConfig.folder); + if (!isEmpty(headers)) { + removeFromHeaderSearchPaths( + project, + getHeaderSearchPath(projectConfig.sourceDir, headers) + ); + } + + fs.writeFileSync( + projectConfig.pbxprojPath, + project.writeSync() + ); +}; diff --git a/local-cli/rnpm/link/src/link.js b/local-cli/rnpm/link/src/link.js new file mode 100644 index 00000000000000..51261aa30f741c --- /dev/null +++ b/local-cli/rnpm/link/src/link.js @@ -0,0 +1,136 @@ +const log = require('npmlog'); +const path = require('path'); +const uniq = require('lodash').uniq; +const flatten = require('lodash').flatten; +const pkg = require('../package.json'); + +const isEmpty = require('lodash').isEmpty; +const promiseWaterfall = require('./promiseWaterfall'); +const registerDependencyAndroid = require('./android/registerNativeModule'); +const registerDependencyIOS = require('./ios/registerNativeModule'); +const isInstalledAndroid = require('./android/isInstalled'); +const isInstalledIOS = require('./ios/isInstalled'); +const copyAssetsAndroid = require('./android/copyAssets'); +const copyAssetsIOS = require('./ios/copyAssets'); +const getProjectDependencies = require('./getProjectDependencies'); +const getDependencyConfig = require('./getDependencyConfig'); +const pollParams = require('./pollParams'); + +log.heading = 'rnpm-link'; + +const commandStub = (cb) => cb(); +const dedupeAssets = (assets) => uniq(assets, asset => path.basename(asset)); + +const promisify = (func) => new Promise((resolve, reject) => + func((err, res) => err ? reject(err) : resolve(res)) +); + +const linkDependencyAndroid = (androidProject, dependency) => { + if (!androidProject || !dependency.config.android) { + return null; + } + + const isInstalled = isInstalledAndroid(androidProject, dependency.name); + + if (isInstalled) { + log.info(`Android module ${dependency.name} is already linked`); + return null; + } + + return pollParams(dependency.config.params).then(params => { + log.info(`Linking ${dependency.name} android dependency`); + + registerDependencyAndroid( + dependency.name, + dependency.config.android, + params, + androidProject + ); + + log.info(`Android module ${dependency.name} has been successfully linked`); + }); +}; + +const linkDependencyIOS = (iOSProject, dependency) => { + if (!iOSProject || !dependency.config.ios) { + return; + } + + const isInstalled = isInstalledIOS(iOSProject, dependency.config.ios); + + if (isInstalled) { + log.info(`iOS module ${dependency.name} is already linked`); + return; + } + + log.info(`Linking ${dependency.name} ios dependency`); + + registerDependencyIOS(dependency.config.ios, iOSProject); + + log.info(`iOS module ${dependency.name} has been successfully linked`); +}; + +const linkAssets = (project, assets) => { + if (isEmpty(assets)) { + return; + } + + if (project.ios) { + log.info('Linking assets to ios project'); + copyAssetsIOS(assets, project.ios); + } + + if (project.android) { + log.info('Linking assets to android project'); + copyAssetsAndroid(assets, project.android.assetsPath); + } + + log.info(`Assets has been successfully linked to your project`); +}; + +/** + * Updates project and linkes all dependencies to it + * + * If optional argument [packageName] is provided, it's the only one that's checked + */ +module.exports = function link(config, args) { + var project; + try { + project = config.getProjectConfig(); + } catch (err) { + log.error( + 'ERRPACKAGEJSON', + 'No package found. Are you sure it\'s a React Native project?' + ); + return Promise.reject(err); + } + + const packageName = args[0]; + + const dependencies = getDependencyConfig( + config, + packageName ? [packageName] : getProjectDependencies() + ); + + const assets = dedupeAssets(dependencies.reduce( + (assets, dependency) => assets.concat(dependency.config.assets), + project.assets + )); + + const tasks = flatten(dependencies.map(dependency => [ + () => promisify(dependency.config.commands.prelink || commandStub), + () => linkDependencyAndroid(project.android, dependency), + () => linkDependencyIOS(project.ios, dependency), + () => promisify(dependency.config.commands.postlink || commandStub), + ])); + + tasks.push(() => linkAssets(project, assets)); + + return promiseWaterfall(tasks).catch(err => { + log.error( + `It seems something went wrong while linking. Error: ${err.message} \n` + + `Please file an issue here: ${pkg.bugs.url}` + ); + throw err; + }); +}; diff --git a/local-cli/rnpm/link/src/pollParams.js b/local-cli/rnpm/link/src/pollParams.js new file mode 100644 index 00000000000000..65540147951aac --- /dev/null +++ b/local-cli/rnpm/link/src/pollParams.js @@ -0,0 +1,9 @@ +var inquirer = require('inquirer'); + +module.exports = (questions) => new Promise((resolve, reject) => { + if (!questions) { + return resolve({}); + } + + inquirer.prompt(questions, resolve); +}); diff --git a/local-cli/rnpm/link/src/promiseWaterfall.js b/local-cli/rnpm/link/src/promiseWaterfall.js new file mode 100644 index 00000000000000..e6df880587403e --- /dev/null +++ b/local-cli/rnpm/link/src/promiseWaterfall.js @@ -0,0 +1,14 @@ +/** + * Given an array of promise creators, executes them in a sequence. + * + * If any of the promises in the chain fails, all subsequent promises + * will be skipped + * + * Returns the value last promise from a sequence resolved + */ +module.exports = function promiseWaterfall(tasks) { + return tasks.reduce( + (prevTaskPromise, task) => prevTaskPromise.then(task), + Promise.resolve() + ); +}; diff --git a/local-cli/rnpm/link/src/unlink.js b/local-cli/rnpm/link/src/unlink.js new file mode 100644 index 00000000000000..e40b091e9737bd --- /dev/null +++ b/local-cli/rnpm/link/src/unlink.js @@ -0,0 +1,117 @@ +const path = require('path'); +const log = require('npmlog'); + +const getProjectDependencies = require('./getProjectDependencies'); +const unregisterDependencyAndroid = require('./android/unregisterNativeModule'); +const unregisterDependencyIOS = require('./ios/unregisterNativeModule'); +const isInstalledAndroid = require('./android/isInstalled'); +const isInstalledIOS = require('./ios/isInstalled'); +const unlinkAssetsAndroid = require('./android/unlinkAssets'); +const unlinkAssetsIOS = require('./ios/unlinkAssets'); +const getDependencyConfig = require('./getDependencyConfig'); +const difference = require('lodash').difference; +const isEmpty = require('lodash').isEmpty; +const flatten = require('lodash').flatten; + +log.heading = 'rnpm-link'; + +const unlinkDependencyAndroid = (androidProject, dependency, packageName) => { + if (!androidProject || !dependency.android) { + return; + } + + const isInstalled = isInstalledAndroid(androidProject, packageName); + + if (!isInstalled) { + log.info(`Android module ${packageName} is not installed`); + return; + } + + log.info(`Unlinking ${packageName} android dependency`); + + unregisterDependencyAndroid(packageName, dependency.android, androidProject); + + log.info(`Android module ${packageName} has been successfully unlinked`); +}; + +const unlinkDependencyIOS = (iOSProject, dependency, packageName) => { + if (!iOSProject || !dependency.ios) { + return; + } + + const isInstalled = isInstalledIOS(iOSProject, dependency.ios); + + if (!isInstalled) { + log.info(`iOS module ${packageName} is not installed`); + return; + } + + log.info(`Unlinking ${packageName} ios dependency`); + + unregisterDependencyIOS(dependency.ios, iOSProject); + + log.info(`iOS module ${packageName} has been successfully unlinked`); +}; + +/** + * Updates project and unlink specific dependency + * + * If optional argument [packageName] is provided, it's the only one + * that's checked + */ +module.exports = function unlink(config, args) { + const packageName = args[0]; + + var project; + var dependency; + + try { + project = config.getProjectConfig(); + } catch (err) { + log.error( + 'ERRPACKAGEJSON', + 'No package found. Are you sure it\'s a React Native project?' + ); + return Promise.reject(err); + } + + try { + dependency = config.getDependencyConfig(packageName); + } catch (err) { + log.warn( + 'ERRINVALIDPROJ', + `Project ${packageName} is not a react-native library` + ); + return Promise.reject(err); + } + + unlinkDependencyAndroid(project.android, dependency, packageName); + unlinkDependencyIOS(project.ios, dependency, packageName); + + const allDependencies = getDependencyConfig(config, getProjectDependencies()); + + const assets = difference( + dependency.assets, + flatten(allDependencies, d => d.assets) + ); + + if (isEmpty(assets)) { + return Promise.resolve(); + } + + if (project.ios) { + log.info('Unlinking assets from ios project'); + unlinkAssetsIOS(assets, project.ios); + } + + if (project.android) { + log.info('Unlinking assets from android project'); + unlinkAssetsAndroid(assets, project.android.assetsPath); + } + + log.info( + `${packageName} assets has been successfully unlinked from your project` + ); + + return Promise.resolve(); +}; diff --git a/local-cli/rnpm/link/test/android/isInstalled.spec.js b/local-cli/rnpm/link/test/android/isInstalled.spec.js new file mode 100644 index 00000000000000..bde18c09e43dd4 --- /dev/null +++ b/local-cli/rnpm/link/test/android/isInstalled.spec.js @@ -0,0 +1,28 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const isInstalled = require('../../src/android/isInstalled'); + +const projectConfig = { + buildGradlePath: 'build.gradle', +}; + +describe('android::isInstalled', () => { + before(() => mock({ + 'build.gradle': fs.readFileSync( + path.join(__dirname, '../fixtures/android/patchedBuild.gradle') + ), + })); + + it('should return true when project is already in build.gradle', () => + expect(isInstalled(projectConfig, 'test')).to.be.true + ); + + it('should return false when project is not in build.gradle', () => + expect(isInstalled(projectConfig, 'test2')).to.be.false + ); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js new file mode 100644 index 00000000000000..d2a5f43c728ac6 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.17/makeImportPatch.js @@ -0,0 +1,31 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makeImportPatch = require('../../../../src/android/patches/0.17/makeImportPatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageImportPath = 'import some.example.project'; + +describe('makeImportPatch@0.17', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.17/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.17 import patch', () => { + const importPatch = makeImportPatch(packageImportPath); + + applyPatch('MainActivity.java', importPatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(importPatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js new file mode 100644 index 00000000000000..99857a5713df09 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.17/makePackagePatch.js @@ -0,0 +1,36 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makePackagePatch = require('../../../../src/android/patches/0.17/makePackagePatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; +const name = 'some-library'; +const params = { + foo: 'foo', + bar: 'bar', +}; + +describe('makePackagePatch@0.17', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.17/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.17 package patch', () => { + const packagePatch = makePackagePatch(packageInstance, params, name); + + applyPatch('MainActivity.java', packagePatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(packagePatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js new file mode 100644 index 00000000000000..ffff9f045f60c3 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.18/makeImportPatch.js @@ -0,0 +1,31 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makeImportPatch = require('../../../../src/android/patches/0.18/makeImportPatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageImportPath = 'import some.example.project'; + +describe('makeImportPatch@0.18', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.18/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.18 import patch', () => { + const importPatch = makeImportPatch(packageImportPath); + + applyPatch('MainActivity.java', importPatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(importPatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js new file mode 100644 index 00000000000000..a2d4802f4e484b --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.18/makePackagePatch.js @@ -0,0 +1,36 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makePackagePatch = require('../../../../src/android/patches/0.18/makePackagePatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; +const name = 'some-library'; +const params = { + foo: 'foo', + bar: 'bar', +}; + +describe('makePackagePatch@0.18', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.18/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.18 package patch', () => { + const packagePatch = makePackagePatch(packageInstance, params, name); + + applyPatch('MainActivity.java', packagePatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(packagePatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js b/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js new file mode 100644 index 00000000000000..b80fc9a577d76e --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.20/makeImportPatch.js @@ -0,0 +1,31 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makeImportPatch = require('../../../../src/android/patches/0.20/makeImportPatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageImportPath = 'import some.example.project'; + +describe('makeImportPatch@0.20', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.20/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.20 import patch', () => { + const importPatch = makeImportPatch(packageImportPath); + + applyPatch('MainActivity.java', importPatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(importPatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js b/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js new file mode 100644 index 00000000000000..87f807636e192b --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/0.20/makePackagePatch.js @@ -0,0 +1,36 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const makePackagePatch = require('../../../../src/android/patches/0.20/makePackagePatch'); +const applyPatch = require('../../../../src/android/patches/applyPatch'); + +const projectConfig = { + mainActivityPath: 'MainActivity.java', +}; + +const packageInstance = 'new SomeLibrary(${foo}, ${bar}, \'something\')'; +const name = 'some-library'; +const params = { + foo: 'foo', + bar: 'bar', +}; + +describe('makePackagePatch@0.20', () => { + before(() => mock({ + 'MainActivity.java': fs.readFileSync( + path.join(__dirname, '../../../fixtures/android/0.20/MainActivity.java') + ), + })); + + it('MainActivity contains a correct 0.20 package patch', () => { + const packagePatch = makePackagePatch(packageInstance, params, name); + + applyPatch('MainActivity.java', packagePatch); + expect(fs.readFileSync('MainActivity.java', 'utf8')) + .to.have.string(packagePatch.patch); + }); + + after(mock.restore); +}); diff --git a/local-cli/rnpm/link/test/android/patches/applyPatch.js b/local-cli/rnpm/link/test/android/patches/applyPatch.js new file mode 100644 index 00000000000000..cc5ba34f1d7092 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/applyPatch.js @@ -0,0 +1,17 @@ +const chai = require('chai'); +const expect = chai.expect; +const applyParams = require('../../../src/android/patches/applyParams'); + +describe('applyParams', () => { + it('apply params to the string', () => { + expect( + applyParams('${foo}', {foo: 'foo'}, 'react-native') + ).to.be.equal('this.getResources().getString(R.strings.reactNative_foo)'); + }); + + it('use null if no params provided', () => { + expect( + applyParams('${foo}', {}, 'react-native') + ).to.be.equal('null'); + }); +}); diff --git a/local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js b/local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js new file mode 100644 index 00000000000000..51136a72ebf241 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/makeBuildPatch.spec.js @@ -0,0 +1,17 @@ +const chai = require('chai'); +const expect = chai.expect; +const makeBuildPatch = require('../../../src/android/patches/makeBuildPatch'); +const applyPatch = require('../../../src/android/patches/applyPatch'); + +const name = 'test'; + +describe('makeBuildPatch', () => { + it('should build a patch function', () => { + expect(makeBuildPatch(name)).to.be.an('object'); + }); + + it('should make a correct patch', () => { + expect(makeBuildPatch(name).patch) + .to.be.equal(` compile project(':${name}')\n`); + }); +}); diff --git a/local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js b/local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js new file mode 100644 index 00000000000000..2dfe97c6c49f66 --- /dev/null +++ b/local-cli/rnpm/link/test/android/patches/makeSettingsPatch.spec.js @@ -0,0 +1,36 @@ +const fs = require('fs'); +const path = require('path'); +const chai = require('chai'); +const expect = chai.expect; +const makeSettingsPatch = require('../../../src/android/patches/makeSettingsPatch'); + +const name = 'test'; +const projectConfig = { + sourceDir: '/home/project/android/app', + settingsGradlePath: '/home/project/android/settings.gradle', +}; +const dependencyConfig = { + sourceDir: `/home/project/node_modules/${name}/android`, +}; + +describe('makeSettingsPatch', () => { + it('should build a patch function', () => { + expect( + makeSettingsPatch(name, dependencyConfig, {}, projectConfig) + ).to.be.an('object'); + }); + + it('should make a correct patch', () => { + const projectDir = path.relative( + path.dirname(projectConfig.settingsGradlePath), + dependencyConfig.sourceDir + ); + + expect(makeSettingsPatch(name, dependencyConfig, projectConfig).patch) + .to.be.equal( + `include ':${name}'\n` + + `project(':${name}').projectDir = ` + + `new File(rootProject.projectDir, '${projectDir}')\n` + ); + }); +}); diff --git a/local-cli/rnpm/link/test/fixtures/android/0.17/MainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.17/MainActivity.java new file mode 100644 index 00000000000000..0a532a7eddfa85 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.17/MainActivity.java @@ -0,0 +1,78 @@ +package com.basic; + +import android.app.Activity; +import android.os.Bundle; +import android.view.KeyEvent; + +import com.facebook.react.LifecycleState; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactRootView; +import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { + + private ReactInstanceManager mReactInstanceManager; + private ReactRootView mReactRootView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mReactRootView = new ReactRootView(this); + + mReactInstanceManager = ReactInstanceManager.builder() + .setApplication(getApplication()) + .setBundleAssetName("index.android.bundle") + .setJSMainModuleName("index.android") + .addPackage(new MainReactPackage()) + .setUseDeveloperSupport(BuildConfig.DEBUG) + .setInitialLifecycleState(LifecycleState.RESUMED) + .build(); + + mReactRootView.startReactApplication(mReactInstanceManager, "Basic", null); + + setContentView(mReactRootView); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { + mReactInstanceManager.showDevOptionsDialog(); + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public void onBackPressed() { + if (mReactInstanceManager != null) { + mReactInstanceManager.onBackPressed(); + } else { + super.onBackPressed(); + } + } + + @Override + public void invokeDefaultOnBackPressed() { + super.onBackPressed(); + } + + @Override + protected void onPause() { + super.onPause(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onPause(); + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onResume(this, this); + } + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/0.17/patchedMainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.17/patchedMainActivity.java new file mode 100644 index 00000000000000..edd841d76211c3 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.17/patchedMainActivity.java @@ -0,0 +1,80 @@ +package com.basic; + +import android.app.Activity; +import com.oblador.vectoricons.VectorIconsPackage; +import android.os.Bundle; +import android.view.KeyEvent; + +import com.facebook.react.LifecycleState; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactRootView; +import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.soloader.SoLoader; + +public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { + + private ReactInstanceManager mReactInstanceManager; + private ReactRootView mReactRootView; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mReactRootView = new ReactRootView(this); + + mReactInstanceManager = ReactInstanceManager.builder() + .setApplication(getApplication()) + .setBundleAssetName("index.android.bundle") + .setJSMainModuleName("index.android") + .addPackage(new MainReactPackage()) + .addPackage(new VectorIconsPackage()) + .setUseDeveloperSupport(BuildConfig.DEBUG) + .setInitialLifecycleState(LifecycleState.RESUMED) + .build(); + + mReactRootView.startReactApplication(mReactInstanceManager, "Basic", null); + + setContentView(mReactRootView); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { + mReactInstanceManager.showDevOptionsDialog(); + return true; + } + return super.onKeyUp(keyCode, event); + } + + @Override + public void onBackPressed() { + if (mReactInstanceManager != null) { + mReactInstanceManager.onBackPressed(); + } else { + super.onBackPressed(); + } + } + + @Override + public void invokeDefaultOnBackPressed() { + super.onBackPressed(); + } + + @Override + protected void onPause() { + super.onPause(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onPause(); + } + } + + @Override + protected void onResume() { + super.onResume(); + + if (mReactInstanceManager != null) { + mReactInstanceManager.onResume(this, this); + } + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/0.18/MainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.18/MainActivity.java new file mode 100644 index 00000000000000..c2ccca8905f7b6 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.18/MainActivity.java @@ -0,0 +1,39 @@ +package com.testrn; + +import com.facebook.react.ReactActivity; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "TestRN"; + } + + /** + * Returns whether dev mode should be enabled. + * This enables e.g. the dev menu. + */ + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage()); + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/0.18/patchedMainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.18/patchedMainActivity.java new file mode 100644 index 00000000000000..3c6331eaf9fb78 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.18/patchedMainActivity.java @@ -0,0 +1,41 @@ +package com.testrn; + +import com.facebook.react.ReactActivity; +import com.oblador.vectoricons.VectorIconsPackage; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "TestRN"; + } + + /** + * Returns whether dev mode should be enabled. + * This enables e.g. the dev menu. + */ + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage(), + new VectorIconsPackage()); + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/0.20/MainActivity.java b/local-cli/rnpm/link/test/fixtures/android/0.20/MainActivity.java new file mode 100644 index 00000000000000..d9df1311598a37 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/0.20/MainActivity.java @@ -0,0 +1,40 @@ +package com.myawesomeproject; + +import com.facebook.react.ReactActivity; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +public class MainActivity extends ReactActivity { + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ + @Override + protected String getMainComponentName() { + return "TestRN"; + } + + /** + * Returns whether dev mode should be enabled. + * This enables e.g. the dev menu. + */ + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage() + ); + } +} diff --git a/local-cli/rnpm/link/test/fixtures/android/build.gradle b/local-cli/rnpm/link/test/fixtures/android/build.gradle new file mode 100644 index 00000000000000..07f260d6d48032 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/build.gradle @@ -0,0 +1,5 @@ +dependencies { + compile fileTree(dir: "libs", include: ["*.jar"]) + compile "com.android.support:appcompat-v7:23.0.1" + compile "com.facebook.react:react-native:0.18.+" +} diff --git a/local-cli/rnpm/link/test/fixtures/android/patchedBuild.gradle b/local-cli/rnpm/link/test/fixtures/android/patchedBuild.gradle new file mode 100644 index 00000000000000..8c75995d38e543 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/patchedBuild.gradle @@ -0,0 +1,6 @@ +dependencies { + compile project(':test') + compile fileTree(dir: "libs", include: ["*.jar"]) + compile "com.android.support:appcompat-v7:23.0.1" + compile "com.facebook.react:react-native:0.18.+" +} diff --git a/local-cli/rnpm/link/test/fixtures/android/patchedSettings.gradle b/local-cli/rnpm/link/test/fixtures/android/patchedSettings.gradle new file mode 100644 index 00000000000000..8854e3d106fae0 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/patchedSettings.gradle @@ -0,0 +1,5 @@ +rootProject.name = 'TestRN' + +include ':app' +include ':test' +project(':test').projectDir = new File(rootProject.projectDir, '../node_modules/test/android') diff --git a/local-cli/rnpm/link/test/fixtures/android/settings.gradle b/local-cli/rnpm/link/test/fixtures/android/settings.gradle new file mode 100644 index 00000000000000..ded35f713e9413 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/android/settings.gradle @@ -0,0 +1,3 @@ +rootProject.name = 'TestRN' + +include ':app' diff --git a/local-cli/rnpm/link/test/fixtures/linearGradient.pbxproj b/local-cli/rnpm/link/test/fixtures/linearGradient.pbxproj new file mode 100644 index 00000000000000..89f34dc5a34376 --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/linearGradient.pbxproj @@ -0,0 +1,258 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + BBD49E3F1AC8DEF000610F8E /* BVLinearGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = BBD49E3A1AC8DEF000610F8E /* BVLinearGradient.m */; }; + BBD49E401AC8DEF000610F8E /* BVLinearGradientManager.m in Sources */ = {isa = PBXBuildFile; fileRef = BBD49E3C1AC8DEF000610F8E /* BVLinearGradientManager.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 58B511D91A9E6C8500147676 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 134814201AA4EA6300B7C361 /* libBVLinearGradient.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libBVLinearGradient.a; sourceTree = BUILT_PRODUCTS_DIR; }; + BBD49E391AC8DEF000610F8E /* BVLinearGradient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVLinearGradient.h; sourceTree = ""; }; + BBD49E3A1AC8DEF000610F8E /* BVLinearGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVLinearGradient.m; sourceTree = ""; }; + BBD49E3B1AC8DEF000610F8E /* BVLinearGradientManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BVLinearGradientManager.h; sourceTree = ""; }; + BBD49E3C1AC8DEF000610F8E /* BVLinearGradientManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = BVLinearGradientManager.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58B511D81A9E6C8500147676 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 134814211AA4EA7D00B7C361 /* Products */ = { + isa = PBXGroup; + children = ( + 134814201AA4EA6300B7C361 /* libBVLinearGradient.a */, + ); + name = Products; + sourceTree = ""; + }; + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + BBD49E391AC8DEF000610F8E /* BVLinearGradient.h */, + BBD49E3A1AC8DEF000610F8E /* BVLinearGradient.m */, + BBD49E3B1AC8DEF000610F8E /* BVLinearGradientManager.h */, + BBD49E3C1AC8DEF000610F8E /* BVLinearGradientManager.m */, + 134814211AA4EA7D00B7C361 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 58B511DA1A9E6C8500147676 /* BVLinearGradient */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BVLinearGradient" */; + buildPhases = ( + 58B511D71A9E6C8500147676 /* Sources */, + 58B511D81A9E6C8500147676 /* Frameworks */, + 58B511D91A9E6C8500147676 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = BVLinearGradient; + productName = RCTDataManager; + productReference = 134814201AA4EA6300B7C361 /* libBVLinearGradient.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 58B511DA1A9E6C8500147676 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BVLinearGradient" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58B511DA1A9E6C8500147676 /* BVLinearGradient */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 58B511D71A9E6C8500147676 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBD49E3F1AC8DEF000610F8E /* BVLinearGradient.m in Sources */, + BBD49E401AC8DEF000610F8E /* BVLinearGradientManager.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 58B511F01A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + "$(SRCROOT)/../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = BVLinearGradient; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 58B511F11A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + "$(SRCROOT)/../react-native/React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = BVLinearGradient; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "BVLinearGradient" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "BVLinearGradient" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511F01A9E6C8500147676 /* Debug */, + 58B511F11A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/local-cli/rnpm/link/test/fixtures/project.pbxproj b/local-cli/rnpm/link/test/fixtures/project.pbxproj new file mode 100644 index 00000000000000..e812b4ca885f0e --- /dev/null +++ b/local-cli/rnpm/link/test/fixtures/project.pbxproj @@ -0,0 +1,778 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */; }; + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; + 00E356F31AD99517003FC87E /* BasicTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* BasicTests.m */; }; + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 78C398B91ACF4ADC00677621 /* libRCTLinking.a */; }; + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */; }; + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */; }; + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; }; + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB11A68108700A75B9A /* LaunchScreen.xib */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; + 146834051AC3E58100842450 /* libReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 146834041AC3E56700842450 /* libReact.a */; }; + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 832341B51AAA6A8300B99B32 /* libRCTText.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTActionSheet; + }; + 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTGeolocation; + }; + 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5115D1A9E6B3D00147676; + remoteInfo = RCTImage; + }; + 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B511DB1A9E6C8500147676; + remoteInfo = RCTNetwork; + }; + 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 832C81801AAF6DEF007FA2F7; + remoteInfo = RCTVibration; + }; + 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = Basic; + }; + 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTSettings; + }; + 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 3C86DF461ADF2C930047B81A; + remoteInfo = RCTWebSocket; + }; + 146834031AC3E56700842450 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 146833FF1AC3E56700842450 /* React.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 83CBBA2E1A601D0E00E9B192; + remoteInfo = React; + }; + 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTLinking; + }; + 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 58B5119B1A9E6C1200147676; + remoteInfo = RCTText; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 008F07F21AC5B25A0029DE68 /* main.jsbundle */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = main.jsbundle; sourceTree = ""; }; + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTActionSheet.xcodeproj; path = "../node_modules/react-native/Libraries/ActionSheetIOS/RCTActionSheet.xcodeproj"; sourceTree = ""; }; + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTGeolocation.xcodeproj; path = "../node_modules/react-native/Libraries/Geolocation/RCTGeolocation.xcodeproj"; sourceTree = ""; }; + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTImage.xcodeproj; path = "../node_modules/react-native/Libraries/Image/RCTImage.xcodeproj"; sourceTree = ""; }; + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTNetwork.xcodeproj; path = "../node_modules/react-native/Libraries/Network/RCTNetwork.xcodeproj"; sourceTree = ""; }; + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTVibration.xcodeproj; path = "../node_modules/react-native/Libraries/Vibration/RCTVibration.xcodeproj"; sourceTree = ""; }; + 00E356EE1AD99517003FC87E /* BasicTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BasicTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 00E356F21AD99517003FC87E /* BasicTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BasicTests.m; sourceTree = ""; }; + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = "../node_modules/react-native/Libraries/Settings/RCTSettings.xcodeproj"; sourceTree = ""; }; + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTWebSocket.xcodeproj; path = "../node_modules/react-native/Libraries/WebSocket/RCTWebSocket.xcodeproj"; sourceTree = ""; }; + 13B07F961A680F5B00A75B9A /* Basic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Basic.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = Basic/AppDelegate.h; sourceTree = ""; }; + 13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = Basic/AppDelegate.m; sourceTree = ""; }; + 13B07FB21A68108700A75B9A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Basic/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Basic/Info.plist; sourceTree = ""; }; + 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = Basic/main.m; sourceTree = ""; }; + 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = ""; }; + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = "../node_modules/react-native/Libraries/LinkingIOS/RCTLinking.xcodeproj"; sourceTree = ""; }; + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00E356EB1AD99517003FC87E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 146834051AC3E58100842450 /* libReact.a in Frameworks */, + 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */, + 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */, + 00C302E81ABCBA2D00DB3ED1 /* libRCTImage.a in Frameworks */, + 133E29F31AD74F7200F7D852 /* libRCTLinking.a in Frameworks */, + 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */, + 139105C61AF99C1200B5F7CC /* libRCTSettings.a in Frameworks */, + 832341BD1AAA6AB300B99B32 /* libRCTText.a in Frameworks */, + 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */, + 139FDEF61B0652A700C62182 /* libRCTWebSocket.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00C302A81ABCB8CE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302B61ABCB90400DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302BC1ABCB91800DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302D41ABCB9D200DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */, + ); + name = Products; + sourceTree = ""; + }; + 00C302E01ABCB9EE00DB3ED1 /* Products */ = { + isa = PBXGroup; + children = ( + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */, + ); + name = Products; + sourceTree = ""; + }; + 00E356EF1AD99517003FC87E /* BasicTests */ = { + isa = PBXGroup; + children = ( + 00E356F21AD99517003FC87E /* BasicTests.m */, + 00E356F01AD99517003FC87E /* Supporting Files */, + ); + path = BasicTests; + sourceTree = ""; + }; + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 00E356F11AD99517003FC87E /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 139105B71AF99BAD00B5F7CC /* Products */ = { + isa = PBXGroup; + children = ( + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */, + ); + name = Products; + sourceTree = ""; + }; + 139FDEE71B06529A00C62182 /* Products */ = { + isa = PBXGroup; + children = ( + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */, + ); + name = Products; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* Basic */ = { + isa = PBXGroup; + children = ( + 008F07F21AC5B25A0029DE68 /* main.jsbundle */, + 13B07FAF1A68108700A75B9A /* AppDelegate.h */, + 13B07FB01A68108700A75B9A /* AppDelegate.m */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */, + 13B07FB71A68108700A75B9A /* main.m */, + ); + name = Basic; + sourceTree = ""; + }; + 146834001AC3E56700842450 /* Products */ = { + isa = PBXGroup; + children = ( + 146834041AC3E56700842450 /* libReact.a */, + ); + name = Products; + sourceTree = ""; + }; + 78C398B11ACF4ADC00677621 /* Products */ = { + isa = PBXGroup; + children = ( + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */, + ); + name = Products; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + 146833FF1AC3E56700842450 /* React.xcodeproj */, + 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */, + 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */, + 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */, + 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */, + 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */, + 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */, + 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */, + 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */, + 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */, + ); + name = Libraries; + sourceTree = ""; + }; + 832341B11AAA6A8300B99B32 /* Products */ = { + isa = PBXGroup; + children = ( + 832341B51AAA6A8300B99B32 /* libRCTText.a */, + ); + name = Products; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + AD9196DA1CABA83E000E8D91 /* NestedGroup */, + 13B07FAE1A68108700A75B9A /* Basic */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* BasicTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* Basic.app */, + 00E356EE1AD99517003FC87E /* BasicTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + AD9196DA1CABA83E000E8D91 /* NestedGroup */ = { + isa = PBXGroup; + children = ( + AD9196DB1CABA844000E8D91 /* Libraries */, + ); + name = NestedGroup; + sourceTree = ""; + }; + AD9196DB1CABA844000E8D91 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 00E356ED1AD99517003FC87E /* BasicTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "BasicTests" */; + buildPhases = ( + 00E356EA1AD99517003FC87E /* Sources */, + 00E356EB1AD99517003FC87E /* Frameworks */, + 00E356EC1AD99517003FC87E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 00E356F51AD99517003FC87E /* PBXTargetDependency */, + ); + name = BasicTests; + productName = BasicTests; + productReference = 00E356EE1AD99517003FC87E /* BasicTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13B07F861A680F5B00A75B9A /* Basic */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Basic" */; + buildPhases = ( + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Basic; + productName = "Hello World"; + productReference = 13B07F961A680F5B00A75B9A /* Basic.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0610; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 00E356ED1AD99517003FC87E = { + CreatedOnToolsVersion = 6.2; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "a" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectReferences = ( + { + ProductGroup = 00C302A81ABCB8CE00DB3ED1 /* Products */; + ProjectRef = 00C302A71ABCB8CE00DB3ED1 /* RCTActionSheet.xcodeproj */; + }, + { + ProductGroup = 00C302B61ABCB90400DB3ED1 /* Products */; + ProjectRef = 00C302B51ABCB90400DB3ED1 /* RCTGeolocation.xcodeproj */; + }, + { + ProductGroup = 00C302BC1ABCB91800DB3ED1 /* Products */; + ProjectRef = 00C302BB1ABCB91800DB3ED1 /* RCTImage.xcodeproj */; + }, + { + ProductGroup = 78C398B11ACF4ADC00677621 /* Products */; + ProjectRef = 78C398B01ACF4ADC00677621 /* RCTLinking.xcodeproj */; + }, + { + ProductGroup = 00C302D41ABCB9D200DB3ED1 /* Products */; + ProjectRef = 00C302D31ABCB9D200DB3ED1 /* RCTNetwork.xcodeproj */; + }, + { + ProductGroup = 139105B71AF99BAD00B5F7CC /* Products */; + ProjectRef = 139105B61AF99BAD00B5F7CC /* RCTSettings.xcodeproj */; + }, + { + ProductGroup = 832341B11AAA6A8300B99B32 /* Products */; + ProjectRef = 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */; + }, + { + ProductGroup = 00C302E01ABCB9EE00DB3ED1 /* Products */; + ProjectRef = 00C302DF1ABCB9EE00DB3ED1 /* RCTVibration.xcodeproj */; + }, + { + ProductGroup = 139FDEE71B06529A00C62182 /* Products */; + ProjectRef = 139FDEE61B06529A00C62182 /* RCTWebSocket.xcodeproj */; + }, + { + ProductGroup = 146834001AC3E56700842450 /* Products */; + ProjectRef = 146833FF1AC3E56700842450 /* React.xcodeproj */; + }, + ); + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* Basic */, + 00E356ED1AD99517003FC87E /* BasicTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXReferenceProxy section */ + 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTActionSheet.a; + remoteRef = 00C302AB1ABCB8CE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTGeolocation.a; + remoteRef = 00C302B91ABCB90400DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302C01ABCB91800DB3ED1 /* libRCTImage.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTImage.a; + remoteRef = 00C302BF1ABCB91800DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTNetwork.a; + remoteRef = 00C302DB1ABCB9D200DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTVibration.a; + remoteRef = 00C302E31ABCB9EE00DB3ED1 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139105C11AF99BAD00B5F7CC /* libRCTSettings.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTSettings.a; + remoteRef = 139105C01AF99BAD00B5F7CC /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 139FDEF41B06529B00C62182 /* libRCTWebSocket.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTWebSocket.a; + remoteRef = 139FDEF31B06529B00C62182 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 146834041AC3E56700842450 /* libReact.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libReact.a; + remoteRef = 146834031AC3E56700842450 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 78C398B91ACF4ADC00677621 /* libRCTLinking.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTLinking.a; + remoteRef = 78C398B81ACF4ADC00677621 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 832341B51AAA6A8300B99B32 /* libRCTText.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTText.a; + remoteRef = 832341B41AAA6A8300B99B32 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + +/* Begin PBXResourcesBuildPhase section */ + 00E356EC1AD99517003FC87E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 13B07FBD1A68108700A75B9A /* LaunchScreen.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "../node_modules/react-native/packager/react-native-xcode.sh"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00E356EA1AD99517003FC87E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 00E356F31AD99517003FC87E /* BasicTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */, + 13B07FC11A68108700A75B9A /* main.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* Basic */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 13B07FB11A68108700A75B9A /* LaunchScreen.xib */ = { + isa = PBXVariantGroup; + children = ( + 13B07FB21A68108700A75B9A /* Base */, + ); + name = LaunchScreen.xib; + path = Basic; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 00E356F61AD99517003FC87E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = BasicTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Basic.app/Basic"; + }; + name = Debug; + }; + 00E356F71AD99517003FC87E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = BasicTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 8.2; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Basic.app/Basic"; + }; + name = Release; + }; + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + DEAD_CODE_STRIPPING = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + INFOPLIST_FILE = "Basic/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = Basic; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + INFOPLIST_FILE = "Basic/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = Basic; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + ); + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "BasicTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00E356F61AD99517003FC87E /* Debug */, + 00E356F71AD99517003FC87E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Basic" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "a" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/local-cli/rnpm/link/test/getDependencyConfig.spec.js b/local-cli/rnpm/link/test/getDependencyConfig.spec.js new file mode 100644 index 00000000000000..7fba3b65bccd99 --- /dev/null +++ b/local-cli/rnpm/link/test/getDependencyConfig.spec.js @@ -0,0 +1,23 @@ +const chai = require('chai'); +const expect = chai.expect; +const getDependencyConfig = require('../src/getDependencyConfig'); +const sinon = require('sinon'); + +describe('getDependencyConfig', () => { + it('should return an array of dependencies\' rnpm config', () => { + const config = { + getDependencyConfig: sinon.stub(), + }; + + expect(getDependencyConfig(config, ['abcd'])).to.be.an.array; + expect(config.getDependencyConfig.callCount).to.equals(1); + }); + + it('should filter out invalid react-native projects', () => { + const config = { + getDependencyConfig: sinon.stub().throws(new Error('Cannot require')), + }; + + expect(getDependencyConfig(config, ['abcd'])).to.deep.equal([]); + }); +}); diff --git a/local-cli/rnpm/link/test/getPrefix.spec.js b/local-cli/rnpm/link/test/getPrefix.spec.js new file mode 100644 index 00000000000000..1b5e7262732d7e --- /dev/null +++ b/local-cli/rnpm/link/test/getPrefix.spec.js @@ -0,0 +1,20 @@ +const chai = require('chai'); +const expect = chai.expect; +const getMainActivityPatch = require('../src/android/getPrefix'); +const newPrefix = 'patches/0.18'; +const oldPrefix = 'patches/0.17'; + +describe('getPrefix', () => { + it('require a specific patch for react-native < 0.18', () => { + expect(getMainActivityPatch('0.17.0-rc')).to.equals(oldPrefix); + expect(getMainActivityPatch('0.17.1-rc2')).to.equals(oldPrefix); + expect(getMainActivityPatch('0.17.2')).to.equals(oldPrefix); + }); + + it('require a specific patch for react-native > 0.18', () => { + expect(getMainActivityPatch('0.19.0')).to.equals(newPrefix); + expect(getMainActivityPatch('0.19.0-rc')).to.equals(newPrefix); + expect(getMainActivityPatch('0.18.0-rc1')).to.equals(newPrefix); + expect(getMainActivityPatch('0.18.2')).to.equals(newPrefix); + }); +}); diff --git a/local-cli/rnpm/link/test/getProjectDependencies.spec.js b/local-cli/rnpm/link/test/getProjectDependencies.spec.js new file mode 100644 index 00000000000000..21abb764828b07 --- /dev/null +++ b/local-cli/rnpm/link/test/getProjectDependencies.spec.js @@ -0,0 +1,27 @@ +const chai = require('chai'); +const expect = chai.expect; +const getProjectDependencies = require('../src/getProjectDependencies'); +const mock = require('mock-require'); +const path = require('path'); + +describe('getProjectDependencies', () => { + + it('should return an array of project dependencies', () => { + mock( + path.join(process.cwd(), './package.json'), + { dependencies: { lodash: '^6.0.0', 'react-native': '^16.0.0' } } + ); + + expect(getProjectDependencies()).to.deep.equals(['lodash']); + }); + + it('should return an empty array when no dependencies set', () => { + mock(path.join(process.cwd(), './package.json'), {}); + expect(getProjectDependencies()).to.deep.equals([]); + }); + + afterEach(() => { + mock.stopAll(); + }); + +}); diff --git a/local-cli/rnpm/link/test/groupFilesByType.spec.js b/local-cli/rnpm/link/test/groupFilesByType.spec.js new file mode 100644 index 00000000000000..ac5ecfc691a582 --- /dev/null +++ b/local-cli/rnpm/link/test/groupFilesByType.spec.js @@ -0,0 +1,23 @@ +const chai = require('chai'); +const expect = chai.expect; +const groupFilesByType = require('../src/groupFilesByType'); + +describe('groupFilesByType', () => { + + it('should group files by its type', () => { + const fonts = [ + 'fonts/a.ttf', + 'fonts/b.ttf', + ]; + const images = [ + 'images/a.jpg', + 'images/c.jpeg', + ]; + + const groupedFiles = groupFilesByType(fonts.concat(images)); + + expect(groupedFiles.font).to.deep.equal(fonts); + expect(groupedFiles.image).to.deep.equal(images); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/addFileToProject.spec.js b/local-cli/rnpm/link/test/ios/addFileToProject.spec.js new file mode 100644 index 00000000000000..141d2ba7130f45 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/addFileToProject.spec.js @@ -0,0 +1,22 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const addFileToProject = require('../../src/ios/addFileToProject'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::addFileToProject', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should add file to a project', () => { + const file = addFileToProject(project, '../fixtures/linearGradient.pbxproj'); + + expect( + project.pbxFileReferenceSection() + ).to.include.keys(file.fileRef); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js b/local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js new file mode 100644 index 00000000000000..696cce114c4218 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/addProjectToLibraries.spec.js @@ -0,0 +1,28 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const PbxFile = require('xcode/lib/pbxFile'); +const addProjectToLibraries = require('../../src/ios/addProjectToLibraries'); +const last = require('lodash').last; + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::addProjectToLibraries', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should append file to Libraries group', () => { + const file = new PbxFile('fakePath'); + const libraries = project.pbxGroupByName('Libraries'); + + addProjectToLibraries(libraries, file); + + const child = last(libraries.children); + + expect(child).to.have.keys(['value', 'comment']); + expect(child.comment).to.equals(file.basename); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/createGroup.spec.js b/local-cli/rnpm/link/test/ios/createGroup.spec.js new file mode 100644 index 00000000000000..f4653ea612c08c --- /dev/null +++ b/local-cli/rnpm/link/test/ios/createGroup.spec.js @@ -0,0 +1,49 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const createGroup = require('../../src/ios/createGroup'); +const getGroup = require('../../src/ios/getGroup'); +const last = require('lodash').last; + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::createGroup', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should create a group with given name', () => { + const createdGroup = createGroup(project, 'Resources'); + expect(createdGroup.name).to.equals('Resources'); + }); + + it('should attach group to main project group', () => { + const createdGroup = createGroup(project, 'Resources'); + const mainGroup = getGroup(project); + + expect( + last(mainGroup.children).comment + ).to.equals(createdGroup.name); + }); + + it('should create a nested group with given path', () => { + const createdGroup = createGroup(project, 'NewGroup/NewNestedGroup'); + const outerGroup = getGroup(project, 'NewGroup'); + + expect( + last(outerGroup.children).comment + ).to.equals(createdGroup.name); + }); + + it('should-not create already created groups', () => { + const createdGroup = createGroup(project, 'Libraries/NewNestedGroup'); + const outerGroup = getGroup(project, 'Libraries'); + const mainGroup = getGroup(project); + + expect( + mainGroup.children.filter(group => group.comment === 'Libraries').length + ).to.equals(1); + expect(last(outerGroup.children).comment).to.equals(createdGroup.name); + }); +}); diff --git a/local-cli/rnpm/link/test/ios/getBuildProperty.spec.js b/local-cli/rnpm/link/test/ios/getBuildProperty.spec.js new file mode 100644 index 00000000000000..1ca3f30a605ed7 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getBuildProperty.spec.js @@ -0,0 +1,19 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getBuildProperty = require('../../src/ios/getBuildProperty'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::getBuildProperty', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return build property from main target', () => { + const plistPath = getBuildProperty(project, 'INFOPLIST_FILE'); + expect(plistPath).to.equals('"Basic/Info.plist"'); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/getGroup.spec.js b/local-cli/rnpm/link/test/ios/getGroup.spec.js new file mode 100644 index 00000000000000..5df45f7f000f78 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getGroup.spec.js @@ -0,0 +1,36 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getGroup = require('../../src/ios/getGroup'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::getGroup', () => { + beforeEach(() => { + project.parseSync(); + }); + + it('should return a top-level group', () => { + const group = getGroup(project, 'Libraries'); + expect(group.children.length > 0).to.be.true; // our test top-level Libraries has children + expect(group.name).to.equals('Libraries'); + }); + + it('should return nested group when specified', () => { + const group = getGroup(project, 'NestedGroup/Libraries'); + expect(group.children.length).to.equals(0); // our test nested Libraries is empty + expect(group.name).to.equals('Libraries'); + }); + + it('should return null when no group found', () => { + const group = getGroup(project, 'I-Dont-Exist'); + expect(group).to.be.null; + }); + + it('should return top-level group when name not specified', () => { + const mainGroupId = project.getFirstProject().firstProject.mainGroup; + const mainGroup = project.getPBXGroupByKey(mainGroupId); + const group = getGroup(project); + expect(group).to.equals(mainGroup); + }); +}); diff --git a/local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js b/local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js new file mode 100644 index 00000000000000..216979ca41273c --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getHeaderSearchPath.spec.js @@ -0,0 +1,58 @@ +const chai = require('chai'); +const expect = chai.expect; +const getHeaderSearchPath = require('../../src/ios/getHeaderSearchPath'); +const path = require('path'); + +const SRC_DIR = path.join('react-native-project', 'ios'); + +describe('ios::getHeaderSearchPath', () => { + + /** + * See https://github.com/Microsoft/react-native-code-push + */ + it('should return correct path when all headers are in root folder', () => { + const files = [ + path.join('react-native-project', 'node_modules', 'package', 'Gradient.h'), + path.join('react-native-project', 'node_modules', 'package', 'Manager.h'), + ]; + + const searchPath = getHeaderSearchPath(SRC_DIR, files); + + expect(searchPath).to.equal( + `"${['$(SRCROOT)', '..', 'node_modules', 'package'].join(path.sep)}"` + ); + }); + + /** + * See https://github.com/facebook/react-native/tree/master/React + */ + it('should return correct path when headers are in multiple folders', () => { + const files = [ + path.join('react-native-project', 'node_modules', 'package', 'src', 'folderA', 'Gradient.h'), + path.join('react-native-project', 'node_modules', 'package', 'src', 'folderB', 'Manager.h'), + ]; + + const searchPath = getHeaderSearchPath(SRC_DIR, files); + + expect(searchPath).to.equal( + `"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"` + ); + }); + + /** + * This is just to make sure the above two does not collide with each other + */ + it('should return correct path when headers are in root and nested folders', () => { + const files = [ + path.join('react-native-project', 'node_modules', 'package', 'src', 'folderA', 'Gradient.h'), + path.join('react-native-project', 'node_modules', 'package', 'src', 'folderB', 'Manager.h'), + path.join('react-native-project', 'node_modules', 'package', 'src', 'Manager.h'), + ]; + + const searchPath = getHeaderSearchPath(SRC_DIR, files); + + expect(searchPath).to.equal( + `"${['$(SRCROOT)', '..', 'node_modules', 'package', 'src'].join(path.sep)}/**"` + ); + }); +}); diff --git a/local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js b/local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js new file mode 100644 index 00000000000000..54f4c0f6e914fa --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getHeadersInFolder.spec.js @@ -0,0 +1,45 @@ +const chai = require('chai'); +const expect = chai.expect; +const getHeadersInFolder = require('../../src/ios/getHeadersInFolder'); +const mock = require('mock-fs'); + +describe('ios::getHeadersInFolder', () => { + + it('should return an array of all headers in given folder', () => { + mock({ + 'FileA.h': '', + 'FileB.h': '', + }); + + const foundHeaders = getHeadersInFolder(process.cwd()); + + expect(foundHeaders.length).to.equals(2); + + getHeadersInFolder(process.cwd()).forEach(headerPath => { + expect(headerPath).to.contain(process.cwd()); + }); + }); + + it('should ignore all headers in Pods, Examples & node_modules', () => { + mock({ + 'FileA.h': '', + 'FileB.h': '', + Pods: { + 'FileC.h': '', + }, + Examples: { + 'FileD.h': '', + }, + node_modules: { + 'FileE.h': '', + }, + }); + + expect(getHeadersInFolder(process.cwd()).length).to.equals(2); + }); + + afterEach(() => { + mock.restore(); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/getPlist.spec.js b/local-cli/rnpm/link/test/ios/getPlist.spec.js new file mode 100644 index 00000000000000..76e072755c2661 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getPlist.spec.js @@ -0,0 +1,23 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getPlist = require('../../src/ios/getPlist'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::getPlist', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return null when `.plist` file missing', () => { + const plistPath = getPlist(project, process.cwd()); + expect(plistPath).to.equals(null); + }); + + it.skip('should return parsed `plist`', () => { + // @todo mock fs here + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/getPlistPath.spec.js b/local-cli/rnpm/link/test/ios/getPlistPath.spec.js new file mode 100644 index 00000000000000..c8c758ac04e3b1 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getPlistPath.spec.js @@ -0,0 +1,19 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getPlistPath = require('../../src/ios/getPlistPath'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::getPlistPath', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return path without Xcode $(SRCROOT)', () => { + const plistPath = getPlistPath(project, '/'); + expect(plistPath).to.equals('/Basic/Info.plist'); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/getProducts.spec.js b/local-cli/rnpm/link/test/ios/getProducts.spec.js new file mode 100644 index 00000000000000..bf483f1b2e69ad --- /dev/null +++ b/local-cli/rnpm/link/test/ios/getProducts.spec.js @@ -0,0 +1,20 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const getProducts = require('../../src/ios/getProducts'); + +const project = xcode.project('test/fixtures/linearGradient.pbxproj'); + +describe('ios::getProducts', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return an array of static libraries project exports', () => { + const products = getProducts(project); + expect(products.length).to.equals(1); + expect(products).to.contains('libBVLinearGradient.a'); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js b/local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js new file mode 100644 index 00000000000000..c321418799eafa --- /dev/null +++ b/local-cli/rnpm/link/test/ios/hasLibraryImported.spec.js @@ -0,0 +1,24 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const hasLibraryImported = require('../../src/ios/hasLibraryImported'); + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::hasLibraryImported', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should return true if project has been already imported', () => { + const libraries = project.pbxGroupByName('Libraries'); + expect(hasLibraryImported(libraries, 'React.xcodeproj')).to.be.true; + }); + + it('should return false if project is not imported', () => { + const libraries = project.pbxGroupByName('Libraries'); + expect(hasLibraryImported(libraries, 'ACME.xcodeproj')).to.be.false; + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/isInstalled.spec.js b/local-cli/rnpm/link/test/ios/isInstalled.spec.js new file mode 100644 index 00000000000000..65983c6f9922fd --- /dev/null +++ b/local-cli/rnpm/link/test/ios/isInstalled.spec.js @@ -0,0 +1,39 @@ +const chai = require('chai'); +const expect = chai.expect; +const mock = require('mock-fs'); +const fs = require('fs'); +const path = require('path'); +const isInstalled = require('../../src/ios/isInstalled'); + +const baseProjectConfig = { + pbxprojPath: 'project.pbxproj', + libraryFolder: 'Libraries', +}; + +describe('ios::isInstalled', () => { + + before(() => { + mock({ + 'project.pbxproj': fs.readFileSync(path.join(__dirname, '../fixtures/project.pbxproj')), + }); + }); + + it('should return true when .xcodeproj in Libraries', () => { + const dependencyConfig = { projectName: 'React.xcodeproj' }; + expect(isInstalled(baseProjectConfig, dependencyConfig)).to.be.true; + }); + + it('should return false when .xcodeproj not in Libraries', () => { + const dependencyConfig = { projectName: 'Missing.xcodeproj' }; + expect(isInstalled(baseProjectConfig, dependencyConfig)).to.be.false; + }); + + it('should return false when `LibraryFolder` is missing', () => { + const dependencyConfig = { projectName: 'React.xcodeproj' }; + const projectConfig = Object.assign({}, baseProjectConfig, { libraryFolder: 'Missing' }); + expect(isInstalled(projectConfig, dependencyConfig)).to.be.false; + }); + + after(mock.restore); + +}); diff --git a/local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js b/local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js new file mode 100644 index 00000000000000..f885fbd8f39164 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/mapHeaderSearchPaths.spec.js @@ -0,0 +1,23 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const mapHeaderSearchPaths = require('../../src/ios/mapHeaderSearchPaths'); + +const project = xcode.project('test/fixtures/project.pbxproj'); +const reactPath = '"$(SRCROOT)/../node_modules/react-native/React/**"'; + +describe('ios::mapHeaderSearchPaths', () => { + + beforeEach(() => { + project.parseSync(); + }); + + it('should iterate over headers with `react` added only', () => { + const path = '../../node_modules/path-to-module/**'; + + mapHeaderSearchPaths(project, paths => { + expect(paths.find(path => path.indexOf(reactPath))).to.be.not.empty; + }); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js b/local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js new file mode 100644 index 00000000000000..49a4c42b463061 --- /dev/null +++ b/local-cli/rnpm/link/test/ios/removeProjectFromLibraries.js @@ -0,0 +1,33 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const PbxFile = require('xcode/lib/pbxFile'); +const addProjectToLibraries = require('../../src/ios/addProjectToLibraries'); +const removeProjectFromLibraries = require('../../src/ios/removeProjectFromLibraries'); +const last = require('lodash').last; + +const project = xcode.project('test/fixtures/project.pbxproj'); + +describe('ios::removeProjectFromLibraries', () => { + + beforeEach(() => { + project.parseSync(); + + addProjectToLibraries( + project.pbxGroupByName('Libraries'), + new PbxFile('fakePath') + ); + }); + + it('should remove file from Libraries group', () => { + const file = new PbxFile('fakePath'); + const libraries = project.pbxGroupByName('Libraries'); + + removeProjectFromLibraries(libraries, file); + + const child = last(libraries.children); + + expect(child.comment).to.not.equals(file.basename); + }); + +}); diff --git a/local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js b/local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js new file mode 100644 index 00000000000000..8220028aeafbec --- /dev/null +++ b/local-cli/rnpm/link/test/ios/removeProjectFromProject.spec.js @@ -0,0 +1,32 @@ +const chai = require('chai'); +const expect = chai.expect; +const xcode = require('xcode'); +const pbxFile = require('xcode/lib/pbxFile'); +const addFileToProject = require('../../src/ios/addFileToProject'); +const removeProjectFromProject = require('../../src/ios/removeProjectFromProject'); + +const project = xcode.project('test/fixtures/project.pbxproj'); +const filePath = '../fixtures/linearGradient.pbxproj'; + +describe('ios::addFileToProject', () => { + + beforeEach(() => { + project.parseSync(); + addFileToProject(project, filePath); + }); + + it('should return removed file', () => { + expect(removeProjectFromProject(project, filePath)).to.be.instanceof(pbxFile); + }); + + it('should remove file from a project', () => { + const file = removeProjectFromProject(project, filePath); + expect(project.pbxFileReferenceSection()).to.not.include.keys(file.fileRef); + }); + + it.skip('should remove file from PBXContainerProxy', () => { + // todo(mike): add in .xcodeproj after Xcode modifications so we can test extra + // removals later. + }); + +}); diff --git a/local-cli/rnpm/link/test/link.spec.js b/local-cli/rnpm/link/test/link.spec.js new file mode 100644 index 00000000000000..9cce9988da09df --- /dev/null +++ b/local-cli/rnpm/link/test/link.spec.js @@ -0,0 +1,199 @@ +const chai = require('chai'); +const expect = chai.expect; +const sinon = require('sinon'); +const mock = require('mock-require'); +const log = require('npmlog'); +const path = require('path'); + +const link = require('../src/link'); + +log.level = 'silent'; + +describe('link', () => { + + beforeEach(() => { + delete require.cache[require.resolve('../src/link')]; + }); + + it('should reject when run in a folder without package.json', (done) => { + const config = { + getProjectConfig: () => { + throw new Error('No package.json found'); + }, + }; + + link(config).catch(() => done()); + }); + + it('should accept a name of a dependency to link', (done) => { + const config = { + getProjectConfig: () => ({ assets: [] }), + getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), + }; + + link(config, ['react-native-gradient']).then(() => { + expect( + config.getDependencyConfig.calledWith('react-native-gradient') + ).to.be.true; + done(); + }); + }); + + it('should read dependencies from package.json when name not provided', (done) => { + const config = { + getProjectConfig: () => ({ assets: [] }), + getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), + }; + + mock( + path.join(process.cwd(), 'package.json'), + { + dependencies: { + 'react-native-test': '*', + }, + } + ); + + link(config, []).then(() => { + expect( + config.getDependencyConfig.calledWith('react-native-test') + ).to.be.true; + done(); + }); + }); + + it('should register native module when android/ios projects are present', (done) => { + const registerNativeModule = sinon.stub(); + const dependencyConfig = {android: {}, ios: {}, assets: [], commands: {}}; + const config = { + getProjectConfig: () => ({android: {}, ios: {}, assets: []}), + getDependencyConfig: sinon.stub().returns(dependencyConfig), + }; + + mock( + '../src/android/isInstalled.js', + sinon.stub().returns(false) + ); + + mock( + '../src/android/registerNativeModule.js', + registerNativeModule + ); + + mock( + '../src/ios/isInstalled.js', + sinon.stub().returns(false) + ); + + mock( + '../src/ios/registerNativeModule.js', + registerNativeModule + ); + + const link = require('../src/link'); + + link(config, ['react-native-blur']).then(() => { + expect(registerNativeModule.calledTwice).to.be.true; + done(); + }); + }); + + it('should not register modules when they are already installed', (done) => { + const registerNativeModule = sinon.stub(); + const dependencyConfig = {ios: {}, android: {}, assets: [], commands: {}}; + const config = { + getProjectConfig: () => ({ ios: {}, android: {}, assets: [] }), + getDependencyConfig: sinon.stub().returns(dependencyConfig), + }; + + mock( + '../src/ios/isInstalled.js', + sinon.stub().returns(true) + ); + + mock( + '../src/android/isInstalled.js', + sinon.stub().returns(true) + ); + + mock( + '../src/ios/registerNativeModule.js', + registerNativeModule + ); + + mock( + '../src/android/registerNativeModule.js', + registerNativeModule + ); + + const link = require('../src/link'); + + link(config, ['react-native-blur']).then(() => { + expect(registerNativeModule.callCount).to.equal(0); + done(); + }); + }); + + it('should run prelink and postlink commands at the appropriate times', (done) => { + const registerNativeModule = sinon.stub(); + const prelink = sinon.stub().yieldsAsync(); + const postlink = sinon.stub().yieldsAsync(); + + mock( + '../src/ios/registerNativeModule.js', + registerNativeModule + ); + + mock( + '../src/ios/isInstalled.js', + sinon.stub().returns(false) + ); + + const config = { + getProjectConfig: () => ({ ios: {}, assets: [] }), + getDependencyConfig: sinon.stub().returns({ + ios: {}, assets: [], commands: { prelink, postlink }, + }), + }; + + const link = require('../src/link'); + + link(config, ['react-native-blur']).then(() => { + expect(prelink.calledBefore(registerNativeModule)).to.be.true; + expect(postlink.calledAfter(registerNativeModule)).to.be.true; + done(); + }); + }); + + it('should copy assets from both project and dependencies projects', (done) => { + const dependencyAssets = ['Fonts/Font.ttf']; + const dependencyConfig = {assets: dependencyAssets, commands: {}}; + const projectAssets = ['Fonts/FontC.ttf']; + const copyAssets = sinon.stub(); + + mock( + '../src/ios/copyAssets.js', + copyAssets + ); + + const config = { + getProjectConfig: () => ({ ios: {}, assets: projectAssets }), + getDependencyConfig: sinon.stub().returns(dependencyConfig), + }; + + const link = require('../src/link'); + + link(config, ['react-native-blur']).then(() => { + expect(copyAssets.calledOnce).to.be.true; + expect(copyAssets.getCall(0).args[0]).to.deep.equals( + projectAssets.concat(dependencyAssets) + ); + done(); + }); + }); + + afterEach(() => { + mock.stopAll(); + }); + +}); diff --git a/local-cli/rnpm/link/test/promiseWaterfall.spec.js b/local-cli/rnpm/link/test/promiseWaterfall.spec.js new file mode 100644 index 00000000000000..55d215038f03cb --- /dev/null +++ b/local-cli/rnpm/link/test/promiseWaterfall.spec.js @@ -0,0 +1,37 @@ +const chai = require('chai'); +const expect = chai.expect; +const sinon = require('sinon'); +const promiseWaterfall = require('../src/promiseWaterfall'); + +describe('promiseWaterfall', () => { + + it('should run promises in a sequence', (done) => { + const tasks = [sinon.stub(), sinon.stub()]; + + promiseWaterfall(tasks).then(() => { + expect(tasks[0].calledBefore(tasks[1])).to.be.true; + done(); + }); + }); + + it('should resolve with last promise value', (done) => { + const tasks = [sinon.stub().returns(1), sinon.stub().returns(2)]; + + promiseWaterfall(tasks).then(value => { + expect(value).to.equal(2); + done(); + }); + }); + + it('should stop the sequence when one of promises is rejected', (done) => { + const error = new Error(); + const tasks = [sinon.stub().throws(error), sinon.stub().returns(2)]; + + promiseWaterfall(tasks).catch(err => { + expect(err).to.equal(error); + expect(tasks[1].callCount).to.equal(0); + done(); + }); + }); + +}); diff --git a/package.json b/package.json index 6ecdcb28406df2..df2d7d249f1dc5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "^[./a-zA-Z0-9$_-]+\\.png$": "RelativeImageStub" }, "testPathIgnorePatterns": [ - "/node_modules/" + "/node_modules/", + "/local-cli/rnpm/" ], "haste": { "defaultPlatform": "ios",