@devicecloud.dev/dcd 3.6.5 → 3.6.7
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.d.ts +8 -0
- package/dist/commands/cloud.js +165 -20
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +5 -1
- package/dist/methods.d.ts +11 -0
- package/dist/methods.js +20 -1
- package/oclif.manifest.json +8 -2
- package/package.json +1 -1
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -44,6 +44,7 @@ export default class Cloud extends Command {
|
|
|
44
44
|
'device-locale': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
45
45
|
'download-artifacts': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
46
46
|
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
47
|
+
debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
47
48
|
env: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
48
49
|
'exclude-flows': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
49
50
|
'exclude-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -66,6 +67,13 @@ export default class Cloud extends Command {
|
|
|
66
67
|
};
|
|
67
68
|
static enableJsonFlag: boolean;
|
|
68
69
|
private versionCheck;
|
|
70
|
+
/**
|
|
71
|
+
* Generate the JSON output file path based on name or upload ID
|
|
72
|
+
* @param name - Optional custom name for the file
|
|
73
|
+
* @param uploadId - Upload ID to use if name is not provided
|
|
74
|
+
* @returns Path to the JSON output file
|
|
75
|
+
*/
|
|
76
|
+
private getJsonOutputPath;
|
|
69
77
|
run(): Promise<{
|
|
70
78
|
uploadId: string;
|
|
71
79
|
consoleUrl: string;
|
package/dist/commands/cloud.js
CHANGED
|
@@ -6,7 +6,6 @@ const core_1 = require("@oclif/core");
|
|
|
6
6
|
const cli_ux_1 = require("@oclif/core/lib/cli-ux");
|
|
7
7
|
const errors_1 = require("@oclif/core/lib/errors");
|
|
8
8
|
const path = require("node:path");
|
|
9
|
-
const fs = require("node:fs");
|
|
10
9
|
const constants_1 = require("../constants");
|
|
11
10
|
const methods_1 = require("../methods");
|
|
12
11
|
const plan_1 = require("../plan");
|
|
@@ -81,11 +80,36 @@ class Cloud extends core_1.Command {
|
|
|
81
80
|
`);
|
|
82
81
|
}
|
|
83
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* Generate the JSON output file path based on name or upload ID
|
|
85
|
+
* @param name - Optional custom name for the file
|
|
86
|
+
* @param uploadId - Upload ID to use if name is not provided
|
|
87
|
+
* @returns Path to the JSON output file
|
|
88
|
+
*/
|
|
89
|
+
getJsonOutputPath(name, uploadId) {
|
|
90
|
+
return name ? `${name}_dcd.json` : `${uploadId}_dcd.json`;
|
|
91
|
+
}
|
|
84
92
|
async run() {
|
|
85
93
|
let output = null;
|
|
94
|
+
// Store debug flag outside try/catch to access it in catch block
|
|
95
|
+
let debugFlag = false;
|
|
96
|
+
let jsonFile = false;
|
|
86
97
|
try {
|
|
87
98
|
const { args, flags, raw } = await this.parse(Cloud);
|
|
88
|
-
let { 'additional-app-binary-ids': nonFlatAdditionalAppBinaryIds, 'additional-app-files': nonFlatAdditionalAppFiles, 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey: apiKeyFlag, apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, 'artifacts-path': artifactsPath, async, config: configFile, 'device-locale': deviceLocale, 'download-artifacts': downloadArtifacts, env, 'exclude-flows': excludeFlows, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'include-tags': includeTags, 'ignore-sha-check': ignoreShaCheck, 'ios-device': iOSDevice, 'ios-version': iOSVersion, 'maestro-version': maestroVersion, name, orientation, quiet, retry, report, 'runner-type': runnerType, 'x86-arch': x86Arch, json, ...rest } = flags;
|
|
99
|
+
let { 'additional-app-binary-ids': nonFlatAdditionalAppBinaryIds, 'additional-app-files': nonFlatAdditionalAppFiles, 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey: apiKeyFlag, apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, 'artifacts-path': artifactsPath, async, config: configFile, debug, 'device-locale': deviceLocale, 'download-artifacts': downloadArtifacts, env, 'exclude-flows': excludeFlows, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'include-tags': includeTags, 'ignore-sha-check': ignoreShaCheck, 'ios-device': iOSDevice, 'ios-version': iOSVersion, 'maestro-version': maestroVersion, name, orientation, quiet, retry, report, 'runner-type': runnerType, 'x86-arch': x86Arch, json, ...rest } = flags;
|
|
100
|
+
// Store debug flag for use in catch block
|
|
101
|
+
debugFlag = debug === true;
|
|
102
|
+
jsonFile = flags['json-file'] === true;
|
|
103
|
+
if (debug) {
|
|
104
|
+
this.log('DEBUG: Starting command execution with debug logging enabled');
|
|
105
|
+
this.log(`DEBUG: CLI Version: ${this.config.version}`);
|
|
106
|
+
this.log(`DEBUG: Node version: ${process.versions.node}`);
|
|
107
|
+
this.log(`DEBUG: OS: ${process.platform} ${process.arch}`);
|
|
108
|
+
}
|
|
109
|
+
if (flags['json-file']) {
|
|
110
|
+
quiet = true;
|
|
111
|
+
this.log('--json-file is true: JSON output will be written to file, forcing --quiet flag for better CI output');
|
|
112
|
+
}
|
|
89
113
|
if (json) {
|
|
90
114
|
const originalStdoutWrite = process.stdout.write;
|
|
91
115
|
process.stdout.write = function (chunk, encodingOrCallback, cb) {
|
|
@@ -104,6 +128,10 @@ class Cloud extends core_1.Command {
|
|
|
104
128
|
const apiKey = apiKeyFlag || process.env.DEVICE_CLOUD_API_KEY;
|
|
105
129
|
if (!apiKey)
|
|
106
130
|
throw new Error('You must provide an API key via --api-key flag or DEVICE_CLOUD_API_KEY environment variable');
|
|
131
|
+
if (debug) {
|
|
132
|
+
this.log(`DEBUG: API URL: ${apiUrl}`);
|
|
133
|
+
this.log(`DEBUG: API Key provided: ${apiKey ? 'Yes' : 'No'}`);
|
|
134
|
+
}
|
|
107
135
|
const [maestroMajorString, maestroMinorString] = maestroVersion.split('.');
|
|
108
136
|
if (report === 'html' &&
|
|
109
137
|
maestroMajorString === '1' &&
|
|
@@ -128,6 +156,15 @@ class Cloud extends core_1.Command {
|
|
|
128
156
|
let finalAdditionalBinaryIds = additionalAppBinaryIds;
|
|
129
157
|
const finalAppFile = appFile ?? firstFile;
|
|
130
158
|
let flowFile = flows ?? secondFile;
|
|
159
|
+
if (debug) {
|
|
160
|
+
this.log(`DEBUG: First file argument: ${firstFile || 'not provided'}`);
|
|
161
|
+
this.log(`DEBUG: Second file argument: ${secondFile || 'not provided'}`);
|
|
162
|
+
this.log(`DEBUG: App binary ID: ${appBinaryId || 'not provided'}`);
|
|
163
|
+
this.log(`DEBUG: App file: ${finalAppFile || 'not provided'}`);
|
|
164
|
+
this.log(`DEBUG: Flow file: ${flowFile || 'not provided'}`);
|
|
165
|
+
this.log(`DEBUG: Additional app binary IDs: ${additionalAppBinaryIds?.join(', ') || 'none'}`);
|
|
166
|
+
this.log(`DEBUG: Additional app files: ${additionalAppFiles?.join(', ') || 'none'}`);
|
|
167
|
+
}
|
|
131
168
|
if (appBinaryId) {
|
|
132
169
|
if (secondFile) {
|
|
133
170
|
throw new Error('You cannot provide both an appBinaryId and a binary file');
|
|
@@ -144,6 +181,11 @@ class Cloud extends core_1.Command {
|
|
|
144
181
|
if (!supportediOSVersions.includes(version)) {
|
|
145
182
|
throw new Error(`${iOSDeviceID} only supports these iOS versions: ${supportediOSVersions.join(', ')}`);
|
|
146
183
|
}
|
|
184
|
+
if (debug) {
|
|
185
|
+
this.log(`DEBUG: iOS device: ${iOSDeviceID}`);
|
|
186
|
+
this.log(`DEBUG: iOS version: ${version}`);
|
|
187
|
+
this.log(`DEBUG: Supported iOS versions: ${supportediOSVersions.join(', ')}`);
|
|
188
|
+
}
|
|
147
189
|
}
|
|
148
190
|
if (androidApiLevel || androidDevice) {
|
|
149
191
|
const androidDeviceID = androidDevice || 'pixel-7';
|
|
@@ -158,6 +200,12 @@ class Cloud extends core_1.Command {
|
|
|
158
200
|
if (!supportedAndroidVersions.includes(version)) {
|
|
159
201
|
throw new Error(`${androidDeviceID} ${googlePlay ? '(Play Store) ' : ''}only supports these Android API levels: ${supportedAndroidVersions.join(', ')}`);
|
|
160
202
|
}
|
|
203
|
+
if (debug) {
|
|
204
|
+
this.log(`DEBUG: Android device: ${androidDeviceID}`);
|
|
205
|
+
this.log(`DEBUG: Android API level: ${version}`);
|
|
206
|
+
this.log(`DEBUG: Google Play enabled: ${googlePlay}`);
|
|
207
|
+
this.log(`DEBUG: Supported Android versions: ${supportedAndroidVersions.join(', ')}`);
|
|
208
|
+
}
|
|
161
209
|
}
|
|
162
210
|
flowFile = path.resolve(flowFile);
|
|
163
211
|
if (!flowFile?.endsWith('.yaml') &&
|
|
@@ -165,14 +213,35 @@ class Cloud extends core_1.Command {
|
|
|
165
213
|
!flowFile?.endsWith('/')) {
|
|
166
214
|
flowFile += '/';
|
|
167
215
|
}
|
|
216
|
+
if (debug) {
|
|
217
|
+
this.log(`DEBUG: Resolved flow file path: ${flowFile}`);
|
|
218
|
+
}
|
|
168
219
|
let executionPlan;
|
|
169
220
|
try {
|
|
221
|
+
if (debug) {
|
|
222
|
+
this.log('DEBUG: Generating execution plan...');
|
|
223
|
+
}
|
|
170
224
|
executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat(), excludeFlows.flat(), configFile);
|
|
225
|
+
if (debug) {
|
|
226
|
+
this.log(`DEBUG: Execution plan generated`);
|
|
227
|
+
this.log(`DEBUG: Total flow files: ${executionPlan.totalFlowFiles}`);
|
|
228
|
+
this.log(`DEBUG: Flows to run: ${executionPlan.flowsToRun.length}`);
|
|
229
|
+
this.log(`DEBUG: Referenced files: ${executionPlan.referencedFiles.length}`);
|
|
230
|
+
this.log(`DEBUG: Sequential flows: ${executionPlan.sequence?.flows.length || 0}`);
|
|
231
|
+
}
|
|
171
232
|
}
|
|
172
233
|
catch (error) {
|
|
234
|
+
if (debug) {
|
|
235
|
+
this.log(`DEBUG: Error generating execution plan: ${error}`);
|
|
236
|
+
}
|
|
173
237
|
throw error;
|
|
174
238
|
}
|
|
175
239
|
const { allExcludeTags, allIncludeTags, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
|
|
240
|
+
if (debug) {
|
|
241
|
+
this.log(`DEBUG: All include tags: ${allIncludeTags?.join(', ') || 'none'}`);
|
|
242
|
+
this.log(`DEBUG: All exclude tags: ${allExcludeTags?.join(', ') || 'none'}`);
|
|
243
|
+
this.log(`DEBUG: Test file names: ${testFileNames.join(', ')}`);
|
|
244
|
+
}
|
|
176
245
|
const pathsShortestToLongest = [
|
|
177
246
|
...testFileNames,
|
|
178
247
|
...referencedFiles,
|
|
@@ -185,7 +254,14 @@ class Cloud extends core_1.Command {
|
|
|
185
254
|
if (isRoot)
|
|
186
255
|
commonRoot = folderPath;
|
|
187
256
|
}
|
|
257
|
+
if (debug) {
|
|
258
|
+
this.log(`DEBUG: Common root directory: ${commonRoot}`);
|
|
259
|
+
}
|
|
188
260
|
const { continueOnFailure = true, flows: sequentialFlows = [] } = sequence ?? {};
|
|
261
|
+
if (debug && sequentialFlows.length > 0) {
|
|
262
|
+
this.log(`DEBUG: Sequential flows: ${sequentialFlows.join(', ')}`);
|
|
263
|
+
this.log(`DEBUG: Continue on failure: ${continueOnFailure}`);
|
|
264
|
+
}
|
|
189
265
|
if (!appBinaryId) {
|
|
190
266
|
if (!(flowFile && finalAppFile)) {
|
|
191
267
|
throw new Error('You must provide a flow file and an app binary id');
|
|
@@ -194,9 +270,15 @@ class Cloud extends core_1.Command {
|
|
|
194
270
|
throw new Error('App file must be a .apk for android or .app/.zip file for iOS');
|
|
195
271
|
}
|
|
196
272
|
if (finalAppFile.endsWith('.zip')) {
|
|
273
|
+
if (debug) {
|
|
274
|
+
this.log(`DEBUG: Verifying iOS app zip file: ${finalAppFile}`);
|
|
275
|
+
}
|
|
197
276
|
await (0, methods_1.verifyAppZip)(finalAppFile);
|
|
198
277
|
}
|
|
199
278
|
}
|
|
279
|
+
if (debug && additionalAppFiles?.length) {
|
|
280
|
+
this.log(`DEBUG: Verifying additional app files: ${additionalAppFiles.join(', ')}`);
|
|
281
|
+
}
|
|
200
282
|
await (0, methods_1.verifyAdditionalAppFiles)(additionalAppFiles);
|
|
201
283
|
const flagLogs = [];
|
|
202
284
|
for (const [k, v] of Object.entries(flags)) {
|
|
@@ -221,16 +303,29 @@ class Cloud extends core_1.Command {
|
|
|
221
303
|
if (!finalBinaryId) {
|
|
222
304
|
if (!finalAppFile)
|
|
223
305
|
throw new Error('You must provide either an app binary id or an app file');
|
|
306
|
+
if (debug) {
|
|
307
|
+
this.log(`DEBUG: Uploading binary file: ${finalAppFile}`);
|
|
308
|
+
}
|
|
224
309
|
const binaryId = await (0, methods_1.uploadBinary)(finalAppFile, apiUrl, apiKey, ignoreShaCheck, !json);
|
|
225
310
|
finalBinaryId = binaryId;
|
|
311
|
+
if (debug) {
|
|
312
|
+
this.log(`DEBUG: Binary uploaded with ID: ${binaryId}`);
|
|
313
|
+
}
|
|
226
314
|
}
|
|
227
315
|
let uploadedBinaryIds = [];
|
|
228
316
|
if (additionalAppFiles?.length) {
|
|
317
|
+
if (debug) {
|
|
318
|
+
this.log(`DEBUG: Uploading additional binary files: ${additionalAppFiles.join(', ')}`);
|
|
319
|
+
}
|
|
229
320
|
uploadedBinaryIds = await (0, methods_1.uploadBinaries)(additionalAppFiles, apiUrl, apiKey, ignoreShaCheck, !json);
|
|
230
321
|
finalAdditionalBinaryIds = [
|
|
231
322
|
...finalAdditionalBinaryIds,
|
|
232
323
|
...uploadedBinaryIds,
|
|
233
324
|
];
|
|
325
|
+
if (debug) {
|
|
326
|
+
this.log(`DEBUG: Additional binaries uploaded with IDs: ${uploadedBinaryIds.join(', ')}`);
|
|
327
|
+
this.log(`DEBUG: Final additional binary IDs: ${finalAdditionalBinaryIds.join(', ')}`);
|
|
328
|
+
}
|
|
234
329
|
}
|
|
235
330
|
const testFormData = new FormData();
|
|
236
331
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
@@ -240,6 +335,12 @@ class Cloud extends core_1.Command {
|
|
|
240
335
|
acc[key] = value.join('=');
|
|
241
336
|
return acc;
|
|
242
337
|
}, {});
|
|
338
|
+
if (debug && Object.keys(envObject).length > 0) {
|
|
339
|
+
this.log(`DEBUG: Environment variables: ${JSON.stringify(envObject)}`);
|
|
340
|
+
}
|
|
341
|
+
if (debug) {
|
|
342
|
+
this.log(`DEBUG: Compressing files from path: ${flowFile}`);
|
|
343
|
+
}
|
|
243
344
|
const buffer = await (0, methods_1.compressFilesFromRelativePath)(flowFile?.endsWith('.yaml') || flowFile?.endsWith('.yml')
|
|
244
345
|
? path.dirname(flowFile)
|
|
245
346
|
: flowFile, [
|
|
@@ -249,6 +350,9 @@ class Cloud extends core_1.Command {
|
|
|
249
350
|
...sequentialFlows,
|
|
250
351
|
]),
|
|
251
352
|
], commonRoot);
|
|
353
|
+
if (debug) {
|
|
354
|
+
this.log(`DEBUG: Compressed file size: ${buffer.length} bytes`);
|
|
355
|
+
}
|
|
252
356
|
const blob = new Blob([buffer], {
|
|
253
357
|
type: exports.mimeTypeLookupByExtension.zip,
|
|
254
358
|
});
|
|
@@ -299,11 +403,19 @@ class Cloud extends core_1.Command {
|
|
|
299
403
|
testFormData.set(key, value);
|
|
300
404
|
}
|
|
301
405
|
}
|
|
406
|
+
if (debug) {
|
|
407
|
+
this.log(`DEBUG: Submitting flow upload request to ${apiUrl}/uploads/flow`);
|
|
408
|
+
}
|
|
302
409
|
const options = {
|
|
303
410
|
body: testFormData,
|
|
304
411
|
headers: { 'x-app-api-key': apiKey },
|
|
305
412
|
};
|
|
306
413
|
const { message, results } = await (0, methods_1.typeSafePost)(apiUrl, '/uploads/flow', options);
|
|
414
|
+
if (debug) {
|
|
415
|
+
this.log(`DEBUG: Flow upload response received`);
|
|
416
|
+
this.log(`DEBUG: Message: ${message}`);
|
|
417
|
+
this.log(`DEBUG: Results count: ${results?.length || 0}`);
|
|
418
|
+
}
|
|
307
419
|
if (!results?.length)
|
|
308
420
|
(0, errors_1.error)('No tests created: ' + message);
|
|
309
421
|
this.log(message);
|
|
@@ -318,6 +430,9 @@ class Cloud extends core_1.Command {
|
|
|
318
430
|
this.log(`Your upload ID is: ${results[0].test_upload_id}`);
|
|
319
431
|
this.log(`Poll upload status using: dcd status --api-key ... --upload-id ${results[0].test_upload_id}`);
|
|
320
432
|
if (async) {
|
|
433
|
+
if (debug) {
|
|
434
|
+
this.log(`DEBUG: Async flag is set, not waiting for results`);
|
|
435
|
+
}
|
|
321
436
|
const jsonOutput = {
|
|
322
437
|
uploadId: results[0].test_upload_id,
|
|
323
438
|
consoleUrl: url,
|
|
@@ -328,11 +443,8 @@ class Cloud extends core_1.Command {
|
|
|
328
443
|
})),
|
|
329
444
|
};
|
|
330
445
|
if (flags['json-file']) {
|
|
331
|
-
const jsonFilePath = name
|
|
332
|
-
|
|
333
|
-
: `${results[0].test_upload_id}_dcd.json`;
|
|
334
|
-
fs.writeFileSync(jsonFilePath, JSON.stringify(jsonOutput, null, 2));
|
|
335
|
-
this.log(`JSON output written to: ${path.resolve(jsonFilePath)}`);
|
|
446
|
+
const jsonFilePath = this.getJsonOutputPath(name, results[0].test_upload_id);
|
|
447
|
+
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
336
448
|
}
|
|
337
449
|
if (json) {
|
|
338
450
|
return jsonOutput;
|
|
@@ -348,15 +460,27 @@ class Cloud extends core_1.Command {
|
|
|
348
460
|
this.log('\nYou can safely close this terminal and the tests will continue\n');
|
|
349
461
|
}
|
|
350
462
|
let sequentialPollFaillures = 0;
|
|
463
|
+
if (debug) {
|
|
464
|
+
this.log(`DEBUG: Starting polling loop for results`);
|
|
465
|
+
}
|
|
351
466
|
await new Promise((resolve, reject) => {
|
|
352
467
|
const intervalId = setInterval(async () => {
|
|
353
468
|
try {
|
|
469
|
+
if (debug) {
|
|
470
|
+
this.log(`DEBUG: Polling for results: ${results[0].test_upload_id}`);
|
|
471
|
+
}
|
|
354
472
|
const { results: updatedResults } = await (0, methods_1.typeSafeGet)(apiUrl, `/results/${results[0].test_upload_id}`, {
|
|
355
473
|
headers: { 'x-app-api-key': apiKey },
|
|
356
474
|
});
|
|
357
475
|
if (!updatedResults) {
|
|
358
476
|
throw new Error('no results');
|
|
359
477
|
}
|
|
478
|
+
if (debug) {
|
|
479
|
+
this.log(`DEBUG: Poll received ${updatedResults.length} results`);
|
|
480
|
+
for (const result of updatedResults) {
|
|
481
|
+
this.log(`DEBUG: Result status: ${result.test_file_name} - ${result.status}`);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
360
484
|
if (!quiet && !json) {
|
|
361
485
|
core_1.ux.action.status =
|
|
362
486
|
'\nStatus Test\n─────────── ───────────────';
|
|
@@ -365,6 +489,9 @@ class Cloud extends core_1.Command {
|
|
|
365
489
|
}
|
|
366
490
|
}
|
|
367
491
|
if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
|
|
492
|
+
if (debug) {
|
|
493
|
+
this.log(`DEBUG: All tests completed, stopping poll`);
|
|
494
|
+
}
|
|
368
495
|
if (!json) {
|
|
369
496
|
core_1.ux.action.stop('completed');
|
|
370
497
|
this.log('\n');
|
|
@@ -382,6 +509,9 @@ class Cloud extends core_1.Command {
|
|
|
382
509
|
clearInterval(intervalId);
|
|
383
510
|
if (downloadArtifacts) {
|
|
384
511
|
try {
|
|
512
|
+
if (debug) {
|
|
513
|
+
this.log(`DEBUG: Downloading artifacts: ${downloadArtifacts}`);
|
|
514
|
+
}
|
|
385
515
|
await (0, methods_1.typeSafePostDownload)(apiUrl, `/results/${results[0].test_upload_id}/download`, {
|
|
386
516
|
body: JSON.stringify({ results: downloadArtifacts }),
|
|
387
517
|
headers: {
|
|
@@ -392,7 +522,10 @@ class Cloud extends core_1.Command {
|
|
|
392
522
|
this.log('\n');
|
|
393
523
|
this.log(`Test artifacts have been downloaded to ${artifactsPath || './artifacts.zip'}`);
|
|
394
524
|
}
|
|
395
|
-
catch {
|
|
525
|
+
catch (error) {
|
|
526
|
+
if (debug) {
|
|
527
|
+
this.log(`DEBUG: Error downloading artifacts: ${error}`);
|
|
528
|
+
}
|
|
396
529
|
this.warn('Failed to download artifacts');
|
|
397
530
|
}
|
|
398
531
|
}
|
|
@@ -402,6 +535,9 @@ class Cloud extends core_1.Command {
|
|
|
402
535
|
return result.id === Math.max(...tries.map((t) => t.id));
|
|
403
536
|
});
|
|
404
537
|
if (resultsWithoutEarlierTries.some((result) => result.status === 'FAILED')) {
|
|
538
|
+
if (debug) {
|
|
539
|
+
this.log(`DEBUG: Some tests failed, returning failed status`);
|
|
540
|
+
}
|
|
405
541
|
const jsonOutput = {
|
|
406
542
|
uploadId: results[0].test_upload_id,
|
|
407
543
|
consoleUrl: url,
|
|
@@ -412,11 +548,8 @@ class Cloud extends core_1.Command {
|
|
|
412
548
|
})),
|
|
413
549
|
};
|
|
414
550
|
if (flags['json-file']) {
|
|
415
|
-
const jsonFilePath = name
|
|
416
|
-
|
|
417
|
-
: `${results[0].test_upload_id}_dcd.json`;
|
|
418
|
-
fs.writeFileSync(jsonFilePath, JSON.stringify(jsonOutput, null, 2));
|
|
419
|
-
this.log(`JSON output written to: ${path.resolve(jsonFilePath)}`);
|
|
551
|
+
const jsonFilePath = this.getJsonOutputPath(name, results[0].test_upload_id);
|
|
552
|
+
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
420
553
|
}
|
|
421
554
|
if (json) {
|
|
422
555
|
output = jsonOutput;
|
|
@@ -424,6 +557,9 @@ class Cloud extends core_1.Command {
|
|
|
424
557
|
reject(new Error('RUN_FAILED'));
|
|
425
558
|
}
|
|
426
559
|
else {
|
|
560
|
+
if (debug) {
|
|
561
|
+
this.log(`DEBUG: All tests passed, returning success status`);
|
|
562
|
+
}
|
|
427
563
|
const jsonOutput = {
|
|
428
564
|
uploadId: results[0].test_upload_id,
|
|
429
565
|
consoleUrl: url,
|
|
@@ -434,11 +570,8 @@ class Cloud extends core_1.Command {
|
|
|
434
570
|
})),
|
|
435
571
|
};
|
|
436
572
|
if (flags['json-file']) {
|
|
437
|
-
const jsonFilePath = name
|
|
438
|
-
|
|
439
|
-
: `${results[0].test_upload_id}_dcd.json`;
|
|
440
|
-
fs.writeFileSync(jsonFilePath, JSON.stringify(jsonOutput, null, 2));
|
|
441
|
-
this.log(`JSON output written to: ${path.resolve(jsonFilePath)}`);
|
|
573
|
+
const jsonFilePath = this.getJsonOutputPath(name, results[0].test_upload_id);
|
|
574
|
+
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
442
575
|
}
|
|
443
576
|
if (json) {
|
|
444
577
|
output = jsonOutput;
|
|
@@ -450,10 +583,14 @@ class Cloud extends core_1.Command {
|
|
|
450
583
|
}
|
|
451
584
|
catch (error) {
|
|
452
585
|
sequentialPollFaillures++;
|
|
453
|
-
if (
|
|
586
|
+
if (debug) {
|
|
587
|
+
this.log(`DEBUG: Error polling for results: ${error}`);
|
|
588
|
+
this.log(`DEBUG: Sequential poll failures: ${sequentialPollFaillures}`);
|
|
589
|
+
}
|
|
590
|
+
if (sequentialPollFaillures > 10) {
|
|
454
591
|
// dropped poll requests shouldn't err user CI
|
|
455
592
|
clearInterval(intervalId);
|
|
456
|
-
throw new Error('unable to fetch results after
|
|
593
|
+
throw new Error('unable to fetch results after 10 attempts');
|
|
457
594
|
}
|
|
458
595
|
this.log('unable to fetch results, trying again...');
|
|
459
596
|
}
|
|
@@ -461,7 +598,15 @@ class Cloud extends core_1.Command {
|
|
|
461
598
|
});
|
|
462
599
|
}
|
|
463
600
|
catch (error) {
|
|
601
|
+
if (debugFlag && error instanceof Error) {
|
|
602
|
+
this.log(`DEBUG: Error in command execution: ${error.message}`);
|
|
603
|
+
this.log(`DEBUG: Error stack: ${error.stack}`);
|
|
604
|
+
}
|
|
464
605
|
if (error instanceof Error && error.message === 'RUN_FAILED') {
|
|
606
|
+
if (jsonFile) {
|
|
607
|
+
// mimic oclif's json functionality
|
|
608
|
+
this.exit(0);
|
|
609
|
+
}
|
|
465
610
|
this.exit(2);
|
|
466
611
|
}
|
|
467
612
|
else {
|
package/dist/constants.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export declare const flags: {
|
|
|
14
14
|
'device-locale': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
15
|
'download-artifacts': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
16
|
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
17
|
+
debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
17
18
|
env: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
19
|
'exclude-flows': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
20
|
'exclude-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
package/dist/constants.js
CHANGED
|
@@ -70,6 +70,10 @@ exports.flags = {
|
|
|
70
70
|
description: 'Custom file path for downloaded artifacts (default: ./artifacts.zip)',
|
|
71
71
|
dependsOn: ['download-artifacts'],
|
|
72
72
|
}),
|
|
73
|
+
debug: core_1.Flags.boolean({
|
|
74
|
+
description: 'Enable detailed debug logging for troubleshooting issues',
|
|
75
|
+
default: false,
|
|
76
|
+
}),
|
|
73
77
|
env: core_1.Flags.file({
|
|
74
78
|
char: 'e',
|
|
75
79
|
description: 'One or more environment variables to inject into your flows',
|
|
@@ -186,7 +190,7 @@ exports.flags = {
|
|
|
186
190
|
description: 'Output results in JSON format - note: will always provide exit code 0',
|
|
187
191
|
}),
|
|
188
192
|
'json-file': core_1.Flags.boolean({
|
|
189
|
-
description: 'Write JSON output to a file with name <run_name>_dcd.json or <upload_id>_dcd.json if no name is provided',
|
|
193
|
+
description: 'Write JSON output to a file with name <run_name>_dcd.json or <upload_id>_dcd.json if no name is provided - note: will always provide exit code 0',
|
|
190
194
|
required: false,
|
|
191
195
|
}),
|
|
192
196
|
};
|
package/dist/methods.d.ts
CHANGED
|
@@ -34,3 +34,14 @@ export declare const getUploadStatus: (apiUrl: string, apiKey: string, options:
|
|
|
34
34
|
status: "PASSED" | "FAILED" | "CANCELLED" | "PENDING";
|
|
35
35
|
}>;
|
|
36
36
|
}>;
|
|
37
|
+
/**
|
|
38
|
+
* Writes JSON data to a file with error handling
|
|
39
|
+
* @param filePath - Path to the output JSON file
|
|
40
|
+
* @param data - Data to be serialized to JSON
|
|
41
|
+
* @param logger - Logger object with log and warn methods
|
|
42
|
+
* @returns true if successful, false if an error occurred
|
|
43
|
+
*/
|
|
44
|
+
export declare const writeJSONFile: (filePath: string, data: any, logger: {
|
|
45
|
+
log: (message: string) => void;
|
|
46
|
+
warn: (message: string) => void;
|
|
47
|
+
}) => void;
|
package/dist/methods.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getUploadStatus = exports.verifyAdditionalAppFiles = exports.uploadBinaries = exports.uploadBinary = exports.extractAppMetadataIos = exports.extractAppMetadataIosZip = exports.extractAppMetadataAndroid = exports.verifyAppZip = exports.compressFilesFromRelativePath = exports.compressFolderToBlob = exports.compressDir = exports.toBuffer = exports.typeSafeGet = exports.typeSafePostDownload = exports.typeSafePost = void 0;
|
|
3
|
+
exports.writeJSONFile = exports.getUploadStatus = exports.verifyAdditionalAppFiles = exports.uploadBinaries = exports.uploadBinary = exports.extractAppMetadataIos = exports.extractAppMetadataIosZip = exports.extractAppMetadataAndroid = exports.verifyAppZip = exports.compressFilesFromRelativePath = exports.compressFolderToBlob = exports.compressDir = exports.toBuffer = exports.typeSafeGet = exports.typeSafePostDownload = exports.typeSafePost = void 0;
|
|
4
4
|
const core_1 = require("@oclif/core");
|
|
5
5
|
const supabase_js_1 = require("@supabase/supabase-js");
|
|
6
6
|
// required polyfill for node 18
|
|
@@ -370,3 +370,22 @@ const getUploadStatus = async (apiUrl, apiKey, options) => {
|
|
|
370
370
|
return response.json();
|
|
371
371
|
};
|
|
372
372
|
exports.getUploadStatus = getUploadStatus;
|
|
373
|
+
/**
|
|
374
|
+
* Writes JSON data to a file with error handling
|
|
375
|
+
* @param filePath - Path to the output JSON file
|
|
376
|
+
* @param data - Data to be serialized to JSON
|
|
377
|
+
* @param logger - Logger object with log and warn methods
|
|
378
|
+
* @returns true if successful, false if an error occurred
|
|
379
|
+
*/
|
|
380
|
+
const writeJSONFile = (filePath, data, logger) => {
|
|
381
|
+
try {
|
|
382
|
+
(0, node_fs_1.writeFileSync)(filePath, JSON.stringify(data, null, 2));
|
|
383
|
+
logger.log(`JSON output written to: ${path.resolve(filePath)}`);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
logger.warn(`Failed to write JSON output to file: ${filePath}`);
|
|
387
|
+
// Use console.debug instead of logger.debug since debug is protected in Command
|
|
388
|
+
logger.warn(`Error details: ${error instanceof Error ? error.message : String(error)}`);
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
exports.writeJSONFile = writeJSONFile;
|
package/oclif.manifest.json
CHANGED
|
@@ -161,6 +161,12 @@
|
|
|
161
161
|
"multiple": false,
|
|
162
162
|
"type": "option"
|
|
163
163
|
},
|
|
164
|
+
"debug": {
|
|
165
|
+
"description": "Enable detailed debug logging for troubleshooting issues",
|
|
166
|
+
"name": "debug",
|
|
167
|
+
"allowNo": false,
|
|
168
|
+
"type": "boolean"
|
|
169
|
+
},
|
|
164
170
|
"env": {
|
|
165
171
|
"char": "e",
|
|
166
172
|
"description": "One or more environment variables to inject into your flows",
|
|
@@ -350,7 +356,7 @@
|
|
|
350
356
|
"type": "option"
|
|
351
357
|
},
|
|
352
358
|
"json-file": {
|
|
353
|
-
"description": "Write JSON output to a file with name <run_name>_dcd.json or <upload_id>_dcd.json if no name is provided",
|
|
359
|
+
"description": "Write JSON output to a file with name <run_name>_dcd.json or <upload_id>_dcd.json if no name is provided - note: will always provide exit code 0",
|
|
354
360
|
"name": "json-file",
|
|
355
361
|
"required": false,
|
|
356
362
|
"allowNo": false,
|
|
@@ -514,5 +520,5 @@
|
|
|
514
520
|
]
|
|
515
521
|
}
|
|
516
522
|
},
|
|
517
|
-
"version": "3.6.
|
|
523
|
+
"version": "3.6.7"
|
|
518
524
|
}
|