@qodalis/cli-speed-test 0.0.3 → 2.0.0-beta.1

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/package.json CHANGED
@@ -1,52 +1,36 @@
1
1
  {
2
2
  "name": "@qodalis/cli-speed-test",
3
- "version": "0.0.3",
4
- "description": "An Angular CLI extension that provides speed test utilities",
3
+ "version": "2.0.0-beta.1",
4
+ "description": "@qodalis/cli extension for running network speed tests.",
5
5
  "author": "Nicolae Lupei, Qodalis Solutions",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "https://github.com/qodalis-solutions/angular-web-cli"
9
+ "url": "https://github.com/qodalis-solutions/web-cli"
10
10
  },
11
11
  "homepage": "https://qodalis.com",
12
12
  "keywords": [
13
- "angular",
14
13
  "cli",
15
14
  "qodalis",
16
15
  "terminal",
17
- "speed-test"
16
+ "speed-test",
17
+ "network",
18
+ "bandwidth"
18
19
  ],
19
20
  "umd": "./umd/index.js",
20
21
  "unpkg": "./umd/index.js",
21
- "peerDependencies": {
22
- "@angular/common": "^16.2.0",
23
- "@angular/core": "^16.2.0"
24
- },
25
22
  "dependencies": {
26
- "tslib": "^2.3.0",
27
- "@qodalis/cli-core": "^0.0.5",
28
- "@qodalis/angular-cli": "^1.0.17"
23
+ "@qodalis/cli-core": "2.0.0-beta.1"
29
24
  },
30
25
  "sideEffects": false,
31
- "cliDependencies": [
32
- {
33
- "name": "axios",
34
- "globalName": "axios",
35
- "version": "1.7.9",
36
- "url": "https://unpkg.com/axios@1.7.9/dist/axios.min.js"
37
- }
38
- ],
39
- "module": "fesm2022/qodalis-cli-speed-test.mjs",
40
- "typings": "index.d.ts",
26
+ "main": "./public-api.js",
27
+ "module": "./public-api.mjs",
28
+ "types": "./public-api.d.ts",
41
29
  "exports": {
42
- "./package.json": {
43
- "default": "./package.json"
44
- },
45
30
  ".": {
46
- "types": "./index.d.ts",
47
- "esm2022": "./esm2022/qodalis-cli-speed-test.mjs",
48
- "esm": "./esm2022/qodalis-cli-speed-test.mjs",
49
- "default": "./fesm2022/qodalis-cli-speed-test.mjs"
31
+ "types": "./public-api.d.ts",
32
+ "import": "./public-api.mjs",
33
+ "require": "./public-api.js"
50
34
  }
51
35
  }
52
- }
36
+ }
@@ -0,0 +1,33 @@
1
+ import * as _qodalis_cli_core from '@qodalis/cli-core';
2
+ import { ICliCommandProcessor, CliProcessorMetadata, CliProcessCommand, ICliExecutionContext, ICliModule } from '@qodalis/cli-core';
3
+
4
+ declare class CliSpeedTestCommandProcessor implements ICliCommandProcessor {
5
+ command: string;
6
+ description: string;
7
+ author: _qodalis_cli_core.ICliCommandAuthor;
8
+ version: string;
9
+ processors?: ICliCommandProcessor[];
10
+ parameters: ({
11
+ name: string;
12
+ description: string;
13
+ type: "string";
14
+ required: boolean;
15
+ } | {
16
+ name: string;
17
+ description: string;
18
+ type: "boolean";
19
+ required: boolean;
20
+ })[];
21
+ metadata?: CliProcessorMetadata;
22
+ constructor();
23
+ processCommand(command: CliProcessCommand, context: ICliExecutionContext): Promise<void>;
24
+ writeDescription(context: ICliExecutionContext): void;
25
+ private runSpeedTest;
26
+ private testDownload;
27
+ private testUpload;
28
+ private printSummary;
29
+ }
30
+
31
+ declare const speedTestModule: ICliModule;
32
+
33
+ export { CliSpeedTestCommandProcessor, speedTestModule };
package/public-api.d.ts CHANGED
@@ -1,2 +1,33 @@
1
- export * from './lib/cli-speed-test.module';
2
- export * from './lib/processors/cli-speed-test-command-processor';
1
+ import * as _qodalis_cli_core from '@qodalis/cli-core';
2
+ import { ICliCommandProcessor, CliProcessorMetadata, CliProcessCommand, ICliExecutionContext, ICliModule } from '@qodalis/cli-core';
3
+
4
+ declare class CliSpeedTestCommandProcessor implements ICliCommandProcessor {
5
+ command: string;
6
+ description: string;
7
+ author: _qodalis_cli_core.ICliCommandAuthor;
8
+ version: string;
9
+ processors?: ICliCommandProcessor[];
10
+ parameters: ({
11
+ name: string;
12
+ description: string;
13
+ type: "string";
14
+ required: boolean;
15
+ } | {
16
+ name: string;
17
+ description: string;
18
+ type: "boolean";
19
+ required: boolean;
20
+ })[];
21
+ metadata?: CliProcessorMetadata;
22
+ constructor();
23
+ processCommand(command: CliProcessCommand, context: ICliExecutionContext): Promise<void>;
24
+ writeDescription(context: ICliExecutionContext): void;
25
+ private runSpeedTest;
26
+ private testDownload;
27
+ private testUpload;
28
+ private printSummary;
29
+ }
30
+
31
+ declare const speedTestModule: ICliModule;
32
+
33
+ export { CliSpeedTestCommandProcessor, speedTestModule };
package/public-api.js ADDED
@@ -0,0 +1,283 @@
1
+ 'use strict';
2
+
3
+ var cliCore = require('@qodalis/cli-core');
4
+
5
+ // src/lib/processors/cli-speed-test-command-processor.ts
6
+
7
+ // src/lib/version.ts
8
+ var LIBRARY_VERSION = "2.0.0-beta.1";
9
+ var API_VERSION = 2;
10
+
11
+ // src/lib/processors/cli-speed-test-command-processor.ts
12
+ var DEFAULT_DOWNLOAD_URL = "https://speed.cloudflare.com/__down?bytes=26214400";
13
+ var DEFAULT_UPLOAD_URL = "https://speed.cloudflare.com/__up";
14
+ var UPLOAD_SIZE = 10 * 1024 * 1024;
15
+ function formatSpeed(bytesPerSecond) {
16
+ const mbps = bytesPerSecond * 8 / (1024 * 1024);
17
+ if (mbps >= 1) return `${mbps.toFixed(2)} Mbps`;
18
+ const kbps = bytesPerSecond * 8 / 1024;
19
+ if (kbps >= 1) return `${kbps.toFixed(1)} Kbps`;
20
+ return `${(bytesPerSecond * 8).toFixed(0)} bps`;
21
+ }
22
+ function formatBytes(bytes) {
23
+ if (bytes >= 1024 * 1024)
24
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
25
+ if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;
26
+ return `${bytes} B`;
27
+ }
28
+ var CliSpeedTestCommandProcessor = class {
29
+ constructor() {
30
+ this.command = "speed-test";
31
+ this.description = "Run an internet speed test";
32
+ this.author = cliCore.DefaultLibraryAuthor;
33
+ this.version = LIBRARY_VERSION;
34
+ this.processors = [];
35
+ this.parameters = [
36
+ {
37
+ name: "download-url",
38
+ description: "Custom URL for the download test",
39
+ type: "string",
40
+ required: false
41
+ },
42
+ {
43
+ name: "upload-url",
44
+ description: "Custom URL for the upload test",
45
+ type: "string",
46
+ required: false
47
+ },
48
+ {
49
+ name: "download-only",
50
+ description: "Only run the download test",
51
+ type: "boolean",
52
+ required: false
53
+ },
54
+ {
55
+ name: "upload-only",
56
+ description: "Only run the upload test",
57
+ type: "boolean",
58
+ required: false
59
+ }
60
+ ];
61
+ this.metadata = {
62
+ icon: "\u{1F680}",
63
+ requiredCoreVersion: ">=2.0.0 <3.0.0",
64
+ requiredCliVersion: ">=2.0.0 <3.0.0"
65
+ };
66
+ this.processors = [
67
+ {
68
+ command: "run",
69
+ description: "Run the internet speed test",
70
+ processCommand: async (command, context) => {
71
+ await this.runSpeedTest(command, context);
72
+ }
73
+ }
74
+ ];
75
+ }
76
+ async processCommand(command, context) {
77
+ await this.runSpeedTest(command, context);
78
+ }
79
+ writeDescription(context) {
80
+ const { writer } = context;
81
+ writer.writeln(this.description);
82
+ writer.writeln();
83
+ writer.writeln("Usage:");
84
+ writer.writeln(
85
+ ` ${writer.wrapInColor("speed-test", cliCore.CliForegroundColor.Cyan)} Run download & upload test`
86
+ );
87
+ writer.writeln(
88
+ ` ${writer.wrapInColor("speed-test --download-only", cliCore.CliForegroundColor.Cyan)} Download only`
89
+ );
90
+ writer.writeln(
91
+ ` ${writer.wrapInColor("speed-test --upload-only", cliCore.CliForegroundColor.Cyan)} Upload only`
92
+ );
93
+ writer.writeln(
94
+ ` ${writer.wrapInColor("speed-test --download-url=<url>", cliCore.CliForegroundColor.Cyan)} Custom download URL`
95
+ );
96
+ writer.writeln(
97
+ ` ${writer.wrapInColor("speed-test --upload-url=<url>", cliCore.CliForegroundColor.Cyan)} Custom upload URL`
98
+ );
99
+ writer.writeln();
100
+ writer.writeln(
101
+ `Press ${writer.wrapInColor("Ctrl+C", cliCore.CliForegroundColor.Yellow)} to abort the test`
102
+ );
103
+ }
104
+ async runSpeedTest(command, context) {
105
+ const downloadOnly = !!command.args["download-only"];
106
+ const uploadOnly = !!command.args["upload-only"];
107
+ const controller = new AbortController();
108
+ const subscription = context.onAbort.subscribe(() => {
109
+ controller.abort();
110
+ });
111
+ const results = {};
112
+ try {
113
+ if (!uploadOnly) {
114
+ const downloadUrl = command.args["download-url"] || DEFAULT_DOWNLOAD_URL;
115
+ results.download = await this.testDownload(
116
+ downloadUrl,
117
+ context,
118
+ controller.signal
119
+ );
120
+ }
121
+ if (!downloadOnly) {
122
+ const uploadUrl = command.args["upload-url"] || DEFAULT_UPLOAD_URL;
123
+ results.upload = await this.testUpload(
124
+ uploadUrl,
125
+ context,
126
+ controller.signal
127
+ );
128
+ }
129
+ this.printSummary(results, context);
130
+ } catch (error) {
131
+ if (error.name === "AbortError" || controller.signal.aborted) {
132
+ context.writer.writeWarning("Speed test aborted");
133
+ } else {
134
+ context.writer.writeError(
135
+ `Speed test failed: ${error.message || error}`
136
+ );
137
+ }
138
+ } finally {
139
+ subscription.unsubscribe();
140
+ }
141
+ }
142
+ async testDownload(url, context, signal) {
143
+ context.writer.writeln();
144
+ const progressBar = context.progressBar;
145
+ const spinner = context.spinner;
146
+ spinner?.show("Connecting...");
147
+ const response = await fetch(url, { signal });
148
+ if (!response.ok) {
149
+ spinner?.hide();
150
+ throw new Error(`Download failed: HTTP ${response.status}`);
151
+ }
152
+ spinner?.hide();
153
+ context.writer.writeInfo("Running download test...");
154
+ const contentLength = parseInt(
155
+ response.headers.get("content-length") || "0",
156
+ 10
157
+ );
158
+ progressBar.show();
159
+ progressBar.update(0);
160
+ const startTime = performance.now();
161
+ let totalBytes = 0;
162
+ const reader = response.body.getReader();
163
+ let lastUpdateTime = startTime;
164
+ while (true) {
165
+ const { done, value } = await reader.read();
166
+ if (done) break;
167
+ totalBytes += value.length;
168
+ const now = performance.now();
169
+ if (now - lastUpdateTime > 250) {
170
+ lastUpdateTime = now;
171
+ const elapsedSec = (now - startTime) / 1e3;
172
+ const avgSpeed2 = totalBytes / elapsedSec;
173
+ if (contentLength > 0) {
174
+ const pct = Math.min(
175
+ totalBytes / contentLength * 100,
176
+ 100
177
+ );
178
+ progressBar.update(pct);
179
+ progressBar.setText(
180
+ `${formatSpeed(avgSpeed2)} | ${formatBytes(totalBytes)} / ${formatBytes(contentLength)}`
181
+ );
182
+ } else {
183
+ progressBar.setText(
184
+ `${formatSpeed(avgSpeed2)} | ${formatBytes(totalBytes)}`
185
+ );
186
+ }
187
+ }
188
+ }
189
+ progressBar.complete();
190
+ progressBar.hide();
191
+ const totalSec = (performance.now() - startTime) / 1e3;
192
+ const avgSpeed = totalBytes / totalSec;
193
+ context.writer.writeSuccess(
194
+ `Download: ${formatSpeed(avgSpeed)} (${formatBytes(totalBytes)} in ${totalSec.toFixed(1)}s)`
195
+ );
196
+ return avgSpeed;
197
+ }
198
+ async testUpload(url, context, signal) {
199
+ context.writer.writeln();
200
+ context.writer.writeInfo(
201
+ `Running upload test (${formatBytes(UPLOAD_SIZE)})...`
202
+ );
203
+ const progressBar = context.progressBar;
204
+ progressBar.show();
205
+ progressBar.update(0);
206
+ const payload = new Uint8Array(UPLOAD_SIZE);
207
+ const chunkSize = 65536;
208
+ for (let offset = 0; offset < UPLOAD_SIZE; offset += chunkSize) {
209
+ const length = Math.min(chunkSize, UPLOAD_SIZE - offset);
210
+ crypto.getRandomValues(new Uint8Array(payload.buffer, offset, length));
211
+ }
212
+ const CHUNK = 1024 * 1024;
213
+ const totalChunks = Math.ceil(UPLOAD_SIZE / CHUNK);
214
+ let uploaded = 0;
215
+ const startTime = performance.now();
216
+ for (let i = 0; i < totalChunks; i++) {
217
+ if (signal.aborted) {
218
+ throw new DOMException("Upload aborted", "AbortError");
219
+ }
220
+ const start = i * CHUNK;
221
+ const end = Math.min(start + CHUNK, UPLOAD_SIZE);
222
+ const chunk = payload.slice(start, end);
223
+ await fetch(url, {
224
+ method: "POST",
225
+ body: chunk,
226
+ signal
227
+ });
228
+ uploaded += chunk.length;
229
+ const elapsed = (performance.now() - startTime) / 1e3;
230
+ const speed = uploaded / elapsed;
231
+ const pct = uploaded / UPLOAD_SIZE * 100;
232
+ progressBar.update(pct);
233
+ progressBar.setText(
234
+ `${formatSpeed(speed)} | ${formatBytes(uploaded)} / ${formatBytes(UPLOAD_SIZE)}`
235
+ );
236
+ }
237
+ const totalSec = (performance.now() - startTime) / 1e3;
238
+ const avgSpeed = UPLOAD_SIZE / totalSec;
239
+ progressBar.complete();
240
+ progressBar.hide();
241
+ context.writer.writeSuccess(
242
+ `Upload: ${formatSpeed(avgSpeed)} (${formatBytes(UPLOAD_SIZE)} in ${totalSec.toFixed(1)}s)`
243
+ );
244
+ return avgSpeed;
245
+ }
246
+ printSummary(results, context) {
247
+ context.writer.writeln();
248
+ const headers = ["Test", "Speed"];
249
+ const rows = [];
250
+ if (results.download !== void 0) {
251
+ rows.push(["Download", formatSpeed(results.download)]);
252
+ }
253
+ if (results.upload !== void 0) {
254
+ rows.push(["Upload", formatSpeed(results.upload)]);
255
+ }
256
+ context.writer.writeTable(headers, rows);
257
+ context.process.output(results);
258
+ }
259
+ };
260
+
261
+ // src/public-api.ts
262
+ var speedTestModule = {
263
+ apiVersion: API_VERSION,
264
+ name: "@qodalis/cli-speed-test",
265
+ processors: [new CliSpeedTestCommandProcessor()],
266
+ translations: {
267
+ es: { "cli.speed-test.description": "Ejecutar una prueba de velocidad de internet" },
268
+ fr: { "cli.speed-test.description": "Lancer un test de d\xE9bit internet" },
269
+ de: { "cli.speed-test.description": "Internet-Geschwindigkeitstest durchf\xFChren" },
270
+ pt: { "cli.speed-test.description": "Executar um teste de velocidade de internet" },
271
+ it: { "cli.speed-test.description": "Eseguire un test di velocit\xE0 internet" },
272
+ ja: { "cli.speed-test.description": "\u30A4\u30F3\u30BF\u30FC\u30CD\u30C3\u30C8\u901F\u5EA6\u30C6\u30B9\u30C8\u3092\u5B9F\u884C" },
273
+ ko: { "cli.speed-test.description": "\uC778\uD130\uB137 \uC18D\uB3C4 \uD14C\uC2A4\uD2B8 \uC2E4\uD589" },
274
+ zh: { "cli.speed-test.description": "\u8FD0\u884C\u7F51\u7EDC\u901F\u5EA6\u6D4B\u8BD5" },
275
+ ru: { "cli.speed-test.description": "\u0417\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0442\u0435\u0441\u0442 \u0441\u043A\u043E\u0440\u043E\u0441\u0442\u0438 \u0438\u043D\u0442\u0435\u0440\u043D\u0435\u0442\u0430" },
276
+ ro: { "cli.speed-test.description": "Ruleaz\u0103 un test de vitez\u0103 a internetului" }
277
+ }
278
+ };
279
+
280
+ exports.CliSpeedTestCommandProcessor = CliSpeedTestCommandProcessor;
281
+ exports.speedTestModule = speedTestModule;
282
+ //# sourceMappingURL=public-api.js.map
283
+ //# sourceMappingURL=public-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../packages/plugins/speed-test/src/lib/version.ts","../../packages/plugins/speed-test/src/lib/processors/cli-speed-test-command-processor.ts","../../packages/plugins/speed-test/src/public-api.ts"],"names":["DefaultLibraryAuthor","CliForegroundColor","avgSpeed"],"mappings":"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","file":"public-api.js","sourcesContent":["\n// Automatically generated during build\nexport const LIBRARY_VERSION = '2.0.0-beta.1';\nexport const API_VERSION = 2;\n ","import {\n CliForegroundColor,\n CliProcessorMetadata,\n DefaultLibraryAuthor,\n CliProcessCommand,\n ICliCommandProcessor,\n ICliExecutionContext,\n} from '@qodalis/cli-core';\nimport { LIBRARY_VERSION } from '../version';\n\n/**\n * Cloudflare speed test endpoints — edge-served, CORS-enabled, fast worldwide.\n * The `bytes` parameter controls the download size.\n */\nconst DEFAULT_DOWNLOAD_URL =\n 'https://speed.cloudflare.com/__down?bytes=26214400';\n\nconst DEFAULT_UPLOAD_URL = 'https://speed.cloudflare.com/__up';\n\n/** Size of upload test payload in bytes (10 MB). */\nconst UPLOAD_SIZE = 10 * 1024 * 1024;\n\n/** Format bytes/second as a human-readable speed string. */\nfunction formatSpeed(bytesPerSecond: number): string {\n const mbps = (bytesPerSecond * 8) / (1024 * 1024);\n if (mbps >= 1) return `${mbps.toFixed(2)} Mbps`;\n const kbps = (bytesPerSecond * 8) / 1024;\n if (kbps >= 1) return `${kbps.toFixed(1)} Kbps`;\n return `${(bytesPerSecond * 8).toFixed(0)} bps`;\n}\n\n/** Format bytes as a human-readable size string. */\nfunction formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${bytes} B`;\n}\n\nexport class CliSpeedTestCommandProcessor implements ICliCommandProcessor {\n command = 'speed-test';\n\n description = 'Run an internet speed test';\n\n author = DefaultLibraryAuthor;\n\n version = LIBRARY_VERSION;\n\n processors?: ICliCommandProcessor[] = [];\n\n parameters = [\n {\n name: 'download-url',\n description: 'Custom URL for the download test',\n type: 'string' as const,\n required: false,\n },\n {\n name: 'upload-url',\n description: 'Custom URL for the upload test',\n type: 'string' as const,\n required: false,\n },\n {\n name: 'download-only',\n description: 'Only run the download test',\n type: 'boolean' as const,\n required: false,\n },\n {\n name: 'upload-only',\n description: 'Only run the upload test',\n type: 'boolean' as const,\n required: false,\n },\n ];\n\n metadata?: CliProcessorMetadata = {\n icon: '🚀',\n requiredCoreVersion: '>=2.0.0 <3.0.0',\n requiredCliVersion: '>=2.0.0 <3.0.0',\n };\n\n constructor() {\n this.processors = [\n {\n command: 'run',\n description: 'Run the internet speed test',\n processCommand: async (command, context) => {\n await this.runSpeedTest(command, context);\n },\n },\n ];\n }\n\n async processCommand(\n command: CliProcessCommand,\n context: ICliExecutionContext,\n ): Promise<void> {\n await this.runSpeedTest(command, context);\n }\n\n writeDescription(context: ICliExecutionContext): void {\n const { writer } = context;\n writer.writeln(this.description);\n writer.writeln();\n writer.writeln('Usage:');\n writer.writeln(\n ` ${writer.wrapInColor('speed-test', CliForegroundColor.Cyan)} Run download & upload test`,\n );\n writer.writeln(\n ` ${writer.wrapInColor('speed-test --download-only', CliForegroundColor.Cyan)} Download only`,\n );\n writer.writeln(\n ` ${writer.wrapInColor('speed-test --upload-only', CliForegroundColor.Cyan)} Upload only`,\n );\n writer.writeln(\n ` ${writer.wrapInColor('speed-test --download-url=<url>', CliForegroundColor.Cyan)} Custom download URL`,\n );\n writer.writeln(\n ` ${writer.wrapInColor('speed-test --upload-url=<url>', CliForegroundColor.Cyan)} Custom upload URL`,\n );\n writer.writeln();\n writer.writeln(\n `Press ${writer.wrapInColor('Ctrl+C', CliForegroundColor.Yellow)} to abort the test`,\n );\n }\n\n private async runSpeedTest(\n command: CliProcessCommand,\n context: ICliExecutionContext,\n ): Promise<void> {\n const downloadOnly = !!command.args['download-only'];\n const uploadOnly = !!command.args['upload-only'];\n\n const controller = new AbortController();\n const subscription = context.onAbort.subscribe(() => {\n controller.abort();\n });\n\n const results: { download?: number; upload?: number } = {};\n\n try {\n if (!uploadOnly) {\n const downloadUrl =\n (command.args['download-url'] as string) ||\n DEFAULT_DOWNLOAD_URL;\n results.download = await this.testDownload(\n downloadUrl,\n context,\n controller.signal,\n );\n }\n\n if (!downloadOnly) {\n const uploadUrl =\n (command.args['upload-url'] as string) ||\n DEFAULT_UPLOAD_URL;\n results.upload = await this.testUpload(\n uploadUrl,\n context,\n controller.signal,\n );\n }\n\n this.printSummary(results, context);\n } catch (error: any) {\n if (error.name === 'AbortError' || controller.signal.aborted) {\n context.writer.writeWarning('Speed test aborted');\n } else {\n context.writer.writeError(\n `Speed test failed: ${error.message || error}`,\n );\n }\n } finally {\n subscription.unsubscribe();\n }\n }\n\n private async testDownload(\n url: string,\n context: ICliExecutionContext,\n signal: AbortSignal,\n ): Promise<number> {\n context.writer.writeln();\n\n const progressBar = context.progressBar;\n const spinner = context.spinner;\n\n // Show spinner while waiting for connection (DNS, TLS, TTFB)\n spinner?.show('Connecting...');\n\n const response = await fetch(url, { signal });\n\n if (!response.ok) {\n spinner?.hide();\n throw new Error(`Download failed: HTTP ${response.status}`);\n }\n\n spinner?.hide();\n context.writer.writeInfo('Running download test...');\n\n const contentLength = parseInt(\n response.headers.get('content-length') || '0',\n 10,\n );\n\n progressBar.show();\n progressBar.update(0);\n\n // Start timing AFTER connection is established (exclude TTFB)\n const startTime = performance.now();\n let totalBytes = 0;\n\n const reader = response.body!.getReader();\n let lastUpdateTime = startTime;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n totalBytes += value.length;\n\n const now = performance.now();\n if (now - lastUpdateTime > 250) {\n lastUpdateTime = now;\n const elapsedSec = (now - startTime) / 1000;\n const avgSpeed = totalBytes / elapsedSec;\n\n if (contentLength > 0) {\n const pct = Math.min(\n (totalBytes / contentLength) * 100,\n 100,\n );\n progressBar.update(pct);\n progressBar.setText(\n `${formatSpeed(avgSpeed)} | ${formatBytes(totalBytes)} / ${formatBytes(contentLength)}`,\n );\n } else {\n progressBar.setText(\n `${formatSpeed(avgSpeed)} | ${formatBytes(totalBytes)}`,\n );\n }\n }\n }\n\n progressBar.complete();\n progressBar.hide();\n\n const totalSec = (performance.now() - startTime) / 1000;\n const avgSpeed = totalBytes / totalSec;\n\n context.writer.writeSuccess(\n `Download: ${formatSpeed(avgSpeed)} (${formatBytes(totalBytes)} in ${totalSec.toFixed(1)}s)`,\n );\n\n return avgSpeed;\n }\n\n private async testUpload(\n url: string,\n context: ICliExecutionContext,\n signal: AbortSignal,\n ): Promise<number> {\n context.writer.writeln();\n context.writer.writeInfo(\n `Running upload test (${formatBytes(UPLOAD_SIZE)})...`,\n );\n\n const progressBar = context.progressBar;\n progressBar.show();\n progressBar.update(0);\n\n // Fill with pseudo-random data to prevent compression from skewing results.\n // crypto.getRandomValues() is limited to 65536 bytes per call.\n const payload = new Uint8Array(UPLOAD_SIZE);\n const chunkSize = 65536;\n for (let offset = 0; offset < UPLOAD_SIZE; offset += chunkSize) {\n const length = Math.min(chunkSize, UPLOAD_SIZE - offset);\n crypto.getRandomValues(new Uint8Array(payload.buffer, offset, length));\n }\n\n // Use multiple smaller fetch() calls to measure upload speed.\n // A single large POST to Cloudflare's __up may be blocked by CORS\n // when custom headers trigger a preflight. Chunking also lets us\n // report progress without XHR.\n const CHUNK = 1024 * 1024; // 1 MB per request\n const totalChunks = Math.ceil(UPLOAD_SIZE / CHUNK);\n let uploaded = 0;\n const startTime = performance.now();\n\n for (let i = 0; i < totalChunks; i++) {\n if (signal.aborted) {\n throw new DOMException('Upload aborted', 'AbortError');\n }\n\n const start = i * CHUNK;\n const end = Math.min(start + CHUNK, UPLOAD_SIZE);\n const chunk = payload.slice(start, end);\n\n await fetch(url, {\n method: 'POST',\n body: chunk,\n signal,\n });\n\n uploaded += chunk.length;\n\n const elapsed = (performance.now() - startTime) / 1000;\n const speed = uploaded / elapsed;\n const pct = (uploaded / UPLOAD_SIZE) * 100;\n progressBar.update(pct);\n progressBar.setText(\n `${formatSpeed(speed)} | ${formatBytes(uploaded)} / ${formatBytes(UPLOAD_SIZE)}`,\n );\n }\n\n const totalSec = (performance.now() - startTime) / 1000;\n const avgSpeed = UPLOAD_SIZE / totalSec;\n\n progressBar.complete();\n progressBar.hide();\n\n context.writer.writeSuccess(\n `Upload: ${formatSpeed(avgSpeed)} (${formatBytes(UPLOAD_SIZE)} in ${totalSec.toFixed(1)}s)`,\n );\n\n return avgSpeed;\n }\n\n private printSummary(\n results: { download?: number; upload?: number },\n context: ICliExecutionContext,\n ): void {\n context.writer.writeln();\n\n const headers = ['Test', 'Speed'];\n const rows: string[][] = [];\n\n if (results.download !== undefined) {\n rows.push(['Download', formatSpeed(results.download)]);\n }\n if (results.upload !== undefined) {\n rows.push(['Upload', formatSpeed(results.upload)]);\n }\n\n context.writer.writeTable(headers, rows);\n context.process.output(results);\n }\n}\n","/*\n * Public API Surface of speed-test\n */\n\nexport * from './lib/processors/cli-speed-test-command-processor';\n\nimport { ICliModule } from '@qodalis/cli-core';\nimport { CliSpeedTestCommandProcessor } from './lib/processors/cli-speed-test-command-processor';\nimport { API_VERSION } from './lib/version';\n\nexport const speedTestModule: ICliModule = {\n apiVersion: API_VERSION,\n name: '@qodalis/cli-speed-test',\n processors: [new CliSpeedTestCommandProcessor()],\n translations: {\n es: { 'cli.speed-test.description': 'Ejecutar una prueba de velocidad de internet' },\n fr: { 'cli.speed-test.description': 'Lancer un test de débit internet' },\n de: { 'cli.speed-test.description': 'Internet-Geschwindigkeitstest durchführen' },\n pt: { 'cli.speed-test.description': 'Executar um teste de velocidade de internet' },\n it: { 'cli.speed-test.description': 'Eseguire un test di velocità internet' },\n ja: { 'cli.speed-test.description': 'インターネット速度テストを実行' },\n ko: { 'cli.speed-test.description': '인터넷 속도 테스트 실행' },\n zh: { 'cli.speed-test.description': '运行网络速度测试' },\n ru: { 'cli.speed-test.description': 'Запустить тест скорости интернета' },\n ro: { 'cli.speed-test.description': 'Rulează un test de viteză a internetului' },\n },\n};\n"]}