How should I handle node module dependencies in an application with a plugin based architecture on Heroku?

I'm working on a node.js based framework/server. This framework starts an express server and automatically loads all plugins from a local directory. The framework and individual plugins each have their own git repository.

The framework is here: https://github.com/Appsecute/appsecute-connector-multi (see server.js for plugin loading).

An example plugin is here: https://github.com/Appsecute/appsecute-connector-multi-heroku (see connector.js for the plugin definition/bootstrap file).

This all works fine locally. Now it's time to get it deployed to Heroku. My plan was to write a small script that cloned the framework + specified plugins and built a folder structure that was ready to be deployed.

I have written this script here: https://github.com/Appsecute/appsecute-connector-multi-deployment

Now, on to my question, is it possible to instruct Heroku to do an 'npm install' in both the root of the application (the framework), as well as specific subdirectories (each of the plugins)?

I know node will look inside the nearest node_modules folder relative to the script that is loading the modules, so if a plugin tries to load a module then node will first look in app_root/plugins/plugin_name/node_modules - which is exactly what I want, I just need Heroku to make sure the modules are there.

I was initially planning on letting my build script go to the various directories and run an npm install, but then I remembered Heroku will do its own npm install.

My other option is to write some code that inspects all the dependencies in each plugins package.json file, then do a union on all the dependencies and write them out in to the package.json in to the root of the app. This way when Heroku does its npm install then all dependencies will be installed. This sounds brittle and will probably fail when different plugins specify a different version of the same module.

And all this finally begs the question, am I just doing this wrong? Should I be looking at a different architecture/repo structure etc.?

After spending a while pursuing the script option I realised I should have been leveraging the Node module system and NPM to take care of all this for me.

I ended up deleting the script and instead of the framework loading plugins from a plugin directory it now expects to be passed an array of plugins to load when it is instantiated, which has ended up making the whole thing quite trivial.

Framework/Server

/**
 * Configures the Node.js Server.
 * @type {*}
 */

var _ = require('underscore'),
    express = require('express'),
    app = express(),
    port = process.env.PORT || 3003;

module.exports = {

    /**
     * Starts the connector multi server.
     * @param {Object} options A hash of options to configure the multi server.
     * @param {Array} options.connectors An array of multi server connectors to load.
     */
    start: function (options) {

        // Sanitize the options
        options = options || {connectors: []};

        // Configure express
        console.log('Configuring express...');
        require('./config/config.js')(app, express);
        require('./config/http-error.js');

        // Load controllers
        console.log('Loading controllers...');
        require('./controllers/appsecute.js')(app);
        require('./controllers/oauth2-client.js')(app);

        // Load connectors
        console.log('Loading connectors...');
        _.each(options.connectors, function (connector) {
            connector(app);
        });

        // Connectors have been loaded, start the multi server
        console.log('Finished loading connectors.');
        console.log('Starting multi connector server...');

        var express_app = app.listen(port);
        console.log("Multi connector server listening on port %d", port);
        return express_app;
    }
};

Deployment Repo that pulls the framework + plugins together for deployment

/**
 * A basic wrapper around the multi server that includes specific connector implementations ready for deployment.
 *
 * To add a new connector to the deployment:
 *  1. Add it as a dependency in package.json
 *  2. Pass it to the multi connector start() call.
 */
require('appsecute-connector-multi').server.start({
    connectors: [
        require('appsecute-connector-multi-heroku'),
        require('appsecute-connector-multi-tender'),
        require('appsecute-connector-multi-circleci'),
        require('appsecute-connector-multi-zendesk'),
        require('appsecute-connector-multi-github'),
        require('appsecute-connector-multi-travisci')
    ]
});

The advantage of doing it this way, asides from simplicity, is that because the framework and all the individual plugins are specified as dependencies in package.json in the deployment project NPM takes care of pulling down all co-dependencies, just as you would expect :)