116 Commits

Author SHA1 Message Date
Roman Shtylman
b7e450177b v2.0.0 2019-09-16 07:50:28 -07:00
Roman Shtylman
24475fef1b update yarn.lock with yarn 1.17.3 2019-09-16 07:49:24 -07:00
Gert Hengeveld
2a74d6be9f HTTPS support, Promise API, modern ES syntax
* Add support for tunneling a local HTTPS server.
* Return a Promise from localtunnel.
2019-09-16 07:30:13 -07:00
Roman Shtylman
d7330a7121 v1.9.2 2019-06-01 12:40:45 -07:00
Roman Shtylman
57549598f3 update History.md 2019-06-01 12:38:36 -07:00
Roman Shtylman
e9d92039bb update debug to 4.1.1 2019-06-01 12:37:26 -07:00
Roman Shtylman
be3dc1a4cc update axios to 0.19.0 2019-06-01 12:36:41 -07:00
Roman Shtylman
4d6db16235 update yarn.lock with integrity entries 2019-06-01 12:35:31 -07:00
Daniel Storgårds
41270db62f Update node versions and default build environment in travis.yml (#307)
* Update node versions in travis.yml
* Use xenial as the default build environment
2019-06-01 12:33:08 -07:00
Roman Shtylman
42ea8ecfa5 Bump to UNRELEASED 2018-09-08 08:23:02 -07:00
Roman Shtylman
5b313bdfaa v1.9.1 2018-09-08 08:21:07 -07:00
Roman Shtylman
ef78c1978e update History.md 2018-09-08 08:20:45 -07:00
Roman Shtylman
d0fc61ca97 update debug to 2.6.9
Resolves audit warning for https://www.npmjs.com/advisories/534

This issue does not directly affect localtunnel since it does not use
the `o` formatter.
2018-09-08 08:18:58 -07:00
Roman Shtylman
a4bf6d16e4 unreleased 2018-04-03 22:38:41 -07:00
Roman Shtylman
9a38b8de0f v1.9.0 2018-04-03 22:36:17 -07:00
Roman Shtylman
c524872323 update History.md 2018-04-03 22:20:39 -07:00
Roman Shtylman
2372ec22cc add _request_ event to print basic request information 2018-04-03 22:16:32 -07:00
Jonas Finnemann Jensen
db22a4efe0 README: add Reference to go-localtunnel (#206) 2018-04-03 22:03:18 -07:00
Roman Shtylman
610484b9d7 ci: add node 8 and 9 for testing 2018-04-02 21:41:47 -07:00
Roman Shtylman
86deca52f2 add yarn.lock 2018-04-01 20:42:45 -07:00
Jimmie
cb3441a339 Add ability to specify env variables (#161)
Update yargs dependency to enable the new functionality.
2018-04-01 20:41:53 -07:00
Daniel Kezerashvili
32fd1fdcbd Add basic request logging functionality (#178) 2018-04-01 20:36:49 -07:00
Roman Shtylman
14cac6f6c8 Add LICENSE 2018-04-01 20:34:37 -07:00
Ricardo Rosales
1f33d4992d Fix host default value message for CLI help text (#211)
per https://github.com/localtunnel/localtunnel/blob/master/lib/Tunnel.js#L17
2018-01-07 10:14:27 -08:00
C. K. Tang
578dc9aaae Replace 'request' with 'axios' (#214) 2018-01-07 10:13:22 -08:00
Tom Osowski
4c136a265c Spelling Fix in Readme (#193) 2017-12-10 08:28:31 -08:00
Roman Shtylman
627cfe4783 1.8.3 2017-06-11 17:20:10 -07:00
Roman Shtylman
dbe0c16024 Update history for 1.8.3 2017-06-11 17:20:00 -07:00
Roman Shtylman
c87bbe82e7 Update History.md 2017-06-11 17:18:23 -07:00
Roman Shtylman
c71ba81972 update request, debug, and openurl dependencies 2017-06-11 17:16:30 -07:00
Roman Shtylman
8efcb3a294 v1.8.2 2016-11-17 22:31:06 -08:00
Roman Shtylman
b9c1901d60 fix host header transform to support Host and host header 2016-11-17 22:28:34 -08:00
Roman Shtylman
edc182125f update request dep to 2.78 2016-11-17 22:27:57 -08:00
Sigve Sebastian Farstad
81c28d4d68 Fix typo in README.md (#143) 2016-09-06 06:58:25 -07:00
Roman Shtylman
9a1d48764a Merge pull request #113 from CircleCode/patch-1
[README] update the link to localtunnel server
2016-03-11 07:51:05 -08:00
Matthieu Codron
371db2870a [README] update the link to localtunnel server 2016-03-11 13:08:31 +01:00
Roman Shtylman
4ae493ae44 v1.8.1 2016-01-20 13:32:02 -08:00
Roman Shtylman
f487effe3a update History 2016-01-20 13:31:46 -08:00
Roman Shtylman
333af2b08f Merge pull request #108 from joshuaxls/master
Fix bug w/ HeaderHostTransformer and binary data
2016-01-20 13:29:50 -08:00
Joshua Slayton
b32041d8aa fix unnecessary binary->string conversion 2016-01-20 13:11:40 -08:00
Roman Shtylman
649de1b840 Merge pull request #107 from TooTallNate/patch-1
README: add missing back tick
2016-01-09 07:42:54 -08:00
Nathan Rajlich
f791217756 README: add missing back tick 2016-01-08 15:58:02 -08:00
Roman Shtylman
cce9d1490a update travis to node 4 and fix travis badge 2015-11-04 20:43:17 -08:00
Roman Shtylman
86cd2d3c58 v1.8.0 2015-11-04 08:04:54 -08:00
Roman Shtylman
d70c743014 update history 2015-11-04 08:04:29 -08:00
Roman Shtylman
4940043378 Merge pull request #102 from aronwoost/patch-01
Emit socket errors, so they can be handled
2015-11-04 07:54:45 -08:00
Aron Woost
b4a22bff64 Re-emit socket error 2015-11-04 12:04:07 +01:00
Roman Shtylman
176ec0479d Merge pull request #92 from cbas/master
Bumped deps to fix upstream SPDX license issues
2015-11-03 16:13:30 -08:00
Sebastiaan Deckers
fe316de3e0 Bumped deps to fix upstream SPDX license issues 2015-11-04 08:10:18 +08:00
Roman Shtylman
4f97434a69 v1.7.0 2015-07-22 17:19:32 -07:00
Roman Shtylman
a46cd02fcb update history 2015-07-22 17:19:15 -07:00
Roman Shtylman
792d9f19bd update history 2015-07-22 17:18:45 -07:00
Roman Shtylman
d0b483b92b Merge pull request #75 from artmees/master
Implement short argv via yargs
2015-07-22 17:16:49 -07:00
Ahmed Abdel Razzak
59d96a3cc6 Update the client to include shorthand options
Providing aliases for all of the supported options
-h, --host
-s, --subdomain
-l, --local-host
-o, --open
-p, --port
--help
--version

Minor clean up in the code removing the blocks that already handled
by yargs ( version, help and required options )

Handle validating that the passed port could be validated as a number

Remove the spaces in empty lines
2015-07-23 02:05:17 +02:00
Ahmed Abdel Razzak
b516ecccfa Replace optimist with yargs 2015-07-23 00:57:01 +02:00
Roman Shtylman
f68b1f06d9 1.6.0 2015-07-15 22:29:49 -07:00
Roman Shtylman
4d09875163 update history 2015-07-15 22:26:40 -07:00
Roman Shtylman
2773fe6923 Merge pull request #74 from davej/patch-1
Increase max event listeners to prevent premature memory-leak warnings
2015-07-15 22:24:47 -07:00
Roman Shtylman
457bd64ecc Merge pull request #85 from zeevl/keep-alive
Keep sockets alive after connecting
2015-07-15 15:19:35 -07:00
Steve Lamb
eb31659345 Keep sockets alive after connecting 2015-07-15 10:29:53 -07:00
Roman Shtylman
3ee8b1b884 Merge pull request #79 from Urucas/master
Add --open param to open tunnel url
2015-06-23 23:29:39 -07:00
vrunoa
15aac729bb add open param; opens url in your browser after localtunnel is launched
add open param; opens url in your browser after localtunnel is launched

update package version

multiplatform open with openurl package

update package.json

correct source code spacing & indentation

update openurl dependency

revert package version

move require('openurl') to the top, correct 4 spaces indentation
2015-06-23 17:10:28 -03:00
Dave Jeffery
e73cfe3e45 Increase max event listeners to prevent premature memory-leak warnings #71 2015-05-31 22:19:45 +01:00
Roman Shtylman
790a642a83 1.5.1 2015-05-26 09:28:59 -04:00
Roman Shtylman
e6539e1225 Merge pull request #72 from Lykathia/patch-license
package.json: Add license to package.json
2015-05-25 23:04:03 -04:00
Evan Lowry
4c0a5dc4eb Add license to package.json 2015-05-25 22:55:41 -03:00
Roman Shtylman
809262cf3d refactor into files 2014-12-21 17:46:38 -08:00
Roman Shtylman
ddb47d2f90 add .npmignore file 2014-10-25 17:21:59 -07:00
Roman Shtylman
a6845ec63b v1.5.0 2014-10-25 17:13:58 -07:00
Roman Shtylman
adecf03f41 update history 2014-10-25 17:13:49 -07:00
Roman Shtylman
9bdb40e97c when remote endpoint has an error, just close it 2014-10-25 17:05:19 -07:00
Roman Shtylman
006ce7733b v1.4.0 2014-08-31 22:59:17 -07:00
Roman Shtylman
624d279c26 History: update 2014-08-31 22:58:43 -07:00
Roman Shtylman
c2b8f2b7ab Merge pull request #57 from kesla/handle-etimedout
don't emit ETIMEDOUT-errors
2014-08-31 22:57:30 -07:00
David Björklund
4d9dcc1711 don't emit ETIMEDOUT-errors 2014-08-28 20:12:09 +02:00
Roman Shtylman
7b1fef982f 1.3.0 2014-05-13 20:33:41 -04:00
Roman Shtylman
b1ebef2b0b fix code style 2014-05-13 20:32:43 -04:00
Tymoteusz Paszun
9ad43778b7 fixed Host header replacement - remembers if header is already replaced and does not remove _transform method 2014-05-13 09:24:08 +02:00
Tymoteusz Paszun
887c444543 added test for chunked requests 2014-05-13 09:16:50 +02:00
Tymoteusz Paszun
828cb2afcb transform Host header when local-host is defined explicitly 2014-05-09 15:07:38 +02:00
Roman Shtylman
8768329fdd update history with two recent minor versions 2014-04-28 19:03:33 -04:00
Roman Shtylman
3026d6a42c 1.2.0 2014-04-28 19:00:12 -04:00
Roman Shtylman
abd461f83a Merge pull request #47 from dscape/patch-1
return client from 'localtunnel' api call
2014-04-28 18:58:54 -04:00
Nuno Job
2acea3d77f Return client
This allows manipulating the client from outside. Allowing, for example, to close a connection.
2014-04-28 23:32:00 +01:00
Roman Shtylman
5d0eb3382a add --version CLI flag to get version info 2014-04-22 19:55:19 -04:00
Roman Shtylman
3b67c8a8ce 1.1.2 2014-04-20 10:51:24 -04:00
Kevin Ingersoll
71552a336e Increase default Mocha timeout 2014-04-20 10:45:21 -04:00
Roman Shtylman
87a23bf28c fix status code check for url request 2014-04-19 19:33:52 -04:00
Roman Shtylman
3d54de851f handle errors from localtunnel server when requesting initial url 2014-04-19 19:30:44 -04:00
Roman Shtylman
92bb807908 fix typo
fixes #45
2014-04-18 09:34:48 -04:00
Roman Shtylman
afbdc3697e 1.1.1 2014-04-15 09:33:34 -04:00
Roman Shtylman
0049f21b55 Merge pull request #38 from LinusU/patch-1
re-throw client errors from bin/lt to let node handle it
2014-04-14 19:27:56 -04:00
Roman Shtylman
509841104b fix for RangeError stack size exceeded
This error would happen when there was a problem connecting to the local
server. The local.on('error') handler should have been a 'once' handler
because we emit the error again if it isn't a CONNREFUSED. So in the
case of a CONNRESET, it would trigger an infinite loop since the error
was being emitted back onto the local variable. Instead we just close
the remote socket and let a new one takes its place.

fixes #36
2014-04-14 15:36:42 -04:00
Linus Unnebäck
92caf2f204 lt: better error handling
Let node handle the displaying of error and setting exit code.
2014-04-14 21:15:11 +02:00
Roman Shtylman
9487797e02 1.1.0 2014-02-24 19:51:14 -05:00
Roman Shtylman
a42f6a8d8d document local-host option 2014-02-24 19:49:40 -05:00
Roman Shtylman
14b4bcb96f add tests for host header transform 2014-02-24 19:43:34 -05:00
Roman Shtylman
4aa65002eb use host header transform only when local-host is specified 2014-02-24 19:43:07 -05:00
Fredi Pevcin
08676ba81d Set Host header accordingly to local-host option 2014-02-24 00:26:38 +01:00
Roman Shtylman
174e7f3982 1.0.0 2014-02-14 00:37:45 -05:00
Roman Shtylman
44be55cd7b readme: fix markdown table
[ci skip]
2014-02-14 00:36:53 -05:00
Roman Shtylman
5c6cd2359c add travis badge
- remove node 0.8
- add node 0.10
2014-02-14 00:34:40 -05:00
Roman Shtylman
2f6f9459ad change main export signature to localtunnel(port, opt, fn)
Makes for a simpler hello world app
2014-02-14 00:32:43 -05:00
Roman Shtylman
7217a08a05 add history.md to track changes 2014-02-13 23:52:12 -05:00
Roman Shtylman
fbfc923a7e remove connect export in favor of single function
Since connect was the only function exported, we can just export the
function directly to make things simpler.
2014-02-13 23:51:29 -05:00
Roman Shtylman
d9bc11b520 default host to localtuunel.me
If no host specified, then default to localtunnel.me

close #31
2014-02-13 23:43:54 -05:00
Roman Shtylman
ad64611bd1 add tests 2014-02-13 23:41:49 -05:00
Roman Shtylman
ac70515143 0.2.2 2014-01-09 11:07:18 -05:00
Roman Shtylman
8d7ccccf21 remove local.unpipe() on remote close
This will happen automatically.

close #28
2014-01-09 11:06:58 -05:00
Roman Shtylman
77091b3d93 0.2.1 2013-12-31 17:34:04 -05:00
Roman Shtylman
4f4a147b45 don't unpipe on local close
Pipe will do this for us
2013-12-31 17:33:49 -05:00
Roman Shtylman
f1d809a84d 0.2.0 2013-12-31 15:39:16 -05:00
Roman Shtylman
eba003bd26 add a .close method to shutdown the tunnel 2013-12-31 15:38:45 -05:00
Roman Shtylman
3354c4c6e3 rework tunnel logic
Refactoring to make things a bit saner and easier to debug.
2013-12-05 11:26:19 -05:00
Roman Shtylman
1c2757e604 Merge pull request #27 from adammck/couldnt-establish-tunnel
Add more verbose error for ECONNREFUSED
2013-11-20 09:34:48 -08:00
Adam Mckaig
790e55e881 Add more verbose error for ECONNREFUSED
If the tunnel server can be reached (at e.g. http://localtunnel.me/?new)
but the tunnel (to e.g. grpi.localtunnel.me:44827) can't actually be
established, the client currently gets stuck in a loop retrying forever
with no indication as to what's wrong. This doesn't fix the loop, since
it does seem desirable to retry forever, but logs:

    [Error: connection refused: localtunnel.me:44827]
2013-11-20 12:29:31 -05:00
16 changed files with 1161 additions and 242 deletions

21
.eslintrc.js Normal file
View File

@@ -0,0 +1,21 @@
module.exports = {
parser: 'babel-eslint',
extends: ['airbnb', 'prettier', 'plugin:jest/recommended'],
plugins: ['prettier', 'jest'],
env: {
'jest/globals': true,
},
rules: {
'prettier/prettier': [
'warn',
{
printWidth: 100,
tabWidth: 2,
bracketSpacing: true,
trailingComma: 'es5',
singleQuote: true,
jsxBracketSameLine: false,
},
],
},
}

2
.gitignore vendored
View File

@@ -1 +1 @@
node_modules /node_modules

View File

@@ -1,4 +1,6 @@
dist: xenial
language: node_js language: node_js
node_js: node_js:
- 0.8 - "8"
- 0.9 - "10"
- "12"

73
CHANGELOG.md Normal file
View File

@@ -0,0 +1,73 @@
# 2.0.0 (2019-09-16)
- Add support for tunneling a local HTTPS server
- Add support for localtunnel server with IP-based tunnel URLs
- Node.js client API is now Promise-based, with backwards compatibility to callback
- Major refactor of entire codebase using modern ES syntax (requires Node.js v8.3.0 or above)
# 1.9.2 (2019-06-01)
- Update debug to 4.1.1
- Update axios to 0.19.0
# 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
# 1.2.0 / 2014-04-28
- return `client` from `localtunnel` API instantiation
# 1.1.0 / 2014-02-24
- add a host header transform to change the 'Host' header in requests
# 1.0.0 / 2014-02-14
- default to localltunnel.me for host
- remove exported `connect` method (just export one function that does the same thing)
- change localtunnel signature to (port, opt, fn)
# 0.2.2 / 2014-01-09

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
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.

113
README.md
View File

@@ -1,20 +1,32 @@
# localtunnel # # 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. 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.
Great for working with browser testing tools like browserling or external api callback services like twilio which require a public url for callbacks. Great for working with browser testing tools like browserling or external api callback services like twilio which require a public url for callbacks.
## installation ## ## Quickstart
```
npx localtunnel --port 8000
```
## Installation
### Globally
``` ```
npm install -g localtunnel npm install -g localtunnel
``` ```
This will install the localtunnel module globally and add the 'lt' client cli tool to your PATH. ### As a dependency in your project
## use ## ```
yarn add localtunnel
```
Super Easy! Assuming your local server is running on port 8000, just use the ```lt``` command to start the tunnel. ## CLI usage
When localtunnel is installed globally, just use the `lt` command to start the tunnel.
``` ```
lt --port 8000 lt --port 8000
@@ -22,42 +34,87 @@ lt --port 8000
Thats it! It will connect to the tunnel server, setup the tunnel, and tell you what url to use for your testing. This url will remain active for the duration of your session; so feel free to share it with others for happy fun time! Thats it! It will connect to the tunnel server, setup the tunnel, and tell you what url to use for your testing. This url will remain active for the duration of your session; so feel free to share it with others for happy fun time!
You can restart your local server all you want, ```lt``` is smart enough to detect this and reconnect once it is back. You can restart your local server all you want, `lt` is smart enough to detect this and reconnect once it is back.
## API ## ### Arguments
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) The localtunnel client is also usable through an API (for test integration, automation, etc)
```javascript ### localtunnel(port [,options][,callback])
var localtunnel = require('localtunnel');
var client = localtunnel.connect({ Creates a new localtunnel to the specified local `port`. Will return a Promise that resolves once you have been assigned a public localtunnel url. `options` can be used to request a specific `subdomain`. A `callback` function can be passed, in which case it won't return a Promise. This exists for backwards compatibility with the old Node-style callback API. You may also pass a single options object with `port` as a property.
// the localtunnel server
host: 'http://localtunnel.me',
// your local application port
port: 12345
});
// when your are assigned a url ```js
client.on('url', function(url) { const localtunnel = require('localtunnel');
// you can now make http requests to the url
// they will be proxied to your local server on port [12345]
});
client.on('error', function(err) { (async () => {
// uh oh! const tunnel = await localtunnel({ port: 3000 });
});
// the assigned public url for your tunnel
// i.e. https://abcdefgjhij.localtunnel.me
tunnel.url;
tunnel.on('close', () => {
// tunnels are closed
});
})();
``` ```
## other clients ## #### options
- `port` (number) [required] The local port number to expose through localtunnel.
- `subdomain` (string) Request a specific subdomain on the proxy server. **Note** You may not actually receive this name depending on availability.
- `host` (string) URL for the upstream proxy server. Defaults to `https://localtunnel.me`.
- `local_host` (string) Proxy to this hostname instead of `localhost`. This will also cause the `Host` header to be re-written to this value in proxied requests.
- `local_https` (boolean) Enable tunneling to local HTTPS server.
- `local_cert` (string) Path to certificate PEM file for local HTTPS server.
- `local_key` (string) Path to certificate key file for local HTTPS server.
- `local_ca` (string) Path to certificate authority file for self-signed certificates.
- `allow_invalid_cert` (boolean) Disable certificate checks for your local HTTPS server (ignore cert/key/ca options).
Refer to [tls.createSecureContext](https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options) for details on the certificate options.
### Tunnel
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
| method | args | description |
| ------ | ---- | ---------------- |
| close | | close the tunnel |
## other clients
Clients in other languages Clients in other languages
*go* [gotunnelme](https://github.com/NoahShen/gotunnelme) _go_ [gotunnelme](https://github.com/NoahShen/gotunnelme)
## server ## _go_ [go-localtunnel](https://github.com/localtunnel/go-localtunnel)
See shtylman/localtunnel-server for details on the server that powers localtunnel. ## server
See [localtunnel/server](//github.com/localtunnel/server) for details on the server that powers localtunnel.
## License
## License ##
MIT MIT

View File

@@ -1,39 +0,0 @@
#!/usr/bin/env node
var lt_client = require('../client');
var argv = require('optimist')
.usage('Usage: $0 --port [num]')
.demand(['port'])
.options('host', {
default: 'http://localtunnel.me',
describe: 'upstream server providing forwarding'
})
.options('subdomain', {
describe: 'request this subdomain'
})
.options('local-host', {
describe: 'tunnel traffic to this host instead of localhost'
})
.default('local-host', 'localhost')
.describe('port', 'internal http server port')
.argv;
var opt = {
host: argv.host,
port: argv.port,
local_host: argv['local-host'],
subdomain: argv.subdomain,
}
var client = lt_client.connect(opt);
// only emitted when the url changes
client.on('url', function(url) {
console.log('your url is: %s', url);
});
client.on('error', function(err) {
console.error(err);
});
// vim: ft=javascript

104
bin/lt.js Executable file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env node
/* eslint-disable no-console */
const openurl = require('openurl');
const yargs = require('yargs');
const localtunnel = require('../localtunnel');
const { version } = require('../package');
const { argv } = yargs
.usage('Usage: lt --port [num] <options>')
.env(true)
.option('p', {
alias: 'port',
describe: 'Internal HTTP server port',
})
.option('h', {
alias: 'host',
describe: 'Upstream server providing forwarding',
default: 'https://localtunnel.me',
})
.option('s', {
alias: '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',
})
.option('local-https', {
describe: 'Tunnel traffic to a local HTTPS server',
})
.option('local-cert', {
describe: 'Path to certificate PEM file for local HTTPS server',
})
.option('local-key', {
describe: 'Path to certificate key file for local HTTPS server',
})
.option('local-ca', {
describe: 'Path to certificate authority file for self-signed certificates',
})
.option('allow-invalid-cert', {
describe: 'Disable certificate checks for your local HTTPS server (ignore cert/key/ca options)',
})
.options('o', {
alias: 'open',
describe: 'Opens the tunnel URL in your browser',
})
.option('print-requests', {
describe: 'Print basic request info',
})
.require('port')
.boolean('local-https')
.boolean('allow-invalid-cert')
.boolean('print-requests')
.help('help', 'Show this help and exit')
.version(version);
if (typeof argv.port !== 'number') {
yargs.showHelp();
console.error('\nInvalid argument: `port` must be a number');
process.exit(1);
}
(async () => {
const tunnel = await localtunnel({
port: argv.port,
host: argv.host,
subdomain: argv.subdomain,
local_host: argv.localHost,
local_https: argv.localHttps,
local_cert: argv.localCert,
local_key: argv.localKey,
local_ca: argv.localCa,
allow_invalid_cert: argv.allowInvalidCert,
}).catch(err => {
throw err;
});
tunnel.on('error', err => {
throw err;
});
console.log('your url is: %s', tunnel.url);
/**
* `cachedUrl` is set when using a proxy server that support resource caching.
* This URL generally remains available after the tunnel itself has closed.
* @see https://github.com/localtunnel/localtunnel/pull/319#discussion_r319846289
*/
if (tunnel.cachedUrl) {
console.log('your cachedUrl is: %s', tunnel.cachedUrl);
}
if (argv.open) {
openurl.open(tunnel.url);
}
if (argv['print-requests']) {
tunnel.on('request', info => {
console.log(new Date().toString(), info.method, info.path);
});
}
})();

160
client.js
View File

@@ -1,160 +0,0 @@
var net = require('net');
var url = require('url');
var EventEmitter = require('events').EventEmitter;
var after = require('after');
var request = require('request');
// request upstream url and connection info
var request_url = function(params, cb) {
request(params, function(err, res, body) {
if (err) {
return cb(err);
}
cb(null, body);
});
};
var connect = function(opt) {
var ev = new EventEmitter();
// local host
var local_host = opt.local_host;
// local port
var local_port = opt.port;
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;
// connect to upstream given connection parameters
var tunnel = function (remote_host, remote_port, dead) {
var remote_opt = {
host: remote_host,
port: remote_port
};
var local_opt = {
host: local_host,
port: local_port
};
var remote_attempts = 0;
(function conn(conn_had_error) {
if (conn_had_error) {
return;
}
// we need a new tunnel
if (++remote_attempts >= 3) {
return dead();
}
// connection to localtunnel server
var remote = net.connect(remote_opt);
remote.once('error', function(err) {
if (err.code !== 'ECONNREFUSED') {
remote.emit('error', err);
}
// retrying connection to local server
setTimeout(conn, 1000);
});
function recon_local() {
remote.pause();
remote_attempts = 0;
// connection to local http server
var local = net.connect(local_opt);
local.once('error', function(err) {
if (err.code !== 'ECONNREFUSED') {
local.emit('error', err);
}
// retrying connection to local server
setTimeout(recon_local, 1000);
});
local.once('connect', function() {
remote.resume();
remote.pipe(local).pipe(remote, {end: false});
});
local.once('close', function(had_error) {
if (had_error) {
return;
}
recon_local();
});
}
remote.once('close', conn);
remote.once('connect', recon_local);
})();
};
var params = {
path: '/',
json: true
};
// where to quest
params.uri = base_uri + ((assigned_domain) ? assigned_domain : '?new');
function init_tunnel() {
// get an id from lt server and setup forwarding tcp connections
request_url(params, function(err, body) {
if (err) {
ev.emit('error', new Error('tunnel server not available: ' + err.message + ', retry 1s'));
// retry interval for id request
return setTimeout(function() {
init_tunnel();
}, 1000);
}
// our assigned hostname and tcp port
var port = body.port;
var host = upstream.hostname;
// store the id so we can try to get the same one
assigned_domain = body.id;
var max_conn = body.max_conn_count || 1;
// after all our tunnels die, we ask for new ones
// this might happen if the upstream server dies
var dead = after(max_conn, function() {
init_tunnel();
});
for (var count = 0 ; count < max_conn ; ++count) {
tunnel(host, port, dead);
}
ev.emit('url', body.url);
});
}
init_tunnel();
return ev;
};
module.exports.connect = connect;
// for backwards compatibility
// old localtunnel modules had server and client code in same module
// so to keep .client working we expose it here
module.exports.client = module.exports;

View File

@@ -0,0 +1,23 @@
const { Transform } = require('stream');
class HeaderHostTransformer extends Transform {
constructor(opts = {}) {
super(opts);
this.host = opts.host || 'localhost';
this.replaced = false;
}
_transform(data, encoding, callback) {
callback(
null,
this.replaced // after replacing the first instance of the Host header we just become a regular passthrough
? data
: data.toString().replace(/(\r\n[Hh]ost: )\S+/, (match, $1) => {
this.replaced = true;
return $1 + this.host;
})
);
}
}
module.exports = HeaderHostTransformer;

163
lib/Tunnel.js Normal file
View File

@@ -0,0 +1,163 @@
/* eslint-disable consistent-return, no-underscore-dangle */
const { parse } = require('url');
const { EventEmitter } = require('events');
const axios = require('axios');
const debug = require('debug')('localtunnel:client');
const TunnelCluster = require('./TunnelCluster');
module.exports = class Tunnel extends EventEmitter {
constructor(opts = {}) {
super(opts);
this.opts = opts;
this.closed = false;
if (!this.opts.host) {
this.opts.host = 'https://localtunnel.me';
}
}
_getInfo(body) {
/* eslint-disable camelcase */
const { id, ip, port, url, cached_url, max_conn_count } = body;
const { host, port: local_port, local_host } = this.opts;
const { local_https, local_cert, local_key, local_ca, allow_invalid_cert } = this.opts;
return {
name: id,
url,
cached_url,
max_conn: max_conn_count || 1,
remote_host: parse(host).hostname,
remote_ip: ip,
remote_port: port,
local_port,
local_host,
local_https,
local_cert,
local_key,
local_ca,
allow_invalid_cert,
};
/* eslint-enable camelcase */
}
// initialize connection
// callback with connection info
_init(cb) {
const opt = this.opts;
const getInfo = this._getInfo.bind(this);
const params = {
responseType: 'json',
};
const baseUri = `${opt.host}/`;
// no subdomain at first, maybe use requested domain
const assignedDomain = opt.subdomain;
// where to quest
const uri = baseUri + (assignedDomain || '?new');
(function getUrl() {
axios
.get(uri, params)
.then(res => {
const body = res.data;
debug('got tunnel information', res.data);
if (res.status !== 200) {
const err = new Error(
(body && body.message) || 'localtunnel server returned an error, please try again'
);
return cb(err);
}
cb(null, getInfo(body));
})
.catch(err => {
debug(`tunnel server offline: ${err.message}, retry 1s`);
return setTimeout(getUrl, 1000);
});
})();
}
_establish(info) {
// increase max event listeners so that localtunnel consumers don't get
// warning messages as soon as they setup even one listener. See #71
this.setMaxListeners(info.max_conn + (EventEmitter.defaultMaxListeners || 10));
this.tunnelCluster = new TunnelCluster(info);
// only emit the url the first time
this.tunnelCluster.once('open', () => {
this.emit('url', info.url);
});
// re-emit socket error
this.tunnelCluster.on('error', err => {
debug('got socket error', err.message);
this.emit('error', err);
});
let tunnelCount = 0;
// track open count
this.tunnelCluster.on('open', tunnel => {
tunnelCount++;
debug('tunnel open [total: %d]', tunnelCount);
const closeHandler = () => {
tunnel.destroy();
};
if (this.closed) {
return closeHandler();
}
this.once('close', closeHandler);
tunnel.once('close', () => {
this.removeListener('close', closeHandler);
});
});
// when a tunnel dies, open a new one
this.tunnelCluster.on('dead', () => {
tunnelCount--;
debug('tunnel dead [total: %d]', tunnelCount);
if (this.closed) {
return;
}
this.tunnelCluster.open();
});
this.tunnelCluster.on('request', req => {
this.emit('request', req);
});
// establish as many tunnels as allowed
for (let count = 0; count < info.max_conn; ++count) {
this.tunnelCluster.open();
}
}
open(cb) {
this._init((err, info) => {
if (err) {
return cb(err);
}
this.clientId = info.name;
this.url = info.url;
// `cached_url` is only returned by proxy servers that support resource caching.
if (info.cached_url) {
this.cachedUrl = info.cached_url;
}
this._establish(info);
cb();
});
}
close() {
this.closed = true;
this.emit('close');
}
};

152
lib/TunnelCluster.js Normal file
View File

@@ -0,0 +1,152 @@
const { EventEmitter } = require('events');
const debug = require('debug')('localtunnel:client');
const fs = require('fs');
const net = require('net');
const tls = require('tls');
const HeaderHostTransformer = require('./HeaderHostTransformer');
// manages groups of tunnels
module.exports = class TunnelCluster extends EventEmitter {
constructor(opts = {}) {
super(opts);
this.opts = opts;
}
open() {
const opt = this.opts;
// Prefer IP if returned by the server
const remoteHostOrIp = opt.remote_ip || opt.remote_host;
const remotePort = opt.remote_port;
const localHost = opt.local_host || 'localhost';
const localPort = opt.local_port;
const localProtocol = opt.local_https ? 'https' : 'http';
const allowInvalidCert = opt.allow_invalid_cert;
debug(
'establishing tunnel %s://%s:%s <> %s:%s',
localProtocol,
localHost,
localPort,
remoteHostOrIp,
remotePort
);
// connection to localtunnel server
const remote = net.connect({
host: remoteHostOrIp,
port: remotePort,
});
remote.setKeepAlive(true);
remote.on('error', err => {
debug('got remote connection error', err.message);
// emit connection refused errors immediately, because they
// indicate that the tunnel can't be established.
if (err.code === 'ECONNREFUSED') {
this.emit(
'error',
new Error(
`connection refused: ${remoteHostOrIp}:${remotePort} (check your firewall settings)`
)
);
}
remote.end();
});
const connLocal = () => {
if (remote.destroyed) {
debug('remote destroyed');
this.emit('dead');
return;
}
debug('connecting locally to %s://%s:%d', localProtocol, localHost, localPort);
remote.pause();
if (allowInvalidCert) {
debug('allowing invalid certificates');
}
const getLocalCertOpts = () =>
allowInvalidCert
? { rejectUnauthorized: false }
: {
cert: fs.readFileSync(opt.local_cert),
key: fs.readFileSync(opt.local_key),
ca: opt.local_ca ? [fs.readFileSync(opt.local_ca)] : undefined,
};
// connection to local http server
const local = opt.local_https
? tls.connect({ host: localHost, port: localPort, ...getLocalCertOpts() })
: net.connect({ host: localHost, port: localPort });
const remoteClose = () => {
debug('remote close');
this.emit('dead');
local.end();
};
remote.once('close', remoteClose);
// 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', err => {
debug('local error %s', err.message);
local.end();
remote.removeListener('close', remoteClose);
if (err.code !== 'ECONNREFUSED') {
return remote.end();
}
// retrying connection to local server
setTimeout(connLocal, 1000);
});
local.once('connect', () => {
debug('connected locally');
remote.resume();
let 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(new HeaderHostTransformer({ host: opt.local_host }));
}
stream.pipe(local).pipe(remote);
// when local closes, also get a new remote
local.once('close', hadError => {
debug('local connection closed [%s]', hadError);
});
});
};
remote.on('data', data => {
const match = data.toString().match(/^(\w+) (\S+)/);
if (match) {
this.emit('request', {
method: match[1],
path: match[2],
});
}
});
// tunnel is considered open when remote connects
remote.once('connect', () => {
this.emit('open', remote);
connLocal();
});
}
};

14
localtunnel.js Normal file
View File

@@ -0,0 +1,14 @@
const Tunnel = require('./lib/Tunnel');
module.exports = function localtunnel(arg1, arg2, arg3) {
const options = typeof arg1 === 'object' ? arg1 : { ...arg2, port: arg1 };
const callback = typeof arg1 === 'object' ? arg2 : arg3;
const client = new Tunnel(options);
if (callback) {
client.open(err => (err ? callback(err) : callback(null, client)));
return client;
}
return new Promise((resolve, reject) =>
client.open(err => (err ? reject(err) : resolve(client)))
);
};

162
localtunnel.spec.js Normal file
View File

@@ -0,0 +1,162 @@
/* eslint-disable no-console */
const crypto = require('crypto');
const http = require('http');
const https = require('https');
const url = require('url');
const assert = require('assert');
const localtunnel = require('./localtunnel');
let fakePort;
before(done => {
const server = http.createServer();
server.on('request', (req, res) => {
res.write(req.headers.host);
res.end();
});
server.listen(() => {
const { port } = server.address();
fakePort = port;
done();
});
});
test('query localtunnel server w/ ident', async done => {
const tunnel = await localtunnel({ port: fakePort });
assert.ok(new RegExp('^https://.*localtunnel.me$').test(tunnel.url));
const parsed = url.parse(tunnel.url);
const opt = {
host: parsed.host,
port: 443,
headers: { host: parsed.hostname },
path: '/',
};
const req = https.request(opt, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', chunk => {
body += chunk;
});
res.on('end', () => {
assert(/.*[.]localtunnel[.]me/.test(body), body);
tunnel.close();
done();
});
});
req.end();
});
test('request specific domain', async () => {
const subdomain = Math.random()
.toString(36)
.substr(2);
const tunnel = await localtunnel({ port: fakePort, subdomain });
assert.ok(new RegExp(`^https://${subdomain}.localtunnel.me$`).test(tunnel.url));
tunnel.close();
});
describe('--local-host localhost', () => {
test('override Host header with local-host', async done => {
const tunnel = await localtunnel({ port: fakePort, local_host: 'localhost' });
assert.ok(new RegExp('^https://.*localtunnel.me$').test(tunnel.url));
const parsed = url.parse(tunnel.url);
const opt = {
host: parsed.host,
port: 443,
headers: { host: parsed.hostname },
path: '/',
};
const req = https.request(opt, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', chunk => {
body += chunk;
});
res.on('end', () => {
assert.equal(body, 'localhost');
tunnel.close();
done();
});
});
req.end();
});
});
describe('--local-host 127.0.0.1', () => {
test('override Host header with local-host', async done => {
const tunnel = await localtunnel({ port: fakePort, local_host: '127.0.0.1' });
assert.ok(new RegExp('^https://.*localtunnel.me$').test(tunnel.url));
const parsed = url.parse(tunnel.url);
const opt = {
host: parsed.host,
port: 443,
headers: {
host: parsed.hostname,
},
path: '/',
};
const req = https.request(opt, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', chunk => {
body += chunk;
});
res.on('end', () => {
assert.equal(body, '127.0.0.1');
tunnel.close();
done();
});
});
req.end();
});
test('send chunked request', async done => {
const tunnel = await localtunnel({ port: fakePort, local_host: '127.0.0.1' });
assert.ok(new RegExp('^https://.*localtunnel.me$').test(tunnel.url));
const parsed = url.parse(tunnel.url);
const opt = {
host: parsed.host,
port: 443,
headers: {
host: parsed.hostname,
'Transfer-Encoding': 'chunked',
},
path: '/',
};
const req = https.request(opt, res => {
res.setEncoding('utf8');
let body = '';
res.on('data', chunk => {
body += chunk;
});
res.on('end', () => {
assert.equal(body, '127.0.0.1');
tunnel.close();
done();
});
});
req.end(crypto.randomBytes(1024 * 8).toString('base64'));
});
});

View File

@@ -1,20 +1,35 @@
{ {
"author": "Roman Shtylman <shtylman@gmail.com>",
"name": "localtunnel", "name": "localtunnel",
"description": "expose localhost to the world", "description": "Expose localhost to the world",
"version": "0.1.3", "version": "2.0.0",
"license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/shtylman/localtunnel.git" "url": "git://github.com/localtunnel/localtunnel.git"
},
"author": "Roman Shtylman <shtylman@gmail.com>",
"contributors": [
"Roman Shtylman <shtylman@gmail.com>",
"Gert Hengeveld <gert@hichroma.com>",
"Tom Coleman <tom@hichroma.com>"
],
"main": "./localtunnel.js",
"bin": {
"lt": "./bin/lt.js"
},
"scripts": {
"test": "mocha --ui qunit --reporter list --timeout 60000 -- *.spec.js"
}, },
"dependencies": { "dependencies": {
"request": "2.11.4", "axios": "0.19.0",
"optimist": "0.3.4", "debug": "4.1.1",
"after": "0.8.1" "openurl": "1.1.1",
"yargs": "13.3.0"
}, },
"devDependencies": {}, "devDependencies": {
"bin": { "mocha": "~1.17.0"
"lt": "./bin/client"
}, },
"main": "./client.js" "engines": {
"node": ">=8.3.0"
}
} }

311
yarn.lock Normal file
View File

@@ -0,0 +1,311 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
ansi-styles@^3.2.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
dependencies:
color-convert "^1.9.0"
axios@0.19.0:
version "0.19.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8"
integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==
dependencies:
follow-redirects "1.5.10"
is-buffer "^2.0.2"
camelcase@^5.0.0:
version "5.3.1"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
cliui@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==
dependencies:
string-width "^3.1.0"
strip-ansi "^5.2.0"
wrap-ansi "^5.1.0"
color-convert@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
dependencies:
color-name "1.1.3"
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
commander@0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-0.6.1.tgz#fa68a14f6a945d54dbbe50d8cdb3320e9e3b1a06"
integrity sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=
commander@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.0.0.tgz#d1b86f901f8b64bd941bdeadaf924530393be928"
integrity sha1-0bhvkB+LZL2UG96tr5JFMDk76Sg=
debug@*, debug@4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
dependencies:
ms "^2.1.1"
debug@=3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
dependencies:
ms "2.0.0"
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
diff@1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/diff/-/diff-1.0.7.tgz#24bbb001c4a7d5522169e7cabdb2c2814ed91cf4"
integrity sha1-JLuwAcSn1VIhaefKvbLCgU7ZHPQ=
emoji-regex@^7.0.1:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
find-up@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73"
integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==
dependencies:
locate-path "^3.0.0"
follow-redirects@1.5.10:
version "1.5.10"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
dependencies:
debug "=3.1.0"
get-caller-file@^2.0.1:
version "2.0.5"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
glob@3.2.3:
version "3.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-3.2.3.tgz#e313eeb249c7affaa5c475286b0e115b59839467"
integrity sha1-4xPusknHr/qlxHUoaw4RW1mDlGc=
dependencies:
graceful-fs "~2.0.0"
inherits "2"
minimatch "~0.2.11"
graceful-fs@~2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-2.0.3.tgz#7cd2cdb228a4a3f36e95efa6cc142de7d1a136d0"
integrity sha1-fNLNsiiko/Nule+mzBQt59GhNtA=
growl@1.7.x:
version "1.7.0"
resolved "https://registry.yarnpkg.com/growl/-/growl-1.7.0.tgz#de2d66136d002e112ba70f3f10c31cf7c350b2da"
integrity sha1-3i1mE20ALhErpw8/EMMc98NQsto=
inherits@2:
version "2.0.4"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-buffer@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
is-fullwidth-code-point@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
jade@0.26.3:
version "0.26.3"
resolved "https://registry.yarnpkg.com/jade/-/jade-0.26.3.tgz#8f10d7977d8d79f2f6ff862a81b0513ccb25686c"
integrity sha1-jxDXl32NefL2/4YqgbBRPMslaGw=
dependencies:
commander "0.6.1"
mkdirp "0.3.0"
locate-path@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e"
integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==
dependencies:
p-locate "^3.0.0"
path-exists "^3.0.0"
lru-cache@2:
version "2.7.3"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.7.3.tgz#6d4524e8b955f95d4f5b58851ce21dd72fb4e952"
integrity sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=
minimatch@~0.2.11:
version "0.2.14"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a"
integrity sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=
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"
integrity sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=
mkdirp@0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.5.tgz#de3e5f8961c88c787ee1368df849ac4413eca8d7"
integrity sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=
mocha@~1.17.0:
version "1.17.1"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-1.17.1.tgz#7f7671d68526d074b7bae660c9099f87e0ea1ccb"
integrity sha1-f3Zx1oUm0HS3uuZgyQmfh+DqHMs=
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"
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
ms@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
openurl@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.1.tgz#3875b4b0ef7a52c156f0db41d4609dbb0f94b387"
integrity sha1-OHW0sO96UsFW8NtB1GCduw+Us4c=
p-limit@^2.0.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.1.tgz#aa07a788cc3151c939b5131f63570f0dd2009537"
integrity sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==
dependencies:
p-try "^2.0.0"
p-locate@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4"
integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==
dependencies:
p-limit "^2.0.0"
p-try@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
path-exists@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515"
integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-main-filename@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
sigmund@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590"
integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=
string-width@^3.0.0, string-width@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==
dependencies:
emoji-regex "^7.0.1"
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
dependencies:
ansi-regex "^4.1.0"
which-module@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
wrap-ansi@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"
integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==
dependencies:
ansi-styles "^3.2.0"
string-width "^3.0.0"
strip-ansi "^5.0.0"
y18n@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
yargs-parser@^13.1.1:
version "13.1.1"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0"
integrity sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==
dependencies:
camelcase "^5.0.0"
decamelize "^1.2.0"
yargs@13.3.0:
version "13.3.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83"
integrity sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==
dependencies:
cliui "^5.0.0"
find-up "^3.0.0"
get-caller-file "^2.0.1"
require-directory "^2.1.1"
require-main-filename "^2.0.0"
set-blocking "^2.0.0"
string-width "^3.0.0"
which-module "^2.0.0"
y18n "^4.0.0"
yargs-parser "^13.1.1"