├── .npmignore ├── .travis.yml ├── history.md ├── index.js ├── .jshintrc ├── .gitignore ├── LICENSE ├── package.json ├── lib ├── monitor.js └── portastic.js ├── test ├── fixtures │ └── helpers.js └── general-test.js ├── bin └── portastic └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !bin/**/* 3 | !lib/**/* 4 | !package.json 5 | !index.js 6 | !LICENSE 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | - 0.12 5 | - iojs 6 | script: 7 | - 'npm run travis' 8 | -------------------------------------------------------------------------------- /history.md: -------------------------------------------------------------------------------- 1 | ### `v1.0.1` 2 | 3 | * Fixed callback issues 4 | 5 | ### `v1.0.0` 6 | 7 | * Full code refactoring, tests and integrations 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var portastic = require('./lib/portastic'); 2 | portastic.Monitor = require('./lib/monitor'); 3 | 4 | module.exports = portastic; 5 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "boss": true, 4 | "bitwise": true, 5 | "noempty": true, 6 | "latedef": true, 7 | "camelcase": false, 8 | "eqeqeq": true, 9 | "smarttabs": true, 10 | "undef": true, 11 | "unused": true, 12 | "newcap": true, 13 | "trailing": true, 14 | "maxlen": 80, 15 | "maxcomplexity": 5, 16 | "indent": 2, 17 | "quotmark": "single", 18 | "strict": false, 19 | "globals": { 20 | "describe": true, 21 | "before": true, 22 | "after": true, 23 | "it": true, 24 | "test": true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io 2 | 3 | ### Node ### 4 | # Logs 5 | logs 6 | *.log 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Alan Hoffmeister 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portastic", 3 | "version": "1.0.1", 4 | "description": "Pure javascript swiss knife for port management", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --tdd --bail test/**/*-test.js", 8 | "travis": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha --report lcovonly -- -R spec test/**/*-test.js && cat ./coverage/lcov.info | ./node_modules/.bin/coveralls && rm -rf ./coverage" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/alanhoff/node-portastic.git" 13 | }, 14 | "keywords": [ 15 | "port", 16 | "management", 17 | "open ports", 18 | "find open", 19 | "interface" 20 | ], 21 | "author": "Alan Hoffmeister ", 22 | "license": "ISC", 23 | "bugs": { 24 | "url": "https://github.com/alanhoff/node-portastic/issues" 25 | }, 26 | "bin": { 27 | "portastic": "./bin/portastic" 28 | }, 29 | "homepage": "https://github.com/alanhoff/node-portastic#readme", 30 | "dependencies": { 31 | "bluebird": "^2.9.34", 32 | "commander": "^2.8.1", 33 | "debug": "^2.2.0" 34 | }, 35 | "devDependencies": { 36 | "chai": "^3.2.0", 37 | "coveralls": "^2.11.4", 38 | "istanbul": "^0.3.17", 39 | "mocha": "^2.2.5" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/monitor.js: -------------------------------------------------------------------------------- 1 | var portastic = require('./portastic'); 2 | var events = require('events'); 3 | var util = require('util'); 4 | 5 | var Monitor = function(ports, options) { 6 | this._ports = ports; 7 | this._options = options || {}; 8 | this._watchers = []; 9 | 10 | if (this._options.autostart === false) 11 | return; 12 | 13 | this.start(); 14 | }; 15 | util.inherits(Monitor, events.EventEmitter); 16 | 17 | Monitor.prototype.start = function() { 18 | if (this._watchers.length) 19 | return this.emit('error', new Error('Monitor already started')); 20 | 21 | var that = this; 22 | this._ports.forEach(function(port) { 23 | that._watchers.push(that._watcher(port)); 24 | }); 25 | }; 26 | 27 | Monitor.prototype.stop = function() { 28 | this._watchers.forEach(function(watcher) { 29 | clearInterval(watcher.intervar); 30 | }); 31 | 32 | this._watchers = []; 33 | }; 34 | 35 | Monitor.prototype._watcher = function(port) { 36 | var that = this; 37 | var setup = { 38 | state: null, 39 | interval: setInterval(function() { 40 | portastic.test(port) 41 | .then(function(open) { 42 | if (setup.state === open) 43 | return; 44 | 45 | that.emit(open ? 'open' : 'close', port); 46 | setup.state = open; 47 | }) 48 | .catch(function(err) { 49 | process.nextTick(function() { 50 | that.emit('error', err); 51 | }); 52 | }); 53 | }, that._options.interval || 100) 54 | }; 55 | 56 | return setup; 57 | }; 58 | 59 | module.exports = Monitor; 60 | -------------------------------------------------------------------------------- /test/fixtures/helpers.js: -------------------------------------------------------------------------------- 1 | var bluebird = require('bluebird'); 2 | var debug = require('debug')('portastic:unit:helpers'); 3 | var net = require('net'); 4 | 5 | exports.startTcp = bluebird.method(function(arr, iface) { 6 | return bluebird.resolve([].concat(arr)) 7 | .map(function(number) { 8 | var def = bluebird.defer(); 9 | 10 | var server = net.createServer(); 11 | debug('Starting TCP server at port %s', number); 12 | 13 | server.on('error', function(err) { 14 | def.reject(err); 15 | }); 16 | 17 | server.on('close', function() { 18 | debug('Server TCP at port %s has stopped', number); 19 | def.resolve(); 20 | }); 21 | 22 | server.listen(number, iface, function(err) { 23 | if (err) 24 | def.reject(err); 25 | 26 | def.resolve(server); 27 | }); 28 | 29 | return def.promise; 30 | }); 31 | }); 32 | 33 | exports.stopTcp = bluebird.method(function(arr) { 34 | return bluebird.resolve([].concat(arr)) 35 | .each(function(server) { 36 | var def = bluebird.defer(); 37 | server.close(def.callback); 38 | 39 | return def.promise; 40 | }); 41 | 42 | }); 43 | 44 | exports.autoClose = bluebird.method(function(ports, iface, promise) { 45 | if (!promise || typeof iface !== 'string') { 46 | promise = iface; 47 | iface = null; 48 | } 49 | 50 | debug('Starting autoclose helper on ports %j', ports); 51 | return exports.startTcp(ports, iface) 52 | .then(function(servers) { 53 | return bluebird.resolve() 54 | .then(promise) 55 | .then(function() { 56 | return exports.stopTcp(servers); 57 | }); 58 | }); 59 | 60 | }); 61 | 62 | exports.captureEvents = function(emitter, arr) { 63 | var emit = emitter.emit; 64 | emitter.emit = function() { 65 | var args = [].slice.call(arguments); 66 | arr.push(args); 67 | emit.apply(emit, args); 68 | }; 69 | }; 70 | -------------------------------------------------------------------------------- /bin/portastic: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var portastic = require('../'); 4 | var commander = require('commander'); 5 | var package = require('../package.json'); 6 | commander.version(package.version); 7 | 8 | // Test if a port is closed 9 | commander 10 | .command('test ') 11 | .alias('t') 12 | .description('Test if a port is closed or open') 13 | .action(function(port) { 14 | portastic.test(port) 15 | .then(function(result) { 16 | console.log('Port %s is %s', port, result ? 'open' : 'closed'); 17 | }); 18 | }); 19 | 20 | // Find available ports 21 | commander 22 | .command('find ') 23 | .alias('f') 24 | .description('Find ports that are available to use') 25 | .option('-r, --retrieve ', 'How many ports to retrieve', parseInt) 26 | .action(function(min, max, options) { 27 | portastic.find({ 28 | min: min, 29 | max: max, 30 | retrieve: options.retrieve 31 | }) 32 | .then(function(ports) { 33 | console.log('Ports available to use: %s', ports.join(', ')); 34 | }); 35 | }); 36 | 37 | // Filter a list of ports 38 | commander 39 | .command('filter ') 40 | .alias('i') 41 | .description('Find ports that are open whithin a list of ports') 42 | .action(function(ports) { 43 | portastic.filter(ports) 44 | .then(function() { 45 | console.log('Ports available to use: %s', ports.join(', ')); 46 | }); 47 | }); 48 | 49 | // Monitor ports 50 | commander 51 | .command('monitor ') 52 | .alias('m') 53 | .description('Monitor a list of ports and logs to the terminal when port state had changed') 54 | .action(function(ports) { 55 | var monitor = new portastic.Monitor(ports); 56 | monitor.on('open', function(port) { 57 | console.log('Port %s is open', port); 58 | }); 59 | 60 | monitor.on('close', function(port) { 61 | console.log('Port %s is closed', port); 62 | }); 63 | }); 64 | 65 | commander.parse(process.argv); 66 | -------------------------------------------------------------------------------- /test/general-test.js: -------------------------------------------------------------------------------- 1 | var portastic = require('../'); 2 | var helpers = require('./fixtures/helpers'); 3 | var expect = require('chai').expect; 4 | var bluebird = require('bluebird'); 5 | 6 | describe('General testing', function() { 7 | 8 | describe('#find', function() { 9 | 10 | it('Should find open ports', function() { 11 | return portastic.find({ 12 | min: 8000, 13 | max: 8001 14 | }) 15 | .then(function(ports) { 16 | expect(ports).to.have.length(2); 17 | expect(ports).to.contain(8000); 18 | expect(ports).to.contain(8001); 19 | }); 20 | }); 21 | 22 | it('Should not return closed ports', function() { 23 | 24 | return helpers.autoClose(8000, function() { 25 | return portastic.find({ 26 | min: 8000, 27 | max: 8001 28 | }) 29 | .then(function(ports) { 30 | expect(ports).to.be.eql([8001]); 31 | }); 32 | }); 33 | }); 34 | 35 | it('Should return the specified amount of ports', function() { 36 | 37 | return portastic.find({ 38 | min: 8000, 39 | max: 8001, 40 | retrieve: 1 41 | }) 42 | .then(function(ports) { 43 | expect(ports).to.have.length(1); 44 | }); 45 | }); 46 | 47 | }); 48 | 49 | describe('#test', function() { 50 | it('Should return true for open ports', function() { 51 | return portastic.test(8000) 52 | .then(function(ports) { 53 | expect(ports).to.be.eql(true); 54 | }); 55 | }); 56 | 57 | it('Should return false for closed ports', function() { 58 | return helpers.autoClose(8000, function() { 59 | return portastic.test(8000) 60 | .then(function(ports) { 61 | expect(ports).to.be.eql(false); 62 | }); 63 | }); 64 | }); 65 | }); 66 | 67 | describe('#filter', function() { 68 | it('Should not return closed ports', function() { 69 | return helpers.autoClose(8000, function() { 70 | return portastic.filter([8000, 8001, 8002]) 71 | .then(function(ports) { 72 | expect(ports).to.have.length(2); 73 | expect(ports).to.contain(8001); 74 | expect(ports).to.contain(8002); 75 | }); 76 | }); 77 | }); 78 | }); 79 | 80 | describe('#monitor', function() { 81 | it('Should emit events for every state change', function() { 82 | var events = []; 83 | var monitor = new portastic.Monitor([8000, 8001]); 84 | helpers.captureEvents(monitor, events); 85 | 86 | return bluebird.resolve() 87 | .delay(500) 88 | .then(function() { 89 | 90 | return helpers.autoClose([8000, 8001], function() { 91 | return bluebird.resolve() 92 | .delay(500) 93 | .then(function() { 94 | return monitor.stop(); 95 | }); 96 | }); 97 | }) 98 | .delay(500) 99 | .then(function() { 100 | expect(events).to.have.length(6); 101 | events.forEach(function(event, i) { 102 | if (i <= 1) 103 | expect(event[0]).to.be.equal('open'); 104 | 105 | if (i > 1 && i <= 3) 106 | expect(event[0]).to.be.equal('close'); 107 | 108 | if (i >= 4) 109 | expect(event[0]).to.be.equal('open'); 110 | }); 111 | }); 112 | }); 113 | }); 114 | 115 | describe("_callback", function(){ 116 | it('bypasses promise error catching', function(done){ 117 | portastic.test(8009, function(callback){ 118 | expect(callback); 119 | done(); 120 | }); 121 | }) 122 | }) 123 | }); 124 | -------------------------------------------------------------------------------- /lib/portastic.js: -------------------------------------------------------------------------------- 1 | var bluebird = require('bluebird'); 2 | var net = require('net'); 3 | var debug = require('debug'); 4 | 5 | module.exports = { 6 | 7 | // Returns a promise that will be resolved with an array of open ports 8 | test: bluebird.method(function(port, iface, callback) { 9 | var that = this; 10 | var log = debug('portastic:test'); 11 | var def = bluebird.defer(); 12 | 13 | // Callback handling 14 | def.promise 15 | .then(function(ports) { 16 | if (!that._callback(callback, [ports])) 17 | return ports; 18 | }) 19 | .catch(function(err) { 20 | if (!that._callback(callback, [err])) 21 | throw err; 22 | }); 23 | 24 | if (typeof iface !== 'string' && !callback) { 25 | callback = iface; 26 | iface = null; 27 | } 28 | 29 | var server = net.createServer(); 30 | 31 | server.on('error', function(err) { 32 | if (err.code === 'EADDRINUSE') { 33 | log('Port %s was in use', port); 34 | return def.resolve(false); 35 | } 36 | 37 | def.reject(err); 38 | }); 39 | 40 | server.on('close', function() { 41 | log('TCP server on port %s closed', port); 42 | def.resolve(true); 43 | }); 44 | 45 | log('Trying to test port %s', port); 46 | server.listen(port, iface, function(err) { 47 | if (err && err.code === 'EADDRINUSE') { 48 | log('Port %s was in use', port); 49 | return def.resolve(false); 50 | } 51 | 52 | if (err) 53 | return def.reject(err); 54 | 55 | server.close(function(err) { 56 | if (err) 57 | return def.reject(err); 58 | 59 | log('Port %s was free', port); 60 | }); 61 | }); 62 | 63 | return def.promise; 64 | }), 65 | 66 | // Filter ports that are in use 67 | filter: bluebird.method(function(ports, iface, callback) { 68 | var that = this; 69 | var log = debug('portastic:filter'); 70 | var def = bluebird.defer(); 71 | 72 | // Callback handling 73 | def.promise 74 | .then(function(ports) { 75 | if (!that._callback(callback, [ports])) 76 | return ports; 77 | }) 78 | .catch(function(err) { 79 | if (!that._callback(callback, [err])) 80 | throw err; 81 | }); 82 | 83 | if (typeof iface !== 'string' && !callback) { 84 | callback = iface; 85 | iface = null; 86 | } 87 | 88 | bluebird.all(ports) 89 | .filter(function(port) { 90 | log('Filtering port %s', port); 91 | return that.test(port, iface); 92 | }) 93 | .then(function(free) { 94 | def.resolve(free); 95 | }) 96 | .catch(function(err) { 97 | def.reject(err); 98 | }); 99 | 100 | return def.promise; 101 | }), 102 | 103 | // Find open ports in a range 104 | find: bluebird.method(function(options, iface, callback) { 105 | var log = debug('portastic:find'); 106 | var that = this; 107 | var ports = []; 108 | var result = []; 109 | 110 | if (typeof iface !== 'string' && !callback) { 111 | callback = iface; 112 | iface = null; 113 | } 114 | 115 | for (var i = options.min; i <= options.max; i++) 116 | ports.push(i); 117 | 118 | log('Trying to find open ports between range %s and %s', options.min, 119 | options.max); 120 | 121 | var promise = bluebird.resolve(ports) 122 | .each(function(port) { 123 | return that.test(port, iface) 124 | .then(function(open) { 125 | if (options.retrieve && result.length >= options.retrieve) { 126 | log('Result reached the maximum of %s ports, returning...', 127 | options.retrieve); 128 | return promise.cancel(); 129 | } 130 | 131 | if (open) { 132 | log('Port %s was open, adding it to the result list', port); 133 | log('Pushing port %s to the result list', port); 134 | return result.push(port); 135 | } else 136 | log('Port %s was not open', port); 137 | }); 138 | }) 139 | .cancellable() 140 | .catch(bluebird.CancellationError, function() { 141 | return; 142 | }) 143 | .then(function() { 144 | if (!that._callback(callback, [ports])) 145 | return result; 146 | }) 147 | .catch(function(err) { 148 | if (!that._callback(callback, [err])) 149 | throw err; 150 | }); 151 | 152 | return promise; 153 | }), 154 | 155 | // Handles callbacks 156 | _callback: function(cb, args) { 157 | if (cb) { 158 | // This will bypass promises errors catching 159 | process.nextTick(function() { 160 | cb.apply(cb, args); 161 | }); 162 | } 163 | 164 | return !!cb; 165 | } 166 | 167 | }; 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # portastic 2 | [![Coverage Status](https://coveralls.io/repos/alanhoff/node-portastic/badge.svg?branch=master)][0] 3 | [![Travis](https://travis-ci.org/alanhoff/node-portastic.svg)][1] 4 | [![Dependencies](https://david-dm.org/alanhoff/node-portastic.svg)][2] 5 | 6 | Pure javascript swiss knife for port management. Find open ports, monitor ports 7 | and other port relates things. 8 | 9 | ### API 10 | 11 | * `portastic.test(port, [interface , [callback]])` 12 | 13 | Test if a port is open. If a callback 14 | is provided it will be called with an `error` parameter and a second parameter 15 | with a `boolean` that tells if the port is open or not. If a callback is not 16 | provided the return value will be a promise that will be fullfied with the 17 | result. 18 | 19 | ```javascript 20 | var portastic = require('portastic'); 21 | 22 | portastic.test(8080) 23 | .then(function(isOpen){ 24 | console.log('Port 8080 is %s', isOpen ? 'open' : 'closed'); 25 | }); 26 | ``` 27 | 28 | * `portastic.find(options, [interface, [callback]])` 29 | 30 | Retrieve a list of open ports between `min` and `max`, if a callback is not 31 | provided this method will resolve a promise with the results. Options can be: 32 | 33 | * `min` The minimum port number to start with 34 | * `max` The maximum port number to scan 35 | * `retrieve` How many ports to collect 36 | 37 | ```javascript 38 | var portastic = require('portastic'); 39 | 40 | portastic.find({ 41 | min: 8000, 42 | max: 8080 43 | }) 44 | .then(function(ports){ 45 | console.log('Ports available between 8000 and 8080 are: %s', 46 | ports.join(', ')); 47 | }); 48 | ``` 49 | 50 | * `portastic.filter(ports..., [interface, [callback]])` 51 | 52 | Test a list of ports and return the open ones. If a callback is not provided 53 | this method will resolve a promise with the results 54 | 55 | ```javascript 56 | var portastic = require('portastic'); 57 | 58 | portastic.filter([8080, 8081, 8082]) 59 | .then(function(ports){ 60 | console.log('The available ports are: %s', ports.join(', ')); 61 | }); 62 | ``` 63 | 64 | * `portastic.Monitor(ports...)` 65 | 66 | Monitor is an `EventEmitter` that emits `open` when a monitored port is 67 | available and `close` when the port has closed. 68 | 69 | ```javascript 70 | var portastic = require('portastic'); 71 | var monitor = new portastic.Monitor([8080, 8081, 8082]); 72 | 73 | monitor.on('open', function(port){ 74 | console.log('Port %s is open', port); 75 | }); 76 | 77 | monitor.on('close', function(port){ 78 | console.log('Port %s is closed', port); 79 | }); 80 | 81 | setTimeout(function(){ 82 | monitor.stop(); // Stops the monitoring after 5 seconds 83 | }, 5000); 84 | ``` 85 | 86 | ### Command line 87 | 88 | It's also possible to use `portastic` as a command line utility, you just need 89 | to install it globally with `npm install -g portastic`. Here is the help command 90 | output. 91 | 92 | ``` 93 | 94 | Usage: portastic [options] [command] 95 | 96 | 97 | Commands: 98 | 99 | test|t Test if a port is closed or open 100 | find|f [options] Find ports that are available to use 101 | filter|i Find ports that are open whithin a list of ports 102 | monitor|m Monitor a list of ports and logs to the terminal when port state had changed 103 | 104 | Options: 105 | 106 | -h, --help output usage information 107 | -V, --version output the version number 108 | 109 | ``` 110 | 111 | ### Testing 112 | 113 | ```bash 114 | git clone git@github.com:alanhoff/node-portastic.git 115 | cd node-portastic 116 | npm install && npm test 117 | ``` 118 | 119 | ### Debugging 120 | 121 | To see debug messages you must set your enviroment variable `DEBUG` to `*` or 122 | `portastic:*`, example: 123 | 124 | ```bash 125 | DEBUG=portastic:\* npm test 126 | ``` 127 | 128 | ### License (ISC) 129 | 130 | ``` 131 | Copyright (c) 2015, Alan Hoffmeister 132 | 133 | Permission to use, copy, modify, and distribute this software for any 134 | purpose with or without fee is hereby granted, provided that the above 135 | copyright notice and this permission notice appear in all copies. 136 | 137 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 138 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 139 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 140 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 141 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 142 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 143 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 144 | ``` 145 | 146 | [0]: https://coveralls.io/github/alanhoff/node-portastic 147 | [1]: https://travis-ci.org/alanhoff/node-portastic 148 | [2]: https://david-dm.org/alanhoff/node-portastic 149 | --------------------------------------------------------------------------------