@vaibhavjha/qrfy 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/LICENSE +21 -0
- package/README.md +110 -0
- package/bin/qrfy.js +37 -0
- package/lib/network.js +15 -0
- package/lib/parser.js +23 -0
- package/lib/qr.js +26 -0
- package/lib/runner.js +56 -0
- package/package.json +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vaibhav Jha
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# QRfy
|
|
2
|
+
|
|
3
|
+
**Scan your dev server instantly.**
|
|
4
|
+
|
|
5
|
+
QRfy wraps any dev server command and generates a QR code in your terminal so you can open it on your phone in seconds. No more typing `192.168.x.x:3000` manually.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/qrfy)
|
|
8
|
+
[](https://github.com/vaibhavjha-dev/qrfy/blob/main/LICENSE)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g qrfy
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Or with other package managers:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# yarn
|
|
22
|
+
yarn global add qrfy
|
|
23
|
+
|
|
24
|
+
# pnpm
|
|
25
|
+
pnpm add -g qrfy
|
|
26
|
+
|
|
27
|
+
# bun
|
|
28
|
+
bun add -g qrfy
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
qrfy <your-dev-command>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Examples
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Next.js
|
|
41
|
+
qrfy next dev
|
|
42
|
+
|
|
43
|
+
# Vite
|
|
44
|
+
qrfy vite
|
|
45
|
+
|
|
46
|
+
# npm scripts
|
|
47
|
+
qrfy npm run dev
|
|
48
|
+
|
|
49
|
+
# Any server
|
|
50
|
+
qrfy python -m http.server 8000
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Output
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
QRfy connected
|
|
57
|
+
|
|
58
|
+
Local: http://localhost:3000
|
|
59
|
+
Mobile: http://192.168.1.5:3000
|
|
60
|
+
|
|
61
|
+
Scan to open on your phone:
|
|
62
|
+
|
|
63
|
+
[QR CODE]
|
|
64
|
+
|
|
65
|
+
Ensure both devices are on the same WiFi
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## How It Works
|
|
69
|
+
|
|
70
|
+
1. Spawns your dev command as a child process
|
|
71
|
+
2. Watches stdout/stderr for a port number
|
|
72
|
+
3. Detects your LAN IP address
|
|
73
|
+
4. Generates a terminal QR code pointing to `http://<your-ip>:<port>`
|
|
74
|
+
|
|
75
|
+
Your dev server output is passed through normally — QRfy just adds the QR code on top.
|
|
76
|
+
|
|
77
|
+
## Package.json Integration
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"scripts": {
|
|
82
|
+
"dev": "qrfy next dev"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Then just `npm run dev` and scan.
|
|
88
|
+
|
|
89
|
+
## Supported Frameworks
|
|
90
|
+
|
|
91
|
+
Works with anything that prints a URL or port to the terminal:
|
|
92
|
+
|
|
93
|
+
- Next.js
|
|
94
|
+
- Vite
|
|
95
|
+
- Create React App
|
|
96
|
+
- Remix
|
|
97
|
+
- Astro
|
|
98
|
+
- SvelteKit
|
|
99
|
+
- Express / Fastify / any Node server
|
|
100
|
+
- Python / Go / Ruby servers
|
|
101
|
+
- Anything else
|
|
102
|
+
|
|
103
|
+
## Requirements
|
|
104
|
+
|
|
105
|
+
- Node.js >= 14
|
|
106
|
+
- Both devices on the same WiFi network
|
|
107
|
+
|
|
108
|
+
## License
|
|
109
|
+
|
|
110
|
+
MIT
|
package/bin/qrfy.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { getLocalIP } = require("../lib/network");
|
|
4
|
+
const { displayQR } = require("../lib/qr");
|
|
5
|
+
const { runServer } = require("../lib/runner");
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
|
|
9
|
+
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
10
|
+
console.log("");
|
|
11
|
+
console.log(" \x1b[1mqrfy\x1b[0m - Scan your dev server instantly");
|
|
12
|
+
console.log("");
|
|
13
|
+
console.log(" \x1b[1mUsage:\x1b[0m");
|
|
14
|
+
console.log(" qrfy <command> [args...]");
|
|
15
|
+
console.log("");
|
|
16
|
+
console.log(" \x1b[1mExamples:\x1b[0m");
|
|
17
|
+
console.log(" qrfy next dev");
|
|
18
|
+
console.log(" qrfy vite");
|
|
19
|
+
console.log(" qrfy npm run dev");
|
|
20
|
+
console.log("");
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ip = getLocalIP();
|
|
25
|
+
|
|
26
|
+
if (!ip) {
|
|
27
|
+
console.error(
|
|
28
|
+
"\x1b[31m QRfy: No network connection found. Connect to WiFi and try again.\x1b[0m"
|
|
29
|
+
);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
runServer(args, (port) => {
|
|
34
|
+
const localUrl = `http://localhost:${port}`;
|
|
35
|
+
const mobileUrl = `http://${ip}:${port}`;
|
|
36
|
+
displayQR(localUrl, mobileUrl);
|
|
37
|
+
});
|
package/lib/network.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const os = require("os");
|
|
2
|
+
|
|
3
|
+
function getLocalIP() {
|
|
4
|
+
const interfaces = os.networkInterfaces();
|
|
5
|
+
for (const name of Object.keys(interfaces)) {
|
|
6
|
+
for (const iface of interfaces[name]) {
|
|
7
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
8
|
+
return iface.address;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { getLocalIP };
|
package/lib/parser.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const PORT_PATTERNS = [
|
|
2
|
+
/https?:\/\/localhost:(\d+)/,
|
|
3
|
+
/https?:\/\/127\.0\.0\.1:(\d+)/,
|
|
4
|
+
/https?:\/\/0\.0\.0\.0:(\d+)/,
|
|
5
|
+
/https?:\/\/\[::\]:(\d+)/,
|
|
6
|
+
/port\s+(\d+)/i,
|
|
7
|
+
/:(\d{4,5})\b/,
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
function extractPort(text) {
|
|
11
|
+
for (const pattern of PORT_PATTERNS) {
|
|
12
|
+
const match = text.match(pattern);
|
|
13
|
+
if (match) {
|
|
14
|
+
const port = parseInt(match[1], 10);
|
|
15
|
+
if (port > 0 && port <= 65535) {
|
|
16
|
+
return port;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = { extractPort };
|
package/lib/qr.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const qrcode = require("qrcode-terminal");
|
|
2
|
+
|
|
3
|
+
function displayQR(localUrl, mobileUrl) {
|
|
4
|
+
console.log("");
|
|
5
|
+
console.log("\x1b[1m\x1b[32m QRfy connected\x1b[0m");
|
|
6
|
+
console.log("");
|
|
7
|
+
console.log(` Local: \x1b[36m${localUrl}\x1b[0m`);
|
|
8
|
+
console.log(` Mobile: \x1b[36m${mobileUrl}\x1b[0m`);
|
|
9
|
+
console.log("");
|
|
10
|
+
console.log(" Scan to open on your phone:");
|
|
11
|
+
console.log("");
|
|
12
|
+
qrcode.generate(mobileUrl, { small: true }, (code) => {
|
|
13
|
+
const indented = code
|
|
14
|
+
.split("\n")
|
|
15
|
+
.map((line) => " " + line)
|
|
16
|
+
.join("\n");
|
|
17
|
+
console.log(indented);
|
|
18
|
+
console.log("");
|
|
19
|
+
console.log(
|
|
20
|
+
" \x1b[33mEnsure both devices are on the same WiFi\x1b[0m"
|
|
21
|
+
);
|
|
22
|
+
console.log("");
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { displayQR };
|
package/lib/runner.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const { spawn } = require("child_process");
|
|
2
|
+
const { extractPort } = require("./parser");
|
|
3
|
+
|
|
4
|
+
function runServer(args, onPort) {
|
|
5
|
+
const child = spawn(args.join(" "), [], {
|
|
6
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
7
|
+
shell: true,
|
|
8
|
+
env: { ...process.env },
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
let detected = false;
|
|
12
|
+
|
|
13
|
+
function handleData(data) {
|
|
14
|
+
const text = data.toString();
|
|
15
|
+
process.stdout.write(text);
|
|
16
|
+
|
|
17
|
+
if (!detected) {
|
|
18
|
+
const port = extractPort(text);
|
|
19
|
+
if (port) {
|
|
20
|
+
detected = true;
|
|
21
|
+
onPort(port);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
child.stdout.on("data", handleData);
|
|
27
|
+
child.stderr.on("data", (data) => {
|
|
28
|
+
const text = data.toString();
|
|
29
|
+
process.stderr.write(text);
|
|
30
|
+
|
|
31
|
+
if (!detected) {
|
|
32
|
+
const port = extractPort(text);
|
|
33
|
+
if (port) {
|
|
34
|
+
detected = true;
|
|
35
|
+
onPort(port);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
child.on("error", (err) => {
|
|
41
|
+
console.error(`\x1b[31m QRfy error: ${err.message}\x1b[0m`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.on("close", (code) => {
|
|
46
|
+
process.exit(code ?? 0);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Forward signals to child
|
|
50
|
+
process.on("SIGINT", () => child.kill("SIGINT"));
|
|
51
|
+
process.on("SIGTERM", () => child.kill("SIGTERM"));
|
|
52
|
+
|
|
53
|
+
return child;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
module.exports = { runServer };
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vaibhavjha/qrfy",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scan your dev server instantly - QR code for local dev URLs",
|
|
5
|
+
"author": "Vaibhav Jha (https://github.com/vaibhavjha-dev)",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/vaibhavjha-dev/qrfy.git"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/vaibhavjha-dev/qrfy#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/vaibhavjha-dev/qrfy/issues"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"qrfy": "./bin/qrfy.js"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"bin/",
|
|
19
|
+
"lib/",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"qr",
|
|
25
|
+
"qrcode",
|
|
26
|
+
"dev-server",
|
|
27
|
+
"mobile",
|
|
28
|
+
"localhost",
|
|
29
|
+
"cli",
|
|
30
|
+
"developer-tools",
|
|
31
|
+
"vite",
|
|
32
|
+
"nextjs",
|
|
33
|
+
"react"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=14"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"qrcode-terminal": "^0.12.0"
|
|
41
|
+
}
|
|
42
|
+
}
|