@o-town/local.dev 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscode/settings.json +1 -0
- package/README.md +144 -0
- package/bin/cli.js +136 -0
- package/example.js +37 -0
- package/lib/index.js +54 -0
- package/lib/tunnel.js +72 -0
- package/lib/ui.js +40 -0
- package/package.json +47 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# local.dev
|
|
2
|
+
|
|
3
|
+
> Expose your local website to the internet — instantly. No signup, no config.
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npx local.dev 3000
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g local.dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or use without installing:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npx local.dev 3000
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## CLI Usage
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
local.dev [port] [options]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Arguments
|
|
32
|
+
|
|
33
|
+
| Argument | Description | Default |
|
|
34
|
+
|----------|--------------------------|---------|
|
|
35
|
+
| `port` | Local port to expose | `3000` |
|
|
36
|
+
|
|
37
|
+
### Options
|
|
38
|
+
|
|
39
|
+
| Flag | Alias | Description |
|
|
40
|
+
|-----------------------|-------|------------------------------------------|
|
|
41
|
+
| `--subdomain` | `-s` | Request a custom subdomain |
|
|
42
|
+
| `--host` | `-H` | Local host to tunnel (default: localhost)|
|
|
43
|
+
| `--qr` | `-q` | Show QR code for the public URL |
|
|
44
|
+
| `--open` | `-o` | Open tunnel URL in your browser |
|
|
45
|
+
| `--print-requests` | `-r` | Log incoming HTTP requests |
|
|
46
|
+
| `--help` | `-h` | Show help |
|
|
47
|
+
| `--version` | `-v` | Show version number |
|
|
48
|
+
|
|
49
|
+
### Examples
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
# Expose port 3000
|
|
53
|
+
local.dev 3000
|
|
54
|
+
|
|
55
|
+
# Expose port 8080 with a custom subdomain
|
|
56
|
+
local.dev 8080 --subdomain myapp
|
|
57
|
+
|
|
58
|
+
# Show QR code (great for mobile testing)
|
|
59
|
+
local.dev 3000 --qr
|
|
60
|
+
|
|
61
|
+
# Open tunnel URL in browser + log requests
|
|
62
|
+
local.dev 3000 --open --print-requests
|
|
63
|
+
|
|
64
|
+
# Expose a non-localhost server
|
|
65
|
+
local.dev 3000 --host 192.168.1.10
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Programmatic API
|
|
71
|
+
|
|
72
|
+
You can also use `local.dev` as a Node.js library:
|
|
73
|
+
|
|
74
|
+
```js
|
|
75
|
+
const { tunnel } = require('local.dev');
|
|
76
|
+
|
|
77
|
+
async function main() {
|
|
78
|
+
// Simple: just pass a port
|
|
79
|
+
const t = await tunnel(3000);
|
|
80
|
+
console.log('Public URL:', t.url);
|
|
81
|
+
|
|
82
|
+
// Stop the tunnel when done
|
|
83
|
+
t.close();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
main();
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### With options
|
|
90
|
+
|
|
91
|
+
```js
|
|
92
|
+
const t = await tunnel({
|
|
93
|
+
port: 8080,
|
|
94
|
+
subdomain: 'myapp',
|
|
95
|
+
host: 'localhost',
|
|
96
|
+
printRequests: true,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log(t.url); // https://myapp.loca.lt
|
|
100
|
+
|
|
101
|
+
t.on('request', (info) => {
|
|
102
|
+
console.log(info.method, info.path, info.statusCode);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
t.on('close', () => {
|
|
106
|
+
console.log('Tunnel closed');
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### API Reference
|
|
111
|
+
|
|
112
|
+
#### `tunnel(port, [options])` → `Promise<TunnelHandle>`
|
|
113
|
+
#### `tunnel(options)` → `Promise<TunnelHandle>`
|
|
114
|
+
|
|
115
|
+
**Options:**
|
|
116
|
+
|
|
117
|
+
| Property | Type | Description |
|
|
118
|
+
|-----------------|---------|--------------------------------------|
|
|
119
|
+
| `port` | number | **Required.** Local port to expose |
|
|
120
|
+
| `host` | string | Local host (default: `'localhost'`) |
|
|
121
|
+
| `subdomain` | string | Requested subdomain |
|
|
122
|
+
| `printRequests` | boolean | Log requests (default: `false`) |
|
|
123
|
+
|
|
124
|
+
#### `TunnelHandle`
|
|
125
|
+
|
|
126
|
+
| Property/Method | Description |
|
|
127
|
+
|-----------------|------------------------------|
|
|
128
|
+
| `.url` | The public HTTPS URL |
|
|
129
|
+
| `.close()` | Close the tunnel |
|
|
130
|
+
| `.on('request', cb)` | Fired on each HTTP request |
|
|
131
|
+
| `.on('close', cb)` | Fired when tunnel closes |
|
|
132
|
+
| `.on('error', cb)` | Fired on tunnel errors |
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## How it works
|
|
137
|
+
|
|
138
|
+
`local.dev` creates a secure tunnel from a public URL to your local machine using the [localtunnel](https://github.com/localtunnel/localtunnel) protocol. Incoming requests hit the public URL and are proxied through to your local server — no port forwarding or firewall changes needed.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const yargs = require('yargs/yargs');
|
|
6
|
+
const { hideBin } = require('yargs/helpers');
|
|
7
|
+
const chalk = require('chalk');
|
|
8
|
+
const ora = require('ora');
|
|
9
|
+
const qrcode = require('qrcode-terminal');
|
|
10
|
+
const { createTunnel } = require('../lib/tunnel');
|
|
11
|
+
const { printBanner, printSuccess, printInfo, printError } = require('../lib/ui');
|
|
12
|
+
const updateNotifier = require('update-notifier');
|
|
13
|
+
const pkg = require('../package.json');
|
|
14
|
+
|
|
15
|
+
// Check for updates silently
|
|
16
|
+
updateNotifier({ pkg }).notify();
|
|
17
|
+
|
|
18
|
+
const argv = yargs(hideBin(process.argv))
|
|
19
|
+
.usage(`\n${chalk.cyan('local.dev')} — expose your local server to the internet\n\nUsage: $0 [port] [options]`)
|
|
20
|
+
.command('$0 [port]', 'Start a tunnel to your local server', (yargs) => {
|
|
21
|
+
yargs.positional('port', {
|
|
22
|
+
describe: 'Local port to expose',
|
|
23
|
+
type: 'number',
|
|
24
|
+
default: 3000,
|
|
25
|
+
});
|
|
26
|
+
})
|
|
27
|
+
.option('subdomain', {
|
|
28
|
+
alias: 's',
|
|
29
|
+
type: 'string',
|
|
30
|
+
describe: 'Request a specific subdomain (e.g. myapp)',
|
|
31
|
+
})
|
|
32
|
+
.option('host', {
|
|
33
|
+
alias: 'H',
|
|
34
|
+
type: 'string',
|
|
35
|
+
describe: 'Local host to tunnel (default: localhost)',
|
|
36
|
+
default: 'localhost',
|
|
37
|
+
})
|
|
38
|
+
.option('qr', {
|
|
39
|
+
alias: 'q',
|
|
40
|
+
type: 'boolean',
|
|
41
|
+
describe: 'Show QR code for the tunnel URL',
|
|
42
|
+
default: false,
|
|
43
|
+
})
|
|
44
|
+
.option('open', {
|
|
45
|
+
alias: 'o',
|
|
46
|
+
type: 'boolean',
|
|
47
|
+
describe: 'Open the tunnel URL in browser',
|
|
48
|
+
default: false,
|
|
49
|
+
})
|
|
50
|
+
.option('print-requests', {
|
|
51
|
+
alias: 'r',
|
|
52
|
+
type: 'boolean',
|
|
53
|
+
describe: 'Print incoming requests to stdout',
|
|
54
|
+
default: false,
|
|
55
|
+
})
|
|
56
|
+
.example('$0 3000', 'Expose localhost:3000')
|
|
57
|
+
.example('$0 8080 --subdomain myapp', 'Expose localhost:8080 as myapp.loca.lt')
|
|
58
|
+
.example('$0 5000 --qr', 'Show QR code for mobile testing')
|
|
59
|
+
.epilog(chalk.gray('Docs: https://localdev.sh • Issues: https://github.com/yourusername/local.dev'))
|
|
60
|
+
.argv;
|
|
61
|
+
|
|
62
|
+
async function main() {
|
|
63
|
+
printBanner();
|
|
64
|
+
|
|
65
|
+
const port = argv.port || 3000;
|
|
66
|
+
const host = argv.host || 'localhost';
|
|
67
|
+
|
|
68
|
+
printInfo(`Tunneling ${chalk.yellow(`${host}:${port}`)} to the internet...`);
|
|
69
|
+
|
|
70
|
+
const spinner = ora({
|
|
71
|
+
text: 'Establishing secure tunnel...',
|
|
72
|
+
color: 'cyan',
|
|
73
|
+
}).start();
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const tunnel = await createTunnel({
|
|
77
|
+
port,
|
|
78
|
+
host,
|
|
79
|
+
subdomain: argv.subdomain,
|
|
80
|
+
printRequests: argv['print-requests'],
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
spinner.stop();
|
|
84
|
+
|
|
85
|
+
printSuccess(tunnel.url);
|
|
86
|
+
|
|
87
|
+
if (argv.qr) {
|
|
88
|
+
console.log('\n' + chalk.gray(' Scan to open on mobile:'));
|
|
89
|
+
qrcode.generate(tunnel.url, { small: true }, (qr) => {
|
|
90
|
+
qr.split('\n').forEach(line => console.log(' ' + line));
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (argv.open) {
|
|
95
|
+
const { exec } = require('child_process');
|
|
96
|
+
const openCmd = process.platform === 'darwin' ? 'open' :
|
|
97
|
+
process.platform === 'win32' ? 'start' : 'xdg-open';
|
|
98
|
+
exec(`${openCmd} ${tunnel.url}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log('');
|
|
102
|
+
console.log(chalk.gray(' Press Ctrl+C to stop the tunnel'));
|
|
103
|
+
console.log('');
|
|
104
|
+
|
|
105
|
+
// Handle incoming request logging
|
|
106
|
+
if (argv['print-requests']) {
|
|
107
|
+
tunnel.on('request', (info) => {
|
|
108
|
+
const time = new Date().toLocaleTimeString();
|
|
109
|
+
const method = chalk.cyan(info.method.padEnd(6));
|
|
110
|
+
const status = info.statusCode >= 400
|
|
111
|
+
? chalk.red(info.statusCode)
|
|
112
|
+
: chalk.green(info.statusCode);
|
|
113
|
+
console.log(` ${chalk.gray(time)} ${method} ${status} ${chalk.white(info.path)}`);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Graceful shutdown
|
|
118
|
+
process.on('SIGINT', async () => {
|
|
119
|
+
console.log('\n' + chalk.gray(' Closing tunnel...'));
|
|
120
|
+
tunnel.close();
|
|
121
|
+
process.exit(0);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
process.on('SIGTERM', async () => {
|
|
125
|
+
tunnel.close();
|
|
126
|
+
process.exit(0);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
} catch (err) {
|
|
130
|
+
spinner.fail('Failed to create tunnel');
|
|
131
|
+
printError(err.message);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
main();
|
package/example.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* local.dev — programmatic usage example
|
|
3
|
+
*
|
|
4
|
+
* Run: node example.js
|
|
5
|
+
* (Make sure you have a server running on port 3000 first)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const { tunnel } = require('./lib/index');
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
console.log('Opening tunnel to localhost:3000...');
|
|
12
|
+
|
|
13
|
+
const t = await tunnel(3000);
|
|
14
|
+
|
|
15
|
+
console.log('');
|
|
16
|
+
console.log('✔ Public URL:', t.url);
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log('Listening for requests... (Ctrl+C to stop)');
|
|
19
|
+
|
|
20
|
+
t.on('request', (info) => {
|
|
21
|
+
console.log(` ${info.method} ${info.path} → ${info.statusCode}`);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
t.on('close', () => {
|
|
25
|
+
console.log('Tunnel closed.');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
process.on('SIGINT', () => {
|
|
29
|
+
t.close();
|
|
30
|
+
process.exit(0);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main().catch((err) => {
|
|
35
|
+
console.error('Error:', err.message);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* local.dev — Expose your local server to the internet.
|
|
5
|
+
*
|
|
6
|
+
* Programmatic API:
|
|
7
|
+
*
|
|
8
|
+
* const { tunnel } = require('local.dev');
|
|
9
|
+
*
|
|
10
|
+
* const t = await tunnel(3000);
|
|
11
|
+
* console.log('Public URL:', t.url);
|
|
12
|
+
*
|
|
13
|
+
* // Later:
|
|
14
|
+
* t.close();
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { createTunnel, TunnelHandle } = require('./tunnel');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Open a tunnel to a local port.
|
|
21
|
+
*
|
|
22
|
+
* @param {number|object} portOrOptions - Port number, or options object
|
|
23
|
+
* @param {object} [options] - Options (if first arg is a port)
|
|
24
|
+
* @returns {Promise<TunnelHandle>}
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* // Simple usage
|
|
28
|
+
* const t = await tunnel(3000);
|
|
29
|
+
* console.log(t.url); // https://xyz.loca.lt
|
|
30
|
+
* t.close();
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // With options
|
|
34
|
+
* const t = await tunnel({ port: 8080, subdomain: 'myapp' });
|
|
35
|
+
*/
|
|
36
|
+
async function tunnel(portOrOptions, options = {}) {
|
|
37
|
+
let opts;
|
|
38
|
+
|
|
39
|
+
if (typeof portOrOptions === 'number') {
|
|
40
|
+
opts = { port: portOrOptions, ...options };
|
|
41
|
+
} else if (typeof portOrOptions === 'object' && portOrOptions !== null) {
|
|
42
|
+
opts = portOrOptions;
|
|
43
|
+
} else {
|
|
44
|
+
throw new TypeError('First argument must be a port number or options object');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return createTunnel(opts);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = {
|
|
51
|
+
tunnel,
|
|
52
|
+
createTunnel,
|
|
53
|
+
TunnelHandle,
|
|
54
|
+
};
|
package/lib/tunnel.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const localtunnel = require('localtunnel');
|
|
4
|
+
const EventEmitter = require('events');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Creates a tunnel to expose a local port to the internet.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} options
|
|
10
|
+
* @param {number} options.port - Local port to expose
|
|
11
|
+
* @param {string} [options.host] - Local host (default: localhost)
|
|
12
|
+
* @param {string} [options.subdomain] - Requested subdomain
|
|
13
|
+
* @param {boolean} [options.printRequests] - Log incoming requests
|
|
14
|
+
* @returns {Promise<TunnelHandle>}
|
|
15
|
+
*/
|
|
16
|
+
async function createTunnel(options = {}) {
|
|
17
|
+
const {
|
|
18
|
+
port,
|
|
19
|
+
host = 'localhost',
|
|
20
|
+
subdomain,
|
|
21
|
+
printRequests = false,
|
|
22
|
+
} = options;
|
|
23
|
+
|
|
24
|
+
if (!port || typeof port !== 'number' || port < 1 || port > 65535) {
|
|
25
|
+
throw new Error(`Invalid port: ${port}. Must be a number between 1 and 65535.`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const tunnelOptions = {
|
|
29
|
+
port,
|
|
30
|
+
local_host: host,
|
|
31
|
+
allow_invalid_cert: false,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
if (subdomain) {
|
|
35
|
+
tunnelOptions.subdomain = subdomain;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const tunnel = await localtunnel(tunnelOptions);
|
|
39
|
+
|
|
40
|
+
const handle = new TunnelHandle(tunnel, { printRequests });
|
|
41
|
+
return handle;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class TunnelHandle extends EventEmitter {
|
|
45
|
+
constructor(tunnel, options = {}) {
|
|
46
|
+
super();
|
|
47
|
+
this._tunnel = tunnel;
|
|
48
|
+
this.url = tunnel.url;
|
|
49
|
+
|
|
50
|
+
// Proxy tunnel events
|
|
51
|
+
tunnel.on('close', () => {
|
|
52
|
+
this.emit('close');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
tunnel.on('error', (err) => {
|
|
56
|
+
this.emit('error', err);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
tunnel.on('request', (info) => {
|
|
60
|
+
this.emit('request', info);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Close the tunnel and free resources.
|
|
66
|
+
*/
|
|
67
|
+
close() {
|
|
68
|
+
this._tunnel.close();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { createTunnel, TunnelHandle };
|
package/lib/ui.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
const BRAND = chalk.bold.cyan('local') + chalk.bold.white('.dev');
|
|
6
|
+
|
|
7
|
+
function printBanner() {
|
|
8
|
+
console.log('');
|
|
9
|
+
console.log(` ${BRAND} ${chalk.gray('v' + require('../package.json').version)}`);
|
|
10
|
+
console.log(` ${chalk.gray('Expose your local server to the internet')}`);
|
|
11
|
+
console.log('');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function printSuccess(url) {
|
|
15
|
+
const line = chalk.gray('─'.repeat(50));
|
|
16
|
+
console.log(` ${line}`);
|
|
17
|
+
console.log('');
|
|
18
|
+
console.log(` ${chalk.green('✔')} Tunnel is ${chalk.green('live')}`);
|
|
19
|
+
console.log('');
|
|
20
|
+
console.log(` ${chalk.gray('URL')} ${chalk.bold.white(url)}`);
|
|
21
|
+
console.log('');
|
|
22
|
+
console.log(` ${line}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function printInfo(msg) {
|
|
26
|
+
console.log(` ${chalk.cyan('→')} ${chalk.gray(msg)}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function printError(msg) {
|
|
30
|
+
console.log('');
|
|
31
|
+
console.log(` ${chalk.red('✖')} ${chalk.red('Error:')} ${msg}`);
|
|
32
|
+
console.log('');
|
|
33
|
+
console.log(` ${chalk.gray('Troubleshooting:')}`);
|
|
34
|
+
console.log(` ${chalk.gray(' • Is your local server running?')}`);
|
|
35
|
+
console.log(` ${chalk.gray(' • Check the port number is correct')}`);
|
|
36
|
+
console.log(` ${chalk.gray(' • Try: local.dev --help')}`);
|
|
37
|
+
console.log('');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { printBanner, printSuccess, printInfo, printError };
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@o-town/local.dev",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Expose your local website to the internet instantly — no config, no signup.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"local.dev": "./bin/cli.js",
|
|
8
|
+
"localdev": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node test.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"tunnel",
|
|
15
|
+
"localhost",
|
|
16
|
+
"expose",
|
|
17
|
+
"ngrok",
|
|
18
|
+
"local",
|
|
19
|
+
"dev",
|
|
20
|
+
"proxy",
|
|
21
|
+
"public-url"
|
|
22
|
+
],
|
|
23
|
+
"author": "local.dev contributors",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"axios": "^1.6.0",
|
|
27
|
+
"chalk": "^4.1.2",
|
|
28
|
+
"http-proxy": "^1.18.1",
|
|
29
|
+
"localtunnel": "^1.8.3",
|
|
30
|
+
"ora": "^5.4.1",
|
|
31
|
+
"qrcode-terminal": "^0.12.0",
|
|
32
|
+
"update-notifier": "^7.3.1",
|
|
33
|
+
"ws": "^8.16.0",
|
|
34
|
+
"yargs": "^17.7.2"
|
|
35
|
+
},
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=14.0.0"
|
|
38
|
+
},
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/yourusername/local.dev.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://localdev.sh",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/yourusername/local.dev/issues"
|
|
46
|
+
}
|
|
47
|
+
}
|