@mu1147-legend/cf-speed-test 1.0.2

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.
Files changed (4) hide show
  1. package/README.md +137 -0
  2. package/index.js +98 -0
  3. package/package.json +27 -0
  4. package/utils.js +287 -0
package/README.md ADDED
@@ -0,0 +1,137 @@
1
+ # ๐Ÿš€ cf-speed-test
2
+
3
+ A fast, lightweight CLI tool to test your internet speed directly from the terminal.
4
+
5
+ Includes:
6
+
7
+ * โšก Download speed
8
+ * โฌ† Upload speed
9
+ * ๐Ÿ“ก Latency (Ping)
10
+ * ๐Ÿ“Š Jitter
11
+ * ๐Ÿ” Multiple test rounds
12
+ * ๐ŸŽฏ Parallel connections
13
+
14
+ ---
15
+
16
+ ## ๐Ÿ“ฆ Installation
17
+
18
+ ### Run instantly (no install)
19
+
20
+ ```bash
21
+ npx cf-speed-test
22
+ ```
23
+
24
+ ---
25
+
26
+ ### Install globally
27
+
28
+ ```bash
29
+ npm install -g cf-speed-test
30
+ ```
31
+
32
+ Then run:
33
+
34
+ ```bash
35
+ netspeed
36
+ ```
37
+
38
+ ---
39
+
40
+ ## โš™๏ธ Usage
41
+
42
+ ```bash
43
+ netspeed
44
+ ```
45
+
46
+ ---
47
+
48
+ ## ๐Ÿ› ๏ธ Options
49
+
50
+ You can customize the test using CLI flags:
51
+
52
+ ```bash
53
+ netspeed --connections=4 --download=50 --upload=20 --rounds=2
54
+ ```
55
+
56
+ ### Available Options
57
+
58
+ | Flag | Description | Default |
59
+ | --------------- | --------------------------------- | ------- |
60
+ | `--connections` | Number of parallel connections | `4` |
61
+ | `--download` | Download size per connection (MB) | `50` |
62
+ | `--upload` | Upload size per connection (MB) | `20` |
63
+ | `--rounds` | Number of test rounds | `2` |
64
+ | `--json` | Output result as JSON | `false` |
65
+
66
+ ---
67
+
68
+ ## ๐Ÿ“Š Example Output
69
+
70
+ ```
71
+ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
72
+ ๐Ÿš€ Legendary Speed Test
73
+
74
+ Round 1
75
+
76
+ โฌ‡ Testing download...
77
+ [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ] 100% 240 Mbps
78
+ โœ” Download: 238 Mbps
79
+
80
+ โฌ† Testing upload...
81
+ [โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆ] 100% 230 Mbps
82
+ โœ” Upload: 225 Mbps
83
+
84
+ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
85
+ Connections: 4
86
+ Latency: 22 ms
87
+ Jitter: 3 ms
88
+
89
+ โฌ‡ Download: 235 Mbps
90
+ โฌ† Upload: 220 Mbps
91
+ โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
92
+ ```
93
+
94
+ ---
95
+
96
+ ## ๐ŸŒ How it works
97
+
98
+ * Uses parallel HTTP requests to measure bandwidth
99
+ * Streams data in chunks for real-time speed calculation
100
+ * Measures latency using lightweight HEAD requests
101
+ * Calculates jitter based on multiple ping attempts
102
+
103
+ ---
104
+
105
+ ## โš ๏ธ Notes
106
+
107
+ * Results may vary depending on your ISP and routing
108
+ * CDN-based testing (Cloudflare) may show higher peak speeds
109
+ * For best results:
110
+
111
+ * Use stable connection
112
+ * Avoid background downloads
113
+ * Run multiple rounds
114
+
115
+ ---
116
+
117
+ ## ๐Ÿงช Advanced Usage
118
+
119
+ ### JSON Output (for scripting)
120
+
121
+ ```bash
122
+ netspeed --json
123
+ ```
124
+
125
+ ---
126
+
127
+ ### High Accuracy Mode
128
+
129
+ ```bash
130
+ netspeed --connections=6 --download=100 --rounds=3
131
+ ```
132
+
133
+ ---
134
+
135
+ ## ๐Ÿ“„ License
136
+
137
+ MIT License
package/index.js ADDED
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env node
2
+
3
+ import chalk from "chalk";
4
+ import { downloadTest, measureLatency, uploadTest } from "./utils.js";
5
+
6
+ const controller = new AbortController();
7
+
8
+ process.on("SIGINT", () => {
9
+ console.log("\nโš ๏ธ Test aborted");
10
+ controller.abort();
11
+ process.exit(1);
12
+ });
13
+
14
+ // args
15
+ const args = process.argv.slice(2);
16
+
17
+ const getArg = (name, def) => {
18
+ const found = args.find((a) => a.startsWith(name));
19
+ return found ? parseInt(found.split("=")[1]) : def;
20
+ };
21
+
22
+ const isJSON = args.includes("--json");
23
+
24
+ const CONNECTIONS = getArg("--connections", 2);
25
+ const DOWNLOAD_MB = getArg("--download", 20);
26
+ const UPLOAD_MB = getArg("--upload", 20);
27
+ const ROUNDS = getArg("--rounds", 2);
28
+
29
+ const DOWNLOAD_URL = `https://speed.cloudflare.com/__down?bytes=${DOWNLOAD_MB * 1024 * 1024}`;
30
+ const UPLOAD_URL = `https://speed.cloudflare.com/__up`;
31
+
32
+ async function run() {
33
+ console.log(chalk.gray("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"));
34
+ console.log(chalk.green("๐Ÿš€ Legendary Speed Test\n"));
35
+
36
+ // latency
37
+ const { latency, jitter } = await measureLatency(DOWNLOAD_URL);
38
+
39
+ let dlArr = [];
40
+ let ulArr = [];
41
+
42
+ for (let i = 0; i < ROUNDS; i++) {
43
+ console.log(chalk.gray(`\nRound ${i + 1}\n`));
44
+
45
+ // ๐Ÿ”ฅ download with glowing animation
46
+ const dl = await downloadTest(
47
+ DOWNLOAD_URL,
48
+ CONNECTIONS,
49
+ controller.signal,
50
+ );
51
+
52
+ console.log(chalk.blue(`โœ” Download: ${dl.toFixed(2)} Mbps`));
53
+
54
+ // ๐Ÿ”ฅ upload with glowing animation
55
+ const ul = await uploadTest(
56
+ UPLOAD_URL,
57
+ UPLOAD_MB,
58
+ CONNECTIONS,
59
+ controller.signal,
60
+ );
61
+
62
+ console.log(chalk.magenta(`โœ” Upload: ${ul.toFixed(2)} Mbps`));
63
+
64
+ dlArr.push(dl);
65
+ ulArr.push(ul);
66
+ }
67
+
68
+ const avg = (arr) => arr.reduce((a, b) => a + b, 0) / arr.length;
69
+
70
+ const result = {
71
+ download: avg(dlArr),
72
+ upload: avg(ulArr),
73
+ latency,
74
+ jitter,
75
+ connections: CONNECTIONS,
76
+ };
77
+
78
+ if (isJSON) {
79
+ console.log(JSON.stringify(result, null, 2));
80
+ return;
81
+ }
82
+
83
+ // final output
84
+ console.log(chalk.gray("\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"));
85
+ console.log(chalk.cyan(`Connections: ${CONNECTIONS}`));
86
+ console.log(chalk.yellow(`Latency: ${latency.toFixed(2)} ms`));
87
+ console.log(chalk.yellow(`Jitter: ${jitter.toFixed(2)} ms\n`));
88
+
89
+ console.log(
90
+ chalk.blueBright(`โฌ‡ Download: ${result.download.toFixed(2)} Mbps`),
91
+ );
92
+ console.log(
93
+ chalk.magentaBright(`โฌ† Upload: ${result.upload.toFixed(2)} Mbps`),
94
+ );
95
+ console.log(chalk.gray("โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n"));
96
+ }
97
+
98
+ run();
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@mu1147-legend/cf-speed-test",
3
+ "author": "Muhammad Ullah",
4
+ "license": "MIT",
5
+ "version": "1.0.2",
6
+ "description": "CLI Speed Test Tool (Download, Upload, Ping, Jitter)",
7
+ "keywords": [
8
+ "speedtest",
9
+ "cli",
10
+ "internet",
11
+ "network",
12
+ "download",
13
+ "upload",
14
+ "ping",
15
+ "jitter",
16
+ "netspeed"
17
+ ],
18
+ "type": "module",
19
+ "bin": {
20
+ "netspeed": "index.js"
21
+ },
22
+ "dependencies": {
23
+ "chalk": "^5.3.0",
24
+ "gradient-string": "^3.0.0",
25
+ "ora": "^7.0.1"
26
+ }
27
+ }
package/utils.js ADDED
@@ -0,0 +1,287 @@
1
+ import gradient from "gradient-string";
2
+ import { performance } from "node:perf_hooks";
3
+
4
+ // ---------------- SAFE FETCH ----------------
5
+ async function safeFetch(url, options = {}, timeout = 8000, retries = 2) {
6
+ for (let attempt = 0; attempt <= retries; attempt++) {
7
+ const controller = new AbortController();
8
+ const id = setTimeout(() => controller.abort(), timeout);
9
+
10
+ try {
11
+ const res = await fetch(url, {
12
+ ...options,
13
+ signal: controller.signal,
14
+ });
15
+
16
+ clearTimeout(id);
17
+ return res;
18
+ } catch (err) {
19
+ clearTimeout(id);
20
+
21
+ if (attempt === retries) {
22
+ throw err;
23
+ }
24
+
25
+ // small delay before retry
26
+ await new Promise((r) => setTimeout(r, 800));
27
+ }
28
+ }
29
+ }
30
+
31
+ // Simplified fetch for POST (no retry to avoid stream reuse issues)
32
+ async function postFetch(url, options = {}) {
33
+ return fetch(url, options);
34
+ }
35
+
36
+ // ---------------- LATENCY ----------------
37
+ export async function measureLatency(url, attempts = 5) {
38
+ const times = [];
39
+
40
+ for (let i = 0; i < attempts; i++) {
41
+ const start = performance.now();
42
+ await safeFetch(url, { method: "HEAD", cache: "no-store" });
43
+ const end = performance.now();
44
+
45
+ times.push(end - start);
46
+ }
47
+
48
+ const avg = times.reduce((a, b) => a + b, 0) / times.length;
49
+
50
+ const jitter =
51
+ times.reduce((a, b) => a + Math.abs(b - avg), 0) / times.length;
52
+
53
+ return { latency: avg, jitter };
54
+ }
55
+
56
+ // ---------------- PROGRESS ----------------
57
+ function renderProgress(percent, speed) {
58
+ const width = 20;
59
+ const safePercent = Math.max(0, Math.min(100, percent));
60
+
61
+ const filled = Math.round((safePercent / 100) * width);
62
+ const bar = "โ–ˆ".repeat(filled) + "โ–‘".repeat(width - filled);
63
+
64
+ process.stdout.write(
65
+ `\r[${bar}] ${safePercent.toFixed(0)}% ${speed.toFixed(2)} Mbps`,
66
+ );
67
+ }
68
+
69
+ // ---------------- DOWNLOAD (FIXED) ----------------
70
+ export async function downloadTest(url, connections, signal) {
71
+ const sizeMatch = url.match(/bytes=(\d+)/);
72
+ const fileSize = sizeMatch ? parseInt(sizeMatch[1]) : 0;
73
+
74
+ const totalBytes = fileSize * connections;
75
+
76
+ const progressArr = new Array(connections).fill(0);
77
+ let frameCount = 0;
78
+
79
+ const start = performance.now();
80
+
81
+ function renderDownloadProgress(percent, speed) {
82
+ const width = 20;
83
+ const safePercent = Math.max(0, Math.min(100, percent));
84
+ const filled = Math.round((safePercent / 100) * width);
85
+ const bar = "โ–ˆ".repeat(filled) + "โ–‘".repeat(width - filled);
86
+
87
+ // Stone gradient glowing animation - every frame
88
+ const clamp = (v) => Math.max(0, Math.min(1, v));
89
+ const shift = (frameCount % 20) / 20;
90
+
91
+ const animatedText = gradient([
92
+ { color: "#555555", pos: 0 },
93
+ { color: "#aaaaaa", pos: clamp(shift) },
94
+ { color: "#ffffff", pos: clamp(shift + 0.1) },
95
+ { color: "#aaaaaa", pos: clamp(shift + 0.2) },
96
+ { color: "#555555", pos: 1 },
97
+ ])(`โฌ‡ Testing download...`);
98
+
99
+ process.stdout.write(
100
+ `\r${animatedText} [${bar}] ${speed.toFixed(2)} Mbps`,
101
+ );
102
+
103
+ frameCount++;
104
+ }
105
+
106
+ const streams = await Promise.all(
107
+ Array.from({ length: connections }, async (_, i) => {
108
+ // ๐Ÿ”ฅ random delay per connection
109
+ const randomDelay = Math.random() * 2000;
110
+ await new Promise((r) => setTimeout(r, randomDelay));
111
+
112
+ const res = await safeFetch(url, {
113
+ method: "GET", // โœ… MUST
114
+ cache: "no-store",
115
+ signal,
116
+ });
117
+
118
+ if (!res.body) return null;
119
+
120
+ return { stream: res.body, index: i };
121
+ }),
122
+ );
123
+
124
+ await Promise.all(
125
+ streams.map(async (item) => {
126
+ if (!item) return;
127
+
128
+ const { stream, index } = item;
129
+
130
+ try {
131
+ for await (const chunk of stream) {
132
+ if (signal?.aborted) return;
133
+
134
+ progressArr[index] += chunk.length;
135
+
136
+ const receivedBytes = progressArr.reduce(
137
+ (a, b) => a + b,
138
+ 0,
139
+ );
140
+
141
+ const elapsed = (performance.now() - start) / 1000;
142
+ if (elapsed <= 0) continue;
143
+
144
+ const speed = (receivedBytes * 8) / (elapsed * 1024 * 1024);
145
+
146
+ const percent = totalBytes
147
+ ? (receivedBytes / totalBytes) * 100
148
+ : 0;
149
+
150
+ renderDownloadProgress(percent, speed);
151
+ }
152
+ } catch (err) {
153
+ if (err.name !== "AbortError") {
154
+ console.error("\nDownload error:", err.message);
155
+ }
156
+ }
157
+ }),
158
+ );
159
+
160
+ process.stdout.write("\n");
161
+
162
+ const duration = (performance.now() - start) / 1000;
163
+ const finalBytes = progressArr.reduce((a, b) => a + b, 0);
164
+
165
+ if (duration <= 0) return 0;
166
+
167
+ return (finalBytes * 8) / (duration * 1024 * 1024);
168
+ }
169
+
170
+ // ---------------- UPLOAD ----------------
171
+ export async function uploadTest(url, sizeMB, connections, signal) {
172
+ const totalBytes = sizeMB * 1024 * 1024 * connections;
173
+
174
+ let uploadedBytes = 0;
175
+ let frameCount = 0;
176
+
177
+ const start = performance.now();
178
+
179
+ function renderProgress(percent, speed) {
180
+ const width = 20;
181
+ const filled = Math.round((percent / 100) * width);
182
+ const bar = "โ–ˆ".repeat(filled) + "โ–‘".repeat(width - filled);
183
+
184
+ // Stone gradient glowing animation - every frame
185
+ const clamp = (v) => Math.max(0, Math.min(1, v));
186
+ const shift = (frameCount % 20) / 20;
187
+
188
+ const animatedText = gradient([
189
+ { color: "#555555", pos: 0 },
190
+ { color: "#aaaaaa", pos: clamp(shift) },
191
+ { color: "#ffffff", pos: clamp(shift + 0.1) },
192
+ { color: "#aaaaaa", pos: clamp(shift + 0.2) },
193
+ { color: "#555555", pos: 1 },
194
+ ])(`โฌ† Testing upload...`);
195
+
196
+ process.stdout.write(
197
+ `\r${animatedText} [${bar}] ${speed.toFixed(2)} Mbps`,
198
+ );
199
+
200
+ frameCount++;
201
+ }
202
+
203
+ const uploadOne = async () => {
204
+ const chunkSize = 256 * 1024; // 256KB
205
+ const totalChunks = (sizeMB * 1024 * 1024) / chunkSize;
206
+
207
+ const stream = new ReadableStream({
208
+ async pull(controller) {
209
+ if (signal?.aborted) {
210
+ controller.close();
211
+ return;
212
+ }
213
+
214
+ if (uploadedBytes >= totalBytes) {
215
+ controller.close();
216
+ return;
217
+ }
218
+
219
+ const chunk = new Uint8Array(chunkSize);
220
+ controller.enqueue(chunk);
221
+
222
+ uploadedBytes += chunk.length;
223
+
224
+ const elapsed = (performance.now() - start) / 1000;
225
+ if (elapsed <= 0) return;
226
+
227
+ const speed = (uploadedBytes * 8) / (elapsed * 1024 * 1024);
228
+
229
+ const percent = (uploadedBytes / totalBytes) * 100;
230
+
231
+ renderProgress(percent, speed);
232
+ },
233
+ });
234
+
235
+ await postFetch(url, {
236
+ method: "POST",
237
+ body: stream,
238
+ duplex: "half",
239
+ signal,
240
+ });
241
+ };
242
+
243
+ // parallel uploads
244
+ await Promise.all(Array.from({ length: connections }, () => uploadOne()));
245
+
246
+ process.stdout.write("\n");
247
+
248
+ const duration = (performance.now() - start) / 1000;
249
+
250
+ if (duration <= 0) return 0;
251
+
252
+ return (uploadedBytes * 8) / (duration * 1024 * 1024);
253
+ }
254
+
255
+ // ---------------- ANIMATION ----------------
256
+ export function animateLoop(text) {
257
+ let i = 0;
258
+ let running = true;
259
+
260
+ const clamp = (v) => Math.max(0, Math.min(1, v));
261
+
262
+ const loop = () => {
263
+ if (!running) return;
264
+
265
+ const shift = (i % 20) / 20;
266
+
267
+ const colored = gradient([
268
+ { color: "#555555", pos: 0 },
269
+ { color: "#aaaaaa", pos: clamp(shift) },
270
+ { color: "#ffffff", pos: clamp(shift + 0.1) },
271
+ { color: "#aaaaaa", pos: clamp(shift + 0.2) },
272
+ { color: "#555555", pos: 1 },
273
+ ])(text);
274
+
275
+ process.stdout.write("\r" + colored);
276
+
277
+ i++;
278
+ setTimeout(loop, 50);
279
+ };
280
+
281
+ loop();
282
+
283
+ return () => {
284
+ running = false;
285
+ process.stdout.write("\r");
286
+ };
287
+ }