1 Commits

Author SHA1 Message Date
Roman Shtylman
e5b04a395b 1.4.1 2014-10-25 17:12:10 -07:00
12 changed files with 327 additions and 849 deletions

View File

@@ -1 +0,0 @@
support

View File

@@ -1,6 +1,3 @@
language: node_js
node_js:
- "4"
- "6"
- "8"
- "9"
- "0.10"

View File

@@ -1,45 +1,3 @@
# 1.9.1 (2018-09-08)
* Update debug to 2.6.9
# 1.9.0 (2018-04-03)
* Add _request_ event to Tunnel emitter
* Update yargs to support config via environment variables
* Add basic request logging when --print-requests argument is used
# 1.8.3 (2017-06-11)
* update request dependency
* update debug dependency
* update openurl dependency
# 1.8.2 (2016-11-17)
* fix host header transform
* update request dependency
# 1.8.1 (2016-01-20)
* fix bug w/ HostHeaderTransformer and binary data
# 1.8.0 (2015-11-04)
* pass socket errors up to top level
# 1.7.0 (2015-07-22)
* add short arg options
# 1.6.0 (2015-05-15)
* keep sockets alive after connecting
* add --open param to CLI
# 1.5.0 (2014-10-25)
* capture all errors on remote socket and restart the tunnel
# 1.4.0 (2014-08-31)
* don't emit errors for ETIMEDOUT

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2018 Roman Shtylman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,6 +1,4 @@
# localtunnel
[![Build Status](https://travis-ci.org/localtunnel/localtunnel.svg?branch=master)](https://travis-ci.org/localtunnel/localtunnel)
# localtunnel [![Build Status](https://travis-ci.org/defunctzombie/localtunnel.png?branch=master)](https://travis-ci.org/defunctzombie/localtunnel) #
localtunnel exposes your localhost to the world for easy testing and sharing! No need to mess with DNS or deploy just to have others test out your changes.
@@ -33,12 +31,6 @@ Below are some common arguments. See `lt --help` for additional arguments
* `--subdomain` request a named subdomain on the localtunnel server (default is random characters)
* `--local-host` proxy to a hostname other than localhost
You may also specify arguments via env variables. E.x.
```
PORT=3000 lt
```
## API ##
The localtunnel client is also usable through an API (for test integration, automation, etc)
@@ -50,22 +42,18 @@ Creates a new localtunnel to the specified local `port`. `fn` will be called onc
```javascript
var localtunnel = require('localtunnel');
var tunnel = localtunnel(port, function(err, tunnel) {
localtunnel(port, function(err, tunnel) {
if (err) ...
// the assigned public url for your tunnel
// i.e. https://abcdefgjhij.localtunnel.me
tunnel.url;
});
tunnel.on('close', function() {
// tunnels are closed
});
```
### opts
* `subdomain` A *string* value requesting a specific subdomain on the proxy server. **Note** You may not actually receive this name depending on availability.
* `subdomain` A *string* value requesting a specific subdomain on the proxy server. **Note** You may not actually receive this name depending on availablily.
* `local_host` Proxy to this hostname instead of `localhost`. This will also cause the `Host` header to be re-written to this value in proxied requests.
### Tunnel
@@ -74,11 +62,10 @@ The `tunnel` instance returned to your callback emits the following events
|event|args|description|
|----|----|----|
|request|info|fires when a request is processed by the tunnel, contains _method_ and _path_ fields|
|error|err|fires when an error happens on the tunnel|
|close||fires when the tunnel has closed|
The `tunnel` instance has the following methods
The `tunnel instance has the following methods
|method|args|description|
|----|----|----|
@@ -90,11 +77,9 @@ Clients in other languages
*go* [gotunnelme](https://github.com/NoahShen/gotunnelme)
*go* [go-localtunnel](https://github.com/localtunnel/go-localtunnel)
## server ##
See [localtunnel/server](//github.com/localtunnel/server) for details on the server that powers localtunnel.
See defunctzombie/localtunnel-server for details on the server that powers localtunnel.
## License ##
MIT

View File

@@ -1,43 +1,32 @@
#!/usr/bin/env node
var lt_client = require('../client');
var open_url = require('openurl');
var argv = require('yargs')
.usage('Usage: $0 --port [num] <options>')
.env(true)
.option('h', {
alias: 'host',
describe: 'Upstream server providing forwarding',
default: 'https://localtunnel.me',
var argv = require('optimist')
.usage('Usage: $0 --port [num]')
.options('host', {
default: 'http://localtunnel.me',
describe: 'upstream server providing forwarding'
})
.option('s', {
alias: 'subdomain',
describe: 'Request this subdomain'
.options('subdomain', {
describe: 'request this subdomain'
})
.option('l', {
alias: 'local-host',
describe: 'Tunnel traffic to this host instead of localhost, override Host header to this host'
.options('local-host', {
describe: 'tunnel traffic to this host instead of localhost, override Host header to this host'
})
.options('o', {
alias: 'open',
describe: 'opens url in your browser'
.options('version', {
describe: 'print version and exit'
})
.option('p', {
alias: 'port',
describe: 'Internal http server port',
})
.option('print-requests', {
describe: 'Print basic request info',
})
.require('port')
.boolean('print-requests')
.help('help', 'Show this help and exit')
.version(require('../package').version)
.describe('port', 'internal http server port')
.argv;
if (typeof argv.port !== 'number') {
require('yargs').showHelp();
console.error('port must be a number');
if (argv.version) {
console.log(require('../package.json').version);
process.exit(0);
}
if (argv.port == null) {
require('optimist').showHelp();
console.error('Missing required arguments: port');
process.exit(1);
}
@@ -48,8 +37,6 @@ var opt = {
subdomain: argv.subdomain,
};
const PrintRequests = argv['print-requests'];
lt_client(opt.port, opt, function(err, tunnel) {
if (err) {
throw err;
@@ -57,19 +44,9 @@ lt_client(opt.port, opt, function(err, tunnel) {
console.log('your url is: %s', tunnel.url);
if (argv.open) {
open_url.open(tunnel.url);
}
tunnel.on('error', function(err) {
throw err;
});
if (PrintRequests) {
tunnel.on('request', function(info) {
console.log(new Date().toString(), info.method, info.path);
});
}
});
// vim: ft=javascript

296
client.js
View File

@@ -1,7 +1,301 @@
var net = require('net');
var url = require('url');
var EventEmitter = require('events').EventEmitter;
var request = require('request');
var debug = require('debug')('localtunnel:client');
var Tunnel = require('./lib/Tunnel');
var stream = require('stream');
var util = require('util');
var Transform = stream.Transform;
var HeaderHostTransformer = function(opts) {
if (!(this instanceof HeaderHostTransformer)) {
return new HeaderHostTransformer(opts);
}
opts = opts || {}
Transform.call(this, opts);
var self = this;
self.host = opts.host || 'localhost';
self.replaced = false;
}
util.inherits(HeaderHostTransformer, Transform);
HeaderHostTransformer.prototype._transform = function (chunk, enc, cb) {
var self = this;
chunk = chunk.toString();
// after replacing the first instance of the Host header
// we just become a regular passthrough
if (!self.replaced) {
self.push(chunk.replace(/(\r\nHost: )\S+/, function(match, $1) {
self.replaced = true;
return $1 + self.host;
}));
}
else {
self.push(chunk);
}
cb();
};
// manages groups of tunnels
var TunnelCluster = function(opt) {
if (!(this instanceof TunnelCluster)) {
return new TunnelCluster(opt);
}
var self = this;
self._opt = opt;
EventEmitter.call(self);
};
TunnelCluster.prototype.__proto__ = EventEmitter.prototype;
// establish a new tunnel
TunnelCluster.prototype.open = function() {
var self = this;
var opt = self._opt || {};
var remote_host = opt.remote_host;
var remote_port = opt.remote_port;
var local_host = opt.local_host || 'localhost';
var local_port = opt.local_port;
debug('establishing tunnel %s:%s <> %s:%s', local_host, local_port, remote_host, remote_port);
// connection to localtunnel server
var remote = net.connect({
host: remote_host,
port: remote_port
});
remote.on('error', function(err) {
// emit connection refused errors immediately, because they
// indicate that the tunnel can't be established.
if (err.code === 'ECONNREFUSED') {
self.emit('error', new Error('connection refused: ' + remote_host + ':' + remote_port + ' (check your firewall settings)'));
}
remote.end();
});
function conn_local() {
if (remote.destroyed) {
debug('remote destroyed');
self.emit('dead');
return;
}
debug('connecting locally to %s:%d', local_host, local_port);
remote.pause();
// connection to local http server
var local = net.connect({
host: local_host,
port: local_port
});
function remote_close() {
debug('remote close');
self.emit('dead');
local.end();
};
remote.once('close', remote_close);
// TODO some languages have single threaded servers which makes opening up
// multiple local connections impossible. We need a smarter way to scale
// and adjust for such instances to avoid beating on the door of the server
local.once('error', function(err) {
debug('local error %s', err.message);
local.end();
remote.removeListener('close', remote_close);
if (err.code !== 'ECONNREFUSED') {
return remote.end();
}
// retrying connection to local server
setTimeout(conn_local, 1000);
});
local.once('connect', function() {
debug('connected locally');
remote.resume();
var stream = remote;
// if user requested specific local host
// then we use host header transform to replace the host header
if (opt.local_host) {
debug('transform Host header to %s', opt.local_host);
stream = remote.pipe(HeaderHostTransformer({ host: opt.local_host }));
}
stream.pipe(local).pipe(remote);
// when local closes, also get a new remote
local.once('close', function(had_error) {
debug('local connection closed [%s]', had_error);
});
});
}
// tunnel is considered open when remote connects
remote.once('connect', function() {
self.emit('open', remote);
conn_local();
});
};
var Tunnel = function(opt) {
if (!(this instanceof Tunnel)) {
return new Tunnel(opt);
}
var self = this;
self._closed = false;
self._opt = opt || {};
self._opt.host = self._opt.host || 'https://localtunnel.me';
};
Tunnel.prototype.__proto__ = EventEmitter.prototype;
// initialize connection
// callback with connection info
Tunnel.prototype._init = function(cb) {
var self = this;
var opt = self._opt;
var params = {
path: '/',
json: true
};
var base_uri = opt.host + '/';
// optionally override the upstream server
var upstream = url.parse(opt.host);
// no subdomain at first, maybe use requested domain
var assigned_domain = opt.subdomain;
// where to quest
params.uri = base_uri + ((assigned_domain) ? assigned_domain : '?new');
(function get_url() {
request(params, function(err, res, body) {
if (err) {
// TODO (shtylman) don't print to stdout?
console.log('tunnel server offline: ' + err.message + ', retry 1s');
return setTimeout(get_url, 1000);
}
if (res.statusCode !== 200) {
var err = new Error((body && body.message) || 'localtunnel server returned an error, please try again');
return cb(err);
}
var port = body.port;
var host = upstream.hostname;
var max_conn = body.max_conn_count || 1;
cb(null, {
remote_host: upstream.hostname,
remote_port: body.port,
name: body.id,
url: body.url,
max_conn: max_conn
});
});
})();
};
Tunnel.prototype._establish = function(info) {
var self = this;
var opt = self._opt;
info.local_host = opt.local_host;
info.local_port = opt.port;
var tunnels = self.tunnel_cluster = TunnelCluster(info);
// only emit the url the first time
tunnels.once('open', function() {
self.emit('url', info.url);
});
var tunnel_count = 0;
// track open count
tunnels.on('open', function(tunnel) {
tunnel_count++;
debug('tunnel open [total: %d]', tunnel_count);
var close_handler = function() {
tunnel.destroy();
};
if (self._closed) {
return close_handler();
}
self.once('close', close_handler);
tunnel.once('close', function() {
self.removeListener('close', close_handler);
});
});
// when a tunnel dies, open a new one
tunnels.on('dead', function(tunnel) {
tunnel_count--;
debug('tunnel dead [total: %d]', tunnel_count);
if (self._closed) {
return;
}
tunnels.open();
});
// establish as many tunnels as allowed
for (var count = 0 ; count < info.max_conn ; ++count) {
tunnels.open();
}
};
Tunnel.prototype.open = function(cb) {
var self = this;
self._init(function(err, info) {
if (err) {
return cb(err);
}
self.url = info.url;
self._establish(info);
cb();
});
};
// shutdown tunnels
Tunnel.prototype.close = function() {
var self = this;
self._closed = true;
self.emit('close');
};
module.exports = function localtunnel(port, opt, fn) {
if (typeof opt === 'function') {

View File

@@ -1,39 +0,0 @@
var stream = require('stream');
var util = require('util');
var Transform = stream.Transform;
var HeaderHostTransformer = function(opts) {
if (!(this instanceof HeaderHostTransformer)) {
return new HeaderHostTransformer(opts);
}
opts = opts || {}
Transform.call(this, opts);
var self = this;
self.host = opts.host || 'localhost';
self.replaced = false;
}
util.inherits(HeaderHostTransformer, Transform);
HeaderHostTransformer.prototype._transform = function (chunk, enc, cb) {
var self = this;
// after replacing the first instance of the Host header
// we just become a regular passthrough
if (!self.replaced) {
chunk = chunk.toString();
self.push(chunk.replace(/(\r\n[Hh]ost: )\S+/, function(match, $1) {
self.replaced = true;
return $1 + self.host;
}));
}
else {
self.push(chunk);
}
cb();
};
module.exports = HeaderHostTransformer;

View File

@@ -1,158 +0,0 @@
var url = require('url');
var EventEmitter = require('events').EventEmitter;
var axios = require('axios');
var debug = require('debug')('localtunnel:client');
var TunnelCluster = require('./TunnelCluster');
var Tunnel = function(opt) {
if (!(this instanceof Tunnel)) {
return new Tunnel(opt);
}
var self = this;
self._closed = false;
self._opt = opt || {};
self._opt.host = self._opt.host || 'https://localtunnel.me';
};
Tunnel.prototype.__proto__ = EventEmitter.prototype;
// initialize connection
// callback with connection info
Tunnel.prototype._init = function(cb) {
var self = this;
var opt = self._opt;
var params = {
responseType: 'json'
};
var base_uri = opt.host + '/';
// optionally override the upstream server
var upstream = url.parse(opt.host);
// no subdomain at first, maybe use requested domain
var assigned_domain = opt.subdomain;
// where to quest
var uri = base_uri + ((assigned_domain) ? assigned_domain : '?new');
(function get_url() {
axios.get(uri, params)
.then(function(res){
var body = res.data;
if (res.status !== 200) {
var err = new Error((body && body.message) || 'localtunnel server returned an error, please try again');
return cb(err);
}
var port = body.port;
var host = upstream.hostname;
var max_conn = body.max_conn_count || 1;
cb(null, {
remote_host: upstream.hostname,
remote_port: body.port,
name: body.id,
url: body.url,
max_conn: max_conn
});
})
.catch(function(err){
// TODO (shtylman) don't print to stdout?
console.log('tunnel server offline: ' + err.message + ', retry 1s');
return setTimeout(get_url, 1000);
})
})();
};
Tunnel.prototype._establish = function(info) {
var self = this;
var opt = self._opt;
// increase max event listeners so that localtunnel consumers don't get
// warning messages as soon as they setup even one listener. See #71
self.setMaxListeners(info.max_conn + (EventEmitter.defaultMaxListeners || 10));
info.local_host = opt.local_host;
info.local_port = opt.port;
var tunnels = self.tunnel_cluster = TunnelCluster(info);
// only emit the url the first time
tunnels.once('open', function() {
self.emit('url', info.url);
});
// re-emit socket error
tunnels.on('error', function(err) {
self.emit('error', err);
});
var tunnel_count = 0;
// track open count
tunnels.on('open', function(tunnel) {
tunnel_count++;
debug('tunnel open [total: %d]', tunnel_count);
var close_handler = function() {
tunnel.destroy();
};
if (self._closed) {
return close_handler();
}
self.once('close', close_handler);
tunnel.once('close', function() {
self.removeListener('close', close_handler);
});
});
// when a tunnel dies, open a new one
tunnels.on('dead', function(tunnel) {
tunnel_count--;
debug('tunnel dead [total: %d]', tunnel_count);
if (self._closed) {
return;
}
tunnels.open();
});
tunnels.on('request', function(info) {
self.emit('request', info);
});
// establish as many tunnels as allowed
for (var count = 0 ; count < info.max_conn ; ++count) {
tunnels.open();
}
};
Tunnel.prototype.open = function(cb) {
var self = this;
self._init(function(err, info) {
if (err) {
return cb(err);
}
self.url = info.url;
self._establish(info);
cb();
});
};
// shutdown tunnels
Tunnel.prototype.close = function() {
var self = this;
self._closed = true;
self.emit('close');
};
module.exports = Tunnel;

View File

@@ -1,133 +0,0 @@
var EventEmitter = require('events').EventEmitter;
var debug = require('debug')('localtunnel:client');
var net = require('net');
var HeaderHostTransformer = require('./HeaderHostTransformer');
// manages groups of tunnels
var TunnelCluster = function(opt) {
if (!(this instanceof TunnelCluster)) {
return new TunnelCluster(opt);
}
var self = this;
self._opt = opt;
EventEmitter.call(self);
};
TunnelCluster.prototype.__proto__ = EventEmitter.prototype;
// establish a new tunnel
TunnelCluster.prototype.open = function() {
var self = this;
var opt = self._opt || {};
var remote_host = opt.remote_host;
var remote_port = opt.remote_port;
var local_host = opt.local_host || 'localhost';
var local_port = opt.local_port;
debug('establishing tunnel %s:%s <> %s:%s', local_host, local_port, remote_host, remote_port);
// connection to localtunnel server
var remote = net.connect({
host: remote_host,
port: remote_port
});
remote.setKeepAlive(true);
remote.on('error', function(err) {
// emit connection refused errors immediately, because they
// indicate that the tunnel can't be established.
if (err.code === 'ECONNREFUSED') {
self.emit('error', new Error('connection refused: ' + remote_host + ':' + remote_port + ' (check your firewall settings)'));
}
remote.end();
});
function conn_local() {
if (remote.destroyed) {
debug('remote destroyed');
self.emit('dead');
return;
}
debug('connecting locally to %s:%d', local_host, local_port);
remote.pause();
// connection to local http server
var local = net.connect({
host: local_host,
port: local_port
});
function remote_close() {
debug('remote close');
self.emit('dead');
local.end();
};
remote.once('close', remote_close);
// TODO some languages have single threaded servers which makes opening up
// multiple local connections impossible. We need a smarter way to scale
// and adjust for such instances to avoid beating on the door of the server
local.once('error', function(err) {
debug('local error %s', err.message);
local.end();
remote.removeListener('close', remote_close);
if (err.code !== 'ECONNREFUSED') {
return remote.end();
}
// retrying connection to local server
setTimeout(conn_local, 1000);
});
local.once('connect', function() {
debug('connected locally');
remote.resume();
var stream = remote;
// if user requested specific local host
// then we use host header transform to replace the host header
if (opt.local_host) {
debug('transform Host header to %s', opt.local_host);
stream = remote.pipe(HeaderHostTransformer({ host: opt.local_host }));
}
stream.pipe(local).pipe(remote);
// when local closes, also get a new remote
local.once('close', function(had_error) {
debug('local connection closed [%s]', had_error);
});
});
}
remote.on('data', function(data) {
const match = data.toString().match(/^(\w+) (\S+)/);
if (match) {
self.emit('request', {
method: match[1],
path: match[2],
});
}
});
// tunnel is considered open when remote connects
remote.once('connect', function() {
self.emit('open', remote);
conn_local();
});
};
module.exports = TunnelCluster;

View File

@@ -2,17 +2,15 @@
"author": "Roman Shtylman <shtylman@gmail.com>",
"name": "localtunnel",
"description": "expose localhost to the world",
"version": "1.9.1",
"license": "MIT",
"version": "1.4.1",
"repository": {
"type": "git",
"url": "git://github.com/localtunnel/localtunnel.git"
"url": "git://github.com/shtylman/localtunnel.git"
},
"dependencies": {
"axios": "0.17.1",
"debug": "2.6.9",
"openurl": "1.1.1",
"yargs": "6.6.0"
"request": "2.11.4",
"optimist": "0.3.4",
"debug": "0.7.4"
},
"devDependencies": {
"mocha": "~1.17.0"
@@ -24,4 +22,4 @@
"lt": "./bin/client"
},
"main": "./client.js"
}
}

379
yarn.lock
View File

@@ -1,379 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
axios@0.17.1:
version "0.17.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.17.1.tgz#2d8e3e5d0bdbd7327f91bc814f5c57660f81824d"
dependencies:
follow-redirects "^1.2.5"
is-buffer "^1.1.5"
builtin-modules@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
camelcase@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a"
cliui@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
dependencies:
string-width "^1.0.1"
strip-ansi "^3.0.1"
wrap-ansi "^2.0.0"
code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
commander@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06"
commander@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.0.0.tgz#d1b86f901f8b64bd941bdeadaf924530393be928"
debug@*, debug@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
dependencies:
ms "2.0.0"
debug@2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
dependencies:
ms "2.0.0"
decamelize@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
diff@1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/diff/-/diff-1.0.7.tgz#24bbb001c4a7d5522169e7cabdb2c2814ed91cf4"
error-ex@^1.2.0:
version "1.3.2"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
dependencies:
is-arrayish "^0.2.1"
find-up@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f"
dependencies:
path-exists "^2.0.0"
pinkie-promise "^2.0.0"
follow-redirects@^1.2.5:
version "1.4.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.4.1.tgz#d8120f4518190f55aac65bb6fc7b85fcd666d6aa"
dependencies:
debug "^3.1.0"
get-caller-file@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
glob@3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.3.tgz#e313eeb249c7affaa5c475286b0e115b59839467"
dependencies:
graceful-fs "~2.0.0"
inherits "2"
minimatch "~0.2.11"
graceful-fs@^4.1.2:
version "4.1.11"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
graceful-fs@~2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-2.0.3.tgz#7cd2cdb228a4a3f36e95efa6cc142de7d1a136d0"
growl@1.7.x:
version "1.7.0"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.7.0.tgz#de2d66136d002e112ba70f3f10c31cf7c350b2da"
hosted-git-info@^2.1.4:
version "2.7.1"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047"
inherits@2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
invert-kv@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6"
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
is-builtin-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
dependencies:
builtin-modules "^1.0.0"
is-fullwidth-code-point@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
dependencies:
number-is-nan "^1.0.0"
is-utf8@^0.2.0:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
jade@0.26.3:
version "0.26.3"
resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c"
dependencies:
commander "0.6.1"
mkdirp "0.3.0"
lcid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
dependencies:
invert-kv "^1.0.0"
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
dependencies:
graceful-fs "^4.1.2"
parse-json "^2.2.0"
pify "^2.0.0"
pinkie-promise "^2.0.0"
strip-bom "^2.0.0"
lru-cache@2:
version "2.7.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
minimatch@~0.2.11:
version "0.2.14"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a"
dependencies:
lru-cache "2"
sigmund "~1.0.0"
mkdirp@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
mkdirp@0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7"
mocha@~1.17.0:
version "1.17.1"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-1.17.1.tgz#7f7671d68526d074b7bae660c9099f87e0ea1ccb"
dependencies:
commander "2.0.0"
debug "*"
diff "1.0.7"
glob "3.2.3"
growl "1.7.x"
jade "0.26.3"
mkdirp "0.3.5"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
normalize-package-data@^2.3.2:
version "2.4.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
dependencies:
hosted-git-info "^2.1.4"
is-builtin-module "^1.0.0"
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
number-is-nan@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
openurl@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.1.tgz#3875b4b0ef7a52c156f0db41d4609dbb0f94b387"
os-locale@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9"
dependencies:
lcid "^1.0.0"
parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
dependencies:
error-ex "^1.2.0"
path-exists@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b"
dependencies:
pinkie-promise "^2.0.0"
path-type@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441"
dependencies:
graceful-fs "^4.1.2"
pify "^2.0.0"
pinkie-promise "^2.0.0"
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
pinkie-promise@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
dependencies:
pinkie "^2.0.0"
pinkie@^2.0.0:
version "2.0.4"
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
read-pkg-up@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02"
dependencies:
find-up "^1.0.0"
read-pkg "^1.0.0"
read-pkg@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28"
dependencies:
load-json-file "^1.0.0"
normalize-package-data "^2.3.2"
path-type "^1.0.0"
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
require-main-filename@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1"
"semver@2 || 3 || 4 || 5":
version "5.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
sigmund@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
spdx-correct@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82"
dependencies:
spdx-expression-parse "^3.0.0"
spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9"
spdx-expression-parse@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0"
dependencies:
spdx-exceptions "^2.1.0"
spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz#e2a303236cac54b04031fa7a5a79c7e701df852f"
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
dependencies:
code-point-at "^1.0.0"
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
strip-ansi@^3.0.0, strip-ansi@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
dependencies:
ansi-regex "^2.0.0"
strip-bom@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
dependencies:
is-utf8 "^0.2.0"
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
dependencies:
spdx-correct "^3.0.0"
spdx-expression-parse "^3.0.0"
which-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f"
wrap-ansi@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85"
dependencies:
string-width "^1.0.1"
strip-ansi "^3.0.1"
y18n@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"
yargs-parser@^4.2.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c"
dependencies:
camelcase "^3.0.0"
yargs@6.6.0:
version "6.6.0"
resolved "http://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208"
dependencies:
camelcase "^3.0.0"
cliui "^3.2.0"
decamelize "^1.1.1"
get-caller-file "^1.0.1"
os-locale "^1.4.0"
read-pkg-up "^1.0.1"
require-directory "^2.1.1"
require-main-filename "^1.0.1"
set-blocking "^2.0.0"
string-width "^1.0.2"
which-module "^1.0.0"
y18n "^3.2.1"
yargs-parser "^4.2.0"