Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 67 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ all: {
* Default: `false`

Whether or not to skip server startup if port is already in use.
Task will fail if port is in use and skippable is false.
Task will fail if port is in use and `skippable` and `useAvailablePort` are false.

### `persist`

Expand All @@ -54,6 +54,14 @@ Task will fail if port is in use and skippable is false.
Whether or not to keep the vnu server running even after grunt exists.
If false, vnu server is killed when grunt exists.

### `useAvailablePort`

* Type: `Boolean`
* Default: `false`

If `true` the task will look for the next available port, if the port
set by the `port` option is in use.

## Example

Consider the following configuration in Gruntfile.js in which the `watch` task is set to run `htmllint` every time the source file changes.
Expand Down Expand Up @@ -89,6 +97,63 @@ module.exports = function (grunt) {
};
```

### Using the First Available Port

If you set `useAvailablePort` to `true`, you will need to pass the actual value to the `htmllint`
task, but the value will be known first during the runtime. Use a function for the `server`
option, which will return the `server` object as a result.

```js
module.exports = function (grunt) {
var vnuPort;

grunt.initConfig({
vnuserver: {
// Name the task to be able to listen to its events.
options: {
// Start with the first free ephemeral port.
port: 49152,
// Try other ports, up to port + 30, if the first one is not free.
useAvailablePort: true
}
},
htmllint: {
options: {
// Connect to the vnu server on the dynamically chosen port.
server: function () {
return {
port: vnuPort
};
}
},
all: {
src: "app.html"
}
},
watch: {
all: {
tasks: ['htmllint'],
files: "app.html"
}
},
});

grunt.loadNpmTasks('grunt-vnuserver');
grunt.loadNpmTasks('grunt-html');
grunt.loadNpmTasks('grunt-contrib-watch');

// Obtain the port, which the vnu server is listening to.
grunt.event.on('vnuserver.listening', function (port) {
vnuPort = port;
// vnuPort = grunt.config.get('vnuserver.options.port');
});

grunt.registerTask('default', ['vnuserver', 'watch']);
};
```

The possibility of using a function for the `server` parameter of the `htmllint` task is currently available in the [dynamic-server-option branch](https://github.com/prantlf/grunt-html/tree/dynamic-server-option) and it will be requested to adopt by the upstream project.

## License

Copyright Bennie Swart.
Expand All @@ -98,3 +163,4 @@ Licensed under the MIT license.
[grunt-html]: https://github.com/jzaefferer/grunt-html
[getting_started]: http://gruntjs.com/getting-started
[vnujar]: https://validator.github.io/validator/
[dynamic-server-option branch]: https://github.com/prantlf/grunt-html/tree/dynamic-server-option
111 changes: 65 additions & 46 deletions tasks/vnuserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,62 +2,81 @@ let javadetect = require('grunt-html/lib/javadetect');
let jar = require('vnu-jar');
let portastic = require('portastic');

let MAX_PORTS = 30;

module.exports = function (grunt) {
grunt.registerTask('vnuserver', 'Start the Nu Html Checker server.', function () {
let opt = this.options({port: 8888, skippable: false, persist: false});
let opt = this.options({port: 8888, skippable: false, persist: false, useAvailablePort: false});
let done = this.async();
portastic.test(opt.port, function (open) {
if (!open) {
if (opt.skippable) {
grunt.log.debug('Port ' + opt.port + ' in use. Skipping server startup.');
done();
} else {
done(Error('Port ' + opt.port + ' in use. To ignore, set skippable: false.'));
}
return;
}

let child;
let cleanup = function () {
let killing = grunt.log.write('Killing vnuserver...');
child.kill('SIGKILL');
killing.ok();
};
if (!opt.persist) {
process.on('exit', cleanup);
let exit = grunt.util.exit;
grunt.util.exit = function () { // This seems to be the only reliable on-exit hook.
cleanup();
return exit.apply(grunt.util, arguments);
};
}
start(opt.port);

javadetect(function(err, java) {
if (err) {
throw err;
}
if (java.version[0] !== '1' || (java.version[0] === '1' && java.version[2] < '8')) {
throw new Error('\nUnsupported Java version used: ' + java.version + '. v1.8 is required!');
}
let args = [(java.arch === 'ia32' ? '-Xss512k' : ''), '-cp', jar, 'nu.validator.servlet.Main', opt.port].filter(x => x);
let vnustartup = grunt.log.write('Starting vnuserver...');
child = grunt.util.spawn({cmd: 'java', args: args}, function(error, stdout, stderr) {
if (error && (error.code !== 1 || error.killed || error.signal)) {
done(false);
}
});
child.stderr.on('data', function (chunk) {
if (chunk.toString().indexOf('INFO:oejs.Server:main: Started') >= 0) {
vnustartup.ok();
function start (port) {
portastic.test(port, function (open) {
if (!open) {
if (opt.useAvailablePort) {
if (port < opt.port + MAX_PORTS) {
grunt.log.debug('Port ' + port + ' in use. Trying another one.');
process.nextTick(function () {
start(port + 1);
});
} else {
done(Error('Port ' + opt.port + ' and ' + MAX_PORTS + ' others in use. To ignore, set skippable: false.'));
}
} else if (opt.skippable) {
grunt.log.debug('Port ' + port + ' in use. Skipping server startup.');
done();
} else {
done(Error('Port ' + port + ' in use. To ignore, set skippable: false.'));
}
if (chunk.toString().indexOf('java.net.BindException: Address already in use') >= 0) {
vnustartup.error();
done(Error('Port ' + opt.port + ' in use. Shutting down.'));
return;
}

grunt.config.set('vnuserver.options.port', port);
grunt.event.emit('vnuserver.listening', port);

let child;
let cleanup = function () {
let killing = grunt.log.write('Killing vnuserver...');
child.kill('SIGKILL');
killing.ok();
};
if (!opt.persist) {
process.on('exit', cleanup);
let exit = grunt.util.exit;
grunt.util.exit = function () { // This seems to be the only reliable on-exit hook.
cleanup();
return exit.apply(grunt.util, arguments);
};
}

javadetect(function(err, java) {
if (err) {
throw err;
}
if (java.version[0] !== '1' || (java.version[0] === '1' && java.version[2] < '8')) {
throw new Error('\nUnsupported Java version used: ' + java.version + '. v1.8 is required!');
}
let args = [(java.arch === 'ia32' ? '-Xss512k' : ''), '-cp', jar, 'nu.validator.servlet.Main', port].filter(x => x);
let vnustartup = grunt.log.write('Starting vnuserver on port ' + port + '...');
child = grunt.util.spawn({cmd: 'java', args: args}, function(error /*, stdout, stderr*/) {
if (error && (error.code !== 1 || error.killed || error.signal)) {
done(false);
}
});
child.stderr.on('data', function (chunk) {
if (chunk.toString().indexOf('INFO:oejs.Server:main: Started') >= 0) {
vnustartup.ok();
done();
}
if (chunk.toString().indexOf('java.net.BindException: Address already in use') >= 0) {
vnustartup.error();
done(Error('Port ' + port + ' in use. Shutting down.'));
cleanup();
}
});
});
});
});
}
});
};