@vaibhavjha/qrfy 1.0.9 → 1.1.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/README.md +37 -1
- package/bin/qrfy.js +42 -13
- package/lib/qr.js +23 -1
- package/lib/tunnel.js +53 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -125,6 +125,34 @@ Now `npm run dev` gives you a QR code every time. Your whole team gets it for fr
|
|
|
125
125
|
|
|
126
126
|
---
|
|
127
127
|
|
|
128
|
+
## Share From Anywhere (Cloudflare Tunnel)
|
|
129
|
+
|
|
130
|
+
Same WiFi not an option? Pass `-t` (or `--tunnel`) and QRfy will expose your dev server through a free [Cloudflare quick tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) — a fresh `*.trycloudflare.com` URL, reachable over cellular, from a coworker's laptop, or a stakeholder on the other side of the world.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
qrfy -t next dev
|
|
134
|
+
qrfy --tunnel npm run dev
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
You'll get a QR for the **public URL** instead of the LAN one. No Cloudflare account needed.
|
|
138
|
+
|
|
139
|
+
**One-time setup** — install `cloudflared`:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# macOS
|
|
143
|
+
brew install cloudflared
|
|
144
|
+
|
|
145
|
+
# Windows
|
|
146
|
+
winget install --id Cloudflare.cloudflared
|
|
147
|
+
|
|
148
|
+
# Linux
|
|
149
|
+
# https://pkg.cloudflare.com/index.html
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> The tunnel URL is fresh every run and tears down when you Ctrl+C. Don't share it for anything you wouldn't put on the open internet.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
128
156
|
## QR for Anything
|
|
129
157
|
|
|
130
158
|
Need a QR code for a URL, a WiFi password, or any piece of text? Use `-q`:
|
|
@@ -143,6 +171,7 @@ No dev server, no port detection — just a scannable QR code in your terminal.
|
|
|
143
171
|
|
|
144
172
|
```bash
|
|
145
173
|
qrfy <command> # Wrap a dev server
|
|
174
|
+
qrfy -t <command> # Wrap + expose via Cloudflare tunnel
|
|
146
175
|
qrfy -q <text|url...> # QR code for any text or URL
|
|
147
176
|
qrfy --help, -h # Show help
|
|
148
177
|
qrfy --version, -v # Show version
|
|
@@ -189,7 +218,7 @@ qrfy --version, -v # Show version
|
|
|
189
218
|
| | Requirement |
|
|
190
219
|
|---|---|
|
|
191
220
|
| **Runtime** | Node.js >= 14 |
|
|
192
|
-
| **Network** | Same WiFi |
|
|
221
|
+
| **Network** | Same WiFi (or `cloudflared` installed for `-t`) |
|
|
193
222
|
| **OS** | macOS, Linux, Windows |
|
|
194
223
|
|
|
195
224
|
---
|
|
@@ -212,6 +241,13 @@ The port wasn't detected from your server's output. Make sure your dev server pr
|
|
|
212
241
|
|
|
213
242
|
</details>
|
|
214
243
|
|
|
244
|
+
<details>
|
|
245
|
+
<summary><b>"cloudflared is not installed" when using <code>-t</code></b></summary>
|
|
246
|
+
|
|
247
|
+
Tunnel mode shells out to Cloudflare's `cloudflared` binary. Install it with the command for your OS shown in the [Share From Anywhere](#share-from-anywhere-cloudflare-tunnel) section, then retry.
|
|
248
|
+
|
|
249
|
+
</details>
|
|
250
|
+
|
|
215
251
|
<details>
|
|
216
252
|
<summary><b>Command not found after install</b></summary>
|
|
217
253
|
|
package/bin/qrfy.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const { getLocalIP } = require("../lib/network");
|
|
4
|
-
const { displayQR, displayStaticQR } = require("../lib/qr");
|
|
4
|
+
const { displayQR, displayStaticQR, displayTunnelQR } = require("../lib/qr");
|
|
5
5
|
const { runServer } = require("../lib/runner");
|
|
6
|
+
const { hasCloudflared, printInstallHint, startTunnel } = require("../lib/tunnel");
|
|
6
7
|
|
|
7
8
|
const { version } = require("../package.json");
|
|
8
9
|
|
|
@@ -18,17 +19,20 @@ if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
|
18
19
|
console.log(` \x1b[1mqrfy\x1b[0m v${version} - Scan your dev server instantly`);
|
|
19
20
|
console.log("");
|
|
20
21
|
console.log(" \x1b[1mUsage:\x1b[0m");
|
|
21
|
-
console.log(" qrfy <command> [args...]
|
|
22
|
-
console.log(" qrfy -
|
|
22
|
+
console.log(" qrfy <command> [args...] Wrap a dev server");
|
|
23
|
+
console.log(" qrfy -t <command> [args...] Wrap + expose via Cloudflare tunnel");
|
|
24
|
+
console.log(" qrfy -q <text|url...> QR code for any text or URL");
|
|
23
25
|
console.log("");
|
|
24
26
|
console.log(" \x1b[1mExamples:\x1b[0m");
|
|
25
27
|
console.log(" qrfy next dev");
|
|
26
28
|
console.log(" qrfy vite");
|
|
27
29
|
console.log(" qrfy npm run dev");
|
|
30
|
+
console.log(" qrfy --tunnel next dev");
|
|
28
31
|
console.log(" qrfy -q https://example.com");
|
|
29
32
|
console.log(" qrfy -q \"hello world\"");
|
|
30
33
|
console.log("");
|
|
31
34
|
console.log(" \x1b[1mOptions:\x1b[0m");
|
|
35
|
+
console.log(" -t, --tunnel Expose via a Cloudflare quick tunnel (needs cloudflared)");
|
|
32
36
|
console.log(" -q, --qr Generate QR for any text or URL");
|
|
33
37
|
console.log(" -h, --help Show this help message");
|
|
34
38
|
console.log(" -v, --version Show version number");
|
|
@@ -47,17 +51,42 @@ if (args[0] === "--qr" || args[0] === "-q") {
|
|
|
47
51
|
return;
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
const
|
|
54
|
+
const tunnel = args.includes("--tunnel") || args.includes("-t");
|
|
55
|
+
const serverArgs = args.filter((a) => a !== "--tunnel" && a !== "-t");
|
|
51
56
|
|
|
52
|
-
if (
|
|
53
|
-
console.error(
|
|
54
|
-
"\x1b[31m QRfy: No network connection found. Connect to WiFi and try again.\x1b[0m"
|
|
55
|
-
);
|
|
57
|
+
if (serverArgs.length === 0) {
|
|
58
|
+
console.error("\x1b[31m QRfy: --tunnel requires a command to wrap (e.g. qrfy -t next dev)\x1b[0m");
|
|
56
59
|
process.exit(1);
|
|
57
60
|
}
|
|
58
61
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
62
|
+
if (tunnel) {
|
|
63
|
+
if (!hasCloudflared()) {
|
|
64
|
+
printInstallHint();
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
runServer(serverArgs, (port) => {
|
|
69
|
+
const tunnelChild = startTunnel(port, (publicUrl) => {
|
|
70
|
+
displayTunnelQR(publicUrl);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
process.on("SIGINT", () => tunnelChild.kill("SIGINT"));
|
|
74
|
+
process.on("SIGTERM", () => tunnelChild.kill("SIGTERM"));
|
|
75
|
+
process.on("exit", () => tunnelChild.kill("SIGTERM"));
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
const ip = getLocalIP();
|
|
79
|
+
|
|
80
|
+
if (!ip) {
|
|
81
|
+
console.error(
|
|
82
|
+
"\x1b[31m QRfy: No network connection found. Connect to WiFi and try again.\x1b[0m"
|
|
83
|
+
);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
runServer(serverArgs, (port) => {
|
|
88
|
+
const localUrl = `http://localhost:${port}`;
|
|
89
|
+
const mobileUrl = `http://${ip}:${port}`;
|
|
90
|
+
displayQR(localUrl, mobileUrl);
|
|
91
|
+
});
|
|
92
|
+
}
|
package/lib/qr.js
CHANGED
|
@@ -23,6 +23,28 @@ function displayQR(localUrl, mobileUrl) {
|
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
function displayTunnelQR(publicUrl) {
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log("\x1b[1m\x1b[34m QRfy tunnel ready\x1b[0m");
|
|
29
|
+
console.log("");
|
|
30
|
+
console.log(` Public: \x1b[32m${publicUrl}\x1b[0m`);
|
|
31
|
+
console.log("");
|
|
32
|
+
console.log(" Scan to open from anywhere:");
|
|
33
|
+
console.log("");
|
|
34
|
+
qrcode.generate(publicUrl, { small: true }, (code) => {
|
|
35
|
+
const indented = code
|
|
36
|
+
.split("\n")
|
|
37
|
+
.map((line) => " " + line)
|
|
38
|
+
.join("\n");
|
|
39
|
+
console.log(indented);
|
|
40
|
+
console.log("");
|
|
41
|
+
console.log(
|
|
42
|
+
" \x1b[33mFresh URL per run — reachable over cellular and remote networks\x1b[0m"
|
|
43
|
+
);
|
|
44
|
+
console.log("");
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
function displayStaticQR(content) {
|
|
27
49
|
console.log("");
|
|
28
50
|
console.log("\x1b[1m\x1b[34m QRfy\x1b[0m");
|
|
@@ -41,4 +63,4 @@ function displayStaticQR(content) {
|
|
|
41
63
|
});
|
|
42
64
|
}
|
|
43
65
|
|
|
44
|
-
module.exports = { displayQR, displayStaticQR };
|
|
66
|
+
module.exports = { displayQR, displayStaticQR, displayTunnelQR };
|
package/lib/tunnel.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const { spawn, spawnSync } = require("child_process");
|
|
2
|
+
|
|
3
|
+
const TUNNEL_URL_REGEX = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/i;
|
|
4
|
+
|
|
5
|
+
function hasCloudflared() {
|
|
6
|
+
const probe = spawnSync(
|
|
7
|
+
process.platform === "win32" ? "where" : "which",
|
|
8
|
+
["cloudflared"],
|
|
9
|
+
{ stdio: "ignore" }
|
|
10
|
+
);
|
|
11
|
+
return probe.status === 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function printInstallHint() {
|
|
15
|
+
console.error("");
|
|
16
|
+
console.error("\x1b[31m QRfy: cloudflared is not installed.\x1b[0m");
|
|
17
|
+
console.error("");
|
|
18
|
+
console.error(" Install it, then re-run with --tunnel:");
|
|
19
|
+
console.error(" macOS: \x1b[36mbrew install cloudflared\x1b[0m");
|
|
20
|
+
console.error(" Windows: \x1b[36mwinget install --id Cloudflare.cloudflared\x1b[0m");
|
|
21
|
+
console.error(" Linux: \x1b[36mhttps://pkg.cloudflare.com/index.html\x1b[0m");
|
|
22
|
+
console.error("");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function startTunnel(port, onUrl) {
|
|
26
|
+
const child = spawn(
|
|
27
|
+
"cloudflared",
|
|
28
|
+
["tunnel", "--no-autoupdate", "--url", `http://localhost:${port}`],
|
|
29
|
+
{ stdio: ["ignore", "pipe", "pipe"] }
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
let resolved = false;
|
|
33
|
+
|
|
34
|
+
function scan(buf) {
|
|
35
|
+
if (resolved) return;
|
|
36
|
+
const match = buf.toString().match(TUNNEL_URL_REGEX);
|
|
37
|
+
if (match) {
|
|
38
|
+
resolved = true;
|
|
39
|
+
onUrl(match[0]);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
child.stdout.on("data", scan);
|
|
44
|
+
child.stderr.on("data", scan);
|
|
45
|
+
|
|
46
|
+
child.on("error", (err) => {
|
|
47
|
+
console.error(`\x1b[31m QRfy tunnel error: ${err.message}\x1b[0m`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return child;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { hasCloudflared, printInstallHint, startTunnel };
|