Repository: cordova-lib Updated Branches: refs/heads/master 9d8124bef -> 5737d74b2
CB-10822 Manage plugins/modules metadata using PlatformJson Project: http://git-wip-us.apache.org/repos/asf/cordova-lib/repo Commit: http://git-wip-us.apache.org/repos/asf/cordova-lib/commit/57f2b3f2 Tree: http://git-wip-us.apache.org/repos/asf/cordova-lib/tree/57f2b3f2 Diff: http://git-wip-us.apache.org/repos/asf/cordova-lib/diff/57f2b3f2 Branch: refs/heads/master Commit: 57f2b3f2a4e6129dab7f9e039823b339bf26db3a Parents: 9d8124b Author: Vladimir Kotikov <v-vlk...@microsoft.com> Authored: Thu Mar 10 14:03:11 2016 +0300 Committer: Vladimir Kotikov <v-vlk...@microsoft.com> Committed: Wed Mar 30 15:33:40 2016 +0300 ---------------------------------------------------------------------- cordova-common/package.json | 5 +- cordova-common/spec/PlatformJson.spec.js | 160 ++++++++++++++++++++++++++ cordova-common/src/PlatformJson.js | 124 ++++++++++++++++++++ 3 files changed, 287 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/57f2b3f2/cordova-common/package.json ---------------------------------------------------------------------- diff --git a/cordova-common/package.json b/cordova-common/package.json index 20c4916..6c5e1f2 100644 --- a/cordova-common/package.json +++ b/cordova-common/package.json @@ -33,14 +33,15 @@ "plist": "^1.2.0", "q": "^1.4.1", "semver": "^5.0.1", - "shelljs": "^0.5.1", + "shelljs": "^0.5.3", "underscore": "^1.8.3", "unorm": "^1.3.3" }, "devDependencies": { "istanbul": "^0.3.17", "jasmine-node": "^1.14.5", - "jshint": "^2.8.0" + "jshint": "^2.8.0", + "rewire": "^2.5.1" }, "contributors": [] } http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/57f2b3f2/cordova-common/spec/PlatformJson.spec.js ---------------------------------------------------------------------- diff --git a/cordova-common/spec/PlatformJson.spec.js b/cordova-common/spec/PlatformJson.spec.js new file mode 100644 index 0000000..c9310a2 --- /dev/null +++ b/cordova-common/spec/PlatformJson.spec.js @@ -0,0 +1,160 @@ +/** + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. +*/ + +var rewire = require('rewire'); +var PlatformJson = rewire('../src/PlatformJson'); +var ModuleMetadata = PlatformJson.__get__('ModuleMetadata'); + +var FAKE_MODULE = { + name: 'fakeModule', + src: 'www/fakeModule.js', + clobbers: [{target: 'window.fakeClobber'}], + merges: [{target: 'window.fakeMerge'}], + runs: true +}; + +describe('PlatformJson class', function() { + it('should be constructable', function () { + expect(new PlatformJson()).toEqual(jasmine.any(PlatformJson)); + }); + + describe('instance', function () { + var platformJson; + var fakePlugin; + + beforeEach(function () { + platformJson = new PlatformJson('/fake/path', 'android'); + fakePlugin = jasmine.createSpyObj('fakePlugin', ['getJsModules']); + fakePlugin.id = 'fakeId'; + fakePlugin.version = '1.0.0'; + fakePlugin.getJsModules.andReturn([FAKE_MODULE]); + }); + + describe('addPluginMetadata method', function () { + it('should not throw if root "modules" property is missing', function () { + expect(function () { + platformJson.addPluginMetadata(fakePlugin); + }).not.toThrow(); + }); + + it('should add each module to "root.modules" array', function () { + platformJson.addPluginMetadata(fakePlugin); + expect(platformJson.root.modules.length).toBe(1); + expect(platformJson.root.modules[0]).toEqual(jasmine.any(ModuleMetadata)); + }); + + it('shouldn\'t add module if there is already module with the same file added', function () { + platformJson.root.modules = [{ + name: 'fakePlugin2', + file: 'plugins/fakeId/www/fakeModule.js' + }]; + + platformJson.addPluginMetadata(fakePlugin); + expect(platformJson.root.modules.length).toBe(1); + expect(platformJson.root.modules[0].name).toBe('fakePlugin2'); + }); + + it('should add entry to plugin_metadata with corresponding version', function () { + platformJson.addPluginMetadata(fakePlugin); + expect(platformJson.root.plugin_metadata[fakePlugin.id]).toBe(fakePlugin.version); + }); + }); + + describe('removePluginMetadata method', function () { + it('should not throw if root "modules" property is missing', function () { + expect(function () { + platformJson.removePluginMetadata(fakePlugin); + }).not.toThrow(); + }); + + it('should remove plugin modules from "root.modules" array based on file path', function () { + + var pluginPaths = [ + 'plugins/fakeId/www/fakeModule.js', + 'plugins/otherPlugin/www/module1.js', + 'plugins/otherPlugin/www/module1.js' + ]; + + platformJson.root.modules = pluginPaths.map(function (p) { return {file: p}; }); + platformJson.removePluginMetadata(fakePlugin); + var resultantPaths = platformJson.root.modules + .map(function (p) { return p.file; }) + .filter(function (f) { return /fakeModule\.js$/.test(f); }); + + expect(resultantPaths.length).toBe(0); + }); + + it('should remove entry from plugin_metadata with corresponding version', function () { + platformJson.root.plugin_metadata = {}; + platformJson.root.plugin_metadata[fakePlugin.id] = fakePlugin.version; + platformJson.removePluginMetadata(fakePlugin); + expect(platformJson.root.plugin_metadata[fakePlugin.id]).not.toBeDefined(); + }); + }); + + describe('generateMetadata method', function () { + it('should generate text metadata containing list of installed modules', function () { + var meta = platformJson.addPluginMetadata(fakePlugin).generateMetadata(); + expect(typeof meta).toBe('string'); + expect(meta.indexOf(JSON.stringify(platformJson.root.modules, null, 4))).toBeGreaterThan(0); + // expect(meta).toMatch(JSON.stringify(platformJson.root.modules, null, 4)); + expect(meta).toMatch(JSON.stringify(platformJson.root.plugin_metadata, null, 4)); + }); + }); + }); +}); + +describe('ModuleMetadata class', function () { + it('should be constructable', function () { + var meta; + expect(function name(params) { + meta = new ModuleMetadata('fakePlugin', {src: 'www/fakeModule.js'}); + }).not.toThrow(); + expect(meta instanceof ModuleMetadata).toBeTruthy(); + }); + + it('should throw if either pluginId or jsModule argument isn\'t specified', function () { + expect(ModuleMetadata).toThrow(); + expect(function () { new ModuleMetadata('fakePlugin', {}); }).toThrow(); + }); + + it('should guess module id either from name property of from module src', function () { + expect(new ModuleMetadata('fakePlugin', {name: 'fakeModule'}).id).toMatch(/fakeModule$/); + expect(new ModuleMetadata('fakePlugin', {src: 'www/fakeModule.js'}).id).toMatch(/fakeModule$/); + }); + + it('should read "clobbers" property from module', function () { + expect(new ModuleMetadata('fakePlugin', {name: 'fakeModule'}).clobbers).not.toBeDefined(); + var metadata = new ModuleMetadata('fakePlugin', FAKE_MODULE); + expect(metadata.clobbers).toEqual(jasmine.any(Array)); + expect(metadata.clobbers[0]).toBe(FAKE_MODULE.clobbers[0].target); + }); + + it('should read "merges" property from module', function () { + expect(new ModuleMetadata('fakePlugin', {name: 'fakeModule'}).merges).not.toBeDefined(); + var metadata = new ModuleMetadata('fakePlugin', FAKE_MODULE); + expect(metadata.merges).toEqual(jasmine.any(Array)); + expect(metadata.merges[0]).toBe(FAKE_MODULE.merges[0].target); + }); + + it('should read "runs" property from module', function () { + expect(new ModuleMetadata('fakePlugin', {name: 'fakeModule'}).runs).not.toBeDefined(); + expect(new ModuleMetadata('fakePlugin', FAKE_MODULE).runs).toBe(true); + }); +}); http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/57f2b3f2/cordova-common/src/PlatformJson.js ---------------------------------------------------------------------- diff --git a/cordova-common/src/PlatformJson.js b/cordova-common/src/PlatformJson.js index 793e976..4e2b287 100644 --- a/cordova-common/src/PlatformJson.js +++ b/cordova-common/src/PlatformJson.js @@ -91,6 +91,38 @@ PlatformJson.prototype.addPlugin = function(pluginId, variables, isTopLevel) { return this; }; +/** + * @chaining + * Generates and adds metadata for provided plugin into associated <platform>.json file + * + * @param {PluginInfo} pluginInfo A pluginInfo instance to add metadata from + * @returns {this} Current PlatformJson instance to allow calls chaining + */ +PlatformJson.prototype.addPluginMetadata = function (pluginInfo) { + + var installedModules = this.root.modules || []; + + var installedPaths = installedModules.map(function (installedModule) { + return installedModule.file; + }); + + var modulesToInstall = pluginInfo.getJsModules(this.platform) + .map(function (module) { + return new ModuleMetadata(pluginInfo.id, module); + }) + .filter(function (metadata) { + // Filter out modules which are already added to metadata + return installedPaths.indexOf(metadata.file) === -1; + }); + + this.root.modules = installedModules.concat(modulesToInstall); + + this.root.plugin_metadata = this.root.plugin_metadata || {}; + this.root.plugin_metadata[pluginInfo.id] = pluginInfo.version; + + return this; +}; + PlatformJson.prototype.removePlugin = function(pluginId, isTopLevel) { var pluginsList = isTopLevel ? this.root.installed_plugins : @@ -101,6 +133,35 @@ PlatformJson.prototype.removePlugin = function(pluginId, isTopLevel) { return this; }; +/** + * @chaining + * Removes metadata for provided plugin from associated file + * + * @param {PluginInfo} pluginInfo A PluginInfo instance to which modules' metadata + * we need to remove + * + * @returns {this} Current PlatformJson instance to allow calls chaining + */ +PlatformJson.prototype.removePluginMetadata = function (pluginInfo) { + var modulesToRemove = pluginInfo.getJsModules(this.platform) + .map(function (jsModule) { + return ['plugins', pluginInfo.id, jsModule.src].join('/'); + }); + + var installedModules = this.root.modules || []; + this.root.modules = installedModules + .filter(function (installedModule) { + // Leave only those metadatas which 'file' is not in removed modules + return (modulesToRemove.indexOf(installedModule.file) === -1); + }); + + if (this.root.plugin_metadata) { + delete this.root.plugin_metadata[pluginInfo.id]; + } + + return this; +}; + PlatformJson.prototype.addInstalledPluginToPrepareQueue = function(pluginDirName, vars, is_top_level) { this.root.prepare_queue.installed.push({'plugin':pluginDirName, 'vars':vars, 'topLevel':is_top_level}); }; @@ -125,6 +186,39 @@ PlatformJson.prototype.makeTopLevel = function(pluginId) { return this; }; +/** + * Generates a metadata for all installed plugins and js modules. The resultant + * string is ready to be written to 'cordova_plugins.js' + * + * @returns {String} cordova_plugins.js contents + */ +PlatformJson.prototype.generateMetadata = function () { + return [ + 'cordova.define(\'cordova/plugin_list\', function(require, exports, module) {', + 'module.exports = ' + JSON.stringify(this.root.modules, null, 4) + ';', + 'module.exports.metadata = ', + '// TOP OF METADATA', + JSON.stringify(this.root.plugin_metadata, null, 4) + ';', + '// BOTTOM OF METADATA', + '});' // Close cordova.define. + ].join('\n'); +}; + +/** + * @chaining + * Generates and then saves metadata to specified file. Doesn't check if file exists. + * + * @param {String} destination File metadata will be written to + * @return {PlatformJson} PlatformJson instance + */ +PlatformJson.prototype.generateAndSaveMetadata = function (destination) { + var meta = this.generateMetadata(); + shelljs.mkdir('-p', path.dirname(destination)); + fs.writeFileSync(destination, meta, 'utf-8'); + + return this; +}; + // convert a munge from the old format ([file][parent][xml] = count) to the current one function fix_munge(root) { root.prepare_queue = root.prepare_queue || {installed:[], uninstalled:[]}; @@ -151,5 +245,35 @@ function fix_munge(root) { return root; } +/** + * @constructor + * @class ModuleMetadata + * + * Creates a ModuleMetadata object that represents module entry in 'cordova_plugins.js' + * file at run time + * + * @param {String} pluginId Plugin id where this module installed from + * @param (JsModule|Object) jsModule A js-module entry from PluginInfo class to generate metadata for + */ +function ModuleMetadata (pluginId, jsModule) { + + if (!pluginId) throw new TypeError('pluginId argument must be a valid plugin id'); + if (!jsModule.src && !jsModule.name) throw new TypeError('jsModule argument must contain src or/and name properties'); + + this.id = pluginId + '.' + ( jsModule.name || jsModule.src.match(/([^\/]+)\.js/)[1] ); + this.file = ['plugins', pluginId, jsModule.src].join('/'); + this.pluginId = pluginId; + + if (jsModule.clobbers && jsModule.clobbers.length > 0) { + this.clobbers = jsModule.clobbers.map(function(o) { return o.target; }); + } + if (jsModule.merges && jsModule.merges.length > 0) { + this.merges = jsModule.merges.map(function(o) { return o.target; }); + } + if (jsModule.runs) { + this.runs = true; + } +} + module.exports = PlatformJson; --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@cordova.apache.org For additional commands, e-mail: commits-h...@cordova.apache.org