Let NodeJS application update itself using NPM

javascript, node.js, npm, spawn

Solution

So I finally fixed this issue. If someone is interested how I did it, this is how:

I built a function using the NPM api to check if the current version is up to date:

 exports.checkVersion = function(req, res) {
    npm.load([], function (err, npm) {
        npm.commands.search(["mediacenterjs"], function(err, data){
            if (err){
                console.log('NPM search error ' + err);
                return;
            } else{
                var currentInfo = checkCurrentVersion();
                for (var key in data) {
                    var obj = data[key];
                    if(obj.name === 'mediacenterjs' && obj.version > currentInfo.version){
                        var message = 'New version '+obj.version+' Available';
                        res.json(message);
                    }
                }
            }
        });
    });
}

var checkCurrentVersion = function(){
    var info = {};
    var data = fs.readFileSync('./package.json' , 'utf8');

    try{
        info = JSON.parse(data);
    }catch(e){
        console.log('JSON Parse Error', e);
    }

    return info;
};

If the version is not up to date I initiate a download (in my case the github master repo url) using node-wget:

var wget = require('wget');
var download = wget.download(src, output, options);
download.on('error', function(err) {
    console.log('Error', err);
    callback(output);
});
download.on('end', function(output) {
    console.log(output);
    callback(output);
});
download.on('progress', function(progress) {
    console.log(progress * 100);
});

The callback kicks off the unzip function bases on @John Munsch 'Self help' script but I added a check to see if there was a previous unzip attempt and if so, I delete the folder:

if(fs.existsSync(dir) === false){
    fs.mkdirSync(dir);
} else {
    rimraf(dir, function (err) { 
        if(err) {
            console.log('Error removing temp folder', err);
        } else {
            fileHandler.downloadFile(src, output, options, function(output){
                console.log('Done', output);
                unzip(req, res, output, dir);
            });
        }
    });
}
console.log("Unzipping New Version...");
var AdmZip = require("adm-zip");
var zip = new AdmZip(output);
zip.extractAllTo(dir, true);

fs.openSync('./configuration/update.js', 'w');

The openSync function kicks off my 'NodeMon' based file (https://github.com/jansmolders86/mediacenterjs/blob/master/server.js) which kills the server because it is listing for changes to that specific file. Finally it restart and starts the following functions:

function installUpdate(output, dir){
    console.log('Installing update...');
    var fsExtra = require("fs.extra");
    fsExtra.copy(dir+'/mediacenterjs-master', './', function (err) {
        if (err) {
            console.error('Error', err);
        } else {
            console.log("success!");
            cleanUp(output, dir);
        }
    });
}

function cleanUp(output, dir) {
    console.log('Cleanup...');
    var rimraf = require('rimraf');
    rimraf(dir, function (e) {
        if(e) {
            console.log('Error removing module', e .red);
        }
    });

    if(fs.existsSync(output) === true){
        fs.unlinkSync(output);
        console.log('Done, restarting server...')
        server.start();
    }
}

Thanks to everyone that helped me out!

Problem

Hej There, I'm trying to add some non-conventional functionality to my NodeJS application but I'm having some trouble. What I'm trying to do is the following: I want to update my server code from the client. (An auto-update functionality if you will.) My first attempt was to utilize the NPM API and run: ``` npm.commands.install([package], function(err, data) ``` But of course this results in an error telling me NPM can not install while the server is running. My second attempt was spawning NPM update using the following code: ``` spawnProcess('npm', ['update'], { cwd: projectPath }, done); ``` The spawnProcess function is a generic spawn function: ``` var projectPath = path.resolve(process.cwd()); var spawnProcess = function(command, args, options, callback) { var spawn = require('child_process').spawn; var process = spawn(command, args, options); var err = false; process.stdout.on('data', function(data) { console.log('stdout', data.toString()); }); process.stderr.on('data', function(data) { err = true; console.log('stderr', data.toString()); }); if (typeof callback === 'function') { process.on('exit', function() { if (!err) { return callback(); } }); } }; ``` But this gives me a stderr followed by a 'CreateProcessW: can not find file' error. I don't quite know what I'm doing wrong. If all else fails I thought it might be possible to write a shellscript killing Node, updating the application and then rebooting it. Something like: ``` kill -9 45728 npm update node server ``` But I don't know if this is a plausible solution and how I would go about executing it from my node server. I'd rather have the spawn function working of course. Any help is welcome. Thanks in advance!

Original source