@devicecloud.dev/dcd 4.1.1 → 4.1.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.
- package/dist/commands/cloud.js +54 -6
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.js +19 -1
- package/dist/plan.d.ts +1 -1
- package/dist/plan.js +21 -3
- package/dist/planMethods.d.ts +1 -1
- package/dist/planMethods.js +33 -8
- package/dist/utils/connectivity.d.ts +29 -0
- package/dist/utils/connectivity.js +100 -0
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/dist/commands/cloud.js
CHANGED
|
@@ -14,6 +14,7 @@ const api_gateway_1 = require("../gateways/api-gateway");
|
|
|
14
14
|
const methods_1 = require("../methods");
|
|
15
15
|
const plan_1 = require("../plan");
|
|
16
16
|
const compatibility_1 = require("../utils/compatibility");
|
|
17
|
+
const connectivity_1 = require("../utils/connectivity");
|
|
17
18
|
const StreamZip = require("node-stream-zip");
|
|
18
19
|
exports.mimeTypeLookupByExtension = {
|
|
19
20
|
apk: 'application/vnd.android.package-archive',
|
|
@@ -308,7 +309,7 @@ class Cloud extends core_1.Command {
|
|
|
308
309
|
if (debug) {
|
|
309
310
|
this.log('DEBUG: Generating execution plan...');
|
|
310
311
|
}
|
|
311
|
-
executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat(), excludeFlows.flat(), configFile);
|
|
312
|
+
executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat(), excludeFlows.flat(), configFile, debug);
|
|
312
313
|
if (debug) {
|
|
313
314
|
this.log(`DEBUG: Execution plan generated`);
|
|
314
315
|
this.log(`DEBUG: Total flow files: ${executionPlan.totalFlowFiles}`);
|
|
@@ -580,6 +581,7 @@ class Cloud extends core_1.Command {
|
|
|
580
581
|
this.log('\nYou can safely close this terminal and the tests will continue\n');
|
|
581
582
|
}
|
|
582
583
|
let sequentialPollFaillures = 0;
|
|
584
|
+
let previousSummary = '';
|
|
583
585
|
if (debug) {
|
|
584
586
|
this.log(`DEBUG: Starting polling loop for results`);
|
|
585
587
|
}
|
|
@@ -599,11 +601,34 @@ class Cloud extends core_1.Command {
|
|
|
599
601
|
this.log(`DEBUG: Result status: ${result.test_file_name} - ${result.status}`);
|
|
600
602
|
}
|
|
601
603
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
604
|
+
// Calculate summary statistics
|
|
605
|
+
const statusCounts = {};
|
|
606
|
+
for (const result of updatedResults) {
|
|
607
|
+
statusCounts[result.status] = (statusCounts[result.status] || 0) + 1;
|
|
608
|
+
}
|
|
609
|
+
const passed = statusCounts.PASSED || 0;
|
|
610
|
+
const failed = statusCounts.FAILED || 0;
|
|
611
|
+
const pending = statusCounts.PENDING || 0;
|
|
612
|
+
const running = statusCounts.RUNNING || 0;
|
|
613
|
+
const total = updatedResults.length;
|
|
614
|
+
const completed = passed + failed;
|
|
615
|
+
// Display quantitative summary in quiet mode only
|
|
616
|
+
const summary = `${completed}/${total} | ✓ ${passed} | ✗ ${failed} | ▶ ${running} | ⏸ ${pending}`;
|
|
617
|
+
if (!json) {
|
|
618
|
+
if (quiet) {
|
|
619
|
+
// Only update status when the summary changes in quiet mode
|
|
620
|
+
if (summary !== previousSummary) {
|
|
621
|
+
core_1.ux.action.status = summary;
|
|
622
|
+
previousSummary = summary;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
// Show detailed table in non-quiet mode (no summary)
|
|
627
|
+
core_1.ux.action.status =
|
|
628
|
+
'\nStatus Test\n─────────── ───────────────';
|
|
629
|
+
for (const { retry_of: isRetry, status, test_file_name: test, } of updatedResults) {
|
|
630
|
+
core_1.ux.action.status += `\n${status.padEnd(10, ' ')} ${test} ${isRetry ? '(retry)' : ''}`;
|
|
631
|
+
}
|
|
607
632
|
}
|
|
608
633
|
}
|
|
609
634
|
if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
|
|
@@ -728,6 +753,29 @@ class Cloud extends core_1.Command {
|
|
|
728
753
|
if (sequentialPollFaillures > 10) {
|
|
729
754
|
// dropped poll requests shouldn't err user CI
|
|
730
755
|
clearInterval(intervalId);
|
|
756
|
+
// Check if the failure is due to internet connectivity issues
|
|
757
|
+
if (debug) {
|
|
758
|
+
this.log('DEBUG: Checking internet connectivity...');
|
|
759
|
+
}
|
|
760
|
+
const connectivityCheck = await (0, connectivity_1.checkInternetConnectivity)();
|
|
761
|
+
if (debug) {
|
|
762
|
+
this.log(`DEBUG: ${connectivityCheck.message}`);
|
|
763
|
+
for (const result of connectivityCheck.endpointResults) {
|
|
764
|
+
if (result.success) {
|
|
765
|
+
this.log(`DEBUG: ✓ ${result.endpoint} - ${result.statusCode} (${result.latencyMs}ms)`);
|
|
766
|
+
}
|
|
767
|
+
else {
|
|
768
|
+
this.log(`DEBUG: ✗ ${result.endpoint} - ${result.error} (${result.latencyMs}ms)`);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
if (!connectivityCheck.connected) {
|
|
773
|
+
// Build detailed error message with endpoint diagnostics
|
|
774
|
+
const endpointDetails = connectivityCheck.endpointResults
|
|
775
|
+
.map((r) => ` - ${r.endpoint}: ${r.error} (${r.latencyMs}ms)`)
|
|
776
|
+
.join('\n');
|
|
777
|
+
throw new Error(`Unable to fetch results after 10 attempts.\n\nInternet connectivity check failed - all test endpoints unreachable:\n${endpointDetails}\n\nPlease verify your network connection and DNS resolution.`);
|
|
778
|
+
}
|
|
731
779
|
throw new Error('unable to fetch results after 10 attempts');
|
|
732
780
|
}
|
|
733
781
|
this.log('unable to fetch results, trying again...');
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
+
import { ConnectivityCheckResult } from '../utils/connectivity';
|
|
2
3
|
type StatusResponse = {
|
|
3
4
|
appBinaryId?: string;
|
|
4
5
|
attempts?: number;
|
|
6
|
+
connectivityCheck?: {
|
|
7
|
+
connected: boolean;
|
|
8
|
+
endpointResults: ConnectivityCheckResult['endpointResults'];
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
5
11
|
consoleUrl?: string;
|
|
6
12
|
error?: string;
|
|
7
13
|
status: 'CANCELLED' | 'FAILED' | 'PASSED' | 'PENDING' | 'RUNNING';
|
package/dist/commands/status.js
CHANGED
|
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
|
|
|
4
4
|
const constants_1 = require("../constants");
|
|
5
5
|
const api_gateway_1 = require("../gateways/api-gateway");
|
|
6
6
|
const methods_1 = require("../methods");
|
|
7
|
+
const connectivity_1 = require("../utils/connectivity");
|
|
7
8
|
class Status extends core_1.Command {
|
|
8
9
|
static description = 'Get the status of an upload by name or upload ID';
|
|
9
10
|
static enableJsonFlag = true;
|
|
@@ -64,10 +65,27 @@ class Status extends core_1.Command {
|
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
if (!status) {
|
|
67
|
-
|
|
68
|
+
// Check if the failure is due to internet connectivity issues
|
|
69
|
+
const connectivityCheck = await (0, connectivity_1.checkInternetConnectivity)();
|
|
70
|
+
let errorMessage;
|
|
71
|
+
if (connectivityCheck.connected) {
|
|
72
|
+
errorMessage = `Failed to get status after 5 attempts. Internet appears functional but unable to reach API. Last error: ${lastError?.message || 'Unknown error'}`;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
// Build detailed error message with endpoint diagnostics
|
|
76
|
+
const endpointDetails = connectivityCheck.endpointResults
|
|
77
|
+
.map((r) => ` - ${r.endpoint}: ${r.error} (${r.latencyMs}ms)`)
|
|
78
|
+
.join('\n');
|
|
79
|
+
errorMessage = `Failed to get status after 5 attempts.\n\nInternet connectivity check failed - all test endpoints unreachable:\n${endpointDetails}\n\nPlease verify your network connection and DNS resolution.\nLast API error: ${lastError?.message || 'Unknown error'}`;
|
|
80
|
+
}
|
|
68
81
|
if (json) {
|
|
69
82
|
return {
|
|
70
83
|
attempts: 5,
|
|
84
|
+
connectivityCheck: {
|
|
85
|
+
connected: connectivityCheck.connected,
|
|
86
|
+
endpointResults: connectivityCheck.endpointResults,
|
|
87
|
+
message: connectivityCheck.message,
|
|
88
|
+
},
|
|
71
89
|
error: errorMessage,
|
|
72
90
|
status: 'FAILED',
|
|
73
91
|
tests: [],
|
package/dist/plan.d.ts
CHANGED
|
@@ -35,5 +35,5 @@ interface IFlowSequence {
|
|
|
35
35
|
continueOnFailure?: boolean;
|
|
36
36
|
flows: string[];
|
|
37
37
|
}
|
|
38
|
-
export declare function plan(input: string, includeTags: string[], excludeTags: string[], excludeFlows?: string[], configFile?: string): Promise<IExecutionPlan>;
|
|
38
|
+
export declare function plan(input: string, includeTags: string[], excludeTags: string[], excludeFlows?: string[], configFile?: string, debug?: boolean): Promise<IExecutionPlan>;
|
|
39
39
|
export {};
|
package/dist/plan.js
CHANGED
|
@@ -64,7 +64,7 @@ function extractDeviceCloudOverrides(config) {
|
|
|
64
64
|
}
|
|
65
65
|
return overrides;
|
|
66
66
|
}
|
|
67
|
-
async function plan(input, includeTags, excludeTags, excludeFlows, configFile) {
|
|
67
|
+
async function plan(input, includeTags, excludeTags, excludeFlows, configFile, debug = false) {
|
|
68
68
|
const normalizedInput = path.normalize(input);
|
|
69
69
|
const flowMetadata = {};
|
|
70
70
|
if (!fs.existsSync(normalizedInput)) {
|
|
@@ -189,12 +189,30 @@ async function plan(input, includeTags, excludeTags, excludeFlows, configFile) {
|
|
|
189
189
|
const config = configPerFlowFile[filePath];
|
|
190
190
|
const name = config?.name || path.parse(filePath).name;
|
|
191
191
|
acc[name] = filePath;
|
|
192
|
+
if (debug) {
|
|
193
|
+
console.log(`[DEBUG] Flow name mapping: "${name}" -> ${filePath}`);
|
|
194
|
+
}
|
|
192
195
|
return acc;
|
|
193
196
|
}, {});
|
|
197
|
+
if (debug && workspaceConfig.executionOrder?.flowsOrder) {
|
|
198
|
+
console.log('[DEBUG] executionOrder.flowsOrder:', workspaceConfig.executionOrder.flowsOrder);
|
|
199
|
+
console.log('[DEBUG] Available flow names:', Object.keys(pathsByName));
|
|
200
|
+
}
|
|
194
201
|
const flowsToRunInSequence = workspaceConfig.executionOrder?.flowsOrder
|
|
195
|
-
?.map((flowOrder) =>
|
|
196
|
-
|
|
202
|
+
?.map((flowOrder) => {
|
|
203
|
+
// Strip .yaml/.yml extension only from the END of the string
|
|
204
|
+
// This supports flowsOrder entries like "my_test.yml" matching "my_test"
|
|
205
|
+
// while preserving extensions in the middle like "(file.yml) Name"
|
|
206
|
+
const normalizedFlowOrder = flowOrder.replace(/\.ya?ml$/i, '');
|
|
207
|
+
if (debug && flowOrder !== normalizedFlowOrder) {
|
|
208
|
+
console.log(`[DEBUG] Stripping trailing extension: "${flowOrder}" -> "${normalizedFlowOrder}"`);
|
|
209
|
+
}
|
|
210
|
+
return (0, planMethods_1.getFlowsToRunInSequence)(pathsByName, [normalizedFlowOrder], debug);
|
|
211
|
+
})
|
|
197
212
|
.flat() || [];
|
|
213
|
+
if (debug) {
|
|
214
|
+
console.log(`[DEBUG] Sequential flows resolved: ${flowsToRunInSequence.length} flow(s)`);
|
|
215
|
+
}
|
|
198
216
|
const normalFlows = allFlows
|
|
199
217
|
.filter((flow) => !flowsToRunInSequence.includes(flow))
|
|
200
218
|
.sort((a, b) => a.localeCompare(b));
|
package/dist/planMethods.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare function getFlowsToRunInSequence(paths: {
|
|
2
2
|
[key: string]: string;
|
|
3
|
-
}, flowOrder: string[]): string[];
|
|
3
|
+
}, flowOrder: string[], debug?: boolean): string[];
|
|
4
4
|
export declare function isFlowFile(filePath: string): boolean;
|
|
5
5
|
export declare const readYamlFileAsJson: (filePath: string) => unknown;
|
|
6
6
|
export declare const readTestYamlFileAsJson: (filePath: string) => {
|
package/dist/planMethods.js
CHANGED
|
@@ -9,25 +9,50 @@ const yaml = require("js-yaml");
|
|
|
9
9
|
const fs = require("node:fs");
|
|
10
10
|
const path = require("node:path");
|
|
11
11
|
const commandsThatRequireFiles = new Set(['addMedia', 'runFlow', 'runScript']);
|
|
12
|
-
function getFlowsToRunInSequence(paths, flowOrder) {
|
|
13
|
-
if (flowOrder.length === 0)
|
|
12
|
+
function getFlowsToRunInSequence(paths, flowOrder, debug = false) {
|
|
13
|
+
if (flowOrder.length === 0) {
|
|
14
|
+
if (debug) {
|
|
15
|
+
console.log('[DEBUG] getFlowsToRunInSequence: flowOrder is empty, returning []');
|
|
16
|
+
}
|
|
14
17
|
return [];
|
|
18
|
+
}
|
|
15
19
|
const orderSet = new Set(flowOrder);
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
20
|
+
const availableNames = Object.keys(paths);
|
|
21
|
+
if (debug) {
|
|
22
|
+
console.log(`[DEBUG] getFlowsToRunInSequence: Looking for flows in order: [${[...orderSet].join(', ')}]`);
|
|
23
|
+
console.log(`[DEBUG] getFlowsToRunInSequence: Available flow names: [${availableNames.join(', ')}]`);
|
|
24
|
+
}
|
|
25
|
+
const namesInOrder = availableNames.filter((key) => orderSet.has(key));
|
|
26
|
+
if (debug) {
|
|
27
|
+
console.log(`[DEBUG] getFlowsToRunInSequence: Matched ${namesInOrder.length} flow(s): [${namesInOrder.join(', ')}]`);
|
|
28
|
+
}
|
|
29
|
+
if (namesInOrder.length === 0) {
|
|
30
|
+
const notFound = [...orderSet].filter((item) => !availableNames.includes(item));
|
|
31
|
+
if (debug) {
|
|
32
|
+
console.log(`[DEBUG] getFlowsToRunInSequence: No flows matched, not found: [${notFound.join(', ')}]`);
|
|
33
|
+
}
|
|
18
34
|
return [];
|
|
35
|
+
}
|
|
19
36
|
const result = [...orderSet].filter((item) => namesInOrder.includes(item));
|
|
20
37
|
if (result.length === 0) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
.join(', ')}`);
|
|
38
|
+
const notFound = [...orderSet].filter((item) => !namesInOrder.includes(item));
|
|
39
|
+
throw new Error(`Could not find flows needed for execution in order: ${notFound.join(', ')}\n\nAvailable flow names:\n${availableNames.join('\n')}`);
|
|
24
40
|
}
|
|
25
41
|
else if (flowOrder
|
|
26
42
|
.slice(0, result.length)
|
|
27
43
|
.every((value, index) => value === result[index])) {
|
|
28
|
-
|
|
44
|
+
const resolvedPaths = result.map((item) => paths[item]);
|
|
45
|
+
if (debug) {
|
|
46
|
+
console.log(`[DEBUG] getFlowsToRunInSequence: Order matches, returning ${resolvedPaths.length} path(s)`);
|
|
47
|
+
}
|
|
48
|
+
return resolvedPaths;
|
|
29
49
|
}
|
|
30
50
|
else {
|
|
51
|
+
if (debug) {
|
|
52
|
+
console.log('[DEBUG] getFlowsToRunInSequence: Order does not match, returning []');
|
|
53
|
+
console.log(`[DEBUG] Expected order: [${flowOrder.slice(0, result.length).join(', ')}]`);
|
|
54
|
+
console.log(`[DEBUG] Actual result: [${result.join(', ')}]`);
|
|
55
|
+
}
|
|
31
56
|
return [];
|
|
32
57
|
}
|
|
33
58
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility for checking internet connectivity using third-party endpoints
|
|
3
|
+
*/
|
|
4
|
+
export type ConnectivityCheckResult = {
|
|
5
|
+
/** Whether internet connectivity was detected */
|
|
6
|
+
connected: boolean;
|
|
7
|
+
/** Detailed results for each endpoint tested */
|
|
8
|
+
endpointResults: Array<{
|
|
9
|
+
/** The endpoint URL that was tested */
|
|
10
|
+
endpoint: string;
|
|
11
|
+
/** Error message if request failed */
|
|
12
|
+
error?: string;
|
|
13
|
+
/** Time taken for the request in milliseconds */
|
|
14
|
+
latencyMs?: number;
|
|
15
|
+
/** HTTP status code if request succeeded */
|
|
16
|
+
statusCode?: number;
|
|
17
|
+
/** Whether this endpoint was reachable */
|
|
18
|
+
success: boolean;
|
|
19
|
+
}>;
|
|
20
|
+
/** Summary message for developers */
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Check if the system has internet connectivity by testing against
|
|
25
|
+
* multiple reliable third-party endpoints with detailed diagnostics.
|
|
26
|
+
*
|
|
27
|
+
* @returns Promise<ConnectivityCheckResult> - Detailed connectivity check results
|
|
28
|
+
*/
|
|
29
|
+
export declare function checkInternetConnectivity(): Promise<ConnectivityCheckResult>;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Utility for checking internet connectivity using third-party endpoints
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkInternetConnectivity = checkInternetConnectivity;
|
|
7
|
+
/**
|
|
8
|
+
* Check if the system has internet connectivity by testing against
|
|
9
|
+
* multiple reliable third-party endpoints with detailed diagnostics.
|
|
10
|
+
*
|
|
11
|
+
* @returns Promise<ConnectivityCheckResult> - Detailed connectivity check results
|
|
12
|
+
*/
|
|
13
|
+
async function checkInternetConnectivity() {
|
|
14
|
+
// Use multiple reliable endpoints to test connectivity
|
|
15
|
+
const testEndpoints = [
|
|
16
|
+
{ url: 'https://www.google.com/generate_204', description: 'Google' },
|
|
17
|
+
{ url: 'https://www.cloudflare.com/cdn-cgi/trace', description: 'Cloudflare' },
|
|
18
|
+
{ url: 'https://1.1.1.1/', description: 'Cloudflare DNS' },
|
|
19
|
+
];
|
|
20
|
+
const endpointResults = [];
|
|
21
|
+
let anySuccess = false;
|
|
22
|
+
// Try each endpoint with a short timeout
|
|
23
|
+
for (const { url, description } of testEndpoints) {
|
|
24
|
+
const startTime = Date.now();
|
|
25
|
+
try {
|
|
26
|
+
const controller = new AbortController();
|
|
27
|
+
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout
|
|
28
|
+
const response = await fetch(url, {
|
|
29
|
+
method: 'HEAD', // Use HEAD to minimize data transfer
|
|
30
|
+
signal: controller.signal,
|
|
31
|
+
// Disable redirects to get faster response
|
|
32
|
+
redirect: 'manual',
|
|
33
|
+
});
|
|
34
|
+
clearTimeout(timeoutId);
|
|
35
|
+
const latencyMs = Date.now() - startTime;
|
|
36
|
+
// Any response (including 3xx redirects) indicates connectivity
|
|
37
|
+
if (response) {
|
|
38
|
+
anySuccess = true;
|
|
39
|
+
endpointResults.push({
|
|
40
|
+
endpoint: `${description} (${url})`,
|
|
41
|
+
success: true,
|
|
42
|
+
statusCode: response.status,
|
|
43
|
+
latencyMs,
|
|
44
|
+
});
|
|
45
|
+
// Found working connection, no need to test more
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
const latencyMs = Date.now() - startTime;
|
|
51
|
+
let errorMessage = 'Unknown error';
|
|
52
|
+
if (error instanceof Error) {
|
|
53
|
+
if (error.name === 'AbortError') {
|
|
54
|
+
errorMessage = 'Request timeout (>3s)';
|
|
55
|
+
}
|
|
56
|
+
else if (error.message.includes('fetch failed')) {
|
|
57
|
+
errorMessage = 'Network request failed (DNS/connection error)';
|
|
58
|
+
}
|
|
59
|
+
else if (error.message.includes('ENOTFOUND')) {
|
|
60
|
+
errorMessage = 'DNS resolution failed';
|
|
61
|
+
}
|
|
62
|
+
else if (error.message.includes('ECONNREFUSED')) {
|
|
63
|
+
errorMessage = 'Connection refused';
|
|
64
|
+
}
|
|
65
|
+
else if (error.message.includes('ETIMEDOUT')) {
|
|
66
|
+
errorMessage = 'Connection timeout';
|
|
67
|
+
}
|
|
68
|
+
else if (error.message.includes('ENETUNREACH')) {
|
|
69
|
+
errorMessage = 'Network unreachable';
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
errorMessage = error.message;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
endpointResults.push({
|
|
76
|
+
endpoint: `${description} (${url})`,
|
|
77
|
+
success: false,
|
|
78
|
+
error: errorMessage,
|
|
79
|
+
latencyMs,
|
|
80
|
+
});
|
|
81
|
+
// Continue to next endpoint if this one fails
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Generate developer-friendly message
|
|
86
|
+
let message;
|
|
87
|
+
if (anySuccess) {
|
|
88
|
+
const successfulEndpoint = endpointResults.find((r) => r.success);
|
|
89
|
+
message = `Internet connectivity verified via ${successfulEndpoint?.endpoint} (${successfulEndpoint?.latencyMs}ms)`;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
const testedEndpoints = endpointResults.map((r) => r.endpoint).join(', ');
|
|
93
|
+
message = `No internet connectivity detected. Tested endpoints: ${testedEndpoints}`;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
connected: anySuccess,
|
|
97
|
+
endpointResults,
|
|
98
|
+
message,
|
|
99
|
+
};
|
|
100
|
+
}
|
package/oclif.manifest.json
CHANGED