You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cordova.apache.org by sg...@apache.org on 2014/09/26 23:20:01 UTC

[02/13] git commit: CB-6481 Added unified hooks support for cordova app and plugins

CB-6481 Added unified hooks support for cordova app and plugins

* Hooks can be defined in .cordova/hooks/hook_type, hooks/hook_type directories, config.xml and plugins/.../plugin.xml
* Javascript hooks retrieved from config.xml and plugins/.../plugin.xml will be run via new module loader
* Introduced before_plugin_install, after_plugin_install and before_plugin_uninstall hooks


Project: http://git-wip-us.apache.org/repos/asf/cordova-lib/repo
Commit: http://git-wip-us.apache.org/repos/asf/cordova-lib/commit/a9724517
Tree: http://git-wip-us.apache.org/repos/asf/cordova-lib/tree/a9724517
Diff: http://git-wip-us.apache.org/repos/asf/cordova-lib/diff/a9724517

Branch: refs/heads/master
Commit: a9724517a5f8528308e44c1557c2e8326625ccb3
Parents: 8abfaf1
Author: daserge <da...@yandex.ru>
Authored: Wed Jul 9 16:08:59 2014 +0400
Committer: daserge <da...@yandex.ru>
Committed: Thu Sep 25 18:59:08 2014 +0400

----------------------------------------------------------------------
 cordova-lib/src/PluginInfo.js                   |  20 +++
 cordova-lib/src/configparser/ConfigParser.js    |  24 +++
 cordova-lib/src/cordova/build.js                |  16 +-
 cordova-lib/src/cordova/compile.js              |   1 +
 cordova-lib/src/cordova/emulate.js              |   1 +
 cordova-lib/src/cordova/lazy_load.js            |   1 +
 .../src/cordova/metadata/windows_parser.js      |   1 +
 cordova-lib/src/cordova/metadata/wp8_parser.js  |   1 +
 cordova-lib/src/cordova/platform.js             |   1 +
 cordova-lib/src/cordova/plugin.js               |   1 +
 cordova-lib/src/cordova/prepare.js              |   1 +
 cordova-lib/src/cordova/run.js                  |   1 +
 cordova-lib/src/cordova/serve.js                |   1 +
 cordova-lib/src/hooks/Context.js                |  52 ++++++
 cordova-lib/src/hooks/Hooker.js                 |  94 +++++++++++
 cordova-lib/src/hooks/ScriptsFinder.js          | 158 ++++++++++++++++++
 cordova-lib/src/hooks/ScriptsRunner.js          | 162 +++++++++++++++++++
 cordova-lib/src/plugman/install.js              |  26 ++-
 cordova-lib/src/plugman/uninstall.js            |  27 +++-
 19 files changed, 578 insertions(+), 11 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/PluginInfo.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/PluginInfo.js b/cordova-lib/src/PluginInfo.js
index 94d6fb1..98e4fec 100644
--- a/cordova-lib/src/PluginInfo.js
+++ b/cordova-lib/src/PluginInfo.js
@@ -200,6 +200,26 @@ function PluginInfo(dirname) {
         var libFiles = _getTagsInPlatform(self._et, 'lib-file', platform, cloneAttribs);
         return libFiles;
     }
+    
+    // <script>
+    // Example:
+    // <script type="before_build" src="scripts/beforeBuild.js" />
+    self.getHookScripts = getHookScripts;
+    function getHookScripts(hook, platforms) {
+        var scriptElements =  self._et.findall('./script');
+
+        if(platforms) {
+            platforms.forEach(function (platform) {
+                scriptElements = scriptElements.concat(self._et.findall('./platform[@name="' + platform + '"]/script'));
+            });
+        }
+
+        function filterScriptByHookType(el) {
+            return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook;
+        }
+
+        return scriptElements.filter(filterScriptByHookType);
+    }
     ///// End of PluginInfo methods /////
 
 

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/configparser/ConfigParser.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/configparser/ConfigParser.js b/cordova-lib/src/configparser/ConfigParser.js
index da6376e..e02c4e8 100644
--- a/cordova-lib/src/configparser/ConfigParser.js
+++ b/cordova-lib/src/configparser/ConfigParser.js
@@ -228,6 +228,29 @@ ConfigParser.prototype = {
     getSplashScreens: function(platform) {
         return this.getStaticResources(platform, 'splash');
     },
+    
+    /**
+     * Returns all hook scripts for the hook type specified.
+     * @param  {String} hook     The hook type.
+     * @param {Array}  platforms Platforms to look for scripts into (root scripts will be included as well).
+     * @return {Array}               Script elements.
+     */
+    getHookScripts: function(hook, platforms) {
+        var self = this;
+        var scriptElements = self.doc.findall('./script');
+
+        if(platforms) {
+            platforms.forEach(function (platform) {
+                scriptElements = scriptElements.concat(self.doc.findall('./platform[@name="' + platform + '"]/script'));
+            });
+        }
+
+        function filterScriptByHookType(el) {
+            return el.attrib.src && el.attrib.type && el.attrib.type.toLowerCase() === hook;
+        }
+
+        return scriptElements.filter(filterScriptByHookType);
+    },
 
     /**
      * Returns a list of features (IDs)
@@ -316,6 +339,7 @@ ConfigParser.prototype = {
         }
     },
 
+
     /**
      *This does not check for duplicate feature entries
      */

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/build.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/build.js b/cordova-lib/src/cordova/build.js
index 384d9e2..1469c6b 100644
--- a/cordova-lib/src/cordova/build.js
+++ b/cordova-lib/src/cordova/build.js
@@ -21,12 +21,12 @@
           indent:4, unused:vars, latedef:nofunc
 */
 
-var cordova_util      = require('./util'),
-    hooker            = require('./hooker');
+var cordovaUtil      = require('./util'),
+    Hooker           = require('../hooks/Hooker');
 
 // Returns a promise.
 module.exports = function build(options) {
-    var projectRoot = cordova_util.cdProjectRoot();
+    var projectRoot = cordovaUtil.cdProjectRoot();
 
     if (!options) {
         options = {
@@ -36,16 +36,18 @@ module.exports = function build(options) {
         };
     }
 
-    options = cordova_util.preProcessOptions(options);
+    options = cordovaUtil.preProcessOptions(options);
+
+    var hookOptions = { projectRoot: projectRoot, cordova: options };
 
     // fire build hooks
-    var hooks = new hooker(projectRoot);
-    return hooks.fire('before_build', options)
+    var hooker = new Hooker(projectRoot);
+    return hooker.fire('before_build', hookOptions)
     .then(function() {
         return require('./cordova').raw.prepare(options);
     }).then(function() {
         return require('./cordova').raw.compile(options);
     }).then(function() {
-        return hooks.fire('after_build', options);
+        return hooker.fire('after_build', hookOptions);
     });
 };

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/compile.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/compile.js b/cordova-lib/src/cordova/compile.js
index 814d3eb..558b0c0 100644
--- a/cordova-lib/src/cordova/compile.js
+++ b/cordova-lib/src/cordova/compile.js
@@ -31,6 +31,7 @@ module.exports = function compile(options) {
     var projectRoot = cordova_util.cdProjectRoot();
     options = cordova_util.preProcessOptions(options);
 
+    // TODO: Replace with unified Hooker
     var hooks = new hooker(projectRoot);
     var ret = hooks.fire('before_compile', options);
     options.platforms.forEach(function(platform) {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/emulate.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/emulate.js b/cordova-lib/src/cordova/emulate.js
index 2f23197..651a434 100644
--- a/cordova-lib/src/cordova/emulate.js
+++ b/cordova-lib/src/cordova/emulate.js
@@ -32,6 +32,7 @@ module.exports = function emulate(options) {
     var projectRoot = cordova_util.cdProjectRoot();
     options = cordova_util.preProcessOptions(options);
 
+    // TODO: Replace with unified Hooker
     var hooks = new hooker(projectRoot);
     return hooks.fire('before_emulate', options)
     .then(function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/lazy_load.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/lazy_load.js b/cordova-lib/src/cordova/lazy_load.js
index 8ea791b..943adc8 100644
--- a/cordova-lib/src/cordova/lazy_load.js
+++ b/cordova-lib/src/cordova/lazy_load.js
@@ -186,6 +186,7 @@ function custom(platforms, platform) {
         lib_dir = path.join(url, subdir);
         return Q(lib_dir);
     }
+    // TODO: Replace with unified Hooker
     return hooker.fire('before_library_download', {
         platform:platform,
         url:url,

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/metadata/windows_parser.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/metadata/windows_parser.js b/cordova-lib/src/cordova/metadata/windows_parser.js
index 353d68b..3e4a632 100644
--- a/cordova-lib/src/cordova/metadata/windows_parser.js
+++ b/cordova-lib/src/cordova/metadata/windows_parser.js
@@ -244,6 +244,7 @@ module.exports.prototype = {
         var that = this;
         var projectRoot = util.isCordova(process.cwd());
 
+        // TODO: Replace with unified Hooker
         var hooks = new hooker(projectRoot);
         return hooks.fire('pre_package', { wwwPath:this.www_dir(), platforms: [this.isOldProjectTemplate ? 'windows8' : 'windows'] })
         .then(function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/metadata/wp8_parser.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/metadata/wp8_parser.js b/cordova-lib/src/cordova/metadata/wp8_parser.js
index 4d5b332..9798a6e 100644
--- a/cordova-lib/src/cordova/metadata/wp8_parser.js
+++ b/cordova-lib/src/cordova/metadata/wp8_parser.js
@@ -209,6 +209,7 @@ module.exports.prototype = {
         var that = this;
         var projectRoot = util.isCordova(process.cwd());
 
+        // TODO: Replace with unified Hooker
         var hooks = new hooker(projectRoot);
         return hooks.fire('pre_package', { wwwPath:this.www_dir(), platforms: ['wp8']  })
         .then(function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/platform.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/platform.js b/cordova-lib/src/cordova/platform.js
index 487803e..7becd9e 100644
--- a/cordova-lib/src/cordova/platform.js
+++ b/cordova-lib/src/cordova/platform.js
@@ -203,6 +203,7 @@ function check(hooks, projectRoot) {
     var result = Q.defer();
     cordova.raw.create(scratch)
     .then(function () {
+        // TODO: Replace with unified Hooker
         var h = new hooker(scratch);
         // Acquire the version number of each platform we have installed, and output that too.
         Q.all(platforms_on_fs.map(function(p) {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/plugin.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/plugin.js b/cordova-lib/src/cordova/plugin.js
index 5d4562f..f608a38 100644
--- a/cordova-lib/src/cordova/plugin.js
+++ b/cordova-lib/src/cordova/plugin.js
@@ -59,6 +59,7 @@ module.exports = function plugin(command, targets, opts) {
     opts.options = opts.options || [];
     opts.plugins = [];
 
+    // TODO: Replace with unified Hooker
     var hooks = new hooker(projectRoot);
     var platformList = cordova_util.listPlatforms(projectRoot);
 

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/prepare.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/prepare.js b/cordova-lib/src/cordova/prepare.js
index 8b4b545..1906d78 100644
--- a/cordova-lib/src/cordova/prepare.js
+++ b/cordova-lib/src/cordova/prepare.js
@@ -56,6 +56,7 @@ function prepare(options) {
     });
     options.paths = paths;
 
+    // TODO: Replace with unified Hooker
     var hooks = new hooker(projectRoot);
     return hooks.fire('before_prepare', options)
     .then(function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/run.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/run.js b/cordova-lib/src/cordova/run.js
index 8ccdb38..abde56f 100644
--- a/cordova-lib/src/cordova/run.js
+++ b/cordova-lib/src/cordova/run.js
@@ -32,6 +32,7 @@ module.exports = function run(options) {
     var projectRoot = cordova_util.cdProjectRoot();
     options = cordova_util.preProcessOptions(options);
 
+    // TODO: Replace with unified Hooker
     var hooks = new hooker(projectRoot);
     return hooks.fire('before_run', options)
     .then(function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/cordova/serve.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/cordova/serve.js b/cordova-lib/src/cordova/serve.js
index 26c114b..2d88d7b 100644
--- a/cordova-lib/src/cordova/serve.js
+++ b/cordova-lib/src/cordova/serve.js
@@ -247,6 +247,7 @@ module.exports = function server(port) {
     var projectRoot = cordova_util.cdProjectRoot();
     port = +port || 8000;
 
+    // TODO: Replace with unified Hooker
     var hooks = new hooker(projectRoot);
     hooks.fire('before_serve')
     .then(function() {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/hooks/Context.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/hooks/Context.js b/cordova-lib/src/hooks/Context.js
new file mode 100644
index 0000000..936c9c1
--- /dev/null
+++ b/cordova-lib/src/hooks/Context.js
@@ -0,0 +1,52 @@
+/**
+ 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'),
+    fs = require('fs'),
+    path = require('path'),
+    os = require('os'),
+    events = require('../events');
+
+/**
+ * Creates hook script context
+ * @constructor
+ * @param {String} hook The hook type
+ * @param {Object} opts Hook options
+ * @returns {Object} */
+function Context(hook, opts) {
+    this.hook = hook;
+    this.opts = opts;
+    this.cmdLine =  process.argv.join(' ');
+    this.commonModules = {
+        Q: Q, fs: fs, path: path, os: os,
+        events: events, plugin: require('../cordova/plugin'),
+        util: require('util'),
+        cordovaUtil: require('../cordova/util')
+    };
+}
+
+/**
+ * Returns a required module
+ * @param {String} path Module path
+ * @returns {Object} */
+Context.prototype.requireCordovaModule = function (path) {
+    return require(path);
+};
+
+module.exports = Context;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/hooks/Hooker.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/hooks/Hooker.js b/cordova-lib/src/hooks/Hooker.js
new file mode 100644
index 0000000..9951e20
--- /dev/null
+++ b/cordova-lib/src/hooks/Hooker.js
@@ -0,0 +1,94 @@
+/**
+ 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 util  = require('../cordova/util'),
+    events = require('../events'),
+    Q = require('q'),
+    plugin  = require('../cordova/plugin'),
+    ScriptsFinder = require('./ScriptsFinder'),
+    ScriptsRunner = require('./ScriptsRunner'),
+    Context = require('./Context'),
+    CordovaError = require('../CordovaError');
+
+/**
+ * Tries to create a hooker for passed project root.
+ * @constructor
+ */
+function Hooker(projectRoot) {
+    if (!util.isCordova(projectRoot)) {
+        throw new CordovaError('Not a Cordova project ("' + projectRoot + '"), can\'t use hooks.');
+    }
+}
+
+/**
+ * Fires all event handlers and scripts for a passed hook type.
+ * Returns a promise.
+ */
+Hooker.prototype.fire = function fire(hook, opts) {
+    // args check
+    if (!hook) {
+        throw new CordovaError('hook type is not specified');
+    }
+    // execute hook event listeners first
+    return setPluginsProperty(opts).then(function(){
+        setCordovaVersionProperty(opts);
+
+        var handlers = events.listeners(hook);
+        return executeHandlersSerially(handlers, opts);
+    // then execute hook script files
+    }).then(function() {
+        var scripts = ScriptsFinder.getHookScripts(hook, opts);
+        var context = new Context(hook, opts);
+        return ScriptsRunner.runScriptsSerially(scripts, context);
+    });
+};
+
+/**
+ * Sets hook options cordova.plugins list if it was not set.
+ * Returns a promise.
+ */
+function setPluginsProperty(opts) {
+    if(!opts.cordova.plugins) {
+        return plugin().then(function(plugins) {
+            opts.cordova.plugins = plugins;
+            return Q();
+        });
+    }
+    return Q();
+}
+
+/**
+ * Sets hook options cordova.version if it was not set.
+ */
+function setCordovaVersionProperty(opts) {
+    opts.cordova.version = opts.cordova.version || require('../../package').version;
+}
+
+// Returns a promise.
+function executeHandlersSerially(handlers, opts) {
+    if (handlers.length) {
+        // Chain the handlers in series.
+        return handlers.reduce(function(soFar, f) {
+            return soFar.then(function() { return f(opts); });
+        }, Q());
+    } else {
+        return Q(); // Nothing to do.
+    }
+}
+
+module.exports = Hooker;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/hooks/ScriptsFinder.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/hooks/ScriptsFinder.js b/cordova-lib/src/hooks/ScriptsFinder.js
new file mode 100644
index 0000000..5fb9ed2
--- /dev/null
+++ b/cordova-lib/src/hooks/ScriptsFinder.js
@@ -0,0 +1,158 @@
+/**
+ 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 path = require('path'),
+    fs = require('fs'),
+    cordovaUtil = require('../cordova/util'),
+    events = require('../events'),
+    Q = require('q'),
+    plugin  = require('../cordova/plugin'),
+    PluginInfo = require('../PluginInfo'),
+    ConfigParser = require('../configparser/ConfigParser'),
+    CordovaError = require('../CordovaError'),
+    Context = require('./Context');
+
+/**
+ * Implements logic to retrieve hook script files defined in special folders and configuration
+ * files: config.xml, hooks/hook_type, plugins/../plugin.xml, etc
+ */
+module.exports  = {
+    /**
+     * Returns all script files for the hook type specified.
+     */
+    getHookScripts: function(hook, opts) {
+        // args check
+        if (!hook) {
+            throw new CordovaError('hook type is not specified');
+        }
+        return getApplicationHookScripts(hook, opts)
+            .concat(getPluginsHookScripts(hook, opts));
+    }
+};
+
+/**
+ * Returns script files defined on application level.
+ * They are stored in .cordova/hooks folders and in config.xml.
+ */
+function getApplicationHookScripts(hook, opts) {
+    // args check
+    if (!hook) {
+        throw new CordovaError('hook type is not specified');
+    }
+    return getApplicationHookScriptsFromDir(path.join(opts.projectRoot, '.cordova', 'hooks', hook))
+        .concat(getApplicationHookScriptsFromDir(path.join(opts.projectRoot, 'hooks', hook)))
+        .concat(getScriptsFromConfigXml(hook, opts));
+}
+
+/**
+ * Returns script files defined by plugin developers as part of plugin.xml.
+ */
+function getPluginsHookScripts(hook, opts) {
+    // args check
+    if (!hook) {
+        throw new CordovaError('hook type is not specified');
+    }
+
+    // In case before_plugin_install, after_plugin_install, before_plugin_uninstall hooks we receive opts.plugin and
+    // retrieve scripts exclusive for this plugin.
+    if(opts.plugin) {
+        events.emit('debug', 'Executing "' + hook + '"  hook for "' + opts.plugin.id + '" on ' + opts.plugin.platform + '.');
+
+        return getPluginScriptFiles(opts.plugin, hook, [ opts.plugin.platform ]);
+    }
+
+    events.emit('debug', 'Executing "' + hook + '"  hook for all plugins.');
+    return getAllPluginsHookScriptFiles(hook, opts);
+}
+
+/**
+ * Gets application level hooks from the directrory specified.
+ */
+function getApplicationHookScriptsFromDir(dir) {
+    if (!(fs.existsSync(dir))) {
+        return [];
+    }
+
+    var compareNumbers = function(a, b) {
+        // TODO SG looks very complex, do we really need this?
+        return isNaN (parseInt(a, 10)) ? a.toLowerCase().localeCompare(b.toLowerCase ? b.toLowerCase(): b)
+            : parseInt(a, 10) > parseInt(b, 10) ? 1 : parseInt(a, 10) < parseInt(b, 10) ? -1 : 0;
+    };
+
+    var scripts = fs.readdirSync(dir).sort(compareNumbers).filter(function(s) {
+        return s[0] != '.';
+    });
+    return scripts.map(function (scriptPath) {
+        // for old style hook files we don't use module loader for backward compatibility
+        return { path: scriptPath, fullPath: path.join(dir, scriptPath), useModuleLoader: false };
+    });
+}
+
+/**
+ * Gets all scripts defined in config.xml with the specified type and platforms.
+ */
+function getScriptsFromConfigXml(hook, opts) {
+    var configPath = cordovaUtil.projectConfig(opts.projectRoot);
+    var configXml = new ConfigParser(configPath);
+
+    return configXml.getHookScripts(hook, opts.cordova.platforms).map(function(scriptElement) {
+        return {
+            path: scriptElement.attrib.src,
+            fullPath: path.join(opts.projectRoot, scriptElement.attrib.src)
+        };
+    });
+}
+
+/**
+ * Gets hook scripts defined by the plugin.
+ */
+function getPluginScriptFiles(plugin, hook, platforms) {
+    var scriptElements = plugin.pluginInfo.getHookScripts(hook, platforms);
+
+    return scriptElements.map(function(scriptElement) {
+        return {
+            path: scriptElement.attrib.src,
+            fullPath: path.join(plugin.dir, scriptElement.attrib.src),
+            plugin: plugin
+        };
+    });
+}
+
+/**
+ * Gets hook scripts defined by all plugins.
+ */
+function getAllPluginsHookScriptFiles(hook, opts) {
+    var scripts = [];
+    var pluginDir;
+    var pluginInfo;
+    var currentPluginOptions;
+
+    opts.cordova.plugins.forEach(function(pluginId) {
+        pluginDir = path.join(opts.projectRoot, 'plugins', pluginId);
+
+        currentPluginOptions = {
+            id: pluginId,
+            pluginInfo: new PluginInfo.PluginInfo(pluginDir),
+            dir: pluginDir
+        };
+        
+        scripts = scripts.concat(getPluginScriptFiles(currentPluginOptions, hook, opts.cordova.platforms));
+    });
+    return scripts;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/hooks/ScriptsRunner.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/hooks/ScriptsRunner.js b/cordova-lib/src/hooks/ScriptsRunner.js
new file mode 100644
index 0000000..c7ff70d
--- /dev/null
+++ b/cordova-lib/src/hooks/ScriptsRunner.js
@@ -0,0 +1,162 @@
+/**
+ 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'),
+    fs = require('fs'),
+    os = require('os'),
+    path = require('path'),
+    superspawn = require('../cordova/superspawn'),
+    CordovaError = require('../CordovaError'),
+    Context = require('./Context');
+
+var isWindows = os.platform().slice(0, 3) === 'win';
+
+module.exports = {
+    /**
+     * Serially fires scripts either via Q(require(pathToScript)(context)) or via child_process.spawn.
+     * Returns promise.
+     */
+    runScriptsSerially: function(scripts, context) {
+        var deferral = new Q.defer();
+
+        function executePendingScript() {
+            try {
+                if (scripts.length === 0) {
+                    deferral.resolve();
+                    return;
+                }
+                var nextScript = scripts[0];
+                scripts.shift();
+
+                runScript(nextScript, context).then(executePendingScript, function(err){
+                    deferral.reject(err);
+                });
+            } catch (ex) {
+                deferral.reject(ex);
+            }
+        }
+        executePendingScript();
+        return deferral.promise;
+    }
+};
+
+/**
+ * Async runs single script file.
+ */
+function runScript(script, context) {
+    if (typeof script.useModuleLoader == 'undefined') {
+        // if it is not explicitly defined whether we should use modeule loader or not
+        // we assume we should use module loader for .js files
+        script.useModuleLoader = path.extname(script.path).toLowerCase() == '.js';
+    }
+    if(script.useModuleLoader) {
+        return runScriptViaModuleLoader(script, context);
+    } else {
+        return runScriptViaChildProcessSpawn(script, context);
+    }
+}
+
+/**
+ * Runs script using require.
+ * Returns a promise. */
+function runScriptViaModuleLoader(script, context) {
+    if(!fs.existsSync(script.fullPath)) {
+        events.emit('warn', "Script file does't exist and will be skipped: " + script.fullPath);
+        return Q();
+    }
+    var scriptFn = require(script.fullPath);
+    context.scriptLocation = script.fullPath;
+    context.opts.plugin = script.plugin;
+
+    // We can't run script if it is a plain Node script - it will run its commands when we require it.
+    // This is not a desired case as we want to pass context, but added for compatibility.
+    if (scriptFn instanceof Function) {
+        // If hook is async it can return promise instance and we will handle it.
+        return Q(scriptFn(context));
+    } else {
+        return Q();
+    }
+}
+
+/**
+ * Runs script using child_process spawn method.
+ * Returns a promise. */
+function runScriptViaChildProcessSpawn(script, context) {
+    var opts = context.opts;
+    var command = script.fullPath;
+    var args = [opts.projectRoot];
+    if (isWindows) {
+        // TODO: Make shebang sniffing a setting (not everyone will want this).
+        var interpreter = extractSheBangInterpreter(script.fullPath);
+        // we have shebang, so try to run this script using correct interpreter
+        if (interpreter) {
+            args.unshift(command);
+            command = interpreter;
+        }
+    }
+
+    var execOpts = {cwd: opts.projectRoot, printCommand: true, stdio: 'inherit'};
+    execOpts.env = {};
+    execOpts.env.CORDOVA_VERSION = require('../../package').version;
+    execOpts.env.CORDOVA_PLATFORMS = opts.cordova.platforms ? opts.cordova.platforms.join() : '';
+    execOpts.env.CORDOVA_PLUGINS = opts.cordova.plugins ? opts.cordova.plugins.join() : '';
+    execOpts.env.CORDOVA_HOOK = script.fullPath;
+    execOpts.env.CORDOVA_CMDLINE = process.argv.join(' ');
+
+    return superspawn.spawn(command, args, execOpts)
+        .catch(function(err) {
+            // Don't treat non-executable files as errors. They could be READMEs, or Windows-only scripts.
+            if (!isWindows && err.code == 'EACCES') {
+                events.emit('verbose', 'skipped non-executable file: ' + script.fullPath);
+            } else {
+                throw new CordovaError('Hook failed with error code ' + err.code + ': ' + script.fullPath);
+            }
+        });
+}
+
+/**
+ * Extracts shebang interpreter from script' source. */
+function extractSheBangInterpreter(fullpath) {
+    var fileChunk;
+    var octetsRead;
+    var fileData;
+    var hookFd = fs.openSync(fullpath, "r");
+    try {
+        // this is a modern cluster size. no need to read less
+        fileData = new Buffer(4096);
+        octetsRead = fs.readSync(hookFd, fileData, 0, 4096, 0);
+        fileChunk = fileData.toString();
+    } finally {
+        fs.closeSync(hookFd);
+    }
+
+    var hookCmd, shMatch;
+    // Filter out /usr/bin/env so that "/usr/bin/env node" works like "node".
+    var shebangMatch = fileChunk.match(/^#!(?:\/usr\/bin\/env )?([^\r\n]+)/m);
+    if (octetsRead == 4096 && !fileChunk.match(/[\r\n]/))
+        events.emit('warn', 'shebang is too long for "' + fullpath + '"');
+    if (shebangMatch)
+        hookCmd = shebangMatch[1];
+    // Likewise, make /usr/bin/bash work like "bash".
+    if (hookCmd)
+        shMatch = hookCmd.match(/bin\/((?:ba)?sh)$/);
+    if (shMatch)
+        hookCmd = shMatch[1];
+    return hookCmd;
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/plugman/install.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/install.js b/cordova-lib/src/plugman/install.js
index 045afb7..4823b68 100644
--- a/cordova-lib/src/plugman/install.js
+++ b/cordova-lib/src/plugman/install.js
@@ -38,7 +38,9 @@ var path = require('path'),
     shell   = require('shelljs'),
     events = require('../events'),
     plugman = require('./plugman'),
-    isWindows = (os.platform().substr(0,3) === 'win');
+    Hooker = require('../hooks/Hooker'),
+    isWindows = (os.platform().substr(0,3) === 'win'),
+    cordovaUtil = require('../cordova/util');
 
 /* INSTALL FLOW
    ------------
@@ -319,7 +321,27 @@ function runInstall(actions, platform, project_dir, plugin_dir, plugins_dir, opt
                 copyPlugin(plugin_dir, plugins_dir, options.link);
             }
 
-            return handleInstall(actions, pluginInfo, platform, project_dir, plugins_dir, install_plugin_dir, filtered_variables, options);
+            var projectRoot = cordovaUtil.isCordova();
+
+            // using unified hooker
+            var hookOptions = {
+                projectRoot: projectRoot,
+                cordova: { platforms: [ platform ], plugins: options.plugins },
+                plugin: {
+                    id: pluginInfo.id,
+                    pluginInfo: pluginInfo,
+                    platform: install.platform,
+                    dir: install.top_plugin_dir
+                }
+            };
+
+            var hooker = new Hooker(projectRoot);
+
+            hooker.fire('before_plugin_install', hookOptions).then(function() {
+                return handleInstall(actions, pluginInfo, platform, project_dir, plugins_dir, install_plugin_dir, filtered_variables, options);
+            }).then(function(){
+                return hooker.fire('after_plugin_install', hookOptions);
+            });
         }
     ).fail(
         function (error) {

http://git-wip-us.apache.org/repos/asf/cordova-lib/blob/a9724517/cordova-lib/src/plugman/uninstall.js
----------------------------------------------------------------------
diff --git a/cordova-lib/src/plugman/uninstall.js b/cordova-lib/src/plugman/uninstall.js
index 06bcb85..0a48fa2 100644
--- a/cordova-lib/src/plugman/uninstall.js
+++ b/cordova-lib/src/plugman/uninstall.js
@@ -32,11 +32,13 @@ var path = require('path'),
     CordovaError  = require('../CordovaError'),
     underscore = require('underscore'),
     Q = require('q'),
-    underscore = require('underscore'),
     events = require('../events'),
     platform_modules = require('./platforms'),
     plugman = require('./plugman'),
-    promiseutil = require('../util/promise-util');
+    promiseutil = require('../util/promise-util'),
+    Hooker = require('../hooks/Hooker'),
+    PluginInfo = require('../PluginInfo'),
+    cordovaUtil      = require('../cordova/util');
 
 // possible options: cli_variables, www_dir
 // Returns a promise.
@@ -237,8 +239,29 @@ function runUninstallPlatform(actions, platform, project_dir, plugin_dir, plugin
         promise = Q();
     }
 
+    var projectRoot = cordovaUtil.isCordova();
+    var pluginInfo = new PluginInfo.PluginInfo(plugin_dir);
+
+    // using unified hooker
+    var hookerOptions = {
+        projectRoot: projectRoot,
+        cordova: { platforms: [ platform ], plugins: options.plugins },
+        plugin: {
+            id: pluginInfo.id,
+            pluginInfo: pluginInfo,
+            platform: platform,
+            dir: plugin_dir
+        }
+    };
+
+    var hooker = new Hooker(projectRoot);
+
     return promise.then(function() {
+        return hooker.fire('before_plugin_uninstall', hookerOptions);
+    }).then(function() {
         return handleUninstall(actions, platform, plugin_id, plugin_et, project_dir, options.www_dir, plugins_dir, plugin_dir, options.is_top_level, options);
+    }).then(function(){
+        return hooker.fire('after_plugin_uninstall', hookerOptions);
     });
 }