@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.
- package/dist/commands/cloud.d.ts +8 -0
- package/dist/commands/cloud.js +165 -55
- 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 +3 -3
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,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
|
-
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
606
|
+
else {
|
|
607
|
+
this.error(error, { exit: 1 });
|
|
608
|
+
}
|
|
499
609
|
}
|
|
500
610
|
finally {
|
|
501
611
|
if (output) {
|
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
|
}
|
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.
|
|
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
|
+
}
|