Normalize whitespace (#46)
diff --git a/.eslintignore b/.eslintignore
index 161d0c6..d606f61 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1 +1 @@
-spec/fixtures/*
\ No newline at end of file
+spec/fixtures/*
diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 409fc6f..456f48b 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -35,7 +35,7 @@
* [CB-13674](https://issues.apache.org/jira/browse/CB-13674): updated dependencies
### 2.2.0 (Nov 22, 2017)
-* [CB-13471](https://issues.apache.org/jira/browse/CB-13471) File Provider fix belongs in cordova-common
+* [CB-13471](https://issues.apache.org/jira/browse/CB-13471) File Provider fix belongs in cordova-common
* [CB-11244](https://issues.apache.org/jira/browse/CB-11244) Spot fix for upcoming `cordova-android@7` changes. https://github.com/apache/cordova-android/pull/389
### 2.1.1 (Oct 04, 2017)
diff --git a/spec/.eslintrc.yml b/spec/.eslintrc.yml
index 6afba65..043fd14 100644
--- a/spec/.eslintrc.yml
+++ b/spec/.eslintrc.yml
@@ -1,2 +1,2 @@
env:
- jasmine: true
\ No newline at end of file
+ jasmine: true
diff --git a/spec/PlatformJson.spec.js b/spec/PlatformJson.spec.js
index d2c3c7e..f6845bd 100644
--- a/spec/PlatformJson.spec.js
+++ b/spec/PlatformJson.spec.js
@@ -1,190 +1,190 @@
-/**
- 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('Test 001 : 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.and.returnValue([FAKE_MODULE]);
- });
-
- describe('addPluginMetadata method', function () {
- it('Test 002 : should not throw if root "modules" property is missing', function () {
- expect(function () {
- platformJson.addPluginMetadata(fakePlugin);
- }).not.toThrow();
- });
-
- it('Test 003 : 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('Test 004 : 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('Test 005 : 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('Test 006 : should not throw if root "modules" property is missing', function () {
- expect(function () {
- platformJson.removePluginMetadata(fakePlugin);
- }).not.toThrow();
- });
-
- it('Test 007 : 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('Test 008 : 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();
- });
- });
-
- function evaluateCordovaDefineStatement (str) {
- expect(typeof str).toBe('string');
- const fnString = str.replace(/^\s*cordova\.define\('cordova\/plugin_list',\s*([\s\S]+)\);\s*$/, '($1)');
- const mod = {exports: {}};
- global.eval(fnString)(null, mod.exports, mod); // eslint-disable-line no-eval
- return mod;
- }
-
- function expectedMetadata () {
- // Create plain objects from ModuleMetadata instances
- const modules = platformJson.root.modules.map(o => Object.assign({}, o));
- modules.metadata = platformJson.root.plugin_metadata;
- return modules;
- }
-
- describe('generateMetadata method', function () {
- it('Test 009 : should generate text metadata containing list of installed modules', function () {
- const meta = platformJson.addPluginMetadata(fakePlugin).generateMetadata();
- const mod = evaluateCordovaDefineStatement(meta);
-
- expect(mod.exports).toEqual(expectedMetadata());
- });
- });
-
- describe('generateAndSaveMetadata method', function () {
- it('should save generated metadata', function () {
- // Needs to use graceful-fs, since that is used by fs-extra
- const spy = spyOn(require('graceful-fs'), 'writeFileSync');
-
- const dest = require('path').join(__dirname, 'test-destination');
- platformJson.addPluginMetadata(fakePlugin).generateAndSaveMetadata(dest);
-
- expect(spy).toHaveBeenCalledTimes(1);
- const [file, data] = spy.calls.argsFor(0);
- expect(file).toBe(dest);
- const mod = evaluateCordovaDefineStatement(data);
- expect(mod.exports).toEqual(expectedMetadata());
- });
- });
- });
-});
-
-describe('ModuleMetadata class', function () {
- it('Test 010 : should be constructable', function () {
- var meta;
- expect(function () {
- meta = new ModuleMetadata('fakePlugin', {src: 'www/fakeModule.js'});
- }).not.toThrow();
- expect(meta instanceof ModuleMetadata).toBeTruthy();
- });
-
- it('Test 011 : should throw if either pluginId or jsModule argument isn\'t specified', function () {
- expect(ModuleMetadata).toThrow();
- expect(function () { new ModuleMetadata('fakePlugin', {}); }).toThrow(); /* eslint no-new : 0 */
- });
-
- it('Test 012 : 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('Test 013 : 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('Test 014 : 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('Test 015 : 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);
- });
-});
+/**
+ 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('Test 001 : 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.and.returnValue([FAKE_MODULE]);
+ });
+
+ describe('addPluginMetadata method', function () {
+ it('Test 002 : should not throw if root "modules" property is missing', function () {
+ expect(function () {
+ platformJson.addPluginMetadata(fakePlugin);
+ }).not.toThrow();
+ });
+
+ it('Test 003 : 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('Test 004 : 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('Test 005 : 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('Test 006 : should not throw if root "modules" property is missing', function () {
+ expect(function () {
+ platformJson.removePluginMetadata(fakePlugin);
+ }).not.toThrow();
+ });
+
+ it('Test 007 : 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('Test 008 : 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();
+ });
+ });
+
+ function evaluateCordovaDefineStatement (str) {
+ expect(typeof str).toBe('string');
+ const fnString = str.replace(/^\s*cordova\.define\('cordova\/plugin_list',\s*([\s\S]+)\);\s*$/, '($1)');
+ const mod = {exports: {}};
+ global.eval(fnString)(null, mod.exports, mod); // eslint-disable-line no-eval
+ return mod;
+ }
+
+ function expectedMetadata () {
+ // Create plain objects from ModuleMetadata instances
+ const modules = platformJson.root.modules.map(o => Object.assign({}, o));
+ modules.metadata = platformJson.root.plugin_metadata;
+ return modules;
+ }
+
+ describe('generateMetadata method', function () {
+ it('Test 009 : should generate text metadata containing list of installed modules', function () {
+ const meta = platformJson.addPluginMetadata(fakePlugin).generateMetadata();
+ const mod = evaluateCordovaDefineStatement(meta);
+
+ expect(mod.exports).toEqual(expectedMetadata());
+ });
+ });
+
+ describe('generateAndSaveMetadata method', function () {
+ it('should save generated metadata', function () {
+ // Needs to use graceful-fs, since that is used by fs-extra
+ const spy = spyOn(require('graceful-fs'), 'writeFileSync');
+
+ const dest = require('path').join(__dirname, 'test-destination');
+ platformJson.addPluginMetadata(fakePlugin).generateAndSaveMetadata(dest);
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ const [file, data] = spy.calls.argsFor(0);
+ expect(file).toBe(dest);
+ const mod = evaluateCordovaDefineStatement(data);
+ expect(mod.exports).toEqual(expectedMetadata());
+ });
+ });
+ });
+});
+
+describe('ModuleMetadata class', function () {
+ it('Test 010 : should be constructable', function () {
+ var meta;
+ expect(function () {
+ meta = new ModuleMetadata('fakePlugin', {src: 'www/fakeModule.js'});
+ }).not.toThrow();
+ expect(meta instanceof ModuleMetadata).toBeTruthy();
+ });
+
+ it('Test 011 : should throw if either pluginId or jsModule argument isn\'t specified', function () {
+ expect(ModuleMetadata).toThrow();
+ expect(function () { new ModuleMetadata('fakePlugin', {}); }).toThrow(); /* eslint no-new : 0 */
+ });
+
+ it('Test 012 : 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('Test 013 : 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('Test 014 : 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('Test 015 : 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);
+ });
+});
diff --git a/spec/PluginManager.spec.js b/spec/PluginManager.spec.js
index adefa8c..b606286 100644
--- a/spec/PluginManager.spec.js
+++ b/spec/PluginManager.spec.js
@@ -1,137 +1,137 @@
-/**
- 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.
-*/
-
-// Promise-matchers do not work with jasmine 2.0.
-// require('promise-matchers');
-
-var Q = require('q');
-var fs = require('fs-extra');
-var path = require('path');
-var rewire = require('rewire');
-var PluginManager = rewire('../src/PluginManager');
-var PluginInfo = require('../src/PluginInfo/PluginInfo');
-var ConfigChanges = require('../src/ConfigChanges/ConfigChanges');
-
-var DUMMY_PLUGIN = path.join(__dirname, 'fixtures/plugins/org.test.plugins.dummyplugin');
-var FAKE_PLATFORM = 'cordova-atari';
-var FAKE_LOCATIONS = {
- root: '/some/fake/path',
- platformWww: '/some/fake/path/platform_www',
- www: '/some/www/dir'
-};
-
-describe('PluginManager class', function () {
-
- beforeEach(function () {
- spyOn(ConfigChanges, 'PlatformMunger');
- spyOn(fs, 'outputJsonSync');
- spyOn(fs, 'writeFileSync');
- spyOn(fs, 'ensureDirSync');
- });
-
- it('Test 001 : should be constructable', function () {
- expect(new PluginManager(FAKE_PLATFORM, FAKE_LOCATIONS)).toEqual(jasmine.any(PluginManager));
- });
-
- it('Test 002 : should return new instance for every PluginManager.get call', function () {
- expect(PluginManager.get(FAKE_PLATFORM, FAKE_LOCATIONS)).toEqual(jasmine.any(PluginManager));
- expect(PluginManager.get(FAKE_PLATFORM, FAKE_LOCATIONS))
- .not.toBe(PluginManager.get(FAKE_PLATFORM, FAKE_LOCATIONS));
- });
-
- describe('instance', function () {
- var actions, manager;
- var FAKE_PROJECT;
- var fail = jasmine.createSpy('fail');
- var ActionStackOrig = PluginManager.__get__('ActionStack');
-
- beforeEach(function () {
- FAKE_PROJECT = jasmine.createSpyObj('project', ['getInstaller', 'getUninstaller', 'write']);
- manager = new PluginManager('windows', FAKE_LOCATIONS, FAKE_PROJECT);
- actions = jasmine.createSpyObj('actions', ['createAction', 'push', 'process']);
- actions.process.and.returnValue(Q.resolve());
- PluginManager.__set__('ActionStack', function () { return actions; });
- });
-
- afterEach(function () {
- PluginManager.__set__('ActionStack', ActionStackOrig);
- });
-
- describe('addPlugin method', function () {
- it('should return a promise', function () {
- expect(Q.isPromise(manager.addPlugin(null, {}))).toBe(true);
- });
- // Promise-matchers do not work with jasmine 2.0.
- xit('Test 003 : should reject if "plugin" parameter is not specified or not a PluginInfo instance', function (done) {
- expect(manager.addPlugin(null, {})).toHaveBeenRejected(done);
- expect(manager.addPlugin({}, {})).toHaveBeenRejected(done);
- expect(manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {})).not.toHaveBeenRejected(done);
- });
-
- it('Test 004 : should iterate through all plugin\'s files and frameworks', function (done) {
- manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {})
- .then(function () {
- expect(FAKE_PROJECT.getInstaller.calls.count()).toBe(16);
- expect(FAKE_PROJECT.getUninstaller.calls.count()).toBe(16);
-
- expect(actions.push.calls.count()).toBe(16);
- expect(actions.process).toHaveBeenCalled();
- expect(FAKE_PROJECT.write).toHaveBeenCalled();
- })
- .fail(fail)
- .done(function () {
- expect(fail).not.toHaveBeenCalled();
- done();
- });
- });
-
- it('Test 005 : should save plugin metadata to www directory', function (done) {
- var metadataPath = path.join(manager.locations.www, 'cordova_plugins.js');
- var platformWwwMetadataPath = path.join(manager.locations.platformWww, 'cordova_plugins.js');
-
- manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {})
- .then(function () {
- expect(fs.writeFileSync).toHaveBeenCalledWith(metadataPath, jasmine.any(String), 'utf-8');
- expect(fs.writeFileSync).not.toHaveBeenCalledWith(platformWwwMetadataPath, jasmine.any(String), 'utf-8');
- })
- .fail(fail)
- .done(function () {
- expect(fail).not.toHaveBeenCalled();
- done();
- });
- });
-
- it('Test 006 : should save plugin metadata to both www ans platform_www directories when options.usePlatformWww is specified', function (done) {
- var metadataPath = path.join(manager.locations.www, 'cordova_plugins.js');
- var platformWwwMetadataPath = path.join(manager.locations.platformWww, 'cordova_plugins.js');
-
- manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {usePlatformWww: true})
- .then(function () {
- expect(fs.writeFileSync).toHaveBeenCalledWith(metadataPath, jasmine.any(String), 'utf-8');
- expect(fs.writeFileSync).toHaveBeenCalledWith(platformWwwMetadataPath, jasmine.any(String), 'utf-8');
- })
- .fail(fail)
- .done(function () {
- expect(fail).not.toHaveBeenCalled();
- done();
- });
- });
- });
- });
-});
+/**
+ 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.
+*/
+
+// Promise-matchers do not work with jasmine 2.0.
+// require('promise-matchers');
+
+var Q = require('q');
+var fs = require('fs-extra');
+var path = require('path');
+var rewire = require('rewire');
+var PluginManager = rewire('../src/PluginManager');
+var PluginInfo = require('../src/PluginInfo/PluginInfo');
+var ConfigChanges = require('../src/ConfigChanges/ConfigChanges');
+
+var DUMMY_PLUGIN = path.join(__dirname, 'fixtures/plugins/org.test.plugins.dummyplugin');
+var FAKE_PLATFORM = 'cordova-atari';
+var FAKE_LOCATIONS = {
+ root: '/some/fake/path',
+ platformWww: '/some/fake/path/platform_www',
+ www: '/some/www/dir'
+};
+
+describe('PluginManager class', function () {
+
+ beforeEach(function () {
+ spyOn(ConfigChanges, 'PlatformMunger');
+ spyOn(fs, 'outputJsonSync');
+ spyOn(fs, 'writeFileSync');
+ spyOn(fs, 'ensureDirSync');
+ });
+
+ it('Test 001 : should be constructable', function () {
+ expect(new PluginManager(FAKE_PLATFORM, FAKE_LOCATIONS)).toEqual(jasmine.any(PluginManager));
+ });
+
+ it('Test 002 : should return new instance for every PluginManager.get call', function () {
+ expect(PluginManager.get(FAKE_PLATFORM, FAKE_LOCATIONS)).toEqual(jasmine.any(PluginManager));
+ expect(PluginManager.get(FAKE_PLATFORM, FAKE_LOCATIONS))
+ .not.toBe(PluginManager.get(FAKE_PLATFORM, FAKE_LOCATIONS));
+ });
+
+ describe('instance', function () {
+ var actions, manager;
+ var FAKE_PROJECT;
+ var fail = jasmine.createSpy('fail');
+ var ActionStackOrig = PluginManager.__get__('ActionStack');
+
+ beforeEach(function () {
+ FAKE_PROJECT = jasmine.createSpyObj('project', ['getInstaller', 'getUninstaller', 'write']);
+ manager = new PluginManager('windows', FAKE_LOCATIONS, FAKE_PROJECT);
+ actions = jasmine.createSpyObj('actions', ['createAction', 'push', 'process']);
+ actions.process.and.returnValue(Q.resolve());
+ PluginManager.__set__('ActionStack', function () { return actions; });
+ });
+
+ afterEach(function () {
+ PluginManager.__set__('ActionStack', ActionStackOrig);
+ });
+
+ describe('addPlugin method', function () {
+ it('should return a promise', function () {
+ expect(Q.isPromise(manager.addPlugin(null, {}))).toBe(true);
+ });
+ // Promise-matchers do not work with jasmine 2.0.
+ xit('Test 003 : should reject if "plugin" parameter is not specified or not a PluginInfo instance', function (done) {
+ expect(manager.addPlugin(null, {})).toHaveBeenRejected(done);
+ expect(manager.addPlugin({}, {})).toHaveBeenRejected(done);
+ expect(manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {})).not.toHaveBeenRejected(done);
+ });
+
+ it('Test 004 : should iterate through all plugin\'s files and frameworks', function (done) {
+ manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {})
+ .then(function () {
+ expect(FAKE_PROJECT.getInstaller.calls.count()).toBe(16);
+ expect(FAKE_PROJECT.getUninstaller.calls.count()).toBe(16);
+
+ expect(actions.push.calls.count()).toBe(16);
+ expect(actions.process).toHaveBeenCalled();
+ expect(FAKE_PROJECT.write).toHaveBeenCalled();
+ })
+ .fail(fail)
+ .done(function () {
+ expect(fail).not.toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('Test 005 : should save plugin metadata to www directory', function (done) {
+ var metadataPath = path.join(manager.locations.www, 'cordova_plugins.js');
+ var platformWwwMetadataPath = path.join(manager.locations.platformWww, 'cordova_plugins.js');
+
+ manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {})
+ .then(function () {
+ expect(fs.writeFileSync).toHaveBeenCalledWith(metadataPath, jasmine.any(String), 'utf-8');
+ expect(fs.writeFileSync).not.toHaveBeenCalledWith(platformWwwMetadataPath, jasmine.any(String), 'utf-8');
+ })
+ .fail(fail)
+ .done(function () {
+ expect(fail).not.toHaveBeenCalled();
+ done();
+ });
+ });
+
+ it('Test 006 : should save plugin metadata to both www ans platform_www directories when options.usePlatformWww is specified', function (done) {
+ var metadataPath = path.join(manager.locations.www, 'cordova_plugins.js');
+ var platformWwwMetadataPath = path.join(manager.locations.platformWww, 'cordova_plugins.js');
+
+ manager.addPlugin(new PluginInfo(DUMMY_PLUGIN), {usePlatformWww: true})
+ .then(function () {
+ expect(fs.writeFileSync).toHaveBeenCalledWith(metadataPath, jasmine.any(String), 'utf-8');
+ expect(fs.writeFileSync).toHaveBeenCalledWith(platformWwwMetadataPath, jasmine.any(String), 'utf-8');
+ })
+ .fail(fail)
+ .done(function () {
+ expect(fail).not.toHaveBeenCalled();
+ done();
+ });
+ });
+ });
+ });
+});
diff --git a/spec/fixtures/plugins/ChildBrowser/plugin.xml b/spec/fixtures/plugins/ChildBrowser/plugin.xml
index 700ef7c..205d5e7 100644
--- a/spec/fixtures/plugins/ChildBrowser/plugin.xml
+++ b/spec/fixtures/plugins/ChildBrowser/plugin.xml
@@ -36,7 +36,7 @@
<access origin="build.phonegap.com" />
<access origin="s3.amazonaws.com" />
</config-file>
-
+
<info>No matter what platform you are installing to, this notice is very important.</info>
<!-- android -->
@@ -82,7 +82,7 @@
<config-file target="*-Info.plist" parent="AppId">
<string>$APP_ID</string>
</config-file>
-
+
<config-file target="*-Info.plist" parent="CFBundleURLTypes">
<array>
<dict>
@@ -119,8 +119,8 @@
target-dir="Plugins\" />
<!-- modify the project file to include the added files -->
- <config-file target=".csproj" parent=".">
- </config-file>
+ <config-file target=".csproj" parent=".">
+ </config-file>
</platform>
</plugin>
diff --git a/spec/fixtures/plugins/ChildBrowser/src/android/ChildBrowser.java b/spec/fixtures/plugins/ChildBrowser/src/android/ChildBrowser.java
index 36113b6..bbc11dd 100644
--- a/spec/fixtures/plugins/ChildBrowser/src/android/ChildBrowser.java
+++ b/spec/fixtures/plugins/ChildBrowser/src/android/ChildBrowser.java
@@ -14,4 +14,4 @@
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-*/
\ No newline at end of file
+*/
diff --git a/spec/fixtures/plugins/com.adobe.vars/plugin.xml b/spec/fixtures/plugins/com.adobe.vars/plugin.xml
index d1e1bff..9238747 100644
--- a/spec/fixtures/plugins/com.adobe.vars/plugin.xml
+++ b/spec/fixtures/plugins/com.adobe.vars/plugin.xml
@@ -34,16 +34,16 @@
<poop name="GoogleMapsApiKey" value="$API_KEY" />
<package>$PACKAGE_NAME</package>
</config-file>
-
+
</platform>
-
+
<!-- amazon fireos -->
<platform name="amazon-fireos">
<config-file target="AndroidManifest.xml" parent="/manifest">
<poop name="GoogleMapsApiKey" value="$API_KEY" />
<package>$PACKAGE_NAME</package>
</config-file>
-
+
</platform>
<!-- ios -->
diff --git a/spec/fixtures/plugins/org.test.multiple-children/plugin.xml b/spec/fixtures/plugins/org.test.multiple-children/plugin.xml
index 1e30814..53db8f6 100644
--- a/spec/fixtures/plugins/org.test.multiple-children/plugin.xml
+++ b/spec/fixtures/plugins/org.test.multiple-children/plugin.xml
@@ -29,22 +29,22 @@
<platform name="android">
<config-file target="AndroidManifest.xml" parent="/manifest">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
-
+
<!--library-->
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
-
+
<!-- GCM connects to Google Services. -->
<uses-permission android:name="android.permission.INTERNET"/>
-
+
<!-- GCM requires a Google account. -->
<uses-permission android:name="android.permission.GET_ACCOUNTS"/>
-
+
<!-- Keeps the processor from sleeping when a message is received. -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
-
+
<!--
Creates a custom permission so only this app can receive its messages.
-
+
NOTE: the permission *must* be called PACKAGE.permission.C2D_MESSAGE,
where PACKAGE is the application's package name.
-->
@@ -53,12 +53,12 @@
android:protectionLevel="signature"/>
<uses-permission
android:name="$PACKAGE_NAME.permission.C2D_MESSAGE"/>
-
+
<!-- This app has permission to register and receive data message. -->
<uses-permission
android:name="com.google.android.c2dm.permission.RECEIVE"/>
</config-file>
-
+
<config-file target="AndroidManifest.xml" parent="/manifest/application/activity">
<intent-filter>
<action android:name="$PACKAGE_NAME.MESSAGE"/>
@@ -68,15 +68,15 @@
<config-file target="AndroidManifest.xml" parent="/manifest/application">
<activity android:name="com.arellomobile.android.push.PushWebview"/>
-
+
<activity android:name="com.arellomobile.android.push.MessageActivity"/>
-
+
<activity android:name="com.arellomobile.android.push.PushHandlerActivity"/>
-
+
<!--
BroadcastReceiver that will receive intents from GCM
services and handle them to the custom IntentService.
-
+
The com.google.android.c2dm.permission.SEND permission is necessary
so only GCM services can send data messages for the app.
-->
@@ -91,15 +91,15 @@
<category android:name="$PACKAGE_NAME"/>
</intent-filter>
</receiver>
-
+
<!--
Application-specific subclass of PushGCMIntentService that will
handle received messages.
-->
- <service android:name="com.arellomobile.android.push.PushGCMIntentService"/>
-
+ <service android:name="com.arellomobile.android.push.PushGCMIntentService"/>
+
</config-file>
-
+
<config-file target="res/xml/plugins.xml" parent="/plugins">
<plugin name="PushNotification"
value="com.pushwoosh.test.plugin.pushnotifications.PushNotifications" onload="true"/>
diff --git a/spec/fixtures/plugins/org.test.src/plugin.xml b/spec/fixtures/plugins/org.test.src/plugin.xml
index 6d1aa91..78f60f1 100644
--- a/spec/fixtures/plugins/org.test.src/plugin.xml
+++ b/spec/fixtures/plugins/org.test.src/plugin.xml
@@ -99,4 +99,4 @@
<preference name="WindowsToastCapable" value="true"/>
</config-file>
</platform>
-</plugin>
\ No newline at end of file
+</plugin>
diff --git a/spec/fixtures/projects/android/AndroidManifest.xml b/spec/fixtures/projects/android/AndroidManifest.xml
index a97674e..f10faa3 100644
--- a/spec/fixtures/projects/android/AndroidManifest.xml
+++ b/spec/fixtures/projects/android/AndroidManifest.xml
@@ -40,8 +40,8 @@
<uses-permission android:name="android.permission.RECORD_VIDEO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
- <uses-permission android:name="android.permission.WRITE_CONTACTS" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
@@ -51,14 +51,14 @@
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:debuggable="true">
- <activity android:name="ChildApp" android:label="@string/app_name"
+ <activity android:name="ChildApp" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <activity android:name="com.phonegap.DroidGap" android:label="@string/app_name"
+ <activity android:name="com.phonegap.DroidGap" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
</intent-filter>
@@ -66,4 +66,4 @@
</application>
<uses-sdk android:minSdkVersion="5" />
-</manifest>
+</manifest>
diff --git a/spec/fixtures/projects/android_two/AndroidManifest.xml b/spec/fixtures/projects/android_two/AndroidManifest.xml
index 019caae..2761208 100644
--- a/spec/fixtures/projects/android_two/AndroidManifest.xml
+++ b/spec/fixtures/projects/android_two/AndroidManifest.xml
@@ -40,8 +40,8 @@
<uses-permission android:name="android.permission.RECORD_VIDEO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
- <uses-permission android:name="android.permission.WRITE_CONTACTS" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_CONTACTS" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
@@ -51,14 +51,14 @@
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:debuggable="true">
- <activity android:name="ChildApp" android:label="@string/app_name"
+ <activity android:name="ChildApp" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <activity android:name="org.test.DroidGap" android:label="@string/app_name"
+ <activity android:name="org.test.DroidGap" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
</intent-filter>
@@ -66,4 +66,4 @@
</application>
<uses-sdk android:minSdkVersion="5" />
-</manifest>
+</manifest>
diff --git a/spec/fixtures/projects/android_two_no_perms/AndroidManifest.xml b/spec/fixtures/projects/android_two_no_perms/AndroidManifest.xml
index 74aeecc..85155f9 100644
--- a/spec/fixtures/projects/android_two_no_perms/AndroidManifest.xml
+++ b/spec/fixtures/projects/android_two_no_perms/AndroidManifest.xml
@@ -31,14 +31,14 @@
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:debuggable="true">
- <activity android:name="ChildApp" android:label="@string/app_name"
+ <activity android:name="ChildApp" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
- <activity android:name="org.test.DroidGap" android:label="@string/app_name"
+ <activity android:name="org.test.DroidGap" android:label="@string/app_name"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
</intent-filter>
@@ -46,4 +46,4 @@
</application>
<uses-sdk android:minSdkVersion="5" />
-</manifest>
+</manifest>
diff --git a/spec/fixtures/projects/ios-config-xml/SampleApp/SampleApp-Info.plist b/spec/fixtures/projects/ios-config-xml/SampleApp/SampleApp-Info.plist
index 1edf010..22edad6 100644
--- a/spec/fixtures/projects/ios-config-xml/SampleApp/SampleApp-Info.plist
+++ b/spec/fixtures/projects/ios-config-xml/SampleApp/SampleApp-Info.plist
@@ -9,9 +9,9 @@
# 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
diff --git a/spec/fixtures/projects/ios-config-xml/SampleApp/config.xml b/spec/fixtures/projects/ios-config-xml/SampleApp/config.xml
index f0a823b..883c160 100644
--- a/spec/fixtures/projects/ios-config-xml/SampleApp/config.xml
+++ b/spec/fixtures/projects/ios-config-xml/SampleApp/config.xml
@@ -8,9 +8,9 @@
# 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
diff --git a/spec/fixtures/projects/windows/TestApp.jsproj b/spec/fixtures/projects/windows/TestApp.jsproj
index ff6cf68..1e1964f 100644
--- a/spec/fixtures/projects/windows/TestApp.jsproj
+++ b/spec/fixtures/projects/windows/TestApp.jsproj
@@ -68,7 +68,7 @@
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\$(WMSJSProjectDirectory)\Microsoft.VisualStudio.$(WMSJSProject).targets" />
<!-- To modify your build process, add your task inside one of the targets below then uncomment
- that target and the DisableFastUpToDateCheck PropertyGroup.
+ that target and the DisableFastUpToDateCheck PropertyGroup.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
diff --git a/spec/fixtures/projects/windows/TestApp.projitems b/spec/fixtures/projects/windows/TestApp.projitems
index c408373..b97e851 100644
--- a/spec/fixtures/projects/windows/TestApp.projitems
+++ b/spec/fixtures/projects/windows/TestApp.projitems
@@ -31,4 +31,4 @@
</ItemGroup>
<Import Project="CordovaAppDebug.projitems" Condition="Exists('$(MSBuildThisFileDirectory)CordovaAppDebug.projitems') And '$(Configuration)'=='Debug'" />
<Import Project="CordovaAppRelease.projitems" Condition="Exists('$(MSBuildThisFileDirectory)CordovaAppRelease.projitems') And '$(Configuration)'!='Debug'" />
-</Project>
\ No newline at end of file
+</Project>
diff --git a/spec/fixtures/projects/windows/bom_test.xml b/spec/fixtures/projects/windows/bom_test.xml
index 57cadf6..446869d 100644
--- a/spec/fixtures/projects/windows/bom_test.xml
+++ b/spec/fixtures/projects/windows/bom_test.xml
@@ -8,9 +8,9 @@
# 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
diff --git a/spec/fixtures/projects/windows/www/cordova-2.6.0.js b/spec/fixtures/projects/windows/www/cordova-2.6.0.js
index f8c32b2..84d49c3 100644
--- a/spec/fixtures/projects/windows/www/cordova-2.6.0.js
+++ b/spec/fixtures/projects/windows/www/cordova-2.6.0.js
@@ -12,9 +12,9 @@
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
@@ -8042,7 +8042,7 @@
}
// Try to XHR the cordova_plugins.json file asynchronously.
- try { // we commented we were going to try, so let us actually try and catch
+ try { // we commented we were going to try, so let us actually try and catch
var xhr = new context.XMLHttpRequest();
xhr.onreadystatechange = function() {
if (this.readyState != 4) { // not DONE
diff --git a/spec/fixtures/projects/windows/www/css/index.css b/spec/fixtures/projects/windows/www/css/index.css
index 51daa79..583c001 100644
--- a/spec/fixtures/projects/windows/www/css/index.css
+++ b/spec/fixtures/projects/windows/www/css/index.css
@@ -102,13 +102,13 @@
50% { opacity: 0.4; }
to { opacity: 1.0; }
}
-
+
@-webkit-keyframes fade {
from { opacity: 1.0; }
50% { opacity: 0.4; }
to { opacity: 1.0; }
}
-
+
.blink {
animation:fade 3000ms infinite;
-webkit-animation:fade 3000ms infinite;
diff --git a/spec/superspawn.spec.js b/spec/superspawn.spec.js
index 2a9729c..5a63564 100644
--- a/spec/superspawn.spec.js
+++ b/spec/superspawn.spec.js
@@ -1,91 +1,91 @@
-/**
- 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 Q = require('q');
-var superspawn = require('../src/superspawn');
-
-var LS = process.platform === 'win32' ? 'dir' : 'ls';
-
-describe('spawn method', function () {
- var progressSpy, failSpy;
-
- beforeEach(function () {
- progressSpy = jasmine.createSpy('progress');
- failSpy = jasmine.createSpy('fail'); /* eslint no-unused-vars : 0 */
- });
-
- it('Test 001 : should return a promise', function () {
- expect(Q.isPromise(superspawn.spawn(LS))).toBe(true);
- expect(Q.isPromise(superspawn.spawn('invalid_command'))).toBe(true);
- });
-
- it('Test 002 : should notify about stdout "data" events', function (done) {
- superspawn.spawn(LS, [], {stdio: 'pipe'})
- .progress(progressSpy)
- .fin(function () {
- expect(progressSpy).toHaveBeenCalledWith({'stdout': jasmine.any(String)});
- done();
- });
- });
-
- it('Test 003 : should notify about stderr "data" events', function (done) {
- superspawn.spawn(LS, ['doesnt-exist'], {stdio: 'pipe'})
- .progress(progressSpy)
- .fin(function () {
- expect(progressSpy).toHaveBeenCalledWith({'stderr': jasmine.any(String)});
- done();
- });
- });
-
- it('Test 004 : reject handler should pass in Error object with stdout and stderr properties', function (done) {
- var cp = require('child_process');
- spyOn(cp, 'spawn').and.callFake(function (cmd, args, opts) {
- return {
- stdout: {
- setEncoding: function () {},
- on: function (evt, handler) {
- // some sample stdout output
- handler('business as usual');
- }
- },
- stderr: {
- setEncoding: function () {},
- on: function (evt, handler) {
- // some sample stderr output
- handler('mayday mayday');
- }
- },
- on: function (evt, handler) {
- // What's passed to handler here is the exit code, so we can control
- // resolve/reject flow via this argument.
- handler(1); // this will trigger error flow
- },
- removeListener: function () {}
- };
- });
- superspawn.spawn('this aggression', ['will', 'not', 'stand', 'man'], {})
- .catch(function (err) {
- expect(err).toBeDefined();
- expect(err.stdout).toContain('usual');
- expect(err.stderr).toContain('mayday');
- done();
- });
- });
-
-});
+/**
+ 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 Q = require('q');
+var superspawn = require('../src/superspawn');
+
+var LS = process.platform === 'win32' ? 'dir' : 'ls';
+
+describe('spawn method', function () {
+ var progressSpy, failSpy;
+
+ beforeEach(function () {
+ progressSpy = jasmine.createSpy('progress');
+ failSpy = jasmine.createSpy('fail'); /* eslint no-unused-vars : 0 */
+ });
+
+ it('Test 001 : should return a promise', function () {
+ expect(Q.isPromise(superspawn.spawn(LS))).toBe(true);
+ expect(Q.isPromise(superspawn.spawn('invalid_command'))).toBe(true);
+ });
+
+ it('Test 002 : should notify about stdout "data" events', function (done) {
+ superspawn.spawn(LS, [], {stdio: 'pipe'})
+ .progress(progressSpy)
+ .fin(function () {
+ expect(progressSpy).toHaveBeenCalledWith({'stdout': jasmine.any(String)});
+ done();
+ });
+ });
+
+ it('Test 003 : should notify about stderr "data" events', function (done) {
+ superspawn.spawn(LS, ['doesnt-exist'], {stdio: 'pipe'})
+ .progress(progressSpy)
+ .fin(function () {
+ expect(progressSpy).toHaveBeenCalledWith({'stderr': jasmine.any(String)});
+ done();
+ });
+ });
+
+ it('Test 004 : reject handler should pass in Error object with stdout and stderr properties', function (done) {
+ var cp = require('child_process');
+ spyOn(cp, 'spawn').and.callFake(function (cmd, args, opts) {
+ return {
+ stdout: {
+ setEncoding: function () {},
+ on: function (evt, handler) {
+ // some sample stdout output
+ handler('business as usual');
+ }
+ },
+ stderr: {
+ setEncoding: function () {},
+ on: function (evt, handler) {
+ // some sample stderr output
+ handler('mayday mayday');
+ }
+ },
+ on: function (evt, handler) {
+ // What's passed to handler here is the exit code, so we can control
+ // resolve/reject flow via this argument.
+ handler(1); // this will trigger error flow
+ },
+ removeListener: function () {}
+ };
+ });
+ superspawn.spawn('this aggression', ['will', 'not', 'stand', 'man'], {})
+ .catch(function (err) {
+ expect(err).toBeDefined();
+ expect(err.stdout).toContain('usual');
+ expect(err.stderr).toContain('mayday');
+ done();
+ });
+ });
+
+});
diff --git a/src/CordovaLogger.js b/src/CordovaLogger.js
index b5c3564..b6564d2 100644
--- a/src/CordovaLogger.js
+++ b/src/CordovaLogger.js
@@ -1,220 +1,220 @@
-/*
- 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 ansi = require('ansi');
-var EventEmitter = require('events').EventEmitter;
-var CordovaError = require('./CordovaError/CordovaError');
-var EOL = require('os').EOL;
-
-var INSTANCE;
-
-/**
- * @class CordovaLogger
- *
- * Implements logging facility that anybody could use. Should not be
- * instantiated directly, `CordovaLogger.get()` method should be used instead
- * to acquire logger instance
- */
-function CordovaLogger () {
- this.levels = {};
- this.colors = {};
- this.stdout = process.stdout;
- this.stderr = process.stderr;
-
- this.stdoutCursor = ansi(this.stdout);
- this.stderrCursor = ansi(this.stderr);
-
- this.addLevel('verbose', 1000, 'grey');
- this.addLevel('normal', 2000);
- this.addLevel('warn', 2000, 'yellow');
- this.addLevel('info', 3000, 'blue');
- this.addLevel('error', 5000, 'red');
- this.addLevel('results', 10000);
-
- this.setLevel('normal');
-}
-
-/**
- * Static method to create new or acquire existing instance.
- *
- * @return {CordovaLogger} Logger instance
- */
-CordovaLogger.get = function () {
- return INSTANCE || (INSTANCE = new CordovaLogger());
-};
-
-CordovaLogger.VERBOSE = 'verbose';
-CordovaLogger.NORMAL = 'normal';
-CordovaLogger.WARN = 'warn';
-CordovaLogger.INFO = 'info';
-CordovaLogger.ERROR = 'error';
-CordovaLogger.RESULTS = 'results';
-
-/**
- * Emits log message to process' stdout/stderr depending on message's severity
- * and current log level. If severity is less than current logger's level,
- * then the message is ignored.
- *
- * @param {String} logLevel The message's log level. The logger should have
- * corresponding level added (via logger.addLevel), otherwise
- * `CordovaLogger.NORMAL` level will be used.
- * @param {String} message The message, that should be logged to process'
- * stdio
- *
- * @return {CordovaLogger} Current instance, to allow calls chaining.
- */
-CordovaLogger.prototype.log = function (logLevel, message) {
- // if there is no such logLevel defined, or provided level has
- // less severity than active level, then just ignore this call and return
- if (!this.levels[logLevel] || this.levels[logLevel] < this.levels[this.logLevel]) {
- // return instance to allow to chain calls
- return this;
- }
-
- var isVerbose = this.logLevel === 'verbose';
- var cursor = this.stdoutCursor;
-
- if (message instanceof Error || logLevel === CordovaLogger.ERROR) {
- message = formatError(message, isVerbose);
- cursor = this.stderrCursor;
- }
-
- var color = this.colors[logLevel];
- if (color) {
- cursor.bold().fg[color]();
- }
-
- cursor.write(message).reset().write(EOL);
-
- return this;
-};
-
-/**
- * Adds a new level to logger instance. This method also creates a shortcut
- * method to log events with the level provided (i.e. after adding new level
- * 'debug', the method `debug(message)`, equal to logger.log('debug', message),
- * will be added to logger instance)
- *
- * @param {String} level A log level name. The levels with the following
- * names added by default to every instance: 'verbose', 'normal', 'warn',
- * 'info', 'error', 'results'
- * @param {Number} severity A number that represents level's severity.
- * @param {String} color A valid color name, that will be used to log
- * messages with this level. Any CSS color code or RGB value is allowed
- * (according to ansi documentation:
- * https://github.com/TooTallNate/ansi.js#features)
- *
- * @return {CordovaLogger} Current instance, to allow calls chaining.
- */
-CordovaLogger.prototype.addLevel = function (level, severity, color) {
-
- this.levels[level] = severity;
-
- if (color) {
- this.colors[level] = color;
- }
-
- // Define own method with corresponding name
- if (!this[level]) {
- this[level] = this.log.bind(this, level);
- }
-
- return this;
-};
-
-/**
- * Sets the current logger level to provided value. If logger doesn't have level
- * with this name, `CordovaLogger.NORMAL` will be used.
- *
- * @param {String} logLevel Level name. The level with this name should be
- * added to logger before.
- *
- * @return {CordovaLogger} Current instance, to allow calls chaining.
- */
-CordovaLogger.prototype.setLevel = function (logLevel) {
- this.logLevel = this.levels[logLevel] ? logLevel : CordovaLogger.NORMAL;
-
- return this;
-};
-
-/**
- * Adjusts the current logger level according to the passed options.
- *
- * @param {Object|Array} opts An object or args array with options
- *
- * @return {CordovaLogger} Current instance, to allow calls chaining.
- */
-CordovaLogger.prototype.adjustLevel = function (opts) {
- if (opts.verbose || (Array.isArray(opts) && opts.includes('--verbose'))) {
- this.setLevel('verbose');
- } else if (opts.silent || (Array.isArray(opts) && opts.includes('--silent'))) {
- this.setLevel('error');
- }
-
- return this;
-};
-
-/**
- * Attaches logger to EventEmitter instance provided.
- *
- * @param {EventEmitter} eventEmitter An EventEmitter instance to attach
- * logger to.
- *
- * @return {CordovaLogger} Current instance, to allow calls chaining.
- */
-CordovaLogger.prototype.subscribe = function (eventEmitter) {
-
- if (!(eventEmitter instanceof EventEmitter)) { throw new Error('Subscribe method only accepts an EventEmitter instance as argument'); }
-
- eventEmitter.on('verbose', this.verbose)
- .on('log', this.normal)
- .on('info', this.info)
- .on('warn', this.warn)
- .on('warning', this.warn)
- // Set up event handlers for logging and results emitted as events.
- .on('results', this.results);
-
- return this;
-};
-
-function formatError (error, isVerbose) {
- var message = '';
-
- if (error instanceof CordovaError) {
- message = error.toString(isVerbose);
- } else if (error instanceof Error) {
- if (isVerbose) {
- message = error.stack;
- } else {
- message = error.message;
- }
- } else {
- // Plain text error message
- message = error;
- }
-
- if (typeof message === 'string' && !message.toUpperCase().startsWith('ERROR:')) {
- // Needed for backward compatibility with external tools
- message = 'Error: ' + message;
- }
-
- return message;
-}
-
-module.exports = CordovaLogger;
+/*
+ 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 ansi = require('ansi');
+var EventEmitter = require('events').EventEmitter;
+var CordovaError = require('./CordovaError/CordovaError');
+var EOL = require('os').EOL;
+
+var INSTANCE;
+
+/**
+ * @class CordovaLogger
+ *
+ * Implements logging facility that anybody could use. Should not be
+ * instantiated directly, `CordovaLogger.get()` method should be used instead
+ * to acquire logger instance
+ */
+function CordovaLogger () {
+ this.levels = {};
+ this.colors = {};
+ this.stdout = process.stdout;
+ this.stderr = process.stderr;
+
+ this.stdoutCursor = ansi(this.stdout);
+ this.stderrCursor = ansi(this.stderr);
+
+ this.addLevel('verbose', 1000, 'grey');
+ this.addLevel('normal', 2000);
+ this.addLevel('warn', 2000, 'yellow');
+ this.addLevel('info', 3000, 'blue');
+ this.addLevel('error', 5000, 'red');
+ this.addLevel('results', 10000);
+
+ this.setLevel('normal');
+}
+
+/**
+ * Static method to create new or acquire existing instance.
+ *
+ * @return {CordovaLogger} Logger instance
+ */
+CordovaLogger.get = function () {
+ return INSTANCE || (INSTANCE = new CordovaLogger());
+};
+
+CordovaLogger.VERBOSE = 'verbose';
+CordovaLogger.NORMAL = 'normal';
+CordovaLogger.WARN = 'warn';
+CordovaLogger.INFO = 'info';
+CordovaLogger.ERROR = 'error';
+CordovaLogger.RESULTS = 'results';
+
+/**
+ * Emits log message to process' stdout/stderr depending on message's severity
+ * and current log level. If severity is less than current logger's level,
+ * then the message is ignored.
+ *
+ * @param {String} logLevel The message's log level. The logger should have
+ * corresponding level added (via logger.addLevel), otherwise
+ * `CordovaLogger.NORMAL` level will be used.
+ * @param {String} message The message, that should be logged to process'
+ * stdio
+ *
+ * @return {CordovaLogger} Current instance, to allow calls chaining.
+ */
+CordovaLogger.prototype.log = function (logLevel, message) {
+ // if there is no such logLevel defined, or provided level has
+ // less severity than active level, then just ignore this call and return
+ if (!this.levels[logLevel] || this.levels[logLevel] < this.levels[this.logLevel]) {
+ // return instance to allow to chain calls
+ return this;
+ }
+
+ var isVerbose = this.logLevel === 'verbose';
+ var cursor = this.stdoutCursor;
+
+ if (message instanceof Error || logLevel === CordovaLogger.ERROR) {
+ message = formatError(message, isVerbose);
+ cursor = this.stderrCursor;
+ }
+
+ var color = this.colors[logLevel];
+ if (color) {
+ cursor.bold().fg[color]();
+ }
+
+ cursor.write(message).reset().write(EOL);
+
+ return this;
+};
+
+/**
+ * Adds a new level to logger instance. This method also creates a shortcut
+ * method to log events with the level provided (i.e. after adding new level
+ * 'debug', the method `debug(message)`, equal to logger.log('debug', message),
+ * will be added to logger instance)
+ *
+ * @param {String} level A log level name. The levels with the following
+ * names added by default to every instance: 'verbose', 'normal', 'warn',
+ * 'info', 'error', 'results'
+ * @param {Number} severity A number that represents level's severity.
+ * @param {String} color A valid color name, that will be used to log
+ * messages with this level. Any CSS color code or RGB value is allowed
+ * (according to ansi documentation:
+ * https://github.com/TooTallNate/ansi.js#features)
+ *
+ * @return {CordovaLogger} Current instance, to allow calls chaining.
+ */
+CordovaLogger.prototype.addLevel = function (level, severity, color) {
+
+ this.levels[level] = severity;
+
+ if (color) {
+ this.colors[level] = color;
+ }
+
+ // Define own method with corresponding name
+ if (!this[level]) {
+ this[level] = this.log.bind(this, level);
+ }
+
+ return this;
+};
+
+/**
+ * Sets the current logger level to provided value. If logger doesn't have level
+ * with this name, `CordovaLogger.NORMAL` will be used.
+ *
+ * @param {String} logLevel Level name. The level with this name should be
+ * added to logger before.
+ *
+ * @return {CordovaLogger} Current instance, to allow calls chaining.
+ */
+CordovaLogger.prototype.setLevel = function (logLevel) {
+ this.logLevel = this.levels[logLevel] ? logLevel : CordovaLogger.NORMAL;
+
+ return this;
+};
+
+/**
+ * Adjusts the current logger level according to the passed options.
+ *
+ * @param {Object|Array} opts An object or args array with options
+ *
+ * @return {CordovaLogger} Current instance, to allow calls chaining.
+ */
+CordovaLogger.prototype.adjustLevel = function (opts) {
+ if (opts.verbose || (Array.isArray(opts) && opts.includes('--verbose'))) {
+ this.setLevel('verbose');
+ } else if (opts.silent || (Array.isArray(opts) && opts.includes('--silent'))) {
+ this.setLevel('error');
+ }
+
+ return this;
+};
+
+/**
+ * Attaches logger to EventEmitter instance provided.
+ *
+ * @param {EventEmitter} eventEmitter An EventEmitter instance to attach
+ * logger to.
+ *
+ * @return {CordovaLogger} Current instance, to allow calls chaining.
+ */
+CordovaLogger.prototype.subscribe = function (eventEmitter) {
+
+ if (!(eventEmitter instanceof EventEmitter)) { throw new Error('Subscribe method only accepts an EventEmitter instance as argument'); }
+
+ eventEmitter.on('verbose', this.verbose)
+ .on('log', this.normal)
+ .on('info', this.info)
+ .on('warn', this.warn)
+ .on('warning', this.warn)
+ // Set up event handlers for logging and results emitted as events.
+ .on('results', this.results);
+
+ return this;
+};
+
+function formatError (error, isVerbose) {
+ var message = '';
+
+ if (error instanceof CordovaError) {
+ message = error.toString(isVerbose);
+ } else if (error instanceof Error) {
+ if (isVerbose) {
+ message = error.stack;
+ } else {
+ message = error.message;
+ }
+ } else {
+ // Plain text error message
+ message = error;
+ }
+
+ if (typeof message === 'string' && !message.toUpperCase().startsWith('ERROR:')) {
+ // Needed for backward compatibility with external tools
+ message = 'Error: ' + message;
+ }
+
+ return message;
+}
+
+module.exports = CordovaLogger;
diff --git a/src/PluginManager.js b/src/PluginManager.js
index 5a018de..c8f971a 100644
--- a/src/PluginManager.js
+++ b/src/PluginManager.js
@@ -1,149 +1,149 @@
-/*
- 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 Q = require('q');
-var fs = require('fs-extra');
-var path = require('path');
-
-var ActionStack = require('./ActionStack');
-var PlatformJson = require('./PlatformJson');
-var CordovaError = require('./CordovaError/CordovaError');
-var PlatformMunger = require('./ConfigChanges/ConfigChanges').PlatformMunger;
-var PluginInfoProvider = require('./PluginInfo/PluginInfoProvider');
-
-/**
- * @constructor
- * @class PluginManager
- * Represents an entity for adding/removing plugins for platforms
- *
- * @param {String} platform Platform name
- * @param {Object} locations - Platform files and directories
- * @param {IDEProject} ideProject The IDE project to add/remove plugin changes to/from
- */
-function PluginManager (platform, locations, ideProject) {
- this.platform = platform;
- this.locations = locations;
- this.project = ideProject;
-
- var platformJson = PlatformJson.load(locations.root, platform);
- this.munger = new PlatformMunger(platform, locations.root, platformJson, new PluginInfoProvider());
-}
-
-/**
- * @constructs PluginManager
- * A convenience shortcut to new PluginManager(...)
- *
- * @param {String} platform Platform name
- * @param {Object} locations - Platform files and directories
- * @param {IDEProject} ideProject The IDE project to add/remove plugin changes to/from
- * @returns new PluginManager instance
- */
-PluginManager.get = function (platform, locations, ideProject) {
- return new PluginManager(platform, locations, ideProject);
-};
-
-PluginManager.INSTALL = 'install';
-PluginManager.UNINSTALL = 'uninstall';
-
-module.exports = PluginManager;
-
-/**
- * Describes and implements common plugin installation/uninstallation routine. The flow is the following:
- * * Validate and set defaults for options. Note that options are empty by default. Everything
- * needed for platform IDE project must be passed from outside. Plugin variables (which
- * are the part of the options) also must be already populated with 'PACKAGE_NAME' variable.
- * * Collect all plugin's native and web files, get installers/uninstallers and process
- * all these via ActionStack.
- * * Save the IDE project, so the changes made by installers are persisted.
- * * Generate config changes munge for plugin and apply it to all required files
- * * Generate metadata for plugin and plugin modules and save it to 'cordova_plugins.js'
- *
- * @param {PluginInfo} plugin A PluginInfo structure representing plugin to install
- * @param {Object} [options={}] An installation options. It is expected but is not necessary
- * that options would contain 'variables' inner object with 'PACKAGE_NAME' field set by caller.
- *
- * @returns {Promise} Returns a Q promise, either resolved in case of success, rejected otherwise.
- */
-PluginManager.prototype.doOperation = function (operation, plugin, options) {
- if (operation !== PluginManager.INSTALL && operation !== PluginManager.UNINSTALL) { return Q.reject(new CordovaError('The parameter is incorrect. The opeation must be either "add" or "remove"')); }
-
- if (!plugin || plugin.constructor.name !== 'PluginInfo') { return Q.reject(new CordovaError('The parameter is incorrect. The first parameter should be a PluginInfo instance')); }
-
- // Set default to empty object to play safe when accesing properties
- options = options || {};
-
- var self = this;
- var actions = new ActionStack();
-
- // gather all files need to be handled during operation ...
- plugin.getFilesAndFrameworks(this.platform, options)
- .concat(plugin.getAssets(this.platform))
- .concat(plugin.getJsModules(this.platform))
- // ... put them into stack ...
- .forEach(function (item) {
- var installer = self.project.getInstaller(item.itemType);
- var uninstaller = self.project.getUninstaller(item.itemType);
- var actionArgs = [item, plugin, self.project, options];
-
- var action;
- if (operation === PluginManager.INSTALL) {
- action = actions.createAction.apply(actions, [installer, actionArgs, uninstaller, actionArgs]); /* eslint no-useless-call: 0 */
- } else /* op === PluginManager.UNINSTALL */{
- action = actions.createAction.apply(actions, [uninstaller, actionArgs, installer, actionArgs]); /* eslint no-useless-call: 0 */
- }
- actions.push(action);
- });
-
- // ... and run through the action stack
- return actions.process(this.platform)
- .then(function () {
- if (self.project.write) {
- self.project.write();
- }
-
- if (operation === PluginManager.INSTALL) {
- // Ignore passed `is_top_level` option since platform itself doesn't know
- // anything about managing dependencies - it's responsibility of caller.
- self.munger.add_plugin_changes(plugin, options.variables, /* is_top_level= */true, /* should_increment= */true, options.force);
- self.munger.platformJson.addPluginMetadata(plugin);
- } else {
- self.munger.remove_plugin_changes(plugin, /* is_top_level= */true);
- self.munger.platformJson.removePluginMetadata(plugin);
- }
-
- // Save everything (munge and plugin/modules metadata)
- self.munger.save_all();
-
- var metadata = self.munger.platformJson.generateMetadata();
- fs.writeFileSync(path.join(self.locations.www, 'cordova_plugins.js'), metadata, 'utf-8');
-
- // CB-11022 save plugin metadata to both www and platform_www if options.usePlatformWww is specified
- if (options.usePlatformWww) {
- fs.writeFileSync(path.join(self.locations.platformWww, 'cordova_plugins.js'), metadata, 'utf-8');
- }
- });
-};
-
-PluginManager.prototype.addPlugin = function (plugin, installOptions) {
- return this.doOperation(PluginManager.INSTALL, plugin, installOptions);
-};
-
-PluginManager.prototype.removePlugin = function (plugin, uninstallOptions) {
- return this.doOperation(PluginManager.UNINSTALL, plugin, uninstallOptions);
-};
+/*
+ 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 Q = require('q');
+var fs = require('fs-extra');
+var path = require('path');
+
+var ActionStack = require('./ActionStack');
+var PlatformJson = require('./PlatformJson');
+var CordovaError = require('./CordovaError/CordovaError');
+var PlatformMunger = require('./ConfigChanges/ConfigChanges').PlatformMunger;
+var PluginInfoProvider = require('./PluginInfo/PluginInfoProvider');
+
+/**
+ * @constructor
+ * @class PluginManager
+ * Represents an entity for adding/removing plugins for platforms
+ *
+ * @param {String} platform Platform name
+ * @param {Object} locations - Platform files and directories
+ * @param {IDEProject} ideProject The IDE project to add/remove plugin changes to/from
+ */
+function PluginManager (platform, locations, ideProject) {
+ this.platform = platform;
+ this.locations = locations;
+ this.project = ideProject;
+
+ var platformJson = PlatformJson.load(locations.root, platform);
+ this.munger = new PlatformMunger(platform, locations.root, platformJson, new PluginInfoProvider());
+}
+
+/**
+ * @constructs PluginManager
+ * A convenience shortcut to new PluginManager(...)
+ *
+ * @param {String} platform Platform name
+ * @param {Object} locations - Platform files and directories
+ * @param {IDEProject} ideProject The IDE project to add/remove plugin changes to/from
+ * @returns new PluginManager instance
+ */
+PluginManager.get = function (platform, locations, ideProject) {
+ return new PluginManager(platform, locations, ideProject);
+};
+
+PluginManager.INSTALL = 'install';
+PluginManager.UNINSTALL = 'uninstall';
+
+module.exports = PluginManager;
+
+/**
+ * Describes and implements common plugin installation/uninstallation routine. The flow is the following:
+ * * Validate and set defaults for options. Note that options are empty by default. Everything
+ * needed for platform IDE project must be passed from outside. Plugin variables (which
+ * are the part of the options) also must be already populated with 'PACKAGE_NAME' variable.
+ * * Collect all plugin's native and web files, get installers/uninstallers and process
+ * all these via ActionStack.
+ * * Save the IDE project, so the changes made by installers are persisted.
+ * * Generate config changes munge for plugin and apply it to all required files
+ * * Generate metadata for plugin and plugin modules and save it to 'cordova_plugins.js'
+ *
+ * @param {PluginInfo} plugin A PluginInfo structure representing plugin to install
+ * @param {Object} [options={}] An installation options. It is expected but is not necessary
+ * that options would contain 'variables' inner object with 'PACKAGE_NAME' field set by caller.
+ *
+ * @returns {Promise} Returns a Q promise, either resolved in case of success, rejected otherwise.
+ */
+PluginManager.prototype.doOperation = function (operation, plugin, options) {
+ if (operation !== PluginManager.INSTALL && operation !== PluginManager.UNINSTALL) { return Q.reject(new CordovaError('The parameter is incorrect. The opeation must be either "add" or "remove"')); }
+
+ if (!plugin || plugin.constructor.name !== 'PluginInfo') { return Q.reject(new CordovaError('The parameter is incorrect. The first parameter should be a PluginInfo instance')); }
+
+ // Set default to empty object to play safe when accesing properties
+ options = options || {};
+
+ var self = this;
+ var actions = new ActionStack();
+
+ // gather all files need to be handled during operation ...
+ plugin.getFilesAndFrameworks(this.platform, options)
+ .concat(plugin.getAssets(this.platform))
+ .concat(plugin.getJsModules(this.platform))
+ // ... put them into stack ...
+ .forEach(function (item) {
+ var installer = self.project.getInstaller(item.itemType);
+ var uninstaller = self.project.getUninstaller(item.itemType);
+ var actionArgs = [item, plugin, self.project, options];
+
+ var action;
+ if (operation === PluginManager.INSTALL) {
+ action = actions.createAction.apply(actions, [installer, actionArgs, uninstaller, actionArgs]); /* eslint no-useless-call: 0 */
+ } else /* op === PluginManager.UNINSTALL */{
+ action = actions.createAction.apply(actions, [uninstaller, actionArgs, installer, actionArgs]); /* eslint no-useless-call: 0 */
+ }
+ actions.push(action);
+ });
+
+ // ... and run through the action stack
+ return actions.process(this.platform)
+ .then(function () {
+ if (self.project.write) {
+ self.project.write();
+ }
+
+ if (operation === PluginManager.INSTALL) {
+ // Ignore passed `is_top_level` option since platform itself doesn't know
+ // anything about managing dependencies - it's responsibility of caller.
+ self.munger.add_plugin_changes(plugin, options.variables, /* is_top_level= */true, /* should_increment= */true, options.force);
+ self.munger.platformJson.addPluginMetadata(plugin);
+ } else {
+ self.munger.remove_plugin_changes(plugin, /* is_top_level= */true);
+ self.munger.platformJson.removePluginMetadata(plugin);
+ }
+
+ // Save everything (munge and plugin/modules metadata)
+ self.munger.save_all();
+
+ var metadata = self.munger.platformJson.generateMetadata();
+ fs.writeFileSync(path.join(self.locations.www, 'cordova_plugins.js'), metadata, 'utf-8');
+
+ // CB-11022 save plugin metadata to both www and platform_www if options.usePlatformWww is specified
+ if (options.usePlatformWww) {
+ fs.writeFileSync(path.join(self.locations.platformWww, 'cordova_plugins.js'), metadata, 'utf-8');
+ }
+ });
+};
+
+PluginManager.prototype.addPlugin = function (plugin, installOptions) {
+ return this.doOperation(PluginManager.INSTALL, plugin, installOptions);
+};
+
+PluginManager.prototype.removePlugin = function (plugin, uninstallOptions) {
+ return this.doOperation(PluginManager.UNINSTALL, plugin, uninstallOptions);
+};