@devicecloud.dev/dcd 3.6.4 → 3.6.6

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.
@@ -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;
@@ -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,29 +80,34 @@ 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;
86
96
  try {
87
97
  const { args, flags, raw } = await this.parse(Cloud);
88
- // // Log the full command that was run
89
- // const commandParts = ['dcd cloud'];
90
- // if (args.firstFile) commandParts.push(escapeShellValue(args.firstFile));
91
- // if (args.secondFile) commandParts.push(escapeShellValue(args.secondFile));
92
- // for (const [key, value] of Object.entries(flags)) {
93
- // if (value && value.toString().length > 0) {
94
- // if (typeof value === 'boolean') {
95
- // commandParts.push(`--${key}`);
96
- // } else if (Array.isArray(value)) {
97
- // commandParts.push(
98
- // `--${key}=${value.map((v) => escapeShellValue(v)).join(',')}`,
99
- // );
100
- // } else {
101
- // commandParts.push(`--${key}=${escapeShellValue(value.toString())}`);
102
- // }
103
- // }
104
- // }
105
- // this.log(`\nCommand ran: ${commandParts.join(' ')}\n`);
106
- 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;
98
+ 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;
99
+ // Store debug flag for use in catch block
100
+ debugFlag = debug === true;
101
+ if (debug) {
102
+ this.log('DEBUG: Starting command execution with debug logging enabled');
103
+ this.log(`DEBUG: CLI Version: ${this.config.version}`);
104
+ this.log(`DEBUG: Node version: ${process.versions.node}`);
105
+ this.log(`DEBUG: OS: ${process.platform} ${process.arch}`);
106
+ }
107
+ if (flags['json-file']) {
108
+ quiet = true;
109
+ this.log('--json-file is true: JSON output will be written to file, forcing --quiet flag for better CI output');
110
+ }
107
111
  if (json) {
108
112
  const originalStdoutWrite = process.stdout.write;
109
113
  process.stdout.write = function (chunk, encodingOrCallback, cb) {
@@ -122,6 +126,10 @@ class Cloud extends core_1.Command {
122
126
  const apiKey = apiKeyFlag || process.env.DEVICE_CLOUD_API_KEY;
123
127
  if (!apiKey)
124
128
  throw new Error('You must provide an API key via --api-key flag or DEVICE_CLOUD_API_KEY environment variable');
129
+ if (debug) {
130
+ this.log(`DEBUG: API URL: ${apiUrl}`);
131
+ this.log(`DEBUG: API Key provided: ${apiKey ? 'Yes' : 'No'}`);
132
+ }
125
133
  const [maestroMajorString, maestroMinorString] = maestroVersion.split('.');
126
134
  if (report === 'html' &&
127
135
  maestroMajorString === '1' &&
@@ -134,24 +142,10 @@ class Cloud extends core_1.Command {
134
142
  retry = 2;
135
143
  }
136
144
  if (runnerType === 'm4') {
137
- this.log('Note: runnerType m4 is experimental and currently supports iOS only.');
138
- // todo - better platform checking
139
- if (androidApiLevel || androidDevice) {
140
- throw new Error('runnerType m4 only supports iOS');
141
- }
145
+ this.log('Note: runnerType m4 is experimental and currently supports iOS only, Android will revert to default.');
142
146
  }
143
147
  if (runnerType === 'm1') {
144
- this.log('Note: Runner Type m1 is experimental and currently supports Android only, iOS will revert to default.');
145
- // todo - better platform checking
146
- if (iOSDevice || iOSVersion) {
147
- this.log('runnerType m1 only supports Android, reverting to default');
148
- runnerType = 'default';
149
- }
150
- if (androidApiLevel || androidDevice) {
151
- this.log('Runner Type m1 only supports API Level 34 and Pixel 7, unsetting your android options.');
152
- androidApiLevel = undefined;
153
- androidDevice = undefined;
154
- }
148
+ this.log('Note: runnerType m1 is experimental and currently supports Android (Pixel 7, API Level 34) only.');
155
149
  }
156
150
  const additionalAppBinaryIds = nonFlatAdditionalAppBinaryIds?.flat();
157
151
  const additionalAppFiles = nonFlatAdditionalAppFiles?.flat();
@@ -160,6 +154,15 @@ class Cloud extends core_1.Command {
160
154
  let finalAdditionalBinaryIds = additionalAppBinaryIds;
161
155
  const finalAppFile = appFile ?? firstFile;
162
156
  let flowFile = flows ?? secondFile;
157
+ if (debug) {
158
+ this.log(`DEBUG: First file argument: ${firstFile || 'not provided'}`);
159
+ this.log(`DEBUG: Second file argument: ${secondFile || 'not provided'}`);
160
+ this.log(`DEBUG: App binary ID: ${appBinaryId || 'not provided'}`);
161
+ this.log(`DEBUG: App file: ${finalAppFile || 'not provided'}`);
162
+ this.log(`DEBUG: Flow file: ${flowFile || 'not provided'}`);
163
+ this.log(`DEBUG: Additional app binary IDs: ${additionalAppBinaryIds?.join(', ') || 'none'}`);
164
+ this.log(`DEBUG: Additional app files: ${additionalAppFiles?.join(', ') || 'none'}`);
165
+ }
163
166
  if (appBinaryId) {
164
167
  if (secondFile) {
165
168
  throw new Error('You cannot provide both an appBinaryId and a binary file');
@@ -176,6 +179,11 @@ class Cloud extends core_1.Command {
176
179
  if (!supportediOSVersions.includes(version)) {
177
180
  throw new Error(`${iOSDeviceID} only supports these iOS versions: ${supportediOSVersions.join(', ')}`);
178
181
  }
182
+ if (debug) {
183
+ this.log(`DEBUG: iOS device: ${iOSDeviceID}`);
184
+ this.log(`DEBUG: iOS version: ${version}`);
185
+ this.log(`DEBUG: Supported iOS versions: ${supportediOSVersions.join(', ')}`);
186
+ }
179
187
  }
180
188
  if (androidApiLevel || androidDevice) {
181
189
  const androidDeviceID = androidDevice || 'pixel-7';
@@ -190,6 +198,12 @@ class Cloud extends core_1.Command {
190
198
  if (!supportedAndroidVersions.includes(version)) {
191
199
  throw new Error(`${androidDeviceID} ${googlePlay ? '(Play Store) ' : ''}only supports these Android API levels: ${supportedAndroidVersions.join(', ')}`);
192
200
  }
201
+ if (debug) {
202
+ this.log(`DEBUG: Android device: ${androidDeviceID}`);
203
+ this.log(`DEBUG: Android API level: ${version}`);
204
+ this.log(`DEBUG: Google Play enabled: ${googlePlay}`);
205
+ this.log(`DEBUG: Supported Android versions: ${supportedAndroidVersions.join(', ')}`);
206
+ }
193
207
  }
194
208
  flowFile = path.resolve(flowFile);
195
209
  if (!flowFile?.endsWith('.yaml') &&
@@ -197,14 +211,35 @@ class Cloud extends core_1.Command {
197
211
  !flowFile?.endsWith('/')) {
198
212
  flowFile += '/';
199
213
  }
214
+ if (debug) {
215
+ this.log(`DEBUG: Resolved flow file path: ${flowFile}`);
216
+ }
200
217
  let executionPlan;
201
218
  try {
219
+ if (debug) {
220
+ this.log('DEBUG: Generating execution plan...');
221
+ }
202
222
  executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat(), excludeFlows.flat(), configFile);
223
+ if (debug) {
224
+ this.log(`DEBUG: Execution plan generated`);
225
+ this.log(`DEBUG: Total flow files: ${executionPlan.totalFlowFiles}`);
226
+ this.log(`DEBUG: Flows to run: ${executionPlan.flowsToRun.length}`);
227
+ this.log(`DEBUG: Referenced files: ${executionPlan.referencedFiles.length}`);
228
+ this.log(`DEBUG: Sequential flows: ${executionPlan.sequence?.flows.length || 0}`);
229
+ }
203
230
  }
204
231
  catch (error) {
232
+ if (debug) {
233
+ this.log(`DEBUG: Error generating execution plan: ${error}`);
234
+ }
205
235
  throw error;
206
236
  }
207
237
  const { allExcludeTags, allIncludeTags, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
238
+ if (debug) {
239
+ this.log(`DEBUG: All include tags: ${allIncludeTags?.join(', ') || 'none'}`);
240
+ this.log(`DEBUG: All exclude tags: ${allExcludeTags?.join(', ') || 'none'}`);
241
+ this.log(`DEBUG: Test file names: ${testFileNames.join(', ')}`);
242
+ }
208
243
  const pathsShortestToLongest = [
209
244
  ...testFileNames,
210
245
  ...referencedFiles,
@@ -217,7 +252,14 @@ class Cloud extends core_1.Command {
217
252
  if (isRoot)
218
253
  commonRoot = folderPath;
219
254
  }
255
+ if (debug) {
256
+ this.log(`DEBUG: Common root directory: ${commonRoot}`);
257
+ }
220
258
  const { continueOnFailure = true, flows: sequentialFlows = [] } = sequence ?? {};
259
+ if (debug && sequentialFlows.length > 0) {
260
+ this.log(`DEBUG: Sequential flows: ${sequentialFlows.join(', ')}`);
261
+ this.log(`DEBUG: Continue on failure: ${continueOnFailure}`);
262
+ }
221
263
  if (!appBinaryId) {
222
264
  if (!(flowFile && finalAppFile)) {
223
265
  throw new Error('You must provide a flow file and an app binary id');
@@ -226,9 +268,15 @@ class Cloud extends core_1.Command {
226
268
  throw new Error('App file must be a .apk for android or .app/.zip file for iOS');
227
269
  }
228
270
  if (finalAppFile.endsWith('.zip')) {
271
+ if (debug) {
272
+ this.log(`DEBUG: Verifying iOS app zip file: ${finalAppFile}`);
273
+ }
229
274
  await (0, methods_1.verifyAppZip)(finalAppFile);
230
275
  }
231
276
  }
277
+ if (debug && additionalAppFiles?.length) {
278
+ this.log(`DEBUG: Verifying additional app files: ${additionalAppFiles.join(', ')}`);
279
+ }
232
280
  await (0, methods_1.verifyAdditionalAppFiles)(additionalAppFiles);
233
281
  const flagLogs = [];
234
282
  for (const [k, v] of Object.entries(flags)) {
@@ -253,16 +301,29 @@ class Cloud extends core_1.Command {
253
301
  if (!finalBinaryId) {
254
302
  if (!finalAppFile)
255
303
  throw new Error('You must provide either an app binary id or an app file');
304
+ if (debug) {
305
+ this.log(`DEBUG: Uploading binary file: ${finalAppFile}`);
306
+ }
256
307
  const binaryId = await (0, methods_1.uploadBinary)(finalAppFile, apiUrl, apiKey, ignoreShaCheck, !json);
257
308
  finalBinaryId = binaryId;
309
+ if (debug) {
310
+ this.log(`DEBUG: Binary uploaded with ID: ${binaryId}`);
311
+ }
258
312
  }
259
313
  let uploadedBinaryIds = [];
260
314
  if (additionalAppFiles?.length) {
315
+ if (debug) {
316
+ this.log(`DEBUG: Uploading additional binary files: ${additionalAppFiles.join(', ')}`);
317
+ }
261
318
  uploadedBinaryIds = await (0, methods_1.uploadBinaries)(additionalAppFiles, apiUrl, apiKey, ignoreShaCheck, !json);
262
319
  finalAdditionalBinaryIds = [
263
320
  ...finalAdditionalBinaryIds,
264
321
  ...uploadedBinaryIds,
265
322
  ];
323
+ if (debug) {
324
+ this.log(`DEBUG: Additional binaries uploaded with IDs: ${uploadedBinaryIds.join(', ')}`);
325
+ this.log(`DEBUG: Final additional binary IDs: ${finalAdditionalBinaryIds.join(', ')}`);
326
+ }
266
327
  }
267
328
  const testFormData = new FormData();
268
329
  // eslint-disable-next-line unicorn/no-array-reduce
@@ -272,6 +333,12 @@ class Cloud extends core_1.Command {
272
333
  acc[key] = value.join('=');
273
334
  return acc;
274
335
  }, {});
336
+ if (debug && Object.keys(envObject).length > 0) {
337
+ this.log(`DEBUG: Environment variables: ${JSON.stringify(envObject)}`);
338
+ }
339
+ if (debug) {
340
+ this.log(`DEBUG: Compressing files from path: ${flowFile}`);
341
+ }
275
342
  const buffer = await (0, methods_1.compressFilesFromRelativePath)(flowFile?.endsWith('.yaml') || flowFile?.endsWith('.yml')
276
343
  ? path.dirname(flowFile)
277
344
  : flowFile, [
@@ -281,6 +348,9 @@ class Cloud extends core_1.Command {
281
348
  ...sequentialFlows,
282
349
  ]),
283
350
  ], commonRoot);
351
+ if (debug) {
352
+ this.log(`DEBUG: Compressed file size: ${buffer.length} bytes`);
353
+ }
284
354
  const blob = new Blob([buffer], {
285
355
  type: exports.mimeTypeLookupByExtension.zip,
286
356
  });
@@ -331,13 +401,22 @@ class Cloud extends core_1.Command {
331
401
  testFormData.set(key, value);
332
402
  }
333
403
  }
404
+ if (debug) {
405
+ this.log(`DEBUG: Submitting flow upload request to ${apiUrl}/uploads/flow`);
406
+ }
334
407
  const options = {
335
408
  body: testFormData,
336
409
  headers: { 'x-app-api-key': apiKey },
337
410
  };
338
411
  const { message, results } = await (0, methods_1.typeSafePost)(apiUrl, '/uploads/flow', options);
412
+ if (debug) {
413
+ this.log(`DEBUG: Flow upload response received`);
414
+ this.log(`DEBUG: Message: ${message}`);
415
+ this.log(`DEBUG: Results count: ${results?.length || 0}`);
416
+ }
339
417
  if (!results?.length)
340
418
  (0, errors_1.error)('No tests created: ' + message);
419
+ this.log(message);
341
420
  this.log(`\nCreated ${results.length} tests: ${results
342
421
  .map((r) => r.test_file_name)
343
422
  .sort((a, b) => a.localeCompare(b))
@@ -349,6 +428,9 @@ class Cloud extends core_1.Command {
349
428
  this.log(`Your upload ID is: ${results[0].test_upload_id}`);
350
429
  this.log(`Poll upload status using: dcd status --api-key ... --upload-id ${results[0].test_upload_id}`);
351
430
  if (async) {
431
+ if (debug) {
432
+ this.log(`DEBUG: Async flag is set, not waiting for results`);
433
+ }
352
434
  const jsonOutput = {
353
435
  uploadId: results[0].test_upload_id,
354
436
  consoleUrl: url,
@@ -359,11 +441,8 @@ class Cloud extends core_1.Command {
359
441
  })),
360
442
  };
361
443
  if (flags['json-file']) {
362
- const jsonFilePath = name
363
- ? `${name}_dcd.json`
364
- : `${results[0].test_upload_id}_dcd.json`;
365
- fs.writeFileSync(jsonFilePath, JSON.stringify(jsonOutput, null, 2));
366
- this.log(`JSON output written to: ${path.resolve(jsonFilePath)}`);
444
+ const jsonFilePath = this.getJsonOutputPath(name, results[0].test_upload_id);
445
+ (0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
367
446
  }
368
447
  if (json) {
369
448
  return jsonOutput;
@@ -379,15 +458,27 @@ class Cloud extends core_1.Command {
379
458
  this.log('\nYou can safely close this terminal and the tests will continue\n');
380
459
  }
381
460
  let sequentialPollFaillures = 0;
461
+ if (debug) {
462
+ this.log(`DEBUG: Starting polling loop for results`);
463
+ }
382
464
  await new Promise((resolve, reject) => {
383
465
  const intervalId = setInterval(async () => {
384
466
  try {
467
+ if (debug) {
468
+ this.log(`DEBUG: Polling for results: ${results[0].test_upload_id}`);
469
+ }
385
470
  const { results: updatedResults } = await (0, methods_1.typeSafeGet)(apiUrl, `/results/${results[0].test_upload_id}`, {
386
471
  headers: { 'x-app-api-key': apiKey },
387
472
  });
388
473
  if (!updatedResults) {
389
474
  throw new Error('no results');
390
475
  }
476
+ if (debug) {
477
+ this.log(`DEBUG: Poll received ${updatedResults.length} results`);
478
+ for (const result of updatedResults) {
479
+ this.log(`DEBUG: Result status: ${result.test_file_name} - ${result.status}`);
480
+ }
481
+ }
391
482
  if (!quiet && !json) {
392
483
  core_1.ux.action.status =
393
484
  '\nStatus Test\n─────────── ───────────────';
@@ -396,6 +487,9 @@ class Cloud extends core_1.Command {
396
487
  }
397
488
  }
398
489
  if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
490
+ if (debug) {
491
+ this.log(`DEBUG: All tests completed, stopping poll`);
492
+ }
399
493
  if (!json) {
400
494
  core_1.ux.action.stop('completed');
401
495
  this.log('\n');
@@ -413,6 +507,9 @@ class Cloud extends core_1.Command {
413
507
  clearInterval(intervalId);
414
508
  if (downloadArtifacts) {
415
509
  try {
510
+ if (debug) {
511
+ this.log(`DEBUG: Downloading artifacts: ${downloadArtifacts}`);
512
+ }
416
513
  await (0, methods_1.typeSafePostDownload)(apiUrl, `/results/${results[0].test_upload_id}/download`, {
417
514
  body: JSON.stringify({ results: downloadArtifacts }),
418
515
  headers: {
@@ -423,7 +520,10 @@ class Cloud extends core_1.Command {
423
520
  this.log('\n');
424
521
  this.log(`Test artifacts have been downloaded to ${artifactsPath || './artifacts.zip'}`);
425
522
  }
426
- catch {
523
+ catch (error) {
524
+ if (debug) {
525
+ this.log(`DEBUG: Error downloading artifacts: ${error}`);
526
+ }
427
527
  this.warn('Failed to download artifacts');
428
528
  }
429
529
  }
@@ -433,6 +533,9 @@ class Cloud extends core_1.Command {
433
533
  return result.id === Math.max(...tries.map((t) => t.id));
434
534
  });
435
535
  if (resultsWithoutEarlierTries.some((result) => result.status === 'FAILED')) {
536
+ if (debug) {
537
+ this.log(`DEBUG: Some tests failed, returning failed status`);
538
+ }
436
539
  const jsonOutput = {
437
540
  uploadId: results[0].test_upload_id,
438
541
  consoleUrl: url,
@@ -443,11 +546,8 @@ class Cloud extends core_1.Command {
443
546
  })),
444
547
  };
445
548
  if (flags['json-file']) {
446
- const jsonFilePath = name
447
- ? `${name}_dcd.json`
448
- : `${results[0].test_upload_id}_dcd.json`;
449
- fs.writeFileSync(jsonFilePath, JSON.stringify(jsonOutput, null, 2));
450
- this.log(`JSON output written to: ${path.resolve(jsonFilePath)}`);
549
+ const jsonFilePath = this.getJsonOutputPath(name, results[0].test_upload_id);
550
+ (0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
451
551
  }
452
552
  if (json) {
453
553
  output = jsonOutput;
@@ -455,6 +555,9 @@ class Cloud extends core_1.Command {
455
555
  reject(new Error('RUN_FAILED'));
456
556
  }
457
557
  else {
558
+ if (debug) {
559
+ this.log(`DEBUG: All tests passed, returning success status`);
560
+ }
458
561
  const jsonOutput = {
459
562
  uploadId: results[0].test_upload_id,
460
563
  consoleUrl: url,
@@ -465,11 +568,8 @@ class Cloud extends core_1.Command {
465
568
  })),
466
569
  };
467
570
  if (flags['json-file']) {
468
- const jsonFilePath = name
469
- ? `${name}_dcd.json`
470
- : `${results[0].test_upload_id}_dcd.json`;
471
- fs.writeFileSync(jsonFilePath, JSON.stringify(jsonOutput, null, 2));
472
- this.log(`JSON output written to: ${path.resolve(jsonFilePath)}`);
571
+ const jsonFilePath = this.getJsonOutputPath(name, results[0].test_upload_id);
572
+ (0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
473
573
  }
474
574
  if (json) {
475
575
  output = jsonOutput;
@@ -481,10 +581,14 @@ class Cloud extends core_1.Command {
481
581
  }
482
582
  catch (error) {
483
583
  sequentialPollFaillures++;
484
- if (sequentialPollFaillures > 5) {
584
+ if (debug) {
585
+ this.log(`DEBUG: Error polling for results: ${error}`);
586
+ this.log(`DEBUG: Sequential poll failures: ${sequentialPollFaillures}`);
587
+ }
588
+ if (sequentialPollFaillures > 10) {
485
589
  // dropped poll requests shouldn't err user CI
486
590
  clearInterval(intervalId);
487
- throw new Error('unable to fetch results after 5 attempts');
591
+ throw new Error('unable to fetch results after 10 attempts');
488
592
  }
489
593
  this.log('unable to fetch results, trying again...');
490
594
  }
@@ -492,10 +596,16 @@ class Cloud extends core_1.Command {
492
596
  });
493
597
  }
494
598
  catch (error) {
599
+ if (debugFlag && error instanceof Error) {
600
+ this.log(`DEBUG: Error in command execution: ${error.message}`);
601
+ this.log(`DEBUG: Error stack: ${error.stack}`);
602
+ }
495
603
  if (error instanceof Error && error.message === 'RUN_FAILED') {
496
604
  this.exit(2);
497
605
  }
498
- this.error(error, { exit: 1 });
606
+ else {
607
+ this.error(error, { exit: 1 });
608
+ }
499
609
  }
500
610
  finally {
501
611
  if (output) {
@@ -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',
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;
@@ -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",
@@ -514,5 +520,5 @@
514
520
  ]
515
521
  }
516
522
  },
517
- "version": "3.6.4"
523
+ "version": "3.6.6"
518
524
  }
package/package.json CHANGED
@@ -71,7 +71,7 @@
71
71
  "scripts": {
72
72
  "dcd": "./bin/dev.js",
73
73
  "prod": "./bin/run.js",
74
- "build": "shx rm -rf dist && tsc -b",
74
+ "build": "shx rm -rf dist && tsc -b",
75
75
  "lint": "eslint . --ext .ts",
76
76
  "postpack": "shx rm -f oclif.manifest.json",
77
77
  "posttest": "yarn lint",
@@ -79,7 +79,7 @@
79
79
  "prepare": "yarn build",
80
80
  "version": "oclif readme && git add README.md"
81
81
  },
82
- "version": "3.6.4",
82
+ "version": "3.6.6",
83
83
  "bugs": {
84
84
  "url": "https://discord.gg/gm3mJwcNw8"
85
85
  },
@@ -93,4 +93,4 @@
93
93
  ],
94
94
  "types": "dist/index.d.ts",
95
95
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
96
- }
96
+ }