diff --git a/local-cli/core/__tests__/findPlugins.spec.js b/local-cli/core/__tests__/findPlugins.spec.js index 075e2cdbf..3cc7488a3 100644 --- a/local-cli/core/__tests__/findPlugins.spec.js +++ b/local-cli/core/__tests__/findPlugins.spec.js @@ -27,18 +27,27 @@ describe('findPlugins', () => { jest.mock(pjsonPath, () => ({ dependencies: {'rnpm-plugin-test': '*'}, })); - expect(findPlugins([ROOT])).toHaveLength(1); - expect(findPlugins([ROOT])[0]).toBe('rnpm-plugin-test'); + + expect(findPlugins([ROOT])).toHaveProperty('commands'); + expect(findPlugins([ROOT])).toHaveProperty('platforms'); + expect(findPlugins([ROOT]).commands).toHaveLength(1); + expect(findPlugins([ROOT]).commands[0]).toBe('rnpm-plugin-test'); + expect(findPlugins([ROOT]).platforms).toHaveLength(0); }); it('returns an empty array if there are no plugins in this folder', () => { jest.mock(pjsonPath, () => ({})); - expect(findPlugins([ROOT])).toHaveLength(0); + expect(findPlugins([ROOT])).toHaveProperty('commands'); + expect(findPlugins([ROOT])).toHaveProperty('platforms'); + expect(findPlugins([ROOT]).commands).toHaveLength(0); + expect(findPlugins([ROOT]).platforms).toHaveLength(0); }); - it('returns an empty array if there is no package.json in the supplied folder', () => { - expect(Array.isArray(findPlugins(['fake-path']))).toBeTruthy(); - expect(findPlugins(['fake-path'])).toHaveLength(0); + it('returns an object with empty arrays if there is no package.json in the supplied folder', () => { + expect(findPlugins(['fake-path'])).toHaveProperty('commands'); + expect(findPlugins(['fake-path'])).toHaveProperty('platforms'); + expect(findPlugins(['fake-path']).commands).toHaveLength(0); + expect(findPlugins(['fake-path']).platforms).toHaveLength(0); }); it('returns plugins from both dependencies and dev dependencies', () => { @@ -46,7 +55,10 @@ describe('findPlugins', () => { dependencies: {'rnpm-plugin-test': '*'}, devDependencies: {'rnpm-plugin-test-2': '*'}, })); - expect(findPlugins([ROOT])).toHaveLength(2); + expect(findPlugins([ROOT])).toHaveProperty('commands'); + expect(findPlugins([ROOT])).toHaveProperty('platforms'); + expect(findPlugins([ROOT]).commands).toHaveLength(2); + expect(findPlugins([ROOT]).platforms).toHaveLength(0); }); it('returns unique list of plugins', () => { @@ -54,6 +66,6 @@ describe('findPlugins', () => { dependencies: {'rnpm-plugin-test': '*'}, devDependencies: {'rnpm-plugin-test': '*'}, })); - expect(findPlugins([ROOT])).toHaveLength(1); + expect(findPlugins([ROOT]).commands).toHaveLength(1); }); }); diff --git a/local-cli/core/findPlugins.js b/local-cli/core/findPlugins.js index f9dc1bc26..8a3c30a42 100644 --- a/local-cli/core/findPlugins.js +++ b/local-cli/core/findPlugins.js @@ -37,11 +37,19 @@ const findPluginsInReactNativePackage = (pjson) => { return path.join(pjson.name, pjson.rnpm.plugin); }; +const findPlatformsInPackage = (pjson) => { + if (!pjson.rnpm || !pjson.rnpm.platform) { + return []; + } + + return path.join(pjson.name, pjson.rnpm.platform); +}; + const findPluginInFolder = (folder) => { const pjson = readPackage(folder); if (!pjson) { - return []; + return {commands: [], platforms: []}; } const deps = union( @@ -51,27 +59,33 @@ const findPluginInFolder = (folder) => { return deps.reduce( (acc, pkg) => { + let commands = acc.commands; + let platforms = acc.platforms; if (isRNPMPlugin(pkg)) { - return acc.concat(pkg); + commands = commands.concat(pkg); } if (isReactNativePlugin(pkg)) { const pkgJson = readPackage(path.join(folder, 'node_modules', pkg)); - if (!pkgJson) { - return acc; + if (pkgJson) { + commands = commands.concat(findPluginsInReactNativePackage(pkgJson)); + platforms = platforms.concat(findPlatformsInPackage(pkgJson)); } - return acc.concat(findPluginsInReactNativePackage(pkgJson)); } - return acc; + return {commands: commands, platforms: platforms}; }, - [] + {commands: [], platforms: []} ); }; /** * 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 + * @type {Object} Object of commands and platform plugins */ module.exports = function findPlugins(folders) { - return uniq(flatten(folders.map(findPluginInFolder))); + const plugins = folders.map(findPluginInFolder); + return { + commands: uniq(flatten(plugins.map(p => p.commands))), + platforms: uniq(flatten(plugins.map(p => p.platforms))) + }; }; diff --git a/local-cli/core/index.js b/local-cli/core/index.js index 97a2eeb2b..0d43e87c2 100644 --- a/local-cli/core/index.js +++ b/local-cli/core/index.js @@ -15,7 +15,6 @@ const Config = require('../util/Config'); const findPlugins = require('./findPlugins'); const findAssets = require('./findAssets'); const ios = require('./ios'); -const windows = require('./windows'); const wrapCommands = require('./wrapCommands'); /* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error @@ -33,6 +32,10 @@ import type {ConfigT} from 'metro'; export type RNConfig = { ...ConfigT, + /** + * Returns an object with all platform configurations. + */ + getPlatformConfig(): Object, /** * Returns an array of project commands used by the CLI to load */ @@ -55,10 +58,21 @@ const attachPackage = (command, pkg) => Array.isArray(command) ? command.map(cmd => attachPackage(cmd, pkg)) : { ...command, pkg }; +const appRoot = process.cwd(); +const plugins = findPlugins([appRoot]); +const pluginPlatforms = plugins + .platforms + .reduce((acc, pathToPlatforms) => { + // $FlowFixMe non-literal require + return Object.assign(acc, require(path.join(appRoot, 'node_modules', pathToPlatforms))); + }, + {}); + const defaultRNConfig = { + getProjectCommands(): Array { - const appRoot = process.cwd(); - const plugins = findPlugins([appRoot]) + const commands = plugins + .commands .map(pathToCommands => { const name = pathToCommands.split(path.sep)[0]; @@ -70,35 +84,51 @@ const defaultRNConfig = { ); }); - return flatten(plugins); + return flatten(commands); + }, + + getPlatformConfig(): Object { + return { + ios, + android, + ...pluginPlatforms + }; }, getProjectConfig(): Object { + const platforms = this.getPlatformConfig(); const folder = process.cwd(); const rnpm = getRNPMConfig(folder); - return Object.assign({}, rnpm, { - ios: ios.projectConfig(folder, rnpm.ios || {}), - android: android.projectConfig(folder, rnpm.android || {}), - windows: windows.projectConfig(folder, rnpm.windows || {}), + let config = Object.assign({}, rnpm, { assets: findAssets(folder, rnpm.assets), }); + + Object.keys(platforms).forEach(key => { + config[key] = platforms[key].projectConfig(folder, rnpm[key] || {}); + }); + + return config; }, getDependencyConfig(packageName: string) { + const platforms = this.getPlatformConfig(); const folder = path.join(process.cwd(), 'node_modules', packageName); const rnpm = getRNPMConfig( path.join(process.cwd(), 'node_modules', packageName) ); - return Object.assign({}, rnpm, { - ios: ios.dependencyConfig(folder, rnpm.ios || {}), - android: android.dependencyConfig(folder, rnpm.android || {}), - windows: windows.dependencyConfig(folder, rnpm.windows || {}), + let config = Object.assign({}, rnpm, { assets: findAssets(folder, rnpm.assets), commands: wrapCommands(rnpm.commands), params: rnpm.params || [], }); + + Object.keys(platforms).forEach(key => { + config[key] = platforms[key].dependencyConfig(folder, rnpm[key] || {}); + }); + + return config; }, }; diff --git a/local-cli/core/windows/findNamespace.js b/local-cli/core/windows/findNamespace.js deleted file mode 100644 index 4867368f1..000000000 --- a/local-cli/core/windows/findNamespace.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -const fs = require('fs'); -const glob = require('glob'); -const path = require('path'); - -/** - * Gets package's namespace - * by searching for its declaration in all C# files present in the folder - * - * @param {String} folder Folder to find C# files - */ -module.exports = function getNamespace(folder) { - const files = glob.sync('**/*.cs', { cwd: folder }); - - const packages = files - .map(filePath => fs.readFileSync(path.join(folder, filePath), 'utf8')) - .map(file => file.match(/namespace (.*)[\s\S]+IReactPackage/)) - .filter(match => match); - - return packages.length ? packages[0][1] : null; -}; diff --git a/local-cli/core/windows/findPackageClassName.js b/local-cli/core/windows/findPackageClassName.js deleted file mode 100644 index 279610eac..000000000 --- a/local-cli/core/windows/findPackageClassName.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -const fs = require('fs'); -const glob = require('glob'); -const path = require('path'); - -/** - * Gets package's class name (class that implements IReactPackage) - * by searching for its declaration in all C# files present in the folder - * - * @param {String} folder Folder to find C# files - */ -module.exports = function getPackageClassName(folder) { - const files = glob.sync('**/*.cs', { cwd: folder }); - - const packages = files - .map(filePath => fs.readFileSync(path.join(folder, filePath), 'utf8')) - .map(file => file.match(/class (.*) : IReactPackage/)) - .filter(match => match); - - return packages.length ? packages[0][1] : null; -}; diff --git a/local-cli/core/windows/findProject.js b/local-cli/core/windows/findProject.js deleted file mode 100644 index 3b0121f74..000000000 --- a/local-cli/core/windows/findProject.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -const glob = require('glob'); -const path = require('path'); - -/** - * Find an C# project file - * - * @param {String} folder Name of the folder where to seek - * @return {String} - */ -module.exports = function findManifest(folder) { - const csprojPath = glob.sync(path.join('**', '*.csproj'), { - cwd: folder, - ignore: ['node_modules/**', '**/build/**', 'Examples/**', 'examples/**'], - })[0]; - - return csprojPath ? path.join(folder, csprojPath) : null; -}; diff --git a/local-cli/core/windows/findWindowsSolution.js b/local-cli/core/windows/findWindowsSolution.js deleted file mode 100644 index fb6782633..000000000 --- a/local-cli/core/windows/findWindowsSolution.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -const glob = require('glob'); -const path = require('path'); - -/** - * Glob pattern to look for solution file - */ -const GLOB_PATTERN = '**/*.sln'; - -/** - * Regexp matching all test projects - */ -const TEST_PROJECTS = /test|example|sample/i; - -/** - * Base windows folder - */ -const WINDOWS_BASE = 'windows'; - -/** - * These folders will be excluded from search to speed it up - */ -const GLOB_EXCLUDE_PATTERN = ['**/@(node_modules)/**']; - -/** - * Finds windows project by looking for all .sln files - * in given folder. - * - * Returns first match if files are found or null - * - * Note: `./windows/*.sln` are returned regardless of the name - */ -module.exports = function findSolution(folder) { - const projects = glob - .sync(GLOB_PATTERN, { - cwd: folder, - ignore: GLOB_EXCLUDE_PATTERN, - }) - .filter(project => { - return path.dirname(project) === WINDOWS_BASE || !TEST_PROJECTS.test(project); - }) - .sort((projectA, projectB) => { - return path.dirname(projectA) === WINDOWS_BASE ? -1 : 1; - }); - - if (projects.length === 0) { - return null; - } - - return projects[0]; -}; diff --git a/local-cli/core/windows/generateGUID.js b/local-cli/core/windows/generateGUID.js deleted file mode 100644 index 25eb456eb..000000000 --- a/local-cli/core/windows/generateGUID.js +++ /dev/null @@ -1,10 +0,0 @@ -const s4 = () => { - return Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); -}; - -module.exports = function generateGUID() { - return s4() + s4() + '-' + s4() + '-' + s4() + '-' + - s4() + '-' + s4() + s4() + s4(); -}; diff --git a/local-cli/core/windows/index.js b/local-cli/core/windows/index.js deleted file mode 100644 index c535c4c3e..000000000 --- a/local-cli/core/windows/index.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ -'use strict'; - -const findWindowsSolution = require('./findWindowsSolution'); -const findNamespace = require('./findNamespace'); -const findProject = require('./findProject'); -const findPackageClassName = require('./findPackageClassName'); -const path = require('path'); -const generateGUID = require('./generateGUID'); - -const relativeProjectPath = (fullProjPath) => { - const windowsPath = fullProjPath - .substring(fullProjPath.lastIndexOf('node_modules') - 1, fullProjPath.length) - .replace(/\//g, '\\'); - - return '..' + windowsPath; -}; - -const getProjectName = (fullProjPath) => { - return fullProjPath.split('/').slice(-1)[0].replace(/\.csproj/i, ''); -}; - -/** - * Gets windows project config by analyzing given folder and taking some - * defaults specified by user into consideration - */ -exports.projectConfig = function projectConfigWindows(folder, userConfig) { - - const csSolution = userConfig.csSolution || findWindowsSolution(folder); - - if (!csSolution) { - return null; - } - - // expects solutions to be named the same as project folders - const solutionPath = path.join(folder, csSolution); - const windowsAppFolder = csSolution.substring(0, csSolution.lastIndexOf('.sln')); - const src = userConfig.sourceDir || windowsAppFolder; - const sourceDir = path.join(folder, src); - const mainPage = path.join(sourceDir, 'MainPage.cs'); - const projectPath = userConfig.projectPath || findProject(folder); - - return { - sourceDir, - solutionPath, - projectPath, - mainPage, - folder, - userConfig, - }; -}; - -/** - * Same as projectConfigWindows except it returns - * different config that applies to packages only - */ -exports.dependencyConfig = function dependencyConfigWindows(folder, userConfig) { - - const csSolution = userConfig.csSolution || findWindowsSolution(folder); - - if (!csSolution) { - return null; - } - - // expects solutions to be named the same as project folders - const windowsAppFolder = csSolution.substring(0, csSolution.lastIndexOf('.sln')); - const src = userConfig.sourceDir || windowsAppFolder; - - if (!src) { - return null; - } - - const sourceDir = path.join(folder, src); - const packageClassName = findPackageClassName(sourceDir); - const namespace = userConfig.namespace || findNamespace(sourceDir); - const csProj = userConfig.csProj || findProject(folder); - - /** - * This module has no package to export or no namespace - */ - if (!packageClassName || !namespace) { - return null; - } - - const packageUsingPath = userConfig.packageUsingPath || - `using ${namespace};`; - - const packageInstance = userConfig.packageInstance || - `new ${packageClassName}()`; - - const projectGUID = generateGUID(); - const pathGUID = generateGUID(); - const projectName = getProjectName(csProj); - const relativeProjPath = relativeProjectPath(csProj); - - return { - sourceDir, - packageUsingPath, - packageInstance, - projectName, - csProj, - folder, - projectGUID, - pathGUID, - relativeProjPath, - }; -}; diff --git a/local-cli/link/__tests__/link.spec.js b/local-cli/link/__tests__/link.spec.js index 3972a0a68..aaad000e9 100644 --- a/local-cli/link/__tests__/link.spec.js +++ b/local-cli/link/__tests__/link.spec.js @@ -39,6 +39,7 @@ describe('link', () => { it('should accept a name of a dependency to link', (done) => { const config = { + getPlatformConfig: () => ({ios: {}, android: {}}), getProjectConfig: () => ({ assets: [] }), getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), }; @@ -54,6 +55,7 @@ describe('link', () => { it('should read dependencies from package.json when name not provided', (done) => { const config = { + getPlatformConfig: () => ({ios: {}, android: {}}), getProjectConfig: () => ({ assets: [] }), getDependencyConfig: sinon.stub().returns({ assets: [], commands: {} }), }; @@ -80,6 +82,7 @@ describe('link', () => { const registerNativeModule = sinon.stub(); const dependencyConfig = {android: {}, ios: {}, assets: [], commands: {}}; const config = { + getPlatformConfig: () => ({ios: {}, android: {}}), getProjectConfig: () => ({android: {}, ios: {}, assets: []}), getDependencyConfig: sinon.stub().returns(dependencyConfig), }; @@ -116,6 +119,7 @@ describe('link', () => { const registerNativeModule = sinon.stub(); const dependencyConfig = {ios: {}, android: {}, assets: [], commands: {}}; const config = { + getPlatformConfig: () => ({ios: {}, android: {}}), getProjectConfig: () => ({ ios: {}, android: {}, assets: [] }), getDependencyConfig: sinon.stub().returns(dependencyConfig), }; @@ -148,6 +152,62 @@ describe('link', () => { }); }); + it('should register native modules for plugins', (done) => { + const registerNativeModule = sinon.stub(); + const dependencyConfig = {ios: {}, android: {}, test: {}, assets: [], commands: {}}; + const linkPluginConfig = { isInstalled: () => false, register: registerNativeModule }; + const config = { + getPlatformConfig: () => ({ ios: {}, android: {}, test: { linkConfig: () => linkPluginConfig }}), + getProjectConfig: () => ({ ios: {}, android: {}, test: {}, assets: [] }), + getDependencyConfig: sinon.stub().returns(dependencyConfig), + }; + + jest.setMock( + '../ios/isInstalled.js', + sinon.stub().returns(true) + ); + + jest.setMock( + '../android/isInstalled.js', + sinon.stub().returns(true) + ); + + const link = require('../link').func; + + link(['react-native-blur'], config).then(() => { + expect(registerNativeModule.calledOnce).toBeTruthy(); + done(); + }); + }); + + it('should not register native modules for plugins when already installed', (done) => { + const registerNativeModule = sinon.stub(); + const dependencyConfig = {ios: {}, android: {}, test: {}, assets: [], commands: {}}; + const linkPluginConfig = { isInstalled: () => true, register: registerNativeModule}; + const config = { + getPlatformConfig: () => ({ ios: {}, android: {}, test: { linkConfig: () => linkPluginConfig }}), + getProjectConfig: () => ({ ios: {}, android: {}, test: {}, assets: [] }), + getDependencyConfig: sinon.stub().returns(dependencyConfig), + }; + + jest.setMock( + '../ios/isInstalled.js', + sinon.stub().returns(true) + ); + + jest.setMock( + '../android/isInstalled.js', + sinon.stub().returns(true) + ); + + const link = require('../link').func; + + link(['react-native-blur'], config).then(() => { + expect(registerNativeModule.callCount).toEqual(0); + done(); + }); + }); + it('should run prelink and postlink commands at the appropriate times', (done) => { const registerNativeModule = sinon.stub(); const prelink = sinon.stub().yieldsAsync(); @@ -164,6 +224,7 @@ describe('link', () => { ); const config = { + getPlatformConfig: () => ({ ios: {}}), getProjectConfig: () => ({ ios: {}, assets: [] }), getDependencyConfig: sinon.stub().returns({ ios: {}, assets: [], commands: { prelink, postlink }, @@ -181,7 +242,7 @@ describe('link', () => { it('should copy assets from both project and dependencies projects', (done) => { const dependencyAssets = ['Fonts/Font.ttf']; - const dependencyConfig = {assets: dependencyAssets, commands: {}}; + const dependencyConfig = {assets: dependencyAssets, ios: {}, commands: {}}; const projectAssets = ['Fonts/FontC.ttf']; const copyAssets = sinon.stub(); @@ -191,6 +252,7 @@ describe('link', () => { ); const config = { + getPlatformConfig: () => ({ ios: {} }), getProjectConfig: () => ({ ios: {}, assets: projectAssets }), getDependencyConfig: sinon.stub().returns(dependencyConfig), }; diff --git a/local-cli/link/link.js b/local-cli/link/link.js index 969bf63a8..b38faa9dd 100644 --- a/local-cli/link/link.js +++ b/local-cli/link/link.js @@ -27,11 +27,9 @@ const chalk = require('chalk'); const isEmpty = require('lodash').isEmpty; const promiseWaterfall = require('./promiseWaterfall'); const registerDependencyAndroid = require('./android/registerNativeModule'); -const registerDependencyWindows = require('./windows/registerNativeModule'); const registerDependencyIOS = require('./ios/registerNativeModule'); const registerDependencyPods = require('./pods/registerNativeModule'); const isInstalledAndroid = require('./android/isInstalled'); -const isInstalledWindows = require('./windows/isInstalled'); const isInstalledIOS = require('./ios/isInstalled'); const isInstalledPods = require('./pods/isInstalled'); const copyAssetsAndroid = require('./android/copyAssets'); @@ -76,31 +74,40 @@ const linkDependencyAndroid = (androidProject, dependency) => { }); }; -const linkDependencyWindows = (windowsProject, dependency) => { +const linkDependencyPlatforms = (platforms, project, dependency) => { + const ignorePlatforms = ['android', 'ios']; + Object.keys(platforms || {}) + .filter(platform => ignorePlatforms.indexOf(platform) < 0) + .forEach(platform => { + if (!project[platform] || !dependency.config[platform]) { + return null; + } - if (!windowsProject || !dependency.config.windows) { - return null; - } + const linkConfig = platforms[platform] && platforms[platform].linkConfig && platforms[platform].linkConfig(); + if (!linkConfig || !linkConfig.isInstalled || !linkConfig.register) { + return null; + } - const isInstalled = isInstalledWindows(windowsProject, dependency.config.windows); + const isInstalled = linkConfig.isInstalled(project[platform], dependency.config[platform]); - if (isInstalled) { - log.info(chalk.grey(`Windows module ${dependency.name} is already linked`)); - return null; - } + if (isInstalled) { + log.info(chalk.grey(`Platform '${platform}' module ${dependency.name} is already linked`)); + return null; + } - return pollParams(dependency.config.params).then(params => { - log.info(`Linking ${dependency.name} windows dependency`); + return pollParams(dependency.config.params).then(params => { + log.info(`Linking ${dependency.name} ${platform} dependency`); - registerDependencyWindows( - dependency.name, - dependency.config.windows, - params, - windowsProject - ); + linkConfig.register( + dependency.name, + dependency.config[platform], + params, + project[platform] + ); - log.info(`Windows module ${dependency.name} has been successfully linked`); - }); + log.info(`Platform '${platform}' module ${dependency.name} has been successfully linked`); + }); + }); }; const linkDependencyIOS = (iOSProject, dependency) => { @@ -124,7 +131,7 @@ const linkDependencyIOS = (iOSProject, dependency) => { log.info(`iOS module ${dependency.name} has been successfully linked`); }; -const linkAssets = (project, assets) => { +const linkAssets = (platforms, project, assets) => { if (isEmpty(assets)) { return; } @@ -139,6 +146,19 @@ const linkAssets = (project, assets) => { copyAssetsAndroid(assets, project.android.assetsPath); } + const ignorePlatforms = ['android', 'ios']; + Object.keys(platforms || {}) + .filter(platform => ignorePlatforms.indexOf(platform) < 0) + .forEach(platform => { + const linkConfig = platforms[platform] && platforms[platform].linkConfig && platforms[platform].linkConfig(); + if (!linkConfig || !linkConfig.copyAssets) { + return; + } + + log.info(`Linking assets to ${platform} project`); + linkConfig.copyAssets(assets, project[platform]); + }); + log.info('Assets have been successfully linked to your project'); }; @@ -150,9 +170,11 @@ const linkAssets = (project, assets) => { * @param config CLI config, see local-cli/core/index.js */ function link(args: Array, config: RNConfig) { - var project; + let project; + let platforms; try { project = config.getProjectConfig(); + platforms = config.getPlatformConfig(); } catch (err) { log.error( 'ERRPACKAGEJSON', @@ -161,7 +183,8 @@ function link(args: Array, config: RNConfig) { return Promise.reject(err); } - if (!project.android && !project.ios && !project.windows && findReactNativeScripts()) { + const hasProjectConfig = Object.keys(platforms).reduce((acc, key) => acc || key in project, false); + if (!hasProjectConfig && findReactNativeScripts()) { throw new Error( '`react-native link` can not be used in Create React Native App projects. ' + 'If you need to include a library that relies on custom native code, ' + @@ -183,7 +206,7 @@ function link(args: Array, config: RNConfig) { ); const assets = dedupeAssets(dependencies.reduce( - (assets, dependency) => assets.concat(dependency.config.assets), + (acc, dependency) => acc.concat(dependency.config.assets), project.assets )); @@ -191,11 +214,11 @@ function link(args: Array, config: RNConfig) { () => promisify(dependency.config.commands.prelink || commandStub), () => linkDependencyAndroid(project.android, dependency), () => linkDependencyIOS(project.ios, dependency), - () => linkDependencyWindows(project.windows, dependency), + () => linkDependencyPlatforms(platforms, project, dependency), () => promisify(dependency.config.commands.postlink || commandStub), ])); - tasks.push(() => linkAssets(project, assets)); + tasks.push(() => linkAssets(platforms, project, assets)); return promiseWaterfall(tasks).catch(err => { log.error( diff --git a/local-cli/link/unlink.js b/local-cli/link/unlink.js index f4f001853..020802146 100644 --- a/local-cli/link/unlink.js +++ b/local-cli/link/unlink.js @@ -2,11 +2,9 @@ const log = require('npmlog'); const getProjectDependencies = require('./getProjectDependencies'); const unregisterDependencyAndroid = require('./android/unregisterNativeModule'); -const unregisterDependencyWindows = require('./windows/unregisterNativeModule'); const unregisterDependencyIOS = require('./ios/unregisterNativeModule'); const unregisterDependencyPods = require('./pods/unregisterNativeModule'); const isInstalledAndroid = require('./android/isInstalled'); -const isInstalledWindows = require('./windows/isInstalled'); const isInstalledIOS = require('./ios/isInstalled'); const isInstalledPods = require('./pods/isInstalled'); const unlinkAssetsAndroid = require('./android/unlinkAssets'); @@ -42,23 +40,38 @@ const unlinkDependencyAndroid = (androidProject, dependency, packageName) => { log.info(`Android module ${packageName} has been successfully unlinked`); }; -const unlinkDependencyWindows = (windowsProject, dependency, packageName) => { - if (!windowsProject || !dependency.windows) { - return; - } +const unlinkDependencyPlatforms = (platforms, project, dependency, packageName) => { - const isInstalled = isInstalledWindows(windowsProject, dependency.windows); + const ignorePlatforms = ['android', 'ios']; + Object.keys(platforms || {}) + .filter(platform => ignorePlatforms.indexOf(platform) < 0) + .forEach(platform => { + if (!project[platform] || !dependency[platform]) { + return null; + } - if (!isInstalled) { - log.info(`Windows module ${packageName} is not installed`); - return; - } + const linkConfig = platforms[platform] && platforms[platform].linkConfig && platforms[platform].linkConfig(); + if (!linkConfig || !linkConfig.isInstalled || !linkConfig.unregister) { + return null; + } - log.info(`Unlinking ${packageName} windows dependency`); + const isInstalled = linkConfig.isInstalled(project[platform], dependency[platform]); - unregisterDependencyWindows(packageName, dependency.windows, windowsProject); + if (!isInstalled) { + log.info(`Platform '${platform}' module ${packageName} is not installed`); + return; + } - log.info(`Windows module ${packageName} has been successfully unlinked`); + log.info(`Unlinking ${packageName} ${platform} dependency`); + + linkConfig.unregister( + packageName, + dependency[platform], + project[platform] + ); + + log.info(`Platform '${platform}' module ${dependency.name} has been successfully unlinked`); + }); }; const unlinkDependencyIOS = (iOSProject, dependency, packageName, iOSDependencies) => { @@ -94,10 +107,12 @@ const unlinkDependencyIOS = (iOSProject, dependency, packageName, iOSDependencie function unlink(args, config) { const packageName = args[0]; - var project; - var dependency; + let platforms; + let project; + let dependency; try { + platforms = config.getPlatformConfig(); project = config.getProjectConfig(); } catch (err) { log.error( @@ -125,7 +140,7 @@ function unlink(args, config) { () => promisify(dependency.commands.preunlink || commandStub), () => unlinkDependencyAndroid(project.android, dependency, packageName), () => unlinkDependencyIOS(project.ios, dependency, packageName, iOSDependencies), - () => unlinkDependencyWindows(project.windows, dependency, packageName), + () => unlinkDependencyPlatforms(platforms, project, dependency, packageName), () => promisify(dependency.commands.postunlink || commandStub) ]; diff --git a/local-cli/link/windows/isInstalled.js b/local-cli/link/windows/isInstalled.js deleted file mode 100644 index b96edcb21..000000000 --- a/local-cli/link/windows/isInstalled.js +++ /dev/null @@ -1,8 +0,0 @@ -const fs = require('fs'); -const makeUsingPatch = require('./patches/makeUsingPatch'); - -module.exports = function isInstalled(config, dependencyConfig) { - return fs - .readFileSync(config.mainPage) - .indexOf(makeUsingPatch(dependencyConfig.packageUsingPath).patch) > -1; -}; diff --git a/local-cli/link/windows/patches/applyParams.js b/local-cli/link/windows/patches/applyParams.js deleted file mode 100644 index 21c1e9545..000000000 --- a/local-cli/link/windows/patches/applyParams.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2013-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -const toCamelCase = require('lodash').camelCase; - -module.exports = function applyParams(str, params, prefix) { - return str.replace( - /\$\{(\w+)\}/g, - (pattern, param) => { - const name = toCamelCase(prefix) + '_' + param; - - return params[param] - ? `getResources().getString(R.string.${name})` - : null; - } - ); -}; diff --git a/local-cli/link/windows/patches/applyPatch.js b/local-cli/link/windows/patches/applyPatch.js deleted file mode 100644 index 81757076c..000000000 --- a/local-cli/link/windows/patches/applyPatch.js +++ /dev/null @@ -1,11 +0,0 @@ -const fs = require('fs'); - -module.exports = function applyPatch(file, patch, flip = false) { - - fs.writeFileSync(file, fs - .readFileSync(file, 'utf8') - .replace(patch.pattern, match => { - return flip ? `${patch.patch}${match}` : `${match}${patch.patch}`; - }) - ); -}; diff --git a/local-cli/link/windows/patches/makePackagePatch.js b/local-cli/link/windows/patches/makePackagePatch.js deleted file mode 100644 index ef60e1e6a..000000000 --- a/local-cli/link/windows/patches/makePackagePatch.js +++ /dev/null @@ -1,10 +0,0 @@ -const applyParams = require('./applyParams'); - -module.exports = function makePackagePatch(packageInstance, params, prefix) { - const processedInstance = applyParams(packageInstance, params, prefix); - - return { - pattern: 'new MainReactPackage()', - patch: ',\n ' + processedInstance, - }; -}; diff --git a/local-cli/link/windows/patches/makeProjectPatch.js b/local-cli/link/windows/patches/makeProjectPatch.js deleted file mode 100644 index 6b3d85855..000000000 --- a/local-cli/link/windows/patches/makeProjectPatch.js +++ /dev/null @@ -1,14 +0,0 @@ -module.exports = function makeProjectPatch(windowsConfig) { - - const projectInsert = ` - {${windowsConfig.pathGUID}} - ${windowsConfig.projectName} - - `; - - return { - pattern: '', - patch: projectInsert, - unpatch: new RegExp(`