@qodalis/cli-speed-test 0.0.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/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # Cli extension
2
+
3
+ The `@qodalis/cli-speed-test` package is a CLI extension designed to provide utilities for measuring network speed
4
+
5
+ # Installation
6
+
7
+ ```bash
8
+ packages add @qodalis/cli-speed-test
9
+ ```
10
+
11
+ This command downloads and registers the extension for use within the CLI environment.
@@ -0,0 +1,19 @@
1
+ import { NgModule } from '@angular/core';
2
+ import { resolveCommandProcessorProvider } from '@qodalis/angular-cli';
3
+ import { CliSpeedTestCommandProcessor } from './processors/cli-speed-test-command-processor';
4
+ import * as i0 from "@angular/core";
5
+ export class CliSpeedTestModule {
6
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliSpeedTestModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
7
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.12", ngImport: i0, type: CliSpeedTestModule }); }
8
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliSpeedTestModule, providers: [resolveCommandProcessorProvider(CliSpeedTestCommandProcessor)] }); }
9
+ }
10
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliSpeedTestModule, decorators: [{
11
+ type: NgModule,
12
+ args: [{
13
+ declarations: [],
14
+ imports: [],
15
+ exports: [],
16
+ providers: [resolveCommandProcessorProvider(CliSpeedTestCommandProcessor)],
17
+ }]
18
+ }] });
19
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLXNwZWVkLXRlc3QubW9kdWxlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvc3BlZWQtdGVzdC9zcmMvbGliL2NsaS1zcGVlZC10ZXN0Lm1vZHVsZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBQ3pDLE9BQU8sRUFBRSwrQkFBK0IsRUFBRSxNQUFNLHNCQUFzQixDQUFDO0FBQ3ZFLE9BQU8sRUFBRSw0QkFBNEIsRUFBRSxNQUFNLCtDQUErQyxDQUFDOztBQVE3RixNQUFNLE9BQU8sa0JBQWtCOytHQUFsQixrQkFBa0I7Z0hBQWxCLGtCQUFrQjtnSEFBbEIsa0JBQWtCLGFBRmhCLENBQUMsK0JBQStCLENBQUMsNEJBQTRCLENBQUMsQ0FBQzs7NEZBRWpFLGtCQUFrQjtrQkFOOUIsUUFBUTttQkFBQztvQkFDTixZQUFZLEVBQUUsRUFBRTtvQkFDaEIsT0FBTyxFQUFFLEVBQUU7b0JBQ1gsT0FBTyxFQUFFLEVBQUU7b0JBQ1gsU0FBUyxFQUFFLENBQUMsK0JBQStCLENBQUMsNEJBQTRCLENBQUMsQ0FBQztpQkFDN0UiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBOZ01vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuaW1wb3J0IHsgcmVzb2x2ZUNvbW1hbmRQcm9jZXNzb3JQcm92aWRlciB9IGZyb20gJ0Bxb2RhbGlzL2FuZ3VsYXItY2xpJztcbmltcG9ydCB7IENsaVNwZWVkVGVzdENvbW1hbmRQcm9jZXNzb3IgfSBmcm9tICcuL3Byb2Nlc3NvcnMvY2xpLXNwZWVkLXRlc3QtY29tbWFuZC1wcm9jZXNzb3InO1xuXG5ATmdNb2R1bGUoe1xuICAgIGRlY2xhcmF0aW9uczogW10sXG4gICAgaW1wb3J0czogW10sXG4gICAgZXhwb3J0czogW10sXG4gICAgcHJvdmlkZXJzOiBbcmVzb2x2ZUNvbW1hbmRQcm9jZXNzb3JQcm92aWRlcihDbGlTcGVlZFRlc3RDb21tYW5kUHJvY2Vzc29yKV0sXG59KVxuZXhwb3J0IGNsYXNzIENsaVNwZWVkVGVzdE1vZHVsZSB7fVxuIl19
@@ -0,0 +1,173 @@
1
+ import { DefaultLibraryAuthor } from '@qodalis/cli-core';
2
+ import axios from 'axios';
3
+ export class CliSpeedTestCommandProcessor {
4
+ constructor() {
5
+ this.command = 'speed-test';
6
+ this.description = 'Run an internet speed test';
7
+ this.author = DefaultLibraryAuthor;
8
+ this.processors = [];
9
+ this.processors = [
10
+ {
11
+ command: 'run',
12
+ description: 'Run the internet speed test',
13
+ parameters: [
14
+ {
15
+ name: 'proxy',
16
+ description: 'Use a proxy server for the test',
17
+ type: 'boolean',
18
+ required: false,
19
+ },
20
+ {
21
+ name: 'proxy-url',
22
+ description: 'URL of the proxy server',
23
+ type: 'string',
24
+ required: false,
25
+ defaultValue: '/proxy',
26
+ },
27
+ {
28
+ name: 'download-url',
29
+ description: 'URL to download the file',
30
+ type: 'string',
31
+ required: false,
32
+ },
33
+ {
34
+ name: 'upload-url',
35
+ description: 'URL to upload the file',
36
+ type: 'string',
37
+ required: false,
38
+ },
39
+ ],
40
+ processCommand: async (command, context) => {
41
+ const source = axios.CancelToken.source();
42
+ const subscription = context.onAbort.subscribe(() => {
43
+ source.cancel('Speed test aborted by user');
44
+ subscription.unsubscribe();
45
+ });
46
+ new Promise(async (resolve, reject) => {
47
+ context.writer.writeInfo('Starting live speed test...');
48
+ const proxyUrl = command.args['proxy-url'] || '/speed-test';
49
+ const downloadUrl = command.args['download-url'] ||
50
+ (command.args['proxy']
51
+ ? proxyUrl
52
+ : 'https://nbg1-speed.hetzner.com/100MB.bin');
53
+ const uploadUrl = command.args['upload-url'] ||
54
+ 'https://httpbin.org/post';
55
+ try {
56
+ await this.runDownloadSpeedTest(downloadUrl, context, source.token);
57
+ await this.runUploadSpeedTest(uploadUrl, context, source);
58
+ context.writer.writeInfo('Speed test completed');
59
+ resolve();
60
+ }
61
+ catch (error) {
62
+ context.writer.writeError(`Speed test failed: ${error?.toString()}`);
63
+ reject(error);
64
+ }
65
+ subscription.unsubscribe();
66
+ context.showPrompt();
67
+ });
68
+ },
69
+ },
70
+ ];
71
+ }
72
+ async processCommand(_, context) {
73
+ context.writer.writeError("Use 'speed-test' command with a subcommand");
74
+ }
75
+ writeDescription(context) {
76
+ context.writer.writeln(this.description);
77
+ // Examples
78
+ context.writer.writeln('Examples:');
79
+ context.writer.writeln(' speed-test run');
80
+ }
81
+ /**
82
+ * Run the download speed test with live updates.
83
+ * @param context CLI execution context.
84
+ */
85
+ async runDownloadSpeedTest(url, context, cancelToken) {
86
+ const startTime = performance.now();
87
+ let previousTime = startTime;
88
+ let previousLoaded = 0;
89
+ await axios.get(url, {
90
+ responseType: 'arraybuffer',
91
+ cancelToken: cancelToken,
92
+ headers: {
93
+ Origin: null,
94
+ },
95
+ onDownloadProgress: (progressEvent) => {
96
+ if (!!cancelToken.reason) {
97
+ throw new Error('Speed test aborted by user');
98
+ }
99
+ const currentTime = performance.now();
100
+ const elapsedTime = (currentTime - previousTime) / 1000;
101
+ const bytesSinceLastUpdate = progressEvent.loaded - previousLoaded;
102
+ if (elapsedTime > 0) {
103
+ const speedInBps = bytesSinceLastUpdate / elapsedTime; // Bytes per second
104
+ const speedInMbps = (speedInBps * 8) / (1024 * 1024); // Convert to Mbps
105
+ context.writer.writeln(`Download Speed: ${speedInMbps.toFixed(2)} Mbps (Progress: ${((progressEvent.loaded / progressEvent.total) *
106
+ 100).toFixed(2)}%)`);
107
+ }
108
+ // Update previous values
109
+ previousTime = currentTime;
110
+ previousLoaded = progressEvent.loaded;
111
+ },
112
+ });
113
+ const endTime = performance.now();
114
+ const totalTime = (endTime - startTime) / 1000;
115
+ context.writer.writeSuccess(`Download test completed in ${totalTime.toFixed(2)} seconds`);
116
+ }
117
+ /**
118
+ * Run the upload speed test with live updates.
119
+ * @param context CLI execution context.
120
+ */
121
+ async runUploadSpeedTest(url, context, source) {
122
+ const testData = new Uint8Array(20 * 1024 * 1024); // 20 MB of random data
123
+ testData.fill(0);
124
+ const startTime = performance.now();
125
+ let previousTime = startTime;
126
+ let previousUploaded = 0;
127
+ let uploadComplete = false;
128
+ try {
129
+ await axios.post(url, testData, {
130
+ headers: { 'Content-Type': 'application/octet-stream' },
131
+ cancelToken: source.token,
132
+ responseType: 'stream',
133
+ onUploadProgress: (progressEvent) => {
134
+ if (!!source.token.reason) {
135
+ throw new Error('Speed test aborted by user');
136
+ }
137
+ const currentTime = performance.now();
138
+ const elapsedTime = (currentTime - previousTime) / 1000; // Time since last update
139
+ const bytesSinceLastUpdate = progressEvent.loaded - previousUploaded;
140
+ if (elapsedTime > 0) {
141
+ const speedInBps = bytesSinceLastUpdate / elapsedTime; // Bytes per second
142
+ const speedInMbps = (speedInBps * 8) / (1024 * 1024); // Convert to Mbps
143
+ context.writer.writeln(`Upload Speed: ${speedInMbps.toFixed(2)} Mbps (Progress: ${((progressEvent.loaded / progressEvent.total) *
144
+ 100).toFixed(2)}%)`);
145
+ }
146
+ // Update previous values
147
+ previousTime = currentTime;
148
+ previousUploaded = progressEvent.loaded;
149
+ if (progressEvent.loaded >= progressEvent.total) {
150
+ uploadComplete = true;
151
+ source.cancel('Upload test completed');
152
+ }
153
+ },
154
+ });
155
+ }
156
+ catch (e) {
157
+ if (axios.isCancel(e)) {
158
+ if (!uploadComplete) {
159
+ context.writer.writeError('Upload test aborted by user');
160
+ }
161
+ else {
162
+ const endTime = performance.now();
163
+ const totalTime = (endTime - startTime) / 1000;
164
+ context.writer.writeSuccess(`Upload test completed in ${totalTime.toFixed(2)} seconds`);
165
+ }
166
+ }
167
+ else {
168
+ throw e;
169
+ }
170
+ }
171
+ }
172
+ }
173
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xpLXNwZWVkLXRlc3QtY29tbWFuZC1wcm9jZXNzb3IuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi9wcm9qZWN0cy9zcGVlZC10ZXN0L3NyYy9saWIvcHJvY2Vzc29ycy9jbGktc3BlZWQtdGVzdC1jb21tYW5kLXByb2Nlc3Nvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQU16RCxPQUFPLEtBQXlDLE1BQU0sT0FBTyxDQUFDO0FBRTlELE1BQU0sT0FBTyw0QkFBNEI7SUFTckM7UUFSQSxZQUFPLEdBQUcsWUFBWSxDQUFDO1FBRXZCLGdCQUFXLEdBQUcsNEJBQTRCLENBQUM7UUFFM0MsV0FBTSxHQUFHLG9CQUFvQixDQUFDO1FBRTlCLGVBQVUsR0FBd0MsRUFBRSxDQUFDO1FBR2pELElBQUksQ0FBQyxVQUFVLEdBQUc7WUFDZDtnQkFDSSxPQUFPLEVBQUUsS0FBSztnQkFDZCxXQUFXLEVBQUUsNkJBQTZCO2dCQUMxQyxVQUFVLEVBQUU7b0JBQ1I7d0JBQ0ksSUFBSSxFQUFFLE9BQU87d0JBQ2IsV0FBVyxFQUFFLGlDQUFpQzt3QkFDOUMsSUFBSSxFQUFFLFNBQVM7d0JBQ2YsUUFBUSxFQUFFLEtBQUs7cUJBQ2xCO29CQUNEO3dCQUNJLElBQUksRUFBRSxXQUFXO3dCQUNqQixXQUFXLEVBQUUseUJBQXlCO3dCQUN0QyxJQUFJLEVBQUUsUUFBUTt3QkFDZCxRQUFRLEVBQUUsS0FBSzt3QkFDZixZQUFZLEVBQUUsUUFBUTtxQkFDekI7b0JBQ0Q7d0JBQ0ksSUFBSSxFQUFFLGNBQWM7d0JBQ3BCLFdBQVcsRUFBRSwwQkFBMEI7d0JBQ3ZDLElBQUksRUFBRSxRQUFRO3dCQUNkLFFBQVEsRUFBRSxLQUFLO3FCQUNsQjtvQkFDRDt3QkFDSSxJQUFJLEVBQUUsWUFBWTt3QkFDbEIsV0FBVyxFQUFFLHdCQUF3Qjt3QkFDckMsSUFBSSxFQUFFLFFB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O29CQUMvQyxPQUFPLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FDdkIsNEJBQTRCLFNBQVMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FDN0QsQ0FBQztpQkFDTDthQUNKO2lCQUFNO2dCQUNILE1BQU0sQ0FBQyxDQUFDO2FBQ1g7U0FDSjtJQUNMLENBQUM7Q0FDSiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IERlZmF1bHRMaWJyYXJ5QXV0aG9yIH0gZnJvbSAnQHFvZGFsaXMvY2xpLWNvcmUnO1xuaW1wb3J0IHtcbiAgICBDbGlQcm9jZXNzQ29tbWFuZCxcbiAgICBJQ2xpQ29tbWFuZFByb2Nlc3NvcixcbiAgICBJQ2xpRXhlY3V0aW9uQ29udGV4dCxcbn0gZnJvbSAnQHFvZGFsaXMvY2xpLWNvcmUnO1xuaW1wb3J0IGF4aW9zLCB7IENhbmNlbFRva2VuLCBDYW5jZWxUb2tlblNvdXJjZSB9IGZyb20gJ2F4aW9zJztcblxuZXhwb3J0IGNsYXNzIENsaVNwZWVkVGVzdENvbW1hbmRQcm9jZXNzb3IgaW1wbGVtZW50cyBJQ2xpQ29tbWFuZFByb2Nlc3NvciB7XG4gICAgY29tbWFuZCA9ICdzcGVlZC10ZXN0JztcblxuICAgIGRlc2NyaXB0aW9uID0gJ1J1biBhbiBpbnRlcm5ldCBzcGVlZCB0ZXN0JztcblxuICAgIGF1dGhvciA9IERlZmF1bHRMaWJyYXJ5QXV0aG9yO1xuXG4gICAgcHJvY2Vzc29ycz86IElDbGlDb21tYW5kUHJvY2Vzc29yW10gfCB1bmRlZmluZWQgPSBbXTtcblxuICAgIGNvbnN0cnVjdG9yKCkge1xuICAgICAgICB0aGlzLnByb2Nlc3NvcnMgPSBbXG4gICAgICAgICAgICB7XG4gICAgICAgICAgICAgICAgY29tbWFuZDogJ3J1bicsXG4gICAgICAgICAgICAgICAgZGVzY3JpcHRpb246ICdSdW4gdGhlIGludGVybmV0IHNwZWVkIHRlc3QnLFxuICAgICAgICAgICAgICAgIHBhcmFtZXRlcnM6IFtcbiAgICAgICAgICAgICAgICAgICAge1xuICAgICAgICAgICAgICAgICAgICAgICAgbmFtZTogJ3Byb3h5JyxcbiAgICAgICAgICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uOiAnVXNlIGEgcHJveHkgc2VydmVyIGZvciB0aGUgdGVzdCcsXG4gICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiAnYm9vbGVhbicsXG4gICAgICAgICAgICAgICAgICAgICAgICByZXF1aXJlZDogZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWU6ICdwcm94eS11cmwnLFxuICAgICAgICAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb246ICdVUkwgb2YgdGhlIHByb3h5IHNlcnZlcicsXG4gICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlcXVpcmVkOiBmYWxzZSxcbiAgICAgICAgICAgICAgICAgICAgICAgIGRlZmF1bHRWYWx1ZTogJy9wcm94eScsXG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWU6ICdkb3dubG9hZC11cmwnLFxuICAgICAgICAgICAgICAgICAgICAgICAgZGVzY3JpcHRpb246ICdVUkwgdG8gZG93bmxvYWQgdGhlIGZpbGUnLFxuICAgICAgICAgICAgICAgICAgICAgICAgdHlwZTogJ3N0cmluZycsXG4gICAgICAgICAgICAgICAgICAgICAgICByZXF1aXJlZDogZmFsc2UsXG4gICAgICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgICAgICAgIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWU6ICd1cGxvYWQtdXJsJyxcbiAgICAgICAgICAgICAgICAgICAgICAgIGRlc2NyaXB0aW9uOiAnVVJMIHRvIHVwbG9hZCB0aGUgZmlsZScsXG4gICAgICAgICAgICAgICAgICAgICAgICB0eXBlOiAnc3RyaW5nJyxcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlcXVpcmVkOiBmYWxzZSxcbiAgICAgICAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgICBdLFxuICAgICAgICAgICAgICAgIHByb2Nlc3NDb21tYW5kOiBhc3luYyAoY29tbWFuZCwgY29udGV4dCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBzb3VyY2U6IENhbmNlbFRva2VuU291cmNlID1cbiAgICAgICAgICAgICAgICAgICAgICAgIGF4aW9zLkNhbmNlbFRva2VuLnNvdXJjZSgpO1xuXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHN1YnNjcmlwdGlvbiA9IGNvbnRleHQub25BYm9ydC5zdWJzY3JpYmUoKCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICAgICAgc291cmNlLmNhbmNlbCgnU3BlZWQgdGVzdCBhYm9ydGVkIGJ5IHVzZXInKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCk7XG4gICAgICAgICAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICAgICAgICAgIG5ldyBQcm9taXNlPHZvaWQ+KGFzeW5jIChyZXNvbHZlLCByZWplY3QpID0+IHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRleHQud3JpdGVyLndyaXRlSW5mbygnU3RhcnRpbmcgbGl2ZSBzcGVlZCB0ZXN0Li4uJyk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IHByb3h5VXJsID1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21tYW5kLmFyZ3NbJ3Byb3h5LXVybCddIHx8ICcvc3BlZWQtdGVzdCc7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IGRvd25sb2FkVXJsID1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21tYW5kLmFyZ3NbJ2Rvd25sb2FkLXVybCddIHx8XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKGNvbW1hbmQuYXJnc1sncHJveHknXVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA/IHByb3h5VXJsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDogJ2h0dHBzOi8vbmJnMS1zcGVlZC5oZXR6bmVyLmNvbS8xMDBNQi5iaW4nKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgY29uc3QgdXBsb2FkVXJsID1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21tYW5kLmFyZ3NbJ3VwbG9hZC11cmwnXSB8fFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICdodHRwczovL2h0dHBiaW4ub3JnL3Bvc3QnO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICB0cnkge1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF3YWl0IHRoaXMucnVuRG93bmxvYWRTcGVlZFRlc3QoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRvd25sb2FkVXJsLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250ZXh0LFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2UudG9rZW4sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKTtcblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF3YWl0IHRoaXMucnVuVXBsb2FkU3BlZWRUZXN0KFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1cGxvYWRVcmwsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRleHQsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvdXJjZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICApO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGV4dC53cml0ZXIud3JpdGVJbmZvKCdTcGVlZCB0ZXN0IGNvbXBsZXRlZCcpO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVzb2x2ZSgpO1xuICAgICAgICAgICAgICAgICAgICAgICAgfSBjYXRjaCAoZXJyb3IpIHtcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb250ZXh0LndyaXRlci53cml0ZUVycm9yKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBgU3BlZWQgdGVzdCBmYWlsZWQ6ICR7ZXJyb3I/LnRvU3RyaW5nKCl9YCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICApO1xuXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVqZWN0KGVycm9yKTtcbiAgICAgICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2NyaXB0aW9uLnVuc3Vic2NyaWJlKCk7XG5cbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRleHQuc2hvd1Byb21wdCgpO1xuICAgICAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgICB9LFxuICAgICAgICAgICAgfSxcbiAgICAgICAgXTtcbiAgICB9XG5cbiAgICBhc3luYyBwcm9jZXNzQ29tbWFuZChcbiAgICAgICAgXzogQ2xpUHJvY2Vzc0NvbW1hbmQsXG4gICAgICAgIGNvbnRleHQ6IElDbGlFeGVjdXRpb25Db250ZXh0LFxuICAgICk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb250ZXh0LndyaXRlci53cml0ZUVycm9yKFwiVXNlICdzcGVlZC10ZXN0JyBjb21tYW5kIHdpdGggYSBzdWJjb21tYW5kXCIpO1xuICAgIH1cblxuICAgIHdyaXRlRGVzY3JpcHRpb24oY29udGV4dDogSUNsaUV4ZWN1dGlvbkNvbnRleHQpOiB2b2lkIHtcbiAgICAgICAgY29udGV4dC53cml0ZXIud3JpdGVsbih0aGlzLmRlc2NyaXB0aW9uKTtcbiAgICAgICAgLy8gRXhhbXBsZXNcbiAgICAgICAgY29udGV4dC53cml0ZXIud3JpdGVsbignRXhhbXBsZXM6Jyk7XG4gICAgICAgIGNvbnRleHQud3JpdGVyLndyaXRlbG4oJyAgc3BlZWQtdGVzdCBydW4nKTtcbiAgICB9XG5cbiAgICAvKipcbiAgICAgKiBSdW4gdGhlIGRvd25sb2FkIHNwZWVkIHRlc3Qgd2l0aCBsaXZlIHVwZGF0ZXMuXG4gICAgICogQHBhcmFtIGNvbnRleHQgQ0xJIGV4ZWN1dGlvbiBjb250ZXh0LlxuICAgICAqL1xuICAgIHByaXZhdGUgYXN5bmMgcnVuRG93bmxvYWRTcGVlZFRlc3QoXG4gICAgICAgIHVybDogc3RyaW5nLFxuICAgICAgICBjb250ZXh0OiBJQ2xpRXhlY3V0aW9uQ29udGV4dCxcbiAgICAgICAgY2FuY2VsVG9rZW46IENhbmNlbFRva2VuLFxuICAgICk6IFByb21pc2U8dm9pZD4ge1xuICAgICAgICBjb25zdCBzdGFydFRpbWUgPSBwZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgICAgbGV0IHByZXZpb3VzVGltZSA9IHN0YXJ0VGltZTtcbiAgICAgICAgbGV0IHByZXZpb3VzTG9hZGVkID0gMDtcblxuICAgICAgICBhd2FpdCBheGlvcy5nZXQodXJsLCB7XG4gICAgICAgICAgICByZXNwb25zZVR5cGU6ICdhcnJheWJ1ZmZlcicsXG4gICAgICAgICAgICBjYW5jZWxUb2tlbjogY2FuY2VsVG9rZW4sXG4gICAgICAgICAgICBoZWFkZXJzOiB7XG4gICAgICAgICAgICAgICAgT3JpZ2luOiBudWxsLFxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIG9uRG93bmxvYWRQcm9ncmVzczogKHByb2dyZXNzRXZlbnQpID0+IHtcbiAgICAgICAgICAgICAgICBpZiAoISFjYW5jZWxUb2tlbi5yZWFzb24pIHtcbiAgICAgICAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTcGVlZCB0ZXN0IGFib3J0ZWQgYnkgdXNlcicpO1xuICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgIGNvbnN0IGN1cnJlbnRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICAgICAgICAgICAgY29uc3QgZWxhcHNlZFRpbWUgPSAoY3VycmVudFRpbWUgLSBwcmV2aW91c1RpbWUpIC8gMTAwMDtcbiAgICAgICAgICAgICAgICBjb25zdCBieXRlc1NpbmNlTGFzdFVwZGF0ZSA9XG4gICAgICAgICAgICAgICAgICAgIHByb2dyZXNzRXZlbnQubG9hZGVkIC0gcHJldmlvdXNMb2FkZWQ7XG5cbiAgICAgICAgICAgICAgICBpZiAoZWxhcHNlZFRpbWUgPiAwKSB7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHNwZWVkSW5CcHMgPSBieXRlc1NpbmNlTGFzdFVwZGF0ZSAvIGVsYXBzZWRUaW1lOyAvLyBCeXRlcyBwZXIgc2Vjb25kXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHNwZWVkSW5NYnBzID0gKHNwZWVkSW5CcHMgKiA4KSAvICgxMDI0ICogMTAyNCk7IC8vIENvbnZlcnQgdG8gTWJwc1xuICAgICAgICAgICAgICAgICAgICBjb250ZXh0LndyaXRlci53cml0ZWxuKFxuICAgICAgICAgICAgICAgICAgICAgICAgYERvd25sb2FkIFNwZWVkOiAke3NwZWVkSW5NYnBzLnRvRml4ZWQoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgMixcbiAgICAgICAgICAgICAgICAgICAgICAgICl9IE1icHMgKFByb2dyZXNzOiAkeyhcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAocHJvZ3Jlc3NFdmVudC5sb2FkZWQgLyBwcm9ncmVzc0V2ZW50LnRvdGFsISkgKlxuICAgICAgICAgICAgICAgICAgICAgICAgICAgIDEwMFxuICAgICAgICAgICAgICAgICAgICAgICAgKS50b0ZpeGVkKDIpfSUpYCxcbiAgICAgICAgICAgICAgICAgICAgKTtcbiAgICAgICAgICAgICAgICB9XG5cbiAgICAgICAgICAgICAgICAvLyBVcGRhdGUgcHJldmlvdXMgdmFsdWVzXG4gICAgICAgICAgICAgICAgcHJldmlvdXNUaW1lID0gY3VycmVudFRpbWU7XG4gICAgICAgICAgICAgICAgcHJldmlvdXNMb2FkZWQgPSBwcm9ncmVzc0V2ZW50LmxvYWRlZDtcbiAgICAgICAgICAgIH0sXG4gICAgICAgIH0pO1xuXG4gICAgICAgIGNvbnN0IGVuZFRpbWUgPSBwZXJmb3JtYW5jZS5ub3coKTtcbiAgICAgICAgY29uc3QgdG90YWxUaW1lID0gKGVuZFRpbWUgLSBzdGFydFRpbWUpIC8gMTAwMDtcbiAgICAgICAgY29udGV4dC53cml0ZXIud3JpdGVTdWNjZXNzKFxuICAgICAgICAgICAgYERvd25sb2FkIHRlc3QgY29tcGxldGVkIGluICR7dG90YWxUaW1lLnRvRml4ZWQoMil9IHNlY29uZHNgLFxuICAgICAgICApO1xuICAgIH1cblxuICAgIC8qKlxuICAgICAqIFJ1biB0aGUgdXBsb2FkIHNwZWVkIHRlc3Qgd2l0aCBsaXZlIHVwZGF0ZXMuXG4gICAgICogQHBhcmFtIGNvbnRleHQgQ0xJIGV4ZWN1dGlvbiBjb250ZXh0LlxuICAgICAqL1xuICAgIHByaXZhdGUgYXN5bmMgcnVuVXBsb2FkU3BlZWRUZXN0KFxuICAgICAgICB1cmw6IHN0cmluZyxcbiAgICAgICAgY29udGV4dDogSUNsaUV4ZWN1dGlvbkNvbnRleHQsXG4gICAgICAgIHNvdXJjZTogQ2FuY2VsVG9rZW5Tb3VyY2UsXG4gICAgKTogUHJvbWlzZTx2b2lkPiB7XG4gICAgICAgIGNvbnN0IHRlc3REYXRhID0gbmV3IFVpbnQ4QXJyYXkoMjAgKiAxMDI0ICogMTAyNCk7IC8vIDIwIE1CIG9mIHJhbmRvbSBkYXRhXG4gICAgICAgIHRlc3REYXRhLmZpbGwoMCk7XG4gICAgICAgIGNvbnN0IHN0YXJ0VGltZSA9IHBlcmZvcm1hbmNlLm5vdygpO1xuICAgICAgICBsZXQgcHJldmlvdXNUaW1lID0gc3RhcnRUaW1lO1xuICAgICAgICBsZXQgcHJldmlvdXNVcGxvYWRlZCA9IDA7XG5cbiAgICAgICAgbGV0IHVwbG9hZENvbXBsZXRlID0gZmFsc2U7XG5cbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGF3YWl0IGF4aW9zLnBvc3QodXJsLCB0ZXN0RGF0YSwge1xuICAgICAgICAgICAgICAgIGhlYWRlcnM6IHsgJ0NvbnRlbnQtVHlwZSc6ICdhcHBsaWNhdGlvbi9vY3RldC1zdHJlYW0nIH0sXG4gICAgICAgICAgICAgICAgY2FuY2VsVG9rZW46IHNvdXJjZS50b2tlbixcbiAgICAgICAgICAgICAgICByZXNwb25zZVR5cGU6ICdzdHJlYW0nLFxuICAgICAgICAgICAgICAgIG9uVXBsb2FkUHJvZ3Jlc3M6IChwcm9ncmVzc0V2ZW50KSA9PiB7XG4gICAgICAgICAgICAgICAgICAgIGlmICghIXNvdXJjZS50b2tlbi5yZWFzb24pIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignU3BlZWQgdGVzdCBhYm9ydGVkIGJ5IHVzZXInKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGN1cnJlbnRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IGVsYXBzZWRUaW1lID0gKGN1cnJlbnRUaW1lIC0gcHJldmlvdXNUaW1lKSAvIDEwMDA7IC8vIFRpbWUgc2luY2UgbGFzdCB1cGRhdGVcbiAgICAgICAgICAgICAgICAgICAgY29uc3QgYnl0ZXNTaW5jZUxhc3RVcGRhdGUgPVxuICAgICAgICAgICAgICAgICAgICAgICAgcHJvZ3Jlc3NFdmVudC5sb2FkZWQgLSBwcmV2aW91c1VwbG9hZGVkO1xuXG4gICAgICAgICAgICAgICAgICAgIGlmIChlbGFwc2VkVGltZSA+IDApIHtcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnN0IHNwZWVkSW5CcHMgPSBieXRlc1NpbmNlTGFzdFVwZGF0ZSAvIGVsYXBzZWRUaW1lOyAvLyBCeXRlcyBwZXIgc2Vjb25kXG4gICAgICAgICAgICAgICAgICAgICAgICBjb25zdCBzcGVlZEluTWJwcyA9IChzcGVlZEluQnBzICogOCkgLyAoMTAyNCAqIDEwMjQpOyAvLyBDb252ZXJ0IHRvIE1icHNcbiAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRleHQud3JpdGVyLndyaXRlbG4oXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYFVwbG9hZCBTcGVlZDogJHtzcGVlZEluTWJwcy50b0ZpeGVkKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICl9IE1icHMgKFByb2dyZXNzOiAkeyhcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKHByb2dyZXNzRXZlbnQubG9hZGVkIC8gcHJvZ3Jlc3NFdmVudC50b3RhbCEpICpcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMTAwXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKS50b0ZpeGVkKDIpfSUpYCxcbiAgICAgICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAgICAgICAvLyBVcGRhdGUgcHJldmlvdXMgdmFsdWVzXG4gICAgICAgICAgICAgICAgICAgIHByZXZpb3VzVGltZSA9IGN1cnJlbnRUaW1lO1xuICAgICAgICAgICAgICAgICAgICBwcmV2aW91c1VwbG9hZGVkID0gcHJvZ3Jlc3NFdmVudC5sb2FkZWQ7XG5cbiAgICAgICAgICAgICAgICAgICAgaWYgKHByb2dyZXNzRXZlbnQubG9hZGVkID49IHByb2dyZXNzRXZlbnQudG90YWwhKSB7XG4gICAgICAgICAgICAgICAgICAgICAgICB1cGxvYWRDb21wbGV0ZSA9IHRydWU7XG4gICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2UuY2FuY2VsKCdVcGxvYWQgdGVzdCBjb21wbGV0ZWQnKTtcbiAgICAgICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICB9KTtcbiAgICAgICAgfSBjYXRjaCAoZSkge1xuICAgICAgICAgICAgaWYgKGF4aW9zLmlzQ2FuY2VsKGUpKSB7XG4gICAgICAgICAgICAgICAgaWYgKCF1cGxvYWRDb21wbGV0ZSkge1xuICAgICAgICAgICAgICAgICAgICBjb250ZXh0LndyaXRlci53cml0ZUVycm9yKCdVcGxvYWQgdGVzdCBhYm9ydGVkIGJ5IHVzZXInKTtcbiAgICAgICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICAgICAgICBjb25zdCBlbmRUaW1lID0gcGVyZm9ybWFuY2Uubm93KCk7XG4gICAgICAgICAgICAgICAgICAgIGNvbnN0IHRvdGFsVGltZSA9IChlbmRUaW1lIC0gc3RhcnRUaW1lKSAvIDEwMDA7XG4gICAgICAgICAgICAgICAgICAgIGNvbnRleHQud3JpdGVyLndyaXRlU3VjY2VzcyhcbiAgICAgICAgICAgICAgICAgICAgICAgIGBVcGxvYWQgdGVzdCBjb21wbGV0ZWQgaW4gJHt0b3RhbFRpbWUudG9GaXhlZCgyKX0gc2Vjb25kc2AsXG4gICAgICAgICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgICAgICB0aHJvdyBlO1xuICAgICAgICAgICAgfVxuICAgICAgICB9XG4gICAgfVxufVxuIl19
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Public API Surface of speed-test
3
+ */
4
+ export * from './lib/cli-speed-test.module';
5
+ export * from './lib/processors/cli-speed-test-command-processor';
6
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL3NwZWVkLXRlc3Qvc3JjL3B1YmxpYy1hcGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLDZCQUE2QixDQUFDO0FBRTVDLGNBQWMsbURBQW1ELENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuICogUHVibGljIEFQSSBTdXJmYWNlIG9mIHNwZWVkLXRlc3RcbiAqL1xuXG5leHBvcnQgKiBmcm9tICcuL2xpYi9jbGktc3BlZWQtdGVzdC5tb2R1bGUnO1xuXG5leHBvcnQgKiBmcm9tICcuL2xpYi9wcm9jZXNzb3JzL2NsaS1zcGVlZC10ZXN0LWNvbW1hbmQtcHJvY2Vzc29yJztcbiJdfQ==
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ export * from './public-api';
5
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicW9kYWxpcy1jbGktc3BlZWQtdGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL3NwZWVkLXRlc3Qvc3JjL3FvZGFsaXMtY2xpLXNwZWVkLXRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7O0dBRUc7QUFFSCxjQUFjLGNBQWMsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2VuZXJhdGVkIGJ1bmRsZSBpbmRleC4gRG8gbm90IGVkaXQuXG4gKi9cblxuZXhwb3J0ICogZnJvbSAnLi9wdWJsaWMtYXBpJztcbiJdfQ==
@@ -0,0 +1,202 @@
1
+ import * as i0 from '@angular/core';
2
+ import { NgModule } from '@angular/core';
3
+ import { resolveCommandProcessorProvider } from '@qodalis/angular-cli';
4
+ import { DefaultLibraryAuthor } from '@qodalis/cli-core';
5
+ import axios from 'axios';
6
+
7
+ class CliSpeedTestCommandProcessor {
8
+ constructor() {
9
+ this.command = 'speed-test';
10
+ this.description = 'Run an internet speed test';
11
+ this.author = DefaultLibraryAuthor;
12
+ this.processors = [];
13
+ this.processors = [
14
+ {
15
+ command: 'run',
16
+ description: 'Run the internet speed test',
17
+ parameters: [
18
+ {
19
+ name: 'proxy',
20
+ description: 'Use a proxy server for the test',
21
+ type: 'boolean',
22
+ required: false,
23
+ },
24
+ {
25
+ name: 'proxy-url',
26
+ description: 'URL of the proxy server',
27
+ type: 'string',
28
+ required: false,
29
+ defaultValue: '/proxy',
30
+ },
31
+ {
32
+ name: 'download-url',
33
+ description: 'URL to download the file',
34
+ type: 'string',
35
+ required: false,
36
+ },
37
+ {
38
+ name: 'upload-url',
39
+ description: 'URL to upload the file',
40
+ type: 'string',
41
+ required: false,
42
+ },
43
+ ],
44
+ processCommand: async (command, context) => {
45
+ const source = axios.CancelToken.source();
46
+ const subscription = context.onAbort.subscribe(() => {
47
+ source.cancel('Speed test aborted by user');
48
+ subscription.unsubscribe();
49
+ });
50
+ new Promise(async (resolve, reject) => {
51
+ context.writer.writeInfo('Starting live speed test...');
52
+ const proxyUrl = command.args['proxy-url'] || '/speed-test';
53
+ const downloadUrl = command.args['download-url'] ||
54
+ (command.args['proxy']
55
+ ? proxyUrl
56
+ : 'https://nbg1-speed.hetzner.com/100MB.bin');
57
+ const uploadUrl = command.args['upload-url'] ||
58
+ 'https://httpbin.org/post';
59
+ try {
60
+ await this.runDownloadSpeedTest(downloadUrl, context, source.token);
61
+ await this.runUploadSpeedTest(uploadUrl, context, source);
62
+ context.writer.writeInfo('Speed test completed');
63
+ resolve();
64
+ }
65
+ catch (error) {
66
+ context.writer.writeError(`Speed test failed: ${error?.toString()}`);
67
+ reject(error);
68
+ }
69
+ subscription.unsubscribe();
70
+ context.showPrompt();
71
+ });
72
+ },
73
+ },
74
+ ];
75
+ }
76
+ async processCommand(_, context) {
77
+ context.writer.writeError("Use 'speed-test' command with a subcommand");
78
+ }
79
+ writeDescription(context) {
80
+ context.writer.writeln(this.description);
81
+ // Examples
82
+ context.writer.writeln('Examples:');
83
+ context.writer.writeln(' speed-test run');
84
+ }
85
+ /**
86
+ * Run the download speed test with live updates.
87
+ * @param context CLI execution context.
88
+ */
89
+ async runDownloadSpeedTest(url, context, cancelToken) {
90
+ const startTime = performance.now();
91
+ let previousTime = startTime;
92
+ let previousLoaded = 0;
93
+ await axios.get(url, {
94
+ responseType: 'arraybuffer',
95
+ cancelToken: cancelToken,
96
+ headers: {
97
+ Origin: null,
98
+ },
99
+ onDownloadProgress: (progressEvent) => {
100
+ if (!!cancelToken.reason) {
101
+ throw new Error('Speed test aborted by user');
102
+ }
103
+ const currentTime = performance.now();
104
+ const elapsedTime = (currentTime - previousTime) / 1000;
105
+ const bytesSinceLastUpdate = progressEvent.loaded - previousLoaded;
106
+ if (elapsedTime > 0) {
107
+ const speedInBps = bytesSinceLastUpdate / elapsedTime; // Bytes per second
108
+ const speedInMbps = (speedInBps * 8) / (1024 * 1024); // Convert to Mbps
109
+ context.writer.writeln(`Download Speed: ${speedInMbps.toFixed(2)} Mbps (Progress: ${((progressEvent.loaded / progressEvent.total) *
110
+ 100).toFixed(2)}%)`);
111
+ }
112
+ // Update previous values
113
+ previousTime = currentTime;
114
+ previousLoaded = progressEvent.loaded;
115
+ },
116
+ });
117
+ const endTime = performance.now();
118
+ const totalTime = (endTime - startTime) / 1000;
119
+ context.writer.writeSuccess(`Download test completed in ${totalTime.toFixed(2)} seconds`);
120
+ }
121
+ /**
122
+ * Run the upload speed test with live updates.
123
+ * @param context CLI execution context.
124
+ */
125
+ async runUploadSpeedTest(url, context, source) {
126
+ const testData = new Uint8Array(20 * 1024 * 1024); // 20 MB of random data
127
+ testData.fill(0);
128
+ const startTime = performance.now();
129
+ let previousTime = startTime;
130
+ let previousUploaded = 0;
131
+ let uploadComplete = false;
132
+ try {
133
+ await axios.post(url, testData, {
134
+ headers: { 'Content-Type': 'application/octet-stream' },
135
+ cancelToken: source.token,
136
+ responseType: 'stream',
137
+ onUploadProgress: (progressEvent) => {
138
+ if (!!source.token.reason) {
139
+ throw new Error('Speed test aborted by user');
140
+ }
141
+ const currentTime = performance.now();
142
+ const elapsedTime = (currentTime - previousTime) / 1000; // Time since last update
143
+ const bytesSinceLastUpdate = progressEvent.loaded - previousUploaded;
144
+ if (elapsedTime > 0) {
145
+ const speedInBps = bytesSinceLastUpdate / elapsedTime; // Bytes per second
146
+ const speedInMbps = (speedInBps * 8) / (1024 * 1024); // Convert to Mbps
147
+ context.writer.writeln(`Upload Speed: ${speedInMbps.toFixed(2)} Mbps (Progress: ${((progressEvent.loaded / progressEvent.total) *
148
+ 100).toFixed(2)}%)`);
149
+ }
150
+ // Update previous values
151
+ previousTime = currentTime;
152
+ previousUploaded = progressEvent.loaded;
153
+ if (progressEvent.loaded >= progressEvent.total) {
154
+ uploadComplete = true;
155
+ source.cancel('Upload test completed');
156
+ }
157
+ },
158
+ });
159
+ }
160
+ catch (e) {
161
+ if (axios.isCancel(e)) {
162
+ if (!uploadComplete) {
163
+ context.writer.writeError('Upload test aborted by user');
164
+ }
165
+ else {
166
+ const endTime = performance.now();
167
+ const totalTime = (endTime - startTime) / 1000;
168
+ context.writer.writeSuccess(`Upload test completed in ${totalTime.toFixed(2)} seconds`);
169
+ }
170
+ }
171
+ else {
172
+ throw e;
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ class CliSpeedTestModule {
179
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliSpeedTestModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
180
+ static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.2.12", ngImport: i0, type: CliSpeedTestModule }); }
181
+ static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliSpeedTestModule, providers: [resolveCommandProcessorProvider(CliSpeedTestCommandProcessor)] }); }
182
+ }
183
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: CliSpeedTestModule, decorators: [{
184
+ type: NgModule,
185
+ args: [{
186
+ declarations: [],
187
+ imports: [],
188
+ exports: [],
189
+ providers: [resolveCommandProcessorProvider(CliSpeedTestCommandProcessor)],
190
+ }]
191
+ }] });
192
+
193
+ /*
194
+ * Public API Surface of speed-test
195
+ */
196
+
197
+ /**
198
+ * Generated bundle index. Do not edit.
199
+ */
200
+
201
+ export { CliSpeedTestCommandProcessor, CliSpeedTestModule };
202
+ //# sourceMappingURL=qodalis-cli-speed-test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qodalis-cli-speed-test.mjs","sources":["../../../projects/speed-test/src/lib/processors/cli-speed-test-command-processor.ts","../../../projects/speed-test/src/lib/cli-speed-test.module.ts","../../../projects/speed-test/src/public-api.ts","../../../projects/speed-test/src/qodalis-cli-speed-test.ts"],"sourcesContent":["import { DefaultLibraryAuthor } from '@qodalis/cli-core';\nimport {\n CliProcessCommand,\n ICliCommandProcessor,\n ICliExecutionContext,\n} from '@qodalis/cli-core';\nimport axios, { CancelToken, CancelTokenSource } from 'axios';\n\nexport class CliSpeedTestCommandProcessor implements ICliCommandProcessor {\n command = 'speed-test';\n\n description = 'Run an internet speed test';\n\n author = DefaultLibraryAuthor;\n\n processors?: ICliCommandProcessor[] | undefined = [];\n\n constructor() {\n this.processors = [\n {\n command: 'run',\n description: 'Run the internet speed test',\n parameters: [\n {\n name: 'proxy',\n description: 'Use a proxy server for the test',\n type: 'boolean',\n required: false,\n },\n {\n name: 'proxy-url',\n description: 'URL of the proxy server',\n type: 'string',\n required: false,\n defaultValue: '/proxy',\n },\n {\n name: 'download-url',\n description: 'URL to download the file',\n type: 'string',\n required: false,\n },\n {\n name: 'upload-url',\n description: 'URL to upload the file',\n type: 'string',\n required: false,\n },\n ],\n processCommand: async (command, context) => {\n const source: CancelTokenSource =\n axios.CancelToken.source();\n\n const subscription = context.onAbort.subscribe(() => {\n source.cancel('Speed test aborted by user');\n\n subscription.unsubscribe();\n });\n\n new Promise<void>(async (resolve, reject) => {\n context.writer.writeInfo('Starting live speed test...');\n\n const proxyUrl =\n command.args['proxy-url'] || '/speed-test';\n\n const downloadUrl =\n command.args['download-url'] ||\n (command.args['proxy']\n ? proxyUrl\n : 'https://nbg1-speed.hetzner.com/100MB.bin');\n\n const uploadUrl =\n command.args['upload-url'] ||\n 'https://httpbin.org/post';\n\n try {\n await this.runDownloadSpeedTest(\n downloadUrl,\n context,\n source.token,\n );\n\n await this.runUploadSpeedTest(\n uploadUrl,\n context,\n source,\n );\n\n context.writer.writeInfo('Speed test completed');\n\n resolve();\n } catch (error) {\n context.writer.writeError(\n `Speed test failed: ${error?.toString()}`,\n );\n\n reject(error);\n }\n\n subscription.unsubscribe();\n\n context.showPrompt();\n });\n },\n },\n ];\n }\n\n async processCommand(\n _: CliProcessCommand,\n context: ICliExecutionContext,\n ): Promise<void> {\n context.writer.writeError(\"Use 'speed-test' command with a subcommand\");\n }\n\n writeDescription(context: ICliExecutionContext): void {\n context.writer.writeln(this.description);\n // Examples\n context.writer.writeln('Examples:');\n context.writer.writeln(' speed-test run');\n }\n\n /**\n * Run the download speed test with live updates.\n * @param context CLI execution context.\n */\n private async runDownloadSpeedTest(\n url: string,\n context: ICliExecutionContext,\n cancelToken: CancelToken,\n ): Promise<void> {\n const startTime = performance.now();\n let previousTime = startTime;\n let previousLoaded = 0;\n\n await axios.get(url, {\n responseType: 'arraybuffer',\n cancelToken: cancelToken,\n headers: {\n Origin: null,\n },\n onDownloadProgress: (progressEvent) => {\n if (!!cancelToken.reason) {\n throw new Error('Speed test aborted by user');\n }\n\n const currentTime = performance.now();\n const elapsedTime = (currentTime - previousTime) / 1000;\n const bytesSinceLastUpdate =\n progressEvent.loaded - previousLoaded;\n\n if (elapsedTime > 0) {\n const speedInBps = bytesSinceLastUpdate / elapsedTime; // Bytes per second\n const speedInMbps = (speedInBps * 8) / (1024 * 1024); // Convert to Mbps\n context.writer.writeln(\n `Download Speed: ${speedInMbps.toFixed(\n 2,\n )} Mbps (Progress: ${(\n (progressEvent.loaded / progressEvent.total!) *\n 100\n ).toFixed(2)}%)`,\n );\n }\n\n // Update previous values\n previousTime = currentTime;\n previousLoaded = progressEvent.loaded;\n },\n });\n\n const endTime = performance.now();\n const totalTime = (endTime - startTime) / 1000;\n context.writer.writeSuccess(\n `Download test completed in ${totalTime.toFixed(2)} seconds`,\n );\n }\n\n /**\n * Run the upload speed test with live updates.\n * @param context CLI execution context.\n */\n private async runUploadSpeedTest(\n url: string,\n context: ICliExecutionContext,\n source: CancelTokenSource,\n ): Promise<void> {\n const testData = new Uint8Array(20 * 1024 * 1024); // 20 MB of random data\n testData.fill(0);\n const startTime = performance.now();\n let previousTime = startTime;\n let previousUploaded = 0;\n\n let uploadComplete = false;\n\n try {\n await axios.post(url, testData, {\n headers: { 'Content-Type': 'application/octet-stream' },\n cancelToken: source.token,\n responseType: 'stream',\n onUploadProgress: (progressEvent) => {\n if (!!source.token.reason) {\n throw new Error('Speed test aborted by user');\n }\n\n const currentTime = performance.now();\n const elapsedTime = (currentTime - previousTime) / 1000; // Time since last update\n const bytesSinceLastUpdate =\n progressEvent.loaded - previousUploaded;\n\n if (elapsedTime > 0) {\n const speedInBps = bytesSinceLastUpdate / elapsedTime; // Bytes per second\n const speedInMbps = (speedInBps * 8) / (1024 * 1024); // Convert to Mbps\n context.writer.writeln(\n `Upload Speed: ${speedInMbps.toFixed(\n 2,\n )} Mbps (Progress: ${(\n (progressEvent.loaded / progressEvent.total!) *\n 100\n ).toFixed(2)}%)`,\n );\n }\n\n // Update previous values\n previousTime = currentTime;\n previousUploaded = progressEvent.loaded;\n\n if (progressEvent.loaded >= progressEvent.total!) {\n uploadComplete = true;\n source.cancel('Upload test completed');\n }\n },\n });\n } catch (e) {\n if (axios.isCancel(e)) {\n if (!uploadComplete) {\n context.writer.writeError('Upload test aborted by user');\n } else {\n const endTime = performance.now();\n const totalTime = (endTime - startTime) / 1000;\n context.writer.writeSuccess(\n `Upload test completed in ${totalTime.toFixed(2)} seconds`,\n );\n }\n } else {\n throw e;\n }\n }\n }\n}\n","import { NgModule } from '@angular/core';\nimport { resolveCommandProcessorProvider } from '@qodalis/angular-cli';\nimport { CliSpeedTestCommandProcessor } from './processors/cli-speed-test-command-processor';\n\n@NgModule({\n declarations: [],\n imports: [],\n exports: [],\n providers: [resolveCommandProcessorProvider(CliSpeedTestCommandProcessor)],\n})\nexport class CliSpeedTestModule {}\n","/*\n * Public API Surface of speed-test\n */\n\nexport * from './lib/cli-speed-test.module';\n\nexport * from './lib/processors/cli-speed-test-command-processor';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Generated bundle index. Do not edit.
3
+ */
4
+ /// <amd-module name="@qodalis/cli-speed-test" />
5
+ export * from './public-api';
@@ -0,0 +1,6 @@
1
+ import * as i0 from "@angular/core";
2
+ export declare class CliSpeedTestModule {
3
+ static ɵfac: i0.ɵɵFactoryDeclaration<CliSpeedTestModule, never>;
4
+ static ɵmod: i0.ɵɵNgModuleDeclaration<CliSpeedTestModule, never, never, never>;
5
+ static ɵinj: i0.ɵɵInjectorDeclaration<CliSpeedTestModule>;
6
+ }
@@ -0,0 +1,20 @@
1
+ import { CliProcessCommand, ICliCommandProcessor, ICliExecutionContext } from '@qodalis/cli-core';
2
+ export declare class CliSpeedTestCommandProcessor implements ICliCommandProcessor {
3
+ command: string;
4
+ description: string;
5
+ author: import("@qodalis/cli-core").ICliCommandAuthor;
6
+ processors?: ICliCommandProcessor[] | undefined;
7
+ constructor();
8
+ processCommand(_: CliProcessCommand, context: ICliExecutionContext): Promise<void>;
9
+ writeDescription(context: ICliExecutionContext): void;
10
+ /**
11
+ * Run the download speed test with live updates.
12
+ * @param context CLI execution context.
13
+ */
14
+ private runDownloadSpeedTest;
15
+ /**
16
+ * Run the upload speed test with live updates.
17
+ * @param context CLI execution context.
18
+ */
19
+ private runUploadSpeedTest;
20
+ }
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@qodalis/cli-speed-test",
3
+ "version": "0.0.1",
4
+ "description": "An Angular CLI extension that provides speed test utilities",
5
+ "author": "Nicolae Lupei, Qodalis Solutions",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/qodalis-solutions/qodalis-angular-cli"
10
+ },
11
+ "homepage": "https://qodalis.com",
12
+ "keywords": [
13
+ "angular",
14
+ "cli",
15
+ "qodalis",
16
+ "terminal",
17
+ "speed-test"
18
+ ],
19
+ "umd": "./umd/index.js",
20
+ "unpkg": "./umd/index.js",
21
+ "peerDependencies": {
22
+ "@angular/common": "^16.2.0",
23
+ "@angular/core": "^16.2.0"
24
+ },
25
+ "dependencies": {
26
+ "tslib": "^2.3.0",
27
+ "@qodalis/cli-core": "^0.0.5",
28
+ "@qodalis/angular-cli": "^1.0.17"
29
+ },
30
+ "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",
41
+ "exports": {
42
+ "./package.json": {
43
+ "default": "./package.json"
44
+ },
45
+ ".": {
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"
50
+ }
51
+ }
52
+ }
@@ -0,0 +1,2 @@
1
+ export * from './lib/cli-speed-test.module';
2
+ export * from './lib/processors/cli-speed-test-command-processor';
package/umd/index.js ADDED
@@ -0,0 +1,184 @@
1
+ (function (global, factory) {
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('@qodalis/cli-core'), require('axios')) :
3
+ typeof define === 'function' && define.amd ? define(['@qodalis/cli-core', 'axios'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.cliCore, global.axios));
5
+ })(this, (function (cliCore, axios) { 'use strict';
6
+
7
+ class CliSpeedTestCommandProcessor {
8
+ constructor() {
9
+ this.command = 'speed-test';
10
+ this.description = 'Run an internet speed test';
11
+ this.author = cliCore.DefaultLibraryAuthor;
12
+ this.processors = [];
13
+ this.processors = [
14
+ {
15
+ command: 'run',
16
+ description: 'Run the internet speed test',
17
+ parameters: [
18
+ {
19
+ name: 'proxy',
20
+ description: 'Use a proxy server for the test',
21
+ type: 'boolean',
22
+ required: false,
23
+ },
24
+ {
25
+ name: 'proxy-url',
26
+ description: 'URL of the proxy server',
27
+ type: 'string',
28
+ required: false,
29
+ defaultValue: '/proxy',
30
+ },
31
+ {
32
+ name: 'download-url',
33
+ description: 'URL to download the file',
34
+ type: 'string',
35
+ required: false,
36
+ },
37
+ {
38
+ name: 'upload-url',
39
+ description: 'URL to upload the file',
40
+ type: 'string',
41
+ required: false,
42
+ },
43
+ ],
44
+ processCommand: async (command, context) => {
45
+ const source = axios.CancelToken.source();
46
+ const subscription = context.onAbort.subscribe(() => {
47
+ source.cancel('Speed test aborted by user');
48
+ subscription.unsubscribe();
49
+ });
50
+ new Promise(async (resolve, reject) => {
51
+ context.writer.writeInfo('Starting live speed test...');
52
+ const proxyUrl = command.args['proxy-url'] || '/speed-test';
53
+ const downloadUrl = command.args['download-url'] ||
54
+ (command.args['proxy']
55
+ ? proxyUrl
56
+ : 'https://nbg1-speed.hetzner.com/100MB.bin');
57
+ const uploadUrl = command.args['upload-url'] ||
58
+ 'https://httpbin.org/post';
59
+ try {
60
+ await this.runDownloadSpeedTest(downloadUrl, context, source.token);
61
+ await this.runUploadSpeedTest(uploadUrl, context, source);
62
+ context.writer.writeInfo('Speed test completed');
63
+ resolve();
64
+ }
65
+ catch (error) {
66
+ context.writer.writeError(`Speed test failed: ${error?.toString()}`);
67
+ reject(error);
68
+ }
69
+ subscription.unsubscribe();
70
+ context.showPrompt();
71
+ });
72
+ },
73
+ },
74
+ ];
75
+ }
76
+ async processCommand(_, context) {
77
+ context.writer.writeError("Use 'speed-test' command with a subcommand");
78
+ }
79
+ writeDescription(context) {
80
+ context.writer.writeln(this.description);
81
+ // Examples
82
+ context.writer.writeln('Examples:');
83
+ context.writer.writeln(' speed-test run');
84
+ }
85
+ /**
86
+ * Run the download speed test with live updates.
87
+ * @param context CLI execution context.
88
+ */
89
+ async runDownloadSpeedTest(url, context, cancelToken) {
90
+ const startTime = performance.now();
91
+ let previousTime = startTime;
92
+ let previousLoaded = 0;
93
+ await axios.get(url, {
94
+ responseType: 'arraybuffer',
95
+ cancelToken: cancelToken,
96
+ headers: {
97
+ Origin: null,
98
+ },
99
+ onDownloadProgress: (progressEvent) => {
100
+ if (!!cancelToken.reason) {
101
+ throw new Error('Speed test aborted by user');
102
+ }
103
+ const currentTime = performance.now();
104
+ const elapsedTime = (currentTime - previousTime) / 1000;
105
+ const bytesSinceLastUpdate = progressEvent.loaded - previousLoaded;
106
+ if (elapsedTime > 0) {
107
+ const speedInBps = bytesSinceLastUpdate / elapsedTime; // Bytes per second
108
+ const speedInMbps = (speedInBps * 8) / (1024 * 1024); // Convert to Mbps
109
+ context.writer.writeln(`Download Speed: ${speedInMbps.toFixed(2)} Mbps (Progress: ${((progressEvent.loaded / progressEvent.total) *
110
+ 100).toFixed(2)}%)`);
111
+ }
112
+ // Update previous values
113
+ previousTime = currentTime;
114
+ previousLoaded = progressEvent.loaded;
115
+ },
116
+ });
117
+ const endTime = performance.now();
118
+ const totalTime = (endTime - startTime) / 1000;
119
+ context.writer.writeSuccess(`Download test completed in ${totalTime.toFixed(2)} seconds`);
120
+ }
121
+ /**
122
+ * Run the upload speed test with live updates.
123
+ * @param context CLI execution context.
124
+ */
125
+ async runUploadSpeedTest(url, context, source) {
126
+ const testData = new Uint8Array(20 * 1024 * 1024); // 20 MB of random data
127
+ testData.fill(0);
128
+ const startTime = performance.now();
129
+ let previousTime = startTime;
130
+ let previousUploaded = 0;
131
+ let uploadComplete = false;
132
+ try {
133
+ await axios.post(url, testData, {
134
+ headers: { 'Content-Type': 'application/octet-stream' },
135
+ cancelToken: source.token,
136
+ responseType: 'stream',
137
+ onUploadProgress: (progressEvent) => {
138
+ if (!!source.token.reason) {
139
+ throw new Error('Speed test aborted by user');
140
+ }
141
+ const currentTime = performance.now();
142
+ const elapsedTime = (currentTime - previousTime) / 1000; // Time since last update
143
+ const bytesSinceLastUpdate = progressEvent.loaded - previousUploaded;
144
+ if (elapsedTime > 0) {
145
+ const speedInBps = bytesSinceLastUpdate / elapsedTime; // Bytes per second
146
+ const speedInMbps = (speedInBps * 8) / (1024 * 1024); // Convert to Mbps
147
+ context.writer.writeln(`Upload Speed: ${speedInMbps.toFixed(2)} Mbps (Progress: ${((progressEvent.loaded / progressEvent.total) *
148
+ 100).toFixed(2)}%)`);
149
+ }
150
+ // Update previous values
151
+ previousTime = currentTime;
152
+ previousUploaded = progressEvent.loaded;
153
+ if (progressEvent.loaded >= progressEvent.total) {
154
+ uploadComplete = true;
155
+ source.cancel('Upload test completed');
156
+ }
157
+ },
158
+ });
159
+ }
160
+ catch (e) {
161
+ if (axios.isCancel(e)) {
162
+ if (!uploadComplete) {
163
+ context.writer.writeError('Upload test aborted by user');
164
+ }
165
+ else {
166
+ const endTime = performance.now();
167
+ const totalTime = (endTime - startTime) / 1000;
168
+ context.writer.writeSuccess(`Upload test completed in ${totalTime.toFixed(2)} seconds`);
169
+ }
170
+ }
171
+ else {
172
+ throw e;
173
+ }
174
+ }
175
+ }
176
+ }
177
+
178
+ const module = {
179
+ name: '@qodalis/cli-speed-test',
180
+ processors: [new CliSpeedTestCommandProcessor()],
181
+ };
182
+ cliCore.bootUmdModule(module);
183
+
184
+ }));