Search code examples
javascriptnode.jsvimjake

How do I make JakeJS watch work reliably with Vim?


I'm using a Jakefile to help me update Wordpress pages from the command line. I'm using Jake's watch task to re-build ever time I edit a file. When I edit a file with Vim, after the first successful build, Jake fails with following error :

WatchTask started for: default
cp home.html dist/home.html
exec wp --path=../wordpress post list --post_type=page --format=json --fields=ID,post_name { silent: true }
exec wp --path=../wordpress post update 2 dist/home.html --post_type=page
Success: Updated post 2.
jake aborted.
Error: File-task home.html has no existing file, and no action to create one.
    at FileBase.isNeeded (/usr/local/lib/node_modules/jake/lib/task/file_task.js:50:17)
    at TaskBase.run (/usr/local/lib/node_modules/jake/lib/task/task.js:256:26)
(See full trace by running task with --trace)

I've tried using a sleep function in the rule to delay the rebuild. I tried this because Vim, when saving a file, write the contents to a new temp file and then renames the new temp file to the original file name. I think the build fails because it's trying to build before the file is fully renamed. Using the sleep doesn't work reliably, it may work once or twice but then it fails the same way as above.

Here is my Jakefile:

var shell = require('shelljs');
var sleep = require('sleep');

shell.config.verbose = true;

const destDir = 'dist';
const wpDir = '../wordpress';


var files = new jake.FileList();
files.include('*.html'); 
var outputFiles = files.toArray().map(function(fileName){
                    return destDir + '/' + fileName;
});

var sourceFile = function(name) {
    return name.substr(name.lastIndexOf('/') + 1);
}
function objectToStr(object) {
    var s = '';
    for(var property in object){
        s += property + ': ' + object[property] + '\n'; 
    }
    return s;
}
function rmExt(name) {
    return name.substr(0, name.lastIndexOf('.'));
}

directory(destDir);
task('default', [destDir].concat(outputFiles));
task('clean', function() {
    jake.rmRf(destDir);
});
rule('dist/%.html', sourceFile, function() {
    shell.cp(this.source, this.name);
    var pages = JSON.parse(shell.exec('wp --path=' + wpDir
                    + ' post list --post_type=page --format=json --fields=ID,post_name',
                    { silent: true }).stdout);
    var postId = null;
    var l = pages.length;
    for(var i = 0; i < l; i++){
        if(pages[i].post_name === rmExt(this.source)){
            postId = pages[i].ID;
            break;
        }
    }
    if(postId !== null){
        shell.exec('wp --path=' + wpDir + ' post update ' + postId
                    + ' ' + this.name + ' --post_type=page');
    }else{
        shell.echo('Unable to find matching post ID for file: ' + this.name);
    }
    shell.echo('1');
    sleep.sleep(2);
    shell.echo('2');
});
watchTask('watch', ['default'], function() {
    this.watchFiles.include('*.html');
}

Solution

  • I found a reliable solution using the library node-watch. I replaced the watchTask with a normal Jake task and invoked the task I wanted to call using node-watch to watch the current working directory. The library node-watch is able filter files based on regex so the watch won't be trigger for the Vim swap file and the like. Below is the code I used:

    task('watch', function() {
        var defaultTask = jake.Task['default'];
    
        defaultTask.invoke();
        defaultTask.reenable(true);
    
        watch('./', { filter: /.*.html$/ }, function(evt, name) {
            defaultTask.invoke();
            defaultTask.reenable(true);
        });
    });
    

    Note: I also invoked the task I want to call when the watch task is first run to make sure any changes made before the watch started are built.