@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 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...] Wrap a dev server");
22
- console.log(" qrfy -q <text|url...> QR code for any text or URL");
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 ip = getLocalIP();
54
+ const tunnel = args.includes("--tunnel") || args.includes("-t");
55
+ const serverArgs = args.filter((a) => a !== "--tunnel" && a !== "-t");
51
56
 
52
- if (!ip) {
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
- runServer(args, (port) => {
60
- const localUrl = `http://localhost:${port}`;
61
- const mobileUrl = `http://${ip}:${port}`;
62
- displayQR(localUrl, mobileUrl);
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaibhavjha/qrfy",
3
- "version": "1.0.9",
3
+ "version": "1.1.0",
4
4
  "description": "Scan your dev server instantly - QR code for local dev URLs",
5
5
  "author": "Vaibhav Jha (https://github.com/vaibhavjha-dev)",
6
6
  "repository": {