@testingbot/cli 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +84 -7
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +55 -8
  4. package/dist/logger.js +4 -4
  5. package/dist/models/espresso_options.d.ts +9 -0
  6. package/dist/models/espresso_options.d.ts.map +1 -1
  7. package/dist/models/espresso_options.js +14 -0
  8. package/dist/models/maestro_options.d.ts +20 -7
  9. package/dist/models/maestro_options.d.ts.map +1 -1
  10. package/dist/models/maestro_options.js +22 -9
  11. package/dist/models/testingbot_error.d.ts +3 -0
  12. package/dist/models/testingbot_error.d.ts.map +1 -1
  13. package/dist/models/testingbot_error.js +5 -0
  14. package/dist/models/xcuitest_options.d.ts +9 -0
  15. package/dist/models/xcuitest_options.d.ts.map +1 -1
  16. package/dist/models/xcuitest_options.js +14 -0
  17. package/dist/providers/base_provider.d.ts +119 -0
  18. package/dist/providers/base_provider.d.ts.map +1 -0
  19. package/dist/providers/base_provider.js +296 -0
  20. package/dist/providers/espresso.d.ts +14 -21
  21. package/dist/providers/espresso.d.ts.map +1 -1
  22. package/dist/providers/espresso.js +50 -181
  23. package/dist/providers/login.d.ts +1 -0
  24. package/dist/providers/login.d.ts.map +1 -1
  25. package/dist/providers/login.js +16 -7
  26. package/dist/providers/maestro.d.ts +73 -21
  27. package/dist/providers/maestro.d.ts.map +1 -1
  28. package/dist/providers/maestro.js +842 -276
  29. package/dist/providers/xcuitest.d.ts +14 -21
  30. package/dist/providers/xcuitest.d.ts.map +1 -1
  31. package/dist/providers/xcuitest.js +50 -181
  32. package/dist/upload.d.ts +10 -0
  33. package/dist/upload.d.ts.map +1 -1
  34. package/dist/upload.js +46 -21
  35. package/dist/utils/connectivity.d.ts +26 -0
  36. package/dist/utils/connectivity.d.ts.map +1 -0
  37. package/dist/utils/connectivity.js +131 -0
  38. package/dist/utils/error-helpers.d.ts +26 -0
  39. package/dist/utils/error-helpers.d.ts.map +1 -0
  40. package/dist/utils/error-helpers.js +237 -0
  41. package/dist/utils/file-type-detector.d.ts +4 -1
  42. package/dist/utils/file-type-detector.d.ts.map +1 -1
  43. package/dist/utils/file-type-detector.js +30 -6
  44. package/dist/utils.d.ts.map +1 -1
  45. package/dist/utils.js +8 -3
  46. package/package.json +2 -2
@@ -0,0 +1,131 @@
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
+ exports.formatConnectivityResults = formatConnectivityResults;
8
+ /**
9
+ * Test a single endpoint and return the result
10
+ */
11
+ async function testEndpoint(url, description) {
12
+ const startTime = Date.now();
13
+ try {
14
+ const controller = new AbortController();
15
+ const timeoutId = setTimeout(() => controller.abort(), 3000);
16
+ const response = await fetch(url, {
17
+ method: 'HEAD',
18
+ signal: controller.signal,
19
+ redirect: 'manual',
20
+ });
21
+ clearTimeout(timeoutId);
22
+ const latencyMs = Date.now() - startTime;
23
+ return {
24
+ endpoint: `${description} (${url})`,
25
+ success: true,
26
+ statusCode: response.status,
27
+ latencyMs,
28
+ };
29
+ }
30
+ catch (error) {
31
+ const latencyMs = Date.now() - startTime;
32
+ let errorMessage = 'Unknown error';
33
+ if (error instanceof Error) {
34
+ if (error.name === 'AbortError') {
35
+ errorMessage = 'Request timeout (>3s)';
36
+ }
37
+ else if (error.message.includes('fetch failed')) {
38
+ errorMessage = 'Network request failed (DNS/connection error)';
39
+ }
40
+ else if (error.message.includes('ENOTFOUND')) {
41
+ errorMessage = 'DNS resolution failed';
42
+ }
43
+ else if (error.message.includes('ECONNREFUSED')) {
44
+ errorMessage = 'Connection refused';
45
+ }
46
+ else if (error.message.includes('ETIMEDOUT')) {
47
+ errorMessage = 'Connection timeout';
48
+ }
49
+ else if (error.message.includes('ENETUNREACH')) {
50
+ errorMessage = 'Network unreachable';
51
+ }
52
+ else {
53
+ errorMessage = error.message;
54
+ }
55
+ }
56
+ return {
57
+ endpoint: `${description} (${url})`,
58
+ success: false,
59
+ error: errorMessage,
60
+ latencyMs,
61
+ };
62
+ }
63
+ }
64
+ /**
65
+ * Check if the system has internet connectivity by testing against
66
+ * multiple reliable third-party endpoints in parallel.
67
+ * Returns as soon as one endpoint succeeds, reducing latency significantly.
68
+ */
69
+ async function checkInternetConnectivity() {
70
+ const testEndpoints = [
71
+ { url: 'https://www.google.com/generate_204', description: 'Google' },
72
+ {
73
+ url: 'https://www.cloudflare.com/cdn-cgi/trace',
74
+ description: 'Cloudflare',
75
+ },
76
+ { url: 'https://1.1.1.1/', description: 'Cloudflare DNS' },
77
+ ];
78
+ // Test all endpoints in parallel
79
+ const endpointPromises = testEndpoints.map(({ url, description }) => testEndpoint(url, description));
80
+ // Use Promise.any to return on first success, or collect all failures
81
+ try {
82
+ // Create promises that only resolve on success
83
+ const successPromises = endpointPromises.map(async (promise) => {
84
+ const result = await promise;
85
+ if (result.success) {
86
+ return result;
87
+ }
88
+ throw result; // Throw failures so Promise.any continues to next
89
+ });
90
+ const successResult = await Promise.any(successPromises);
91
+ return {
92
+ connected: true,
93
+ endpointResults: [successResult],
94
+ message: `Internet connectivity verified via ${successResult.endpoint} (${successResult.latencyMs}ms)`,
95
+ };
96
+ }
97
+ catch (aggregateError) {
98
+ // All endpoints failed - collect all results
99
+ const endpointResults = await Promise.all(endpointPromises);
100
+ const testedEndpoints = endpointResults.map((r) => r.endpoint).join(', ');
101
+ return {
102
+ connected: false,
103
+ endpointResults,
104
+ message: `No internet connectivity detected. Tested endpoints: ${testedEndpoints}`,
105
+ };
106
+ }
107
+ }
108
+ /**
109
+ * Format connectivity check results for display
110
+ */
111
+ function formatConnectivityResults(result) {
112
+ const lines = [];
113
+ if (result.connected) {
114
+ lines.push(`āœ“ ${result.message}`);
115
+ }
116
+ else {
117
+ lines.push(`āœ— ${result.message}`);
118
+ lines.push('');
119
+ lines.push('Endpoint results:');
120
+ for (const endpoint of result.endpointResults) {
121
+ lines.push(` • ${endpoint.endpoint}: ${endpoint.error} (${endpoint.latencyMs}ms)`);
122
+ }
123
+ lines.push('');
124
+ lines.push('Troubleshooting steps:');
125
+ lines.push(' 1. Check your internet connection');
126
+ lines.push(' 2. Verify no firewall is blocking outbound connections');
127
+ lines.push(' 3. Check if a VPN or proxy is interfering');
128
+ lines.push(' 4. Try: ping google.com');
129
+ }
130
+ return lines.join('\n');
131
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Error enhancement utilities for better diagnostics
3
+ */
4
+ import { AxiosError } from 'axios';
5
+ import TestingBotError from '../models/testingbot_error';
6
+ /**
7
+ * Enhances generic network errors with more specific diagnostic information
8
+ */
9
+ export declare function enhanceNetworkError(error: Error, url: string): TestingBotError;
10
+ /**
11
+ * Get a user-friendly error message for an HTTP status code
12
+ */
13
+ export declare function getStatusCodeMessage(statusCode: number, serverMessage?: string): string;
14
+ /**
15
+ * Handle Axios errors with enhanced diagnostics
16
+ */
17
+ export declare function handleAxiosError(error: AxiosError, operation: string): TestingBotError;
18
+ /**
19
+ * Check if an error is a network-level error (vs HTTP error)
20
+ */
21
+ export declare function isNetworkError(error: AxiosError): boolean;
22
+ /**
23
+ * Check if error is retryable
24
+ */
25
+ export declare function isRetryableError(error: AxiosError): boolean;
26
+ //# sourceMappingURL=error-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-helpers.d.ts","sourceRoot":"","sources":["../../src/utils/error-helpers.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AACnC,OAAO,eAAe,MAAM,4BAA4B,CAAC;AAEzD;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,MAAM,GACV,eAAe,CAkCjB;AAoFD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,MAAM,EAClB,aAAa,CAAC,EAAE,MAAM,GACrB,MAAM,CAwBR;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,MAAM,GAChB,eAAe,CA8DjB;AA6BD;;GAEG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAEzD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAW3D"}
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ /**
3
+ * Error enhancement utilities for better diagnostics
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.enhanceNetworkError = enhanceNetworkError;
10
+ exports.getStatusCodeMessage = getStatusCodeMessage;
11
+ exports.handleAxiosError = handleAxiosError;
12
+ exports.isNetworkError = isNetworkError;
13
+ exports.isRetryableError = isRetryableError;
14
+ const testingbot_error_1 = __importDefault(require("../models/testingbot_error"));
15
+ /**
16
+ * Enhances generic network errors with more specific diagnostic information
17
+ */
18
+ function enhanceNetworkError(error, url) {
19
+ let hostname;
20
+ let origin;
21
+ try {
22
+ const urlObj = new URL(url);
23
+ hostname = urlObj.hostname;
24
+ origin = urlObj.origin;
25
+ }
26
+ catch {
27
+ hostname = url;
28
+ origin = url;
29
+ }
30
+ const lines = [
31
+ `Network request failed: ${url}`,
32
+ '',
33
+ 'Possible causes:',
34
+ ` 1. No internet connection - check your network connectivity`,
35
+ ` 2. DNS resolution failed - unable to resolve "${hostname}"`,
36
+ ` 3. Firewall or proxy blocking the request`,
37
+ ` 4. API server is down or unreachable`,
38
+ ` 5. SSL/TLS certificate validation failed`,
39
+ '',
40
+ 'Troubleshooting steps:',
41
+ ` • Check internet connection: ping google.com`,
42
+ ` • Test API reachability: curl ${origin}`,
43
+ ` • Verify API URL is correct: ${origin}`,
44
+ ` • Check for proxy/VPN interference`,
45
+ ` • Try again in a few moments if server is temporarily down`,
46
+ '',
47
+ `Original error: ${error.message}`,
48
+ ];
49
+ return new testingbot_error_1.default(lines.join('\n'), { cause: error });
50
+ }
51
+ const STATUS_CODE_MESSAGES = {
52
+ 400: {
53
+ message: 'Invalid request',
54
+ troubleshooting: [
55
+ 'Check that all required parameters are provided',
56
+ 'Verify the file format is correct (APK for Android, IPA/ZIP for iOS)',
57
+ 'Ensure the request payload is valid',
58
+ ],
59
+ },
60
+ 401: {
61
+ message: 'Invalid TestingBot credentials. Please check your API key and secret',
62
+ troubleshooting: [
63
+ 'Run "testingbot login" to authenticate via browser',
64
+ 'Use --api-key and --api-secret command line options',
65
+ 'Set TB_KEY and TB_SECRET environment variables',
66
+ 'Create ~/.testingbot file with content: key:secret',
67
+ ],
68
+ },
69
+ 403: {
70
+ message: 'Access denied',
71
+ troubleshooting: [
72
+ 'Check your account has the required permissions',
73
+ 'Verify your subscription plan includes this feature',
74
+ 'Contact support if you believe this is an error',
75
+ ],
76
+ },
77
+ 404: {
78
+ message: 'Resource not found',
79
+ troubleshooting: [
80
+ 'Verify the resource ID or path is correct',
81
+ 'Check if the resource was deleted or expired',
82
+ ],
83
+ },
84
+ 429: {
85
+ message: 'Your TestingBot credits are depleted',
86
+ troubleshooting: [
87
+ 'Check your remaining credits at https://testingbot.com/members',
88
+ 'Upgrade your plan at https://testingbot.com/pricing',
89
+ 'Contact support if you believe this is an error',
90
+ ],
91
+ },
92
+ 500: {
93
+ message: 'Server error occurred',
94
+ troubleshooting: [
95
+ 'This is a temporary issue on our end',
96
+ 'Please try again in a few moments',
97
+ 'Contact support if the issue persists',
98
+ ],
99
+ },
100
+ 502: {
101
+ message: 'Bad gateway - service temporarily unavailable',
102
+ troubleshooting: [
103
+ 'The service is experiencing issues',
104
+ 'Please try again in a few moments',
105
+ ],
106
+ },
107
+ 503: {
108
+ message: 'Service temporarily unavailable',
109
+ troubleshooting: [
110
+ 'The service is under maintenance or overloaded',
111
+ 'Please try again in a few moments',
112
+ ],
113
+ },
114
+ 504: {
115
+ message: 'Gateway timeout',
116
+ troubleshooting: [
117
+ 'The request took too long to process',
118
+ 'Try with a smaller file or simpler request',
119
+ 'Check your network connection speed',
120
+ ],
121
+ },
122
+ };
123
+ /**
124
+ * Get a user-friendly error message for an HTTP status code
125
+ */
126
+ function getStatusCodeMessage(statusCode, serverMessage) {
127
+ const config = STATUS_CODE_MESSAGES[statusCode];
128
+ if (!config) {
129
+ return serverMessage
130
+ ? `Request failed (HTTP ${statusCode}): ${serverMessage}`
131
+ : `Request failed with HTTP status ${statusCode}`;
132
+ }
133
+ const lines = [config.message];
134
+ if (serverMessage) {
135
+ lines.push(`Details: ${serverMessage}`);
136
+ }
137
+ if (config.troubleshooting && config.troubleshooting.length > 0) {
138
+ lines.push('');
139
+ lines.push('Troubleshooting:');
140
+ for (const step of config.troubleshooting) {
141
+ lines.push(` • ${step}`);
142
+ }
143
+ }
144
+ return lines.join('\n');
145
+ }
146
+ /**
147
+ * Handle Axios errors with enhanced diagnostics
148
+ */
149
+ function handleAxiosError(error, operation) {
150
+ // Network-level errors (no response)
151
+ if (!error.response) {
152
+ if (error.code === 'ECONNREFUSED') {
153
+ return new testingbot_error_1.default(`${operation}: Connection refused. The server may be down or unreachable.\n\n` +
154
+ 'Troubleshooting:\n' +
155
+ ' • Check if the API server is running\n' +
156
+ ' • Verify the API URL is correct\n' +
157
+ ' • Check for firewall or proxy issues', { cause: error });
158
+ }
159
+ if (error.code === 'ENOTFOUND') {
160
+ return new testingbot_error_1.default(`${operation}: DNS resolution failed. Could not resolve the server hostname.\n\n` +
161
+ 'Troubleshooting:\n' +
162
+ ' • Check your internet connection\n' +
163
+ ' • Verify the API URL is correct\n' +
164
+ ' • Try: ping api.testingbot.com', { cause: error });
165
+ }
166
+ if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
167
+ return new testingbot_error_1.default(`${operation}: Connection timed out. The request took too long to complete.\n\n` +
168
+ 'Troubleshooting:\n' +
169
+ ' • Check your internet connection speed\n' +
170
+ ' • Try again - the server may be temporarily slow\n' +
171
+ ' • For large files, ensure stable connection', { cause: error });
172
+ }
173
+ if (error.code === 'CERT_HAS_EXPIRED' ||
174
+ error.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
175
+ return new testingbot_error_1.default(`${operation}: SSL/TLS certificate error.\n\n` +
176
+ 'Troubleshooting:\n' +
177
+ ' • Check your system date and time are correct\n' +
178
+ ' • Update your CA certificates\n' +
179
+ ' • Check for proxy/VPN interference', { cause: error });
180
+ }
181
+ // Generic network error
182
+ return enhanceNetworkError(error, error.config?.url || 'unknown URL');
183
+ }
184
+ // HTTP errors (have response)
185
+ const statusCode = error.response.status;
186
+ const serverMessage = extractServerMessage(error.response.data);
187
+ return new testingbot_error_1.default(`${operation}: ${getStatusCodeMessage(statusCode, serverMessage)}`, { cause: error });
188
+ }
189
+ /**
190
+ * Extract error message from various server response formats
191
+ */
192
+ function extractServerMessage(data) {
193
+ if (!data)
194
+ return undefined;
195
+ if (typeof data === 'string') {
196
+ // Try to parse as JSON
197
+ try {
198
+ const parsed = JSON.parse(data);
199
+ return parsed.message || parsed.error || data;
200
+ }
201
+ catch {
202
+ return data;
203
+ }
204
+ }
205
+ if (typeof data === 'object') {
206
+ const obj = data;
207
+ if (typeof obj.message === 'string')
208
+ return obj.message;
209
+ if (typeof obj.error === 'string')
210
+ return obj.error;
211
+ if (typeof obj.errors === 'string')
212
+ return obj.errors;
213
+ if (Array.isArray(obj.errors))
214
+ return obj.errors.join(', ');
215
+ }
216
+ return undefined;
217
+ }
218
+ /**
219
+ * Check if an error is a network-level error (vs HTTP error)
220
+ */
221
+ function isNetworkError(error) {
222
+ return !error.response && !!error.code;
223
+ }
224
+ /**
225
+ * Check if error is retryable
226
+ */
227
+ function isRetryableError(error) {
228
+ // Network errors are usually retryable
229
+ if (isNetworkError(error)) {
230
+ return true;
231
+ }
232
+ // Some HTTP errors are retryable
233
+ const retryableStatusCodes = [408, 429, 500, 502, 503, 504];
234
+ return error.response
235
+ ? retryableStatusCodes.includes(error.response.status)
236
+ : false;
237
+ }
@@ -9,7 +9,10 @@ export interface FileTypeResult {
9
9
  export declare function detectFileType(filePath: string): Promise<FileTypeResult | undefined>;
10
10
  /**
11
11
  * Detect platform (Android or iOS) from app file.
12
- * Uses magic bytes for content detection, with extension fallback for zip-based formats.
12
+ * Uses a combination of magic bytes and file extension for reliable detection.
13
+ *
14
+ * Android: .apk, .apks files
15
+ * iOS: .ipa, .app, .zip files (when not APK)
13
16
  */
14
17
  export declare function detectPlatformFromFile(filePath: string): Promise<'Android' | 'iOS' | undefined>;
15
18
  //# sourceMappingURL=file-type-detector.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"file-type-detector.d.ts","sourceRoot":"","sources":["../../src/utils/file-type-detector.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CASrC;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC,CAmBxC"}
1
+ {"version":3,"file":"file-type-detector.d.ts","sourceRoot":"","sources":["../../src/utils/file-type-detector.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CASrC;AASD;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC,CAgCxC"}
@@ -1,7 +1,11 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.detectFileType = detectFileType;
4
7
  exports.detectPlatformFromFile = detectPlatformFromFile;
8
+ const node_path_1 = __importDefault(require("node:path"));
5
9
  /**
6
10
  * Detect file type from file content using magic bytes.
7
11
  * Returns undefined if the file type cannot be determined.
@@ -17,22 +21,42 @@ async function detectFileType(filePath) {
17
21
  return undefined;
18
22
  }
19
23
  }
24
+ /**
25
+ * Get file extension in lowercase without the dot
26
+ */
27
+ function getExtension(filePath) {
28
+ return node_path_1.default.extname(filePath).toLowerCase().slice(1);
29
+ }
20
30
  /**
21
31
  * Detect platform (Android or iOS) from app file.
22
- * Uses magic bytes for content detection, with extension fallback for zip-based formats.
32
+ * Uses a combination of magic bytes and file extension for reliable detection.
33
+ *
34
+ * Android: .apk, .apks files
35
+ * iOS: .ipa, .app, .zip files (when not APK)
23
36
  */
24
37
  async function detectPlatformFromFile(filePath) {
38
+ const ext = getExtension(filePath);
25
39
  const fileType = await detectFileType(filePath);
40
+ // Check for Android APK files
26
41
  if (fileType) {
27
- // APK files are detected as 'application/zip' with ext 'apk'
28
- // or as 'application/vnd.android.package-archive'
29
42
  if (fileType.ext === 'apk' ||
30
43
  fileType.mime === 'application/vnd.android.package-archive') {
31
44
  return 'Android';
32
45
  }
33
- if (fileType.ext === 'zip' || fileType.mime === 'application/zip') {
34
- return 'iOS';
35
- }
46
+ }
47
+ // Extension-based detection (more reliable for mobile apps)
48
+ // APK and APKS are Android
49
+ if (ext === 'apk' || ext === 'apks') {
50
+ return 'Android';
51
+ }
52
+ // IPA, APP are iOS
53
+ if (ext === 'ipa' || ext === 'app') {
54
+ return 'iOS';
55
+ }
56
+ // ZIP files could be either, but commonly used for iOS simulator builds
57
+ // If magic bytes detected it as zip and extension is .zip, assume iOS
58
+ if (ext === 'zip' && fileType?.mime === 'application/zip') {
59
+ return 'iOS';
36
60
  }
37
61
  return undefined;
38
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";oBAOkB,MAAM;yBAID,MAAM;IAI3B;;;OAGG;wBACiB,MAAM,MAAM,MAAM,GAAG,MAAM;IAa/C;;OAEG;kCAC2B,MAAM,GAAG,SAAS,GAAG,IAAI;;AA7BzD,wBAiDE"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":";oBAOkB,MAAM;yBAID,MAAM;IAI3B;;;OAGG;wBACiB,MAAM,MAAM,MAAM,GAAG,MAAM;IAa/C;;OAEG;kCAC2B,MAAM,GAAG,SAAS,GAAG,IAAI;;AA7BzD,wBAuDE"}
package/dist/utils.js CHANGED
@@ -5,7 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const package_json_1 = __importDefault(require("../package.json"));
7
7
  const logger_1 = __importDefault(require("./logger"));
8
- const colors_1 = __importDefault(require("colors"));
8
+ const picocolors_1 = __importDefault(require("picocolors"));
9
9
  let versionCheckDisplayed = false;
10
10
  exports.default = {
11
11
  getUserAgent() {
@@ -41,8 +41,13 @@ exports.default = {
41
41
  const currentVersion = this.getCurrentVersion();
42
42
  if (this.compareVersions(currentVersion, latestVersion) < 0) {
43
43
  versionCheckDisplayed = true;
44
- logger_1.default.warn(colors_1.default.yellow(`\nšŸ“¦ A new version of testingbotctl is available: ${colors_1.default.green(latestVersion)} (current: ${currentVersion})`));
45
- logger_1.default.warn(colors_1.default.yellow(` Run ${colors_1.default.cyan('npm update -g testingbotctl')} to update.\n`));
44
+ const border = '─'.repeat(80);
45
+ logger_1.default.info(`\nCLI Version: ${picocolors_1.default.cyan(currentVersion)}\n`);
46
+ logger_1.default.warn(picocolors_1.default.yellow(border));
47
+ logger_1.default.warn(picocolors_1.default.yellow('⚠ Update Available'));
48
+ logger_1.default.warn(picocolors_1.default.yellow(` A new version of the TestingBot CLI is available: ${picocolors_1.default.green(latestVersion)}`));
49
+ logger_1.default.warn(picocolors_1.default.yellow(` Run: ${picocolors_1.default.cyan('npm install -g @testingbot/cli@latest')}`));
50
+ logger_1.default.warn(picocolors_1.default.yellow(border) + '\n');
46
51
  }
47
52
  },
48
53
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testingbot/cli",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "CLI tool to run Espresso, XCUITest, and Maestro tests on TestingBot's cloud infrastructure",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -49,12 +49,12 @@
49
49
  "dependencies": {
50
50
  "archiver": "^7.0.1",
51
51
  "axios": "^1.13.2",
52
- "colors": "^1.4.0",
53
52
  "commander": "^14.0.2",
54
53
  "file-type": "^21.1.1",
55
54
  "form-data": "^4.0.5",
56
55
  "glob": "^13.0.0",
57
56
  "js-yaml": "^4.1.1",
57
+ "picocolors": "^1.1.1",
58
58
  "progress-stream": "^2.0.0",
59
59
  "socket.io-client": "^4.8.1",
60
60
  "tracer": "^1.3.0"