You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by st...@apache.org on 2017/12/14 06:21:09 UTC

[cordova-lib] 03/06: CB-13056 : removed PlatformApiPoly and tests

This is an automated email from the ASF dual-hosted git repository.

steven pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cordova-lib.git

commit 82c29778f32e7a2f1aa495e98d2f8d93e693d1a5
Author: Audrey So <au...@apache.org>
AuthorDate: Tue Dec 12 09:54:23 2017 -0800

    CB-13056 : removed PlatformApiPoly and tests
---
 spec/cordova/platforms/PlatformApiPoly.spec.js | 321 -----------
 spec/cordova/platforms/platforms.spec.js       |  26 -
 spec/cordova/util.spec.js                      |  24 -
 src/cordova/util.js                            |  11 -
 src/platforms/PlatformApiPoly.js               | 724 -------------------------
 5 files changed, 1106 deletions(-)

diff --git a/spec/cordova/platforms/PlatformApiPoly.spec.js b/spec/cordova/platforms/PlatformApiPoly.spec.js
deleted file mode 100644
index c15c17c..0000000
--- a/spec/cordova/platforms/PlatformApiPoly.spec.js
+++ /dev/null
@@ -1,321 +0,0 @@
-/**
-    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');
-var et = require('elementtree');
-var path = require('path');
-var shell = require('shelljs');
-var xmlHelpers = require('cordova-common').xmlHelpers;
-var ActionStack = require('cordova-common').ActionStack;
-var superspawn = require('cordova-common').superspawn;
-var PluginInfo = require('cordova-common').PluginInfo;
-var ConfigParser = require('cordova-common').ConfigParser;
-var knownPlatforms = require('../../../src/platforms/platforms');
-var PlatformApiPoly = require('../../../src/platforms/PlatformApiPoly');
-
-var PLATFORM = 'browser';
-var PLATFORM_VERSION = '4.1.0';
-var PLATFORM_LIB = '/some/platform/lib';
-var CORDOVA_ROOT = path.join(__dirname, '../fixtures/projects/platformApi');
-var PLATFORM_ROOT = path.join(CORDOVA_ROOT, 'platforms/browser');
-var DUMMY_PLUGIN = path.join(__dirname, '../fixtures/plugins/test');
-var TEST_XML = '<?xml version="1.0" encoding="UTF-8"?>\n' +
-    '<widget xmlns     = "http://www.w3.org/ns/widgets"\n' +
-    '        xmlns:cdv = "http://cordova.apache.org/ns/1.0"\n' +
-    '        id        = "io.cordova.hellocordova"\n' +
-    '        version   = "0.0.1">\n' +
-    '    <name>Hello Cordova</name>\n' +
-    '    <description>\n' +
-    '        A sample Apache Cordova application that responds to the deviceready event.\n' +
-    '    </description>\n' +
-    '    <author href="http://cordova.io" email="dev@cordova.apache.org">\n' +
-    '        Apache Cordova Team\n' +
-    '    </author>\n' +
-    '    <content src="index.html" />\n' +
-    '    <access origin="*" />\n' +
-    '    <preference name="fullscreen" value="true" />\n' +
-    '    <preference name="webviewbounce" value="true" />\n' +
-    '</widget>\n';
-
-var platformApiPolyPublicMethods = [
-    'getPlatformInfo',
-    'prepare',
-    'addPlugin',
-    'removePlugin',
-    'updatePlugin',
-    'build',
-    'run',
-    'clean',
-    'requirements'
-];
-
-describe('PlatformApi polyfill', function () {
-    var platformApi;
-
-    beforeEach(function () {
-        var originalParseElementtreeSync = xmlHelpers.parseElementtreeSync;
-        spyOn(xmlHelpers, 'parseElementtreeSync').and.callFake(function (configPath) {
-            return /config\.xml$/.test(configPath) ? new et.ElementTree(et.XML(TEST_XML)) :
-                originalParseElementtreeSync(configPath);
-        });
-
-        platformApi = new PlatformApiPoly(PLATFORM, PLATFORM_ROOT);
-    });
-
-    it('should be constructable', function () {
-        var api;
-        expect(function () { api = new PlatformApiPoly(PLATFORM, PLATFORM_ROOT); }).not.toThrow();
-        expect(api).toEqual(jasmine.any(PlatformApiPoly));
-    });
-    /* eslint-disable no-unused-vars */
-    it('should fail when unknown platform is specified', function () {
-        var api;
-        expect(function () { api = new PlatformApiPoly('fakePlatform', PLATFORM_ROOT); }).toThrow();
-    });
-
-    it('should fail when mandatory argument is not specified', function () {
-        var api;
-        expect(function () { api = new PlatformApiPoly(PLATFORM); }).toThrow();
-        expect(function () { api = new PlatformApiPoly(null, PLATFORM_ROOT); }).toThrow();
-    });
-    /* eslint-enable no-unused-vars */
-    it('should have fields defined', function () {
-        expect(platformApi.platform).toBe(PLATFORM);
-        expect(platformApi.root).toBe(PLATFORM_ROOT);
-    });
-
-    it('should have \'static\' methods defined', function () {
-        expect(platformApi.constructor.createPlatform).toEqual(jasmine.any(Function));
-        expect(platformApi.constructor.updatePlatform).toEqual(jasmine.any(Function));
-    });
-
-    it('should have methods defined', function () {
-        platformApiPolyPublicMethods.forEach(function (methodName) {
-            expect(platformApi[methodName]).toEqual(jasmine.any(Function));
-        });
-    });
-
-    describe('methods:', function () {
-
-        var FAKE_PROJECT;
-        var FAKE_CONFIG;
-        var OPTIONS;
-        var getPlatformApi; // eslint-disable-line no-unused-vars
-        var fail;
-        var success;
-
-        beforeEach(function () {
-            getPlatformApi = spyOn(knownPlatforms, 'getPlatformApi').and.returnValue(platformApi);
-
-            spyOn(shell, 'cp');
-            spyOn(shell, 'rm');
-            spyOn(shell, 'mkdir');
-            spyOn(fs, 'writeFileSync');
-
-            fail = jasmine.createSpy('fail');
-            success = jasmine.createSpy('success');
-
-            FAKE_CONFIG = new ConfigParser('/fake/config.xml');
-            FAKE_PROJECT = {locations: {platforms: path.dirname(PLATFORM_ROOT), www: path.join(CORDOVA_ROOT, 'www')}, projectConfig: FAKE_CONFIG};
-            OPTIONS = {platformDetails: {libDir: PLATFORM_LIB, platform: PLATFORM, version: PLATFORM_VERSION}};
-        });
-
-        describe('static create/updatePlatform methods', function () {
-            var spawn;
-
-            beforeEach(function () {
-                spawn = spyOn(superspawn, 'spawn').and.returnValue(Q());
-            });
-
-            it('should create/update platform through running platforms\' scripts', function (done) {
-                Q.all([PlatformApiPoly.createPlatform(PLATFORM_ROOT, FAKE_CONFIG, OPTIONS),
-                    PlatformApiPoly.updatePlatform(PLATFORM_ROOT, OPTIONS)])
-                    .then(function () {
-                        expect(spawn).toHaveBeenCalled();
-                        expect(spawn.calls.count()).toBe(2);
-                    }).fail(function (err) {
-                        expect(err).not.toBeDefined();
-                    }).fin(done);
-            });
-
-            it('should pass down arguments to platforms\' scripts', function (done) {
-                Q.all([PlatformApiPoly.createPlatform(PLATFORM_ROOT, FAKE_CONFIG, OPTIONS),
-                    PlatformApiPoly.updatePlatform(PLATFORM_ROOT, OPTIONS)])
-                    .then(function () {
-                        expect(spawn).toHaveBeenCalled();
-                        expect(spawn.calls.count()).toBe(2);
-                        expect(spawn.calls.argsFor(0)[0]).toBe(path.join(PLATFORM_LIB, 'bin/create'));
-                        expect(spawn.calls.argsFor(0)[1]).toContain(PLATFORM_ROOT);
-                        expect(spawn.calls.argsFor(1)[0]).toBe(path.join(PLATFORM_LIB, 'bin/update'));
-                        expect(spawn.calls.argsFor(1)[1]).toContain(PLATFORM_ROOT);
-                    }).fail(function (err) {
-                        expect(err).not.toBeDefined();
-                    }).fin(done);
-            });
-
-            it('should copy cordova JS sources into created platform', function (done) {
-                Q.all([PlatformApiPoly.createPlatform(PLATFORM_ROOT, FAKE_CONFIG, OPTIONS),
-                    PlatformApiPoly.updatePlatform(PLATFORM_ROOT, OPTIONS)])
-                    .then(function () {
-                        expect(shell.cp).toHaveBeenCalled();
-                        expect(shell.cp.calls.count()).toBe(2);
-                    }).fail(fail)
-                    .fin(function () {
-                        expect(fail).not.toHaveBeenCalled();
-                        done();
-                    });
-            });
-
-            it('should fail immediately if options.platformInfo is not specified', function (done) {
-                Q.all([PlatformApiPoly.createPlatform(PLATFORM_ROOT, FAKE_CONFIG),
-                    PlatformApiPoly.updatePlatform(PLATFORM_ROOT, FAKE_CONFIG)])
-                    .then(success)
-                    .fail(fail)
-                    .fin(function function_name (argument) {
-                        expect(success).not.toHaveBeenCalled();
-                        expect(fail).toHaveBeenCalled();
-                        expect(spawn).not.toHaveBeenCalled();
-                        done();
-                    });
-            });
-        });
-
-        describe('prepare method', function () {
-            beforeEach(function () {
-                spyOn(platformApi._parser, 'update_www');
-                spyOn(platformApi._parser, 'update_project').and.returnValue(Q());
-            });
-
-            it('should return promise', function (done) {
-                var promise = platformApi.prepare(FAKE_PROJECT, OPTIONS);
-                expect(Q.isPromise(promise)).toBeTruthy();
-                promise.fin(done);
-            });
-
-            it('should call parser\'s corresponding methods', function (done) {
-                platformApi.prepare(FAKE_PROJECT, OPTIONS)
-                    .then(function () {
-                        [platformApi._parser.update_www, platformApi._parser.update_project]
-                            .forEach(function (method) {
-                                expect(method).toHaveBeenCalled();
-                            });
-                    })
-                    .fail(fail)
-                    .fin(function () {
-                        expect(fail).not.toHaveBeenCalled();
-                        done();
-                    });
-            });
-        });
-
-        describe('pluginAdd method', function () {
-            var plugin, actionsProcess;
-
-            beforeEach(function () {
-                plugin = new PluginInfo(DUMMY_PLUGIN);
-                actionsProcess = spyOn(ActionStack.prototype, 'process').and.callThrough();
-            });
-
-            it('should return promise', function (done) {
-                var promise = platformApi.addPlugin(plugin);
-                expect(Q.isPromise(promise)).toBeTruthy();
-                promise.fin(done);
-            });
-
-            it('should fail if plugin parameter is not specified', function (done) {
-                platformApi.addPlugin()
-                    .then(success)
-                    .fail(fail)
-                    .fin(function () {
-                        expect(success).not.toHaveBeenCalled();
-                        expect(fail).toHaveBeenCalled();
-                        done();
-                    });
-            });
-
-            it('should process all plugin files through action stack', function (done) {
-                platformApi.addPlugin(plugin)
-                    .then(success)
-                    .fail(fail)
-                    .fin(function () {
-                        expect(actionsProcess).toHaveBeenCalled();
-                        expect(success).toHaveBeenCalled();
-                        expect(fail).not.toHaveBeenCalled();
-                        done();
-                    });
-            });
-        });
-
-        describe('platform actions', function () {
-            var spawnSpy;
-
-            beforeEach(function () {
-                spawnSpy = spyOn(superspawn, 'spawn');
-            });
-
-            it('should return promise', function (done) {
-                var ops = [
-                    platformApi.build(/* opts */),
-                    platformApi.run(/* opts */),
-                    platformApi.clean(/* opts */),
-                    platformApi.requirements()
-                ];
-
-                ops.forEach(function (op) {
-                    expect(Q.isPromise(op));
-                });
-                Q.all(ops).fin(done);
-            });
-
-            it('should do their job through running platforms\' scripts', function (done) {
-                var ops = [
-                    platformApi.build(/* opts */),
-                    platformApi.run(/* opts */),
-                    platformApi.clean(/* opts */)
-                ];
-
-                Q.all(ops)
-                    .then(function () {
-                        expect(spawnSpy).toHaveBeenCalled();
-                        expect(spawnSpy.calls.count()).toEqual(3);
-                    }).fin(done);
-            });
-
-            it('should convert and pass down options to platforms\' scripts', function (done) {
-                var options = {
-                    release: true,
-                    nobuild: true,
-                    device: true,
-                    target: 'FakeDevice',
-                    archs: ['arm', 'x86'],
-                    buildConfig: '/some/path'
-                };
-                spawnSpy.and.returnValue(Q());
-                platformApi.build(options)
-                    .then(function () {
-                        ['--release', '--nobuild', '--device', '--target=' + options.target, '--archs=arm,x86', '--buildConfig=' + options.buildConfig]
-                            .forEach(function (arg) {
-                                expect(spawnSpy.calls[0].args[1]).toContain(arg);
-                            });
-                    }).fin(done);
-            });
-        });
-    });
-});
diff --git a/spec/cordova/platforms/platforms.spec.js b/spec/cordova/platforms/platforms.spec.js
index cf09410..d1b083a 100644
--- a/spec/cordova/platforms/platforms.spec.js
+++ b/spec/cordova/platforms/platforms.spec.js
@@ -17,7 +17,6 @@
     under the License.
 */
 
-var fs = require('fs');
 var os = require('os');
 var path = require('path');
 var rewire = require('rewire');
@@ -30,9 +29,6 @@ var platforms = rewire('../../../src/platforms/platforms');
 var CORDOVA_ROOT = path.join(__dirname, '../fixtures/projects/platformApi');
 var PLATFORM_WITH_API = path.join(CORDOVA_ROOT, 'platforms/windows');
 var PLATFORM_SYMLINK = path.join(os.tmpdir(), 'cordova_windows_symlink');
-var PlatformApiPoly = require('../../../src/platforms/PlatformApiPoly');
-
-var browserParser = require('../../../src/cordova/metadata/browser_parser.js');
 
 shell.ln('-sf', PLATFORM_WITH_API, PLATFORM_SYMLINK);
 
@@ -60,28 +56,6 @@ describe('getPlatformApi method', function () {
         expect(util.requireNoCache.calls.argsFor(0)[0]).toEqual(path.join(CORDOVA_ROOT, 'platforms/windows/cordova/Api.js'));
     });
 
-    it('should return PlatformApi polyfill if PlatformApi is not defined by platform', function () {
-        spyOn(browserParser, 'dirExists').and.returnValue(true);
-        spyOn(fs, 'existsSync').and.callFake(function (somePath) {
-            if (somePath === 'PLATFORM_WOUT_API') {
-                return true;
-            }
-            return false;
-        });
-        spyOn(events, 'emit').and.returnValue(true);
-        spyOn(util, 'convertToRealPathSafe').and.returnValue('PLATFORM_WOUT_API');
-        spyOn(util, 'requireNoCache').and.callThrough();
-        var platformApi = platforms.getPlatformApi('browser', 'PLATFORM_WOUT_API');
-        expect(platformApi).toEqual(jasmine.any(PlatformApiPoly));
-        expect(util.convertToRealPathSafe.calls.count()).toEqual(1);
-        expect(events.emit.calls.count()).toEqual(3);
-        expect(events.emit.calls.argsFor(0)[1]).toContain('Unable to load PlatformApi from platform. Error: Cannot find module');
-        expect(events.emit.calls.argsFor(1)[1]).toEqual('Platform not found or needs polyfill.');
-        expect(events.emit.calls.argsFor(2)[1]).toEqual('Failed to require PlatformApi instance for platform "browser". Using polyfill instead.');
-        expect(util.isCordova.calls.count()).toEqual(0);
-        expect(util.requireNoCache.calls.count()).toEqual(0);
-    });
-
     it('should throw error if using deprecated platform', function () {
         try {
             platforms.getPlatformApi('android', path.join(CORDOVA_ROOT, 'platforms/android'));
diff --git a/spec/cordova/util.spec.js b/spec/cordova/util.spec.js
index 91045e8..f273bdb 100644
--- a/spec/cordova/util.spec.js
+++ b/spec/cordova/util.spec.js
@@ -317,23 +317,6 @@ describe('util module', function () {
                 );
             });
 
-            it('Test 028 : should throw error if platform is not supported', function () {
-                spyOn(events, 'emit').and.returnValue(true);
-                expect(function () { util.getPlatformApiFunction('some/path', 'somePlatform'); }).toThrow();
-                expect(events.emit.calls.count()).toBe(2);
-                expect(events.emit.calls.argsFor(0)[1]).toBe('Unable to load PlatformApi from platform. Error: Cannot find module \'some/path\'');
-                expect(events.emit.calls.argsFor(1)[1]).toBe('The platform "somePlatform" does not appear to be a valid cordova platform. It is missing API.js. somePlatform not supported.');
-            });
-
-            it('Test 029 : should use polyfill if blackberry10, webos, ubuntu', function () {
-                spyOn(events, 'emit').and.returnValue(true);
-                util.getPlatformApiFunction('some/path', 'blackberry10');
-                expect(events.emit.calls.count()).toBe(3);
-                expect(events.emit.calls.argsFor(0)[1]).toBe('Unable to load PlatformApi from platform. Error: Cannot find module \'some/path\'');
-                expect(events.emit.calls.argsFor(1)[1]).toBe('The platform "blackberry10" does not appear to be a valid cordova platform. It is missing API.js. blackberry10 not supported.');
-                expect(events.emit.calls.argsFor(2)[1]).toBe('Failed to require PlatformApi instance for platform "blackberry10". Using polyfill instead.');
-            });
-
             it('Test 030 : successfully find platform Api', function () {
                 spyOn(events, 'emit').and.returnValue(true);
                 var specPlugDir = __dirname.replace('spec-cordova', 'spec-plugman');
@@ -341,13 +324,6 @@ describe('util module', function () {
                 expect(events.emit.calls.count()).toBe(1);
                 expect(events.emit.calls.argsFor(0)[1]).toBe('PlatformApi successfully found for platform windows');
             });
-
-            it('Test 031 : should inform user that entry point should be called Api.js', function () {
-                spyOn(events, 'emit').and.returnValue(true);
-                var specPlugDir = __dirname.replace('spec-cordova', 'spec-plugman');
-                expect(function () { util.getPlatformApiFunction((path.join(specPlugDir, 'fixtures', 'projects', 'platformApi', 'platforms', 'windows', 'cordova', 'lib', 'PluginInfo.js')), 'windows'); }).toThrow();
-                expect(events.emit.calls.argsFor(0)[1]).toBe('File name should be called Api.js.');
-            });
         });
     });
 });
diff --git a/src/cordova/util.js b/src/cordova/util.js
index 972ca89..ac68bc2 100644
--- a/src/cordova/util.js
+++ b/src/cordova/util.js
@@ -503,16 +503,5 @@ function getPlatformApiFunction (libDir, platform) {
         }
     }
 
-    if (!PlatformApi) {
-        // The platform just does not expose Api and we will try to polyfill it
-        var polyPlatforms = ['blackberry10', 'browser', 'ubuntu', 'webos'];
-        if (polyPlatforms.indexOf(platform) > -1) {
-            events.emit('verbose', 'Failed to require PlatformApi instance for platform "' + platform +
-            '". Using polyfill instead.');
-            PlatformApi = require('../platforms/PlatformApiPoly.js');
-        } else {
-            throw new Error('Your ' + platform + ' platform does not have Api.js');
-        }
-    }
     return PlatformApi;
 }
diff --git a/src/platforms/PlatformApiPoly.js b/src/platforms/PlatformApiPoly.js
deleted file mode 100644
index 9a9c0d7..0000000
--- a/src/platforms/PlatformApiPoly.js
+++ /dev/null
@@ -1,724 +0,0 @@
-/**
-    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');
-var path = require('path');
-var shell = require('shelljs');
-var semver = require('semver');
-
-var common = require('../plugman/platforms/common');
-
-var superspawn = require('cordova-common').superspawn;
-var xmlHelpers = require('cordova-common').xmlHelpers;
-var knownPlatforms = require('./platforms');
-var CordovaError = require('cordova-common').CordovaError;
-var PluginInfo = require('cordova-common').PluginInfo;
-var ConfigParser = require('cordova-common').ConfigParser;
-var PlatformJson = require('cordova-common').PlatformJson;
-var ActionStack = require('cordova-common').ActionStack;
-var PlatformMunger = require('cordova-common').ConfigChanges.PlatformMunger;
-var PluginInfoProvider = require('cordova-common').PluginInfoProvider;
-
-/**
- * Class, that acts as abstraction over particular platform. Encapsulates the
- *   platform's properties and methods.
- *
- * Platform that implements own PlatformApi instance _should implement all
- *   prototype methods_ of this class to be fully compatible with cordova-lib.
- *
- * The PlatformApi instance also should define the following field:
- *
- * * platform: String that defines a platform name.
- */
-function PlatformApiPoly (platform, platformRootDir, events) {
-    if (!platform) throw new CordovaError('\'platform\' argument is missing');
-    if (!platformRootDir) throw new CordovaError('platformRootDir argument is missing');
-
-    this.root = platformRootDir;
-    this.platform = platform;
-    this.events = events || require('cordova-common').events;
-
-    if (!(platform in knownPlatforms)) { throw new CordovaError('Unknown platform ' + platform); }
-
-    var ParserConstructor = require(knownPlatforms[platform].parser_file);
-    this._parser = new ParserConstructor(this.root);
-    this._handler = require(knownPlatforms[platform].handler_file);
-
-    this._platformJson = PlatformJson.load(this.root, platform);
-    this._pluginInfoProvider = new PluginInfoProvider();
-    this._munger = new PlatformMunger(platform, this.root, this._platformJson, this._pluginInfoProvider);
-}
-
-/**
- * Installs platform to specified directory and creates a platform project.
- *
- * @param  {String}  destinationDir  A directory, where platform should be
- *   created/installed.
- * @param  {ConfigParser} [projectConfig] A ConfigParser instance, used to get
- *   some application properties for new platform like application name, package
- *   id, etc. If not defined, this means that platform is used as standalone
- *   project and is not a part of cordova project, so platform will use some
- *   default values.
- * @param  {Object}   [options]  An options object. The most common options are:
- * @param  {String}   [options.customTemplate]  A path to custom template, that
- *   should override the default one from platform.
- * @param  {Boolean}  [options.link=false]  Flag that indicates that platform's
- *   sources will be linked to installed platform instead of copying.
- *
- * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
- *   instance or rejected with CordovaError.
- */
-PlatformApiPoly.createPlatform = function (destinationDir, projectConfig, options) {
-    if (!options || !options.platformDetails) {
-        return Q.reject(new CordovaError('Failed to find platform\'s \'create\' script. ' +
-            'Either \'options\' parameter or \'platformDetails\' option is missing'));
-    }
-
-    var command = path.join(options.platformDetails.libDir, 'bin', 'create');
-    var commandArguments = getCreateArgs(destinationDir, projectConfig, options);
-
-    return superspawn.spawn(command, commandArguments,
-        { printCommand: true, stdio: 'inherit', chmod: true })
-        .then(function () {
-            var platformApi = knownPlatforms
-                .getPlatformApi(options.platformDetails.platform, destinationDir);
-            copyCordovaSrc(options.platformDetails.libDir, platformApi.getPlatformInfo());
-            return platformApi;
-        });
-};
-
-/**
- * Updates already installed platform.
- *
- * @param  {String}  destinationDir  A directory, where existing platform
- *   installed, that should be updated.
- * @param  {Object}  [options]  An options object. The most common options are:
- * @param  {String}  [options.customTemplate]  A path to custom template, that
- *   should override the default one from platform.
- * @param  {Boolean}  [options.link=false]  Flag that indicates that platform's sources
- *   will be linked to installed platform instead of copying.
- *
- * @return {Promise<PlatformApi>} Promise either fulfilled with PlatformApi
- *   instance or rejected with CordovaError.
- */
-PlatformApiPoly.updatePlatform = function (destinationDir, options) {
-    if (!options || !options.platformDetails) {
-        return Q.reject(new CordovaError('Failed to find platform\'s \'create\' script. ' +
-            'Either \'options\' parameter or \'platformDetails\' option is missing'));
-    }
-
-    var command = path.join(options.platformDetails.libDir, 'bin', 'update');
-    return superspawn.spawn(command, [destinationDir],
-        { printCommand: true, stdio: 'inherit', chmod: true })
-        .then(function () {
-            var platformApi = knownPlatforms
-                .getPlatformApi(options.platformDetails.platform, destinationDir);
-            copyCordovaSrc(options.platformDetails.libDir, platformApi.getPlatformInfo());
-            return platformApi;
-        });
-};
-
-/**
- * Gets a CordovaPlatform object, that represents the platform structure.
- *
- * @return  {CordovaPlatform}  A structure that contains the description of
- *   platform's file structure and other properties of platform.
- */
-PlatformApiPoly.prototype.getPlatformInfo = function () {
-    var self = this;
-    var result = {};
-    result.locations = {
-        www: self._parser.www_dir(),
-        platformWww: path.join(self.root, 'platform_www'),
-        configXml: self._parser.config_xml(),
-        // NOTE: Due to platformApi spec we need to return relative paths here
-        cordovaJs: path.relative(self.root, self._parser.cordovajs_path.call(self.parser, self.root)),
-        cordovaJsSrc: path.relative(self.root, self._parser.cordovajs_src_path.call(self.parser, self.root))
-    };
-    result.root = self.root;
-    result.name = self.platform;
-    self.version = result.version = self.version || getPlatformVersion(self.root) || knownPlatforms[self.platform].version;
-    result.projectConfig = self._config;
-
-    return result;
-};
-
-/**
- * Updates installed platform with provided www assets and new app
- *   configuration. This method is required for CLI workflow and will be called
- *   each time before build, so the changes, made to app configuration and www
- *   code, will be applied to platform.
- *
- * @param {CordovaProject} cordovaProject A CordovaProject instance, that defines a
- *   project structure and configuration, that should be applied to platform
- *   (contains project's www location and ConfigParser instance for project's
- *   config).
- *
- * @return  {Promise}  Return a promise either fulfilled, or rejected with
- *   CordovaError instance.
- */
-PlatformApiPoly.prototype.prepare = function (cordovaProject, options) {
-    // First cleanup current config and merge project's one into own
-    var defaultConfig = path.join(this.root, 'cordova', 'defaults.xml');
-    var ownConfig = this.getPlatformInfo().locations.configXml;
-
-    var sourceCfg = cordovaProject.projectConfig;
-    // If defaults.xml is present, overwrite platform config.xml with it.
-    // Otherwise save whatever is there as defaults so it can be
-    // restored or copy project config into platform if none exists.
-    if (fs.existsSync(defaultConfig)) {
-        this.events.emit('verbose', 'Generating config.xml from defaults for platform "' + this.platform + '"');
-        shell.cp('-f', defaultConfig, ownConfig);
-    } else if (fs.existsSync(ownConfig)) {
-        shell.cp('-f', ownConfig, defaultConfig);
-    } else {
-        shell.cp('-f', sourceCfg.path, ownConfig);
-    }
-
-    this._munger.reapply_global_munge().save_all();
-
-    this._config = new ConfigParser(ownConfig);
-    xmlHelpers.mergeXml(cordovaProject.projectConfig.doc.getroot(),
-        this._config.doc.getroot(), this.platform, true);
-    this._config.write();
-
-    // Update own www dir with project's www assets and plugins' assets and js-files
-    this._parser.update_www(cordovaProject.locations.www);
-
-    // update project according to config.xml changes.
-    return this._parser.update_project(this._config, options);
-};
-
-/**
- * Installs a new plugin into platform. This method only copies non-www files
- *   (sources, libs, etc.) to platform. It also doesn't resolves the
- *   dependencies of plugin. Both of handling of www files, such as assets and
- *   js-files and resolving dependencies are the responsibility of caller.
- *
- * @param  {PluginInfo}  plugin  A PluginInfo instance that represents plugin
- *   that will be installed.
- * @param  {Object}  installOptions  An options object. Possible options below:
- * @param  {Boolean}  installOptions.link: Flag that specifies that plugin
- *   sources will be symlinked to app's directory instead of copying (if
- *   possible).
- * @param  {Object}  installOptions.variables  An object that represents
- *   variables that will be used to install plugin. See more details on plugin
- *   variables in documentation:
- *   https://cordova.apache.org/docs/en/4.0.0/plugin_ref_spec.md.html
- *
- * @return  {Promise}  Return a promise either fulfilled, or rejected with
- *   CordovaError instance.
- */
-PlatformApiPoly.prototype.addPlugin = function (plugin, installOptions) {
-
-    if (!plugin || !(plugin instanceof PluginInfo)) {
-        return Q.reject('The parameter is incorrect. The first parameter ' +
-            'should be valid PluginInfo instance');
-    }
-
-    installOptions = installOptions || {};
-    installOptions.variables = installOptions.variables || {};
-    // CB-10108 platformVersion option is required for proper plugin installation
-    installOptions.platformVersion = installOptions.platformVersion ||
-        this.getPlatformInfo().version;
-
-    var self = this;
-    var actions = new ActionStack();
-    var projectFile = this._handler.parseProjectFile && this._handler.parseProjectFile(this.root);
-
-    // gather all files needs to be handled during install
-    plugin.getFilesAndFrameworks(this.platform)
-        .concat(plugin.getAssets(this.platform))
-        .concat(plugin.getJsModules(this.platform))
-        .forEach(function (item) {
-            actions.push(actions.createAction(
-                self._getInstaller(item.itemType), [item, plugin.dir, plugin.id, installOptions, projectFile],
-                self._getUninstaller(item.itemType), [item, plugin.dir, plugin.id, installOptions, projectFile]));
-        });
-
-    // run through the action stack
-    return actions.process(this.platform, this.root)
-        .then(function () {
-            if (projectFile) {
-                projectFile.write();
-            }
-
-            // Add PACKAGE_NAME variable into vars
-            if (!installOptions.variables.PACKAGE_NAME) {
-                installOptions.variables.PACKAGE_NAME = self._handler.package_name(self.root);
-            }
-
-            self._munger
-                // Ignore passed `is_top_level` option since platform itself doesn't know
-                // anything about managing dependencies - it's responsibility of caller.
-                .add_plugin_changes(plugin, installOptions.variables, /* is_top_level= */true, /* should_increment= */true)
-                .save_all();
-
-            var targetDir = installOptions.usePlatformWww ?
-                self.getPlatformInfo().locations.platformWww :
-                self.getPlatformInfo().locations.www;
-
-            self._addModulesInfo(plugin, targetDir);
-        });
-};
-
-/**
- * Removes an installed plugin from platform.
- *
- * Since method accepts PluginInfo instance as input parameter instead of plugin
- *   id, caller shoud take care of managing/storing PluginInfo instances for
- *   future uninstalls.
- *
- * @param  {PluginInfo}  plugin  A PluginInfo instance that represents plugin
- *   that will be installed.
- *
- * @return  {Promise}  Return a promise either fulfilled, or rejected with
- *   CordovaError instance.
- */
-PlatformApiPoly.prototype.removePlugin = function (plugin, uninstallOptions) {
-
-    uninstallOptions = uninstallOptions || {};
-    // CB-10108 platformVersion option is required for proper plugin installation
-    uninstallOptions.platformVersion = uninstallOptions.platformVersion ||
-        this.getPlatformInfo().version;
-
-    var self = this;
-    var actions = new ActionStack();
-    var projectFile = this._handler.parseProjectFile && this._handler.parseProjectFile(this.root);
-
-    // queue up plugin files
-    plugin.getFilesAndFrameworks(this.platform)
-        .concat(plugin.getAssets(this.platform))
-        .concat(plugin.getJsModules(this.platform))
-        .forEach(function (item) {
-            actions.push(actions.createAction(
-                self._getUninstaller(item.itemType), [item, plugin.dir, plugin.id, uninstallOptions, projectFile],
-                self._getInstaller(item.itemType), [item, plugin.dir, plugin.id, uninstallOptions, projectFile]));
-        });
-
-    // run through the action stack
-    return actions.process(this.platform, this.root)
-        .then(function () {
-            if (projectFile) {
-                projectFile.write();
-            }
-
-            self._munger
-                // Ignore passed `is_top_level` option since platform itself doesn't know
-                // anything about managing dependencies - it's responsibility of caller.
-                .remove_plugin_changes(plugin, /* is_top_level= */true)
-                .save_all();
-
-            var targetDir = uninstallOptions.usePlatformWww ?
-                self.getPlatformInfo().locations.platformWww :
-                self.getPlatformInfo().locations.www;
-
-            self._removeModulesInfo(plugin, targetDir);
-            // Remove stale plugin directory
-            // TODO: this should be done by plugin files uninstaller
-            shell.rm('-rf', path.resolve(self.root, 'Plugins', plugin.id));
-        });
-};
-
-PlatformApiPoly.prototype.updatePlugin = function (plugin, updateOptions) {
-    var self = this;
-
-    // Set up assets installer to copy asset files into platform_www dir instead of www
-    updateOptions = updateOptions || {};
-    updateOptions.usePlatformWww = true;
-
-    return this.removePlugin(plugin, updateOptions)
-        .then(function () {
-            return self.addPlugin(plugin, updateOptions);
-        });
-};
-
-/**
- * Builds an application package for current platform.
- *
- * @param  {Object}  buildOptions  A build options. This object's structure is
- *   highly depends on platform's specific. The most common options are:
- * @param  {Boolean}  buildOptions.debug  Indicates that packages should be
- *   built with debug configuration. This is set to true by default unless the
- *   'release' option is not specified.
- * @param  {Boolean}  buildOptions.release  Indicates that packages should be
- *   built with release configuration. If not set to true, debug configuration
- *   will be used.
- * @param   {Boolean}  buildOptions.device  Specifies that built app is intended
- *   to run on device
- * @param   {Boolean}  buildOptions.emulator: Specifies that built app is
- *   intended to run on emulator
- * @param   {String}  buildOptions.target  Specifies the device id that will be
- *   used to run built application.
- * @param   {Boolean}  buildOptions.nobuild  Indicates that this should be a
- *   dry-run call, so no build artifacts will be produced.
- * @param   {String[]}  buildOptions.archs  Specifies chip architectures which
- *   app packages should be built for. List of valid architectures is depends on
- *   platform.
- * @param   {String}  buildOptions.buildConfig  The path to build configuration
- *   file. The format of this file is depends on platform.
- * @param   {String[]} buildOptions.argv Raw array of command-line arguments,
- *   passed to `build` command. The purpose of this property is to pass a
- *   platform-specific arguments, and eventually let platform define own
- *   arguments processing logic.
- *
- * @return {Promise<Object[]>} A promise either fulfilled with an array of build
- *   artifacts (application packages) if package was built successfully,
- *   or rejected with CordovaError. The resultant build artifact objects is not
- *   strictly typed and may conatin arbitrary set of fields as in sample below.
- *
- *     {
- *         architecture: 'x86',
- *         buildType: 'debug',
- *         path: '/path/to/build',
- *         type: 'app'
- *     }
- *
- * The return value in most cases will contain only one item but in some cases
- *   there could be multiple items in output array, e.g. when multiple
- *   arhcitectures is specified.
- */
-PlatformApiPoly.prototype.build = function (buildOptions) {
-    var command = path.join(this.root, 'cordova', 'build');
-    var commandArguments = getBuildArgs(buildOptions);
-    return superspawn.spawn(command, commandArguments, {
-        printCommand: true, stdio: 'inherit', chmod: true });
-};
-
-/**
- * Builds an application package for current platform and runs it on
- *   specified/default device. If no 'device'/'emulator'/'target' options are
- *   specified, then tries to run app on default device if connected, otherwise
- *   runs the app on emulator.
- *
- * @param   {Object}  runOptions  An options object. The structure is the same
- *   as for build options.
- *
- * @return {Promise} A promise either fulfilled if package was built and ran
- *   successfully, or rejected with CordovaError.
- */
-PlatformApiPoly.prototype.run = function (runOptions) {
-    var command = path.join(this.root, 'cordova', 'run');
-    var commandArguments = getBuildArgs(runOptions);
-    return superspawn.spawn(command, commandArguments, {
-        printCommand: true, stdio: 'inherit', chmod: true });
-};
-
-/**
- * Cleans out the build artifacts from platform's directory.
- *
- * @return  {Promise}  Return a promise either fulfilled, or rejected with
- *   CordovaError.
- */
-PlatformApiPoly.prototype.clean = function () {
-    var cmd = path.join(this.root, 'cordova', 'clean');
-    return superspawn.spawn(cmd, [], { printCommand: true, stdio: 'inherit', chmod: true });
-};
-
-/**
- * Performs a requirements check for current platform. Each platform defines its
- *   own set of requirements, which should be resolved before platform can be
- *   built successfully.
- *
- * @return  {Promise<Requirement[]>}  Promise, resolved with set of Requirement
- *   objects for current platform.
- */
-PlatformApiPoly.prototype.requirements = function () {
-    var modulePath = path.join(this.root, 'cordova', 'lib', 'check_reqs');
-    try {
-        return require(modulePath).check_all();
-    } catch (e) {
-        var errorMsg = 'Failed to check requirements for ' + this.platform + ' platform. ' +
-            'check_reqs module is missing for platform. Skipping it...';
-        return Q.reject(errorMsg);
-    }
-};
-
-module.exports = PlatformApiPoly;
-
-/**
- * Converts arguments, passed to createPlatform to command-line args to
- *   'bin/create' script for specific platform.
- *
- * @param   {ProjectInfo}  project  A current project information. The vauest
- *   which this method interested in are project.config - config.xml abstraction
- *   - and platformsLocation - to get install destination.
- * @param   {Object}       options  Set of properties for create script.
- *
- * @return  {String[]}     An array or arguments which can be passed to
- *   'bin/create'.
- */
-function getCreateArgs (destinationDir, projectConfig, options) {
-    var platformName = options.platformDetails.platform;
-    var platformVersion = options.platformDetails.version;
-
-    var args = [];
-    args.push(destinationDir); // output
-    args.push(projectConfig.packageName().replace(/[^\w.]/g, '_'));
-    // CB-6992 it is necessary to normalize characters
-    // because node and shell scripts handles unicode symbols differently
-    // We need to normalize the name to NFD form since iOS uses NFD unicode form
-    var name = projectConfig.name();
-    if (platformName === 'ios') {
-        var unorm = require('unorm');
-        name = unorm.nfd(name);
-    }
-    args.push(name);
-
-    if (options.customTemplate) {
-        args.push(options.customTemplate);
-    }
-
-    if (/android|ios/.exec(platformName) &&
-        semver.gt(platformVersion, '3.3.0')) args.push('--cli');
-
-    if (options.link) args.push('--link');
-
-    if (platformName === 'android' && semver.gte(platformVersion, '4.0.0-dev')) {
-        var activityName = projectConfig.android_activityName();
-        if (activityName) {
-            args.push('--activity-name', activityName.replace(/\W/g, ''));
-        }
-    }
-
-    return args;
-}
-
-/**
- * Reconstructs the buildOptions tat will be passed along to platform scripts.
- *   This is an ugly temporary fix. The code spawning or otherwise calling into
- *   platform code should be dealing with this based on the parsed args object.
- *
- * @param   {Object}  options  A build options set, passed to `build` method
- *
- * @return  {String[]}         An array or arguments which can be passed to
- *   `create` build script.
- */
-function getBuildArgs (options) {
-    // if no options passed, empty object will be returned
-    if (!options) return [];
-
-    var downstreamArgs = [];
-    var argNames = [
-        'debug',
-        'release',
-        'device',
-        'emulator',
-        'nobuild',
-        'list'
-    ];
-
-    argNames.forEach(function (flag) {
-        if (options[flag]) {
-            downstreamArgs.push('--' + flag);
-        }
-    });
-
-    if (options.buildConfig) {
-        downstreamArgs.push('--buildConfig=' + options.buildConfig);
-    }
-    if (options.target) {
-        downstreamArgs.push('--target=' + options.target);
-    }
-    if (options.archs) {
-        downstreamArgs.push('--archs=' + options.archs);
-    }
-
-    var unparsedArgs = options.argv || [];
-    return downstreamArgs.concat(unparsedArgs);
-}
-
-/**
- * Removes the specified modules from list of installed modules and updates
- *   platform_json and cordova_plugins.js on disk.
- *
- * @param   {PluginInfo}  plugin  PluginInfo instance for plugin, which modules
- *   needs to be added.
- * @param   {String}  targetDir  The directory, where updated cordova_plugins.js
- *   should be written to.
- */
-PlatformApiPoly.prototype._addModulesInfo = function (plugin, targetDir) {
-    var installedModules = this._platformJson.root.modules || [];
-
-    var installedPaths = installedModules.map(function (installedModule) {
-        return installedModule.file;
-    });
-
-    var modulesToInstall = plugin.getJsModules(this.platform)
-        .filter(function (moduleToInstall) {
-            return installedPaths.indexOf(moduleToInstall.file) === -1;
-        }).map(function (moduleToInstall) {
-            var moduleName = plugin.id + '.' + (moduleToInstall.name || moduleToInstall.src.match(/([^\/]+)\.js/)[1]); // eslint-disable-line no-useless-escape
-            var obj = {
-                file: ['plugins', plugin.id, moduleToInstall.src].join('/'),
-                id: moduleName,
-                pluginId: plugin.id
-            };
-            if (moduleToInstall.clobbers.length > 0) {
-                obj.clobbers = moduleToInstall.clobbers.map(function (o) { return o.target; });
-            }
-            if (moduleToInstall.merges.length > 0) {
-                obj.merges = moduleToInstall.merges.map(function (o) { return o.target; });
-            }
-            if (moduleToInstall.runs) {
-                obj.runs = true;
-            }
-
-            return obj;
-        });
-
-    this._platformJson.root.modules = installedModules.concat(modulesToInstall);
-    if (!this._platformJson.root.plugin_metadata) {
-        this._platformJson.root.plugin_metadata = {};
-    }
-    this._platformJson.root.plugin_metadata[plugin.id] = plugin.version;
-
-    this._writePluginModules(targetDir);
-    this._platformJson.save();
-};
-
-/**
- * Removes the specified modules from list of installed modules and updates
- *   platform_json and cordova_plugins.js on disk.
- *
- * @param   {PluginInfo}  plugin  PluginInfo instance for plugin, which modules
- *   needs to be removed.
- * @param   {String}  targetDir  The directory, where updated cordova_plugins.js
- *   should be written to.
- */
-PlatformApiPoly.prototype._removeModulesInfo = function (plugin, targetDir) {
-    var installedModules = this._platformJson.root.modules || [];
-    var modulesToRemove = plugin.getJsModules(this.platform)
-        .map(function (jsModule) {
-            return ['plugins', plugin.id, jsModule.src].join('/');
-        });
-
-    var updatedModules = installedModules
-        .filter(function (installedModule) {
-            return (modulesToRemove.indexOf(installedModule.file) === -1);
-        });
-
-    this._platformJson.root.modules = updatedModules;
-    if (this._platformJson.root.plugin_metadata) {
-        delete this._platformJson.root.plugin_metadata[plugin.id];
-    }
-
-    this._writePluginModules(targetDir);
-    this._platformJson.save();
-};
-
-/**
- * Fetches all installed modules, generates cordova_plugins contents and writes
- *   it to file.
- *
- * @param   {String}  targetDir  Directory, where write cordova_plugins.js to.
- *   Ususally it is either <platform>/www or <platform>/platform_www
- *   directories.
- */
-PlatformApiPoly.prototype._writePluginModules = function (targetDir) {
-    // Write out moduleObjects as JSON wrapped in a cordova module to cordova_plugins.js
-    var final_contents = 'cordova.define(\'cordova/plugin_list\', function(require, exports, module) {\n';
-    final_contents += 'module.exports = ' + JSON.stringify(this._platformJson.root.modules, null, '    ') + ';\n';
-    final_contents += 'module.exports.metadata = \n';
-    final_contents += '// TOP OF METADATA\n';
-    final_contents += JSON.stringify(this._platformJson.root.plugin_metadata || {}, null, '    ') + '\n';
-    final_contents += '// BOTTOM OF METADATA\n';
-    final_contents += '});'; // Close cordova.define.
-
-    shell.mkdir('-p', targetDir);
-    fs.writeFileSync(path.join(targetDir, 'cordova_plugins.js'), final_contents, 'utf-8');
-};
-
-PlatformApiPoly.prototype._getInstaller = function (type) {
-    var self = this;
-    return function (item, plugin_dir, plugin_id, options, project) {
-        var installer = self._handler[type] || common[type];
-
-        var wwwDest = options.usePlatformWww ?
-            self.getPlatformInfo().locations.platformWww :
-            self._handler.www_dir(self.root);
-
-        var installerArgs = type === 'asset' ? [wwwDest] :
-            type === 'js-module' ? [plugin_id, wwwDest] :
-                [self.root, plugin_id, options, project];
-
-        installer.install.apply(null, [item, plugin_dir].concat(installerArgs));
-    };
-};
-
-PlatformApiPoly.prototype._getUninstaller = function (type) {
-    var self = this;
-    return function (item, plugin_dir, plugin_id, options, project) {
-        var uninstaller = self._handler[type] || common[type];
-
-        var wwwDest = options.usePlatformWww ?
-            self.getPlatformInfo().locations.platformWww :
-            self._handler.www_dir(self.root);
-
-        var uninstallerArgs = (type === 'asset' || type === 'js-module') ? [wwwDest, plugin_id] :
-            [self.root, plugin_id, options, project];
-
-        uninstaller.uninstall.apply(null, [item].concat(uninstallerArgs));
-    };
-};
-
-/**
- * Copies cordova.js itself and cordova-js source into installed/updated
- *   platform's `platform_www` directory.
- *
- * @param   {String}  sourceLib    Path to platform library. Required to acquire
- *   cordova-js sources.
- * @param   {PlatformInfo}  platformInfo  PlatformInfo structure, required for
- *   detecting copied files destination.
- */
-function copyCordovaSrc (sourceLib, platformInfo) {
-    // Copy the cordova.js file to platforms/<platform>/platform_www/
-    // The www dir is nuked on each prepare so we keep cordova.js in platform_www
-    shell.mkdir('-p', platformInfo.locations.platformWww);
-    shell.cp('-f', path.join(platformInfo.locations.www, 'cordova.js'),
-        path.join(platformInfo.locations.platformWww, 'cordova.js'));
-
-    // Copy cordova-js-src directory into platform_www directory.
-    // We need these files to build cordova.js if using browserify method.
-    var cordovaJsSrcPath = path.resolve(sourceLib, platformInfo.locations.cordovaJsSrc);
-
-    // only exists for platforms that have shipped cordova-js-src directory
-    if (fs.existsSync(cordovaJsSrcPath)) {
-        shell.cp('-rf', cordovaJsSrcPath, platformInfo.locations.platformWww);
-    }
-}
-
-/**
- * Gets platform version from 'version' file
- *
- * @param   {String}  platformRoot  Platform location
- * @return  {String|null}           Stringified version of platform or null
- *   if it is not possible to retrieve version
- */
-function getPlatformVersion (platformRoot) {
-    var versionFile = path.join(platformRoot, 'cordova/version');
-
-    if (!fs.existsSync(versionFile)) {
-        return null;
-    }
-
-    var version = shell.cat(versionFile).match(/VERSION\s=\s["'](.*)["'];/m);
-    return version && version[1];
-}

-- 
To stop receiving notification emails like this one, please contact
"commits@cordova.apache.org" <co...@cordova.apache.org>.

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cordova.apache.org
For additional commands, e-mail: commits-help@cordova.apache.org