@devicecloud.dev/dcd 3.6.5 → 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.
- package/dist/commands/cloud.d.ts +8 -0
- package/dist/commands/cloud.js +159 -20
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +4 -0
- package/dist/methods.d.ts +11 -0
- package/dist/methods.js +20 -1
- package/oclif.manifest.json +7 -1
- 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,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
|
-
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
|
+
}
|
|
89
111
|
if (json) {
|
|
90
112
|
const originalStdoutWrite = process.stdout.write;
|
|
91
113
|
process.stdout.write = function (chunk, encodingOrCallback, cb) {
|
|
@@ -104,6 +126,10 @@ class Cloud extends core_1.Command {
|
|
|
104
126
|
const apiKey = apiKeyFlag || process.env.DEVICE_CLOUD_API_KEY;
|
|
105
127
|
if (!apiKey)
|
|
106
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
|
+
}
|
|
107
133
|
const [maestroMajorString, maestroMinorString] = maestroVersion.split('.');
|
|
108
134
|
if (report === 'html' &&
|
|
109
135
|
maestroMajorString === '1' &&
|
|
@@ -128,6 +154,15 @@ class Cloud extends core_1.Command {
|
|
|
128
154
|
let finalAdditionalBinaryIds = additionalAppBinaryIds;
|
|
129
155
|
const finalAppFile = appFile ?? firstFile;
|
|
130
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
|
+
}
|
|
131
166
|
if (appBinaryId) {
|
|
132
167
|
if (secondFile) {
|
|
133
168
|
throw new Error('You cannot provide both an appBinaryId and a binary file');
|
|
@@ -144,6 +179,11 @@ class Cloud extends core_1.Command {
|
|
|
144
179
|
if (!supportediOSVersions.includes(version)) {
|
|
145
180
|
throw new Error(`${iOSDeviceID} only supports these iOS versions: ${supportediOSVersions.join(', ')}`);
|
|
146
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
|
+
}
|
|
147
187
|
}
|
|
148
188
|
if (androidApiLevel || androidDevice) {
|
|
149
189
|
const androidDeviceID = androidDevice || 'pixel-7';
|
|
@@ -158,6 +198,12 @@ class Cloud extends core_1.Command {
|
|
|
158
198
|
if (!supportedAndroidVersions.includes(version)) {
|
|
159
199
|
throw new Error(`${androidDeviceID} ${googlePlay ? '(Play Store) ' : ''}only supports these Android API levels: ${supportedAndroidVersions.join(', ')}`);
|
|
160
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
|
+
}
|
|
161
207
|
}
|
|
162
208
|
flowFile = path.resolve(flowFile);
|
|
163
209
|
if (!flowFile?.endsWith('.yaml') &&
|
|
@@ -165,14 +211,35 @@ class Cloud extends core_1.Command {
|
|
|
165
211
|
!flowFile?.endsWith('/')) {
|
|
166
212
|
flowFile += '/';
|
|
167
213
|
}
|
|
214
|
+
if (debug) {
|
|
215
|
+
this.log(`DEBUG: Resolved flow file path: ${flowFile}`);
|
|
216
|
+
}
|
|
168
217
|
let executionPlan;
|
|
169
218
|
try {
|
|
219
|
+
if (debug) {
|
|
220
|
+
this.log('DEBUG: Generating execution plan...');
|
|
221
|
+
}
|
|
170
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
|
+
}
|
|
171
230
|
}
|
|
172
231
|
catch (error) {
|
|
232
|
+
if (debug) {
|
|
233
|
+
this.log(`DEBUG: Error generating execution plan: ${error}`);
|
|
234
|
+
}
|
|
173
235
|
throw error;
|
|
174
236
|
}
|
|
175
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
|
+
}
|
|
176
243
|
const pathsShortestToLongest = [
|
|
177
244
|
...testFileNames,
|
|
178
245
|
...referencedFiles,
|
|
@@ -185,7 +252,14 @@ class Cloud extends core_1.Command {
|
|
|
185
252
|
if (isRoot)
|
|
186
253
|
commonRoot = folderPath;
|
|
187
254
|
}
|
|
255
|
+
if (debug) {
|
|
256
|
+
this.log(`DEBUG: Common root directory: ${commonRoot}`);
|
|
257
|
+
}
|
|
188
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
|
+
}
|
|
189
263
|
if (!appBinaryId) {
|
|
190
264
|
if (!(flowFile && finalAppFile)) {
|
|
191
265
|
throw new Error('You must provide a flow file and an app binary id');
|
|
@@ -194,9 +268,15 @@ class Cloud extends core_1.Command {
|
|
|
194
268
|
throw new Error('App file must be a .apk for android or .app/.zip file for iOS');
|
|
195
269
|
}
|
|
196
270
|
if (finalAppFile.endsWith('.zip')) {
|
|
271
|
+
if (debug) {
|
|
272
|
+
this.log(`DEBUG: Verifying iOS app zip file: ${finalAppFile}`);
|
|
273
|
+
}
|
|
197
274
|
await (0, methods_1.verifyAppZip)(finalAppFile);
|
|
198
275
|
}
|
|
199
276
|
}
|
|
277
|
+
if (debug && additionalAppFiles?.length) {
|
|
278
|
+
this.log(`DEBUG: Verifying additional app files: ${additionalAppFiles.join(', ')}`);
|
|
279
|
+
}
|
|
200
280
|
await (0, methods_1.verifyAdditionalAppFiles)(additionalAppFiles);
|
|
201
281
|
const flagLogs = [];
|
|
202
282
|
for (const [k, v] of Object.entries(flags)) {
|
|
@@ -221,16 +301,29 @@ class Cloud extends core_1.Command {
|
|
|
221
301
|
if (!finalBinaryId) {
|
|
222
302
|
if (!finalAppFile)
|
|
223
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
|
+
}
|
|
224
307
|
const binaryId = await (0, methods_1.uploadBinary)(finalAppFile, apiUrl, apiKey, ignoreShaCheck, !json);
|
|
225
308
|
finalBinaryId = binaryId;
|
|
309
|
+
if (debug) {
|
|
310
|
+
this.log(`DEBUG: Binary uploaded with ID: ${binaryId}`);
|
|
311
|
+
}
|
|
226
312
|
}
|
|
227
313
|
let uploadedBinaryIds = [];
|
|
228
314
|
if (additionalAppFiles?.length) {
|
|
315
|
+
if (debug) {
|
|
316
|
+
this.log(`DEBUG: Uploading additional binary files: ${additionalAppFiles.join(', ')}`);
|
|
317
|
+
}
|
|
229
318
|
uploadedBinaryIds = await (0, methods_1.uploadBinaries)(additionalAppFiles, apiUrl, apiKey, ignoreShaCheck, !json);
|
|
230
319
|
finalAdditionalBinaryIds = [
|
|
231
320
|
...finalAdditionalBinaryIds,
|
|
232
321
|
...uploadedBinaryIds,
|
|
233
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
|
+
}
|
|
234
327
|
}
|
|
235
328
|
const testFormData = new FormData();
|
|
236
329
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
@@ -240,6 +333,12 @@ class Cloud extends core_1.Command {
|
|
|
240
333
|
acc[key] = value.join('=');
|
|
241
334
|
return acc;
|
|
242
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
|
+
}
|
|
243
342
|
const buffer = await (0, methods_1.compressFilesFromRelativePath)(flowFile?.endsWith('.yaml') || flowFile?.endsWith('.yml')
|
|
244
343
|
? path.dirname(flowFile)
|
|
245
344
|
: flowFile, [
|
|
@@ -249,6 +348,9 @@ class Cloud extends core_1.Command {
|
|
|
249
348
|
...sequentialFlows,
|
|
250
349
|
]),
|
|
251
350
|
], commonRoot);
|
|
351
|
+
if (debug) {
|
|
352
|
+
this.log(`DEBUG: Compressed file size: ${buffer.length} bytes`);
|
|
353
|
+
}
|
|
252
354
|
const blob = new Blob([buffer], {
|
|
253
355
|
type: exports.mimeTypeLookupByExtension.zip,
|
|
254
356
|
});
|
|
@@ -299,11 +401,19 @@ class Cloud extends core_1.Command {
|
|
|
299
401
|
testFormData.set(key, value);
|
|
300
402
|
}
|
|
301
403
|
}
|
|
404
|
+
if (debug) {
|
|
405
|
+
this.log(`DEBUG: Submitting flow upload request to ${apiUrl}/uploads/flow`);
|
|
406
|
+
}
|
|
302
407
|
const options = {
|
|
303
408
|
body: testFormData,
|
|
304
409
|
headers: { 'x-app-api-key': apiKey },
|
|
305
410
|
};
|
|
306
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
|
+
}
|
|
307
417
|
if (!results?.length)
|
|
308
418
|
(0, errors_1.error)('No tests created: ' + message);
|
|
309
419
|
this.log(message);
|
|
@@ -318,6 +428,9 @@ class Cloud extends core_1.Command {
|
|
|
318
428
|
this.log(`Your upload ID is: ${results[0].test_upload_id}`);
|
|
319
429
|
this.log(`Poll upload status using: dcd status --api-key ... --upload-id ${results[0].test_upload_id}`);
|
|
320
430
|
if (async) {
|
|
431
|
+
if (debug) {
|
|
432
|
+
this.log(`DEBUG: Async flag is set, not waiting for results`);
|
|
433
|
+
}
|
|
321
434
|
const jsonOutput = {
|
|
322
435
|
uploadId: results[0].test_upload_id,
|
|
323
436
|
consoleUrl: url,
|
|
@@ -328,11 +441,8 @@ class Cloud extends core_1.Command {
|
|
|
328
441
|
})),
|
|
329
442
|
};
|
|
330
443
|
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)}`);
|
|
444
|
+
const jsonFilePath = this.getJsonOutputPath(name, results[0].test_upload_id);
|
|
445
|
+
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
336
446
|
}
|
|
337
447
|
if (json) {
|
|
338
448
|
return jsonOutput;
|
|
@@ -348,15 +458,27 @@ class Cloud extends core_1.Command {
|
|
|
348
458
|
this.log('\nYou can safely close this terminal and the tests will continue\n');
|
|
349
459
|
}
|
|
350
460
|
let sequentialPollFaillures = 0;
|
|
461
|
+
if (debug) {
|
|
462
|
+
this.log(`DEBUG: Starting polling loop for results`);
|
|
463
|
+
}
|
|
351
464
|
await new Promise((resolve, reject) => {
|
|
352
465
|
const intervalId = setInterval(async () => {
|
|
353
466
|
try {
|
|
467
|
+
if (debug) {
|
|
468
|
+
this.log(`DEBUG: Polling for results: ${results[0].test_upload_id}`);
|
|
469
|
+
}
|
|
354
470
|
const { results: updatedResults } = await (0, methods_1.typeSafeGet)(apiUrl, `/results/${results[0].test_upload_id}`, {
|
|
355
471
|
headers: { 'x-app-api-key': apiKey },
|
|
356
472
|
});
|
|
357
473
|
if (!updatedResults) {
|
|
358
474
|
throw new Error('no results');
|
|
359
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
|
+
}
|
|
360
482
|
if (!quiet && !json) {
|
|
361
483
|
core_1.ux.action.status =
|
|
362
484
|
'\nStatus Test\n─────────── ───────────────';
|
|
@@ -365,6 +487,9 @@ class Cloud extends core_1.Command {
|
|
|
365
487
|
}
|
|
366
488
|
}
|
|
367
489
|
if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
|
|
490
|
+
if (debug) {
|
|
491
|
+
this.log(`DEBUG: All tests completed, stopping poll`);
|
|
492
|
+
}
|
|
368
493
|
if (!json) {
|
|
369
494
|
core_1.ux.action.stop('completed');
|
|
370
495
|
this.log('\n');
|
|
@@ -382,6 +507,9 @@ class Cloud extends core_1.Command {
|
|
|
382
507
|
clearInterval(intervalId);
|
|
383
508
|
if (downloadArtifacts) {
|
|
384
509
|
try {
|
|
510
|
+
if (debug) {
|
|
511
|
+
this.log(`DEBUG: Downloading artifacts: ${downloadArtifacts}`);
|
|
512
|
+
}
|
|
385
513
|
await (0, methods_1.typeSafePostDownload)(apiUrl, `/results/${results[0].test_upload_id}/download`, {
|
|
386
514
|
body: JSON.stringify({ results: downloadArtifacts }),
|
|
387
515
|
headers: {
|
|
@@ -392,7 +520,10 @@ class Cloud extends core_1.Command {
|
|
|
392
520
|
this.log('\n');
|
|
393
521
|
this.log(`Test artifacts have been downloaded to ${artifactsPath || './artifacts.zip'}`);
|
|
394
522
|
}
|
|
395
|
-
catch {
|
|
523
|
+
catch (error) {
|
|
524
|
+
if (debug) {
|
|
525
|
+
this.log(`DEBUG: Error downloading artifacts: ${error}`);
|
|
526
|
+
}
|
|
396
527
|
this.warn('Failed to download artifacts');
|
|
397
528
|
}
|
|
398
529
|
}
|
|
@@ -402,6 +533,9 @@ class Cloud extends core_1.Command {
|
|
|
402
533
|
return result.id === Math.max(...tries.map((t) => t.id));
|
|
403
534
|
});
|
|
404
535
|
if (resultsWithoutEarlierTries.some((result) => result.status === 'FAILED')) {
|
|
536
|
+
if (debug) {
|
|
537
|
+
this.log(`DEBUG: Some tests failed, returning failed status`);
|
|
538
|
+
}
|
|
405
539
|
const jsonOutput = {
|
|
406
540
|
uploadId: results[0].test_upload_id,
|
|
407
541
|
consoleUrl: url,
|
|
@@ -412,11 +546,8 @@ class Cloud extends core_1.Command {
|
|
|
412
546
|
})),
|
|
413
547
|
};
|
|
414
548
|
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)}`);
|
|
549
|
+
const jsonFilePath = this.getJsonOutputPath(name, results[0].test_upload_id);
|
|
550
|
+
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
420
551
|
}
|
|
421
552
|
if (json) {
|
|
422
553
|
output = jsonOutput;
|
|
@@ -424,6 +555,9 @@ class Cloud extends core_1.Command {
|
|
|
424
555
|
reject(new Error('RUN_FAILED'));
|
|
425
556
|
}
|
|
426
557
|
else {
|
|
558
|
+
if (debug) {
|
|
559
|
+
this.log(`DEBUG: All tests passed, returning success status`);
|
|
560
|
+
}
|
|
427
561
|
const jsonOutput = {
|
|
428
562
|
uploadId: results[0].test_upload_id,
|
|
429
563
|
consoleUrl: url,
|
|
@@ -434,11 +568,8 @@ class Cloud extends core_1.Command {
|
|
|
434
568
|
})),
|
|
435
569
|
};
|
|
436
570
|
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)}`);
|
|
571
|
+
const jsonFilePath = this.getJsonOutputPath(name, results[0].test_upload_id);
|
|
572
|
+
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
442
573
|
}
|
|
443
574
|
if (json) {
|
|
444
575
|
output = jsonOutput;
|
|
@@ -450,10 +581,14 @@ class Cloud extends core_1.Command {
|
|
|
450
581
|
}
|
|
451
582
|
catch (error) {
|
|
452
583
|
sequentialPollFaillures++;
|
|
453
|
-
if (
|
|
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) {
|
|
454
589
|
// dropped poll requests shouldn't err user CI
|
|
455
590
|
clearInterval(intervalId);
|
|
456
|
-
throw new Error('unable to fetch results after
|
|
591
|
+
throw new Error('unable to fetch results after 10 attempts');
|
|
457
592
|
}
|
|
458
593
|
this.log('unable to fetch results, trying again...');
|
|
459
594
|
}
|
|
@@ -461,6 +596,10 @@ class Cloud extends core_1.Command {
|
|
|
461
596
|
});
|
|
462
597
|
}
|
|
463
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
|
+
}
|
|
464
603
|
if (error instanceof Error && error.message === 'RUN_FAILED') {
|
|
465
604
|
this.exit(2);
|
|
466
605
|
}
|
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',
|
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",
|
|
@@ -514,5 +520,5 @@
|
|
|
514
520
|
]
|
|
515
521
|
}
|
|
516
522
|
},
|
|
517
|
-
"version": "3.6.
|
|
523
|
+
"version": "3.6.6"
|
|
518
524
|
}
|