@vaibhavjha/qrfy 1.0.8 → 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,12 +125,56 @@ 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
+
156
+ ## QR for Anything
157
+
158
+ Need a QR code for a URL, a WiFi password, or any piece of text? Use `-q`:
159
+
160
+ ```bash
161
+ qrfy -q https://example.com
162
+ qrfy -q "hello world"
163
+ qrfy -q "WIFI:T:WPA;S:MyNetwork;P:mypassword;;"
164
+ ```
165
+
166
+ No dev server, no port detection — just a scannable QR code in your terminal.
167
+
168
+ ---
169
+
128
170
  ## CLI Options
129
171
 
130
172
  ```bash
131
- qrfy <command> # Wrap a dev server
132
- qrfy --help, -h # Show help
133
- qrfy --version, -v # Show version
173
+ qrfy <command> # Wrap a dev server
174
+ qrfy -t <command> # Wrap + expose via Cloudflare tunnel
175
+ qrfy -q <text|url...> # QR code for any text or URL
176
+ qrfy --help, -h # Show help
177
+ qrfy --version, -v # Show version
134
178
  ```
135
179
 
136
180
  ---
@@ -174,7 +218,7 @@ qrfy --version, -v # Show version
174
218
  | | Requirement |
175
219
  |---|---|
176
220
  | **Runtime** | Node.js >= 14 |
177
- | **Network** | Same WiFi |
221
+ | **Network** | Same WiFi (or `cloudflared` installed for `-t`) |
178
222
  | **OS** | macOS, Linux, Windows |
179
223
 
180
224
  ---
@@ -197,6 +241,13 @@ The port wasn't detected from your server's output. Make sure your dev server pr
197
241
 
198
242
  </details>
199
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
+
200
251
  <details>
201
252
  <summary><b>Command not found after install</b></summary>
202
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 } = 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,31 +19,74 @@ 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 <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");
22
25
  console.log("");
23
26
  console.log(" \x1b[1mExamples:\x1b[0m");
24
27
  console.log(" qrfy next dev");
25
28
  console.log(" qrfy vite");
26
29
  console.log(" qrfy npm run dev");
30
+ console.log(" qrfy --tunnel next dev");
31
+ console.log(" qrfy -q https://example.com");
32
+ console.log(" qrfy -q \"hello world\"");
27
33
  console.log("");
28
34
  console.log(" \x1b[1mOptions:\x1b[0m");
35
+ console.log(" -t, --tunnel Expose via a Cloudflare quick tunnel (needs cloudflared)");
36
+ console.log(" -q, --qr Generate QR for any text or URL");
29
37
  console.log(" -h, --help Show this help message");
30
38
  console.log(" -v, --version Show version number");
31
39
  console.log("");
32
40
  process.exit(0);
33
41
  }
34
42
 
35
- const ip = getLocalIP();
43
+ // Static QR: explicit QR mode
44
+ if (args[0] === "--qr" || args[0] === "-q") {
45
+ const content = args.slice(1).join(" ");
46
+ if (!content) {
47
+ console.error("\x1b[31m QRfy: -q requires content\x1b[0m");
48
+ process.exit(1);
49
+ }
50
+ displayStaticQR(content);
51
+ return;
52
+ }
53
+
54
+ const tunnel = args.includes("--tunnel") || args.includes("-t");
55
+ const serverArgs = args.filter((a) => a !== "--tunnel" && a !== "-t");
36
56
 
37
- if (!ip) {
38
- console.error(
39
- "\x1b[31m QRfy: No network connection found. Connect to WiFi and try again.\x1b[0m"
40
- );
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");
41
59
  process.exit(1);
42
60
  }
43
61
 
44
- runServer(args, (port) => {
45
- const localUrl = `http://localhost:${port}`;
46
- const mobileUrl = `http://${ip}:${port}`;
47
- displayQR(localUrl, mobileUrl);
48
- });
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,4 +23,44 @@ function displayQR(localUrl, mobileUrl) {
23
23
  });
24
24
  }
25
25
 
26
- module.exports = { displayQR };
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
+
48
+ function displayStaticQR(content) {
49
+ console.log("");
50
+ console.log("\x1b[1m\x1b[34m QRfy\x1b[0m");
51
+ console.log("");
52
+ console.log(` Content: \x1b[32m${content}\x1b[0m`);
53
+ console.log("");
54
+ console.log(" Scan with your phone:");
55
+ console.log("");
56
+ qrcode.generate(content, { small: true }, (code) => {
57
+ const indented = code
58
+ .split("\n")
59
+ .map((line) => " " + line)
60
+ .join("\n");
61
+ console.log(indented);
62
+ console.log("");
63
+ });
64
+ }
65
+
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.8",
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": {