@devicecloud.dev/dcd 4.0.4 → 4.1.1

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.
@@ -9,8 +9,6 @@ export default class Cloud extends Command {
9
9
  static enableJsonFlag: boolean;
10
10
  static examples: string[];
11
11
  static flags: {
12
- 'additional-app-binary-ids': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
13
- 'additional-app-files': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
14
12
  'android-api-level': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
15
13
  'android-device': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
16
14
  apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -19,6 +17,8 @@ export default class Cloud extends Command {
19
17
  'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
20
18
  'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
21
19
  'junit-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ 'allure-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
21
+ 'html-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
22
22
  async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
23
23
  config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
24
24
  debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -50,8 +50,20 @@ export default class Cloud extends Command {
50
50
  'runner-type': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
51
51
  'show-crosshairs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
52
52
  'skip-chrome-onboarding': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
53
- 'x86-arch': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
54
53
  };
55
54
  private versionCheck;
56
55
  run(): Promise<any>;
56
+ /**
57
+ * Handle downloading reports based on the report type specified
58
+ * @param reportType The type of report to download ('junit', 'allure', or 'html')
59
+ * @param apiUrl The base URL for the API server
60
+ * @param apiKey The API key for authentication
61
+ * @param uploadId The unique identifier for the test upload
62
+ * @param junitPath Optional file path where the JUnit report should be saved
63
+ * @param allurePath Optional file path where the Allure report should be saved
64
+ * @param htmlPath Optional file path where the HTML report should be saved
65
+ * @param debug Whether debug logging is enabled
66
+ * @returns Promise that resolves when all reports have been downloaded
67
+ */
68
+ private handleReportDownloads;
57
69
  }
@@ -9,12 +9,12 @@ const node_child_process_1 = require("node:child_process");
9
9
  const fs = require("node:fs");
10
10
  const os = require("node:os");
11
11
  const path = require("node:path");
12
- const streamZip = require("node-stream-zip");
13
12
  const constants_1 = require("../constants");
14
13
  const api_gateway_1 = require("../gateways/api-gateway");
15
14
  const methods_1 = require("../methods");
16
15
  const plan_1 = require("../plan");
17
16
  const compatibility_1 = require("../utils/compatibility");
17
+ const StreamZip = require("node-stream-zip");
18
18
  exports.mimeTypeLookupByExtension = {
19
19
  apk: 'application/vnd.android.package-archive',
20
20
  yaml: 'application/x-yaml',
@@ -71,7 +71,7 @@ class Cloud extends core_1.Command {
71
71
  let jsonFile = false;
72
72
  try {
73
73
  const { args, flags, raw } = await this.parse(Cloud);
74
- 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, 'junit-path': junitPath, async, config: configFile, debug, 'device-locale': deviceLocale, 'download-artifacts': downloadArtifacts, 'dry-run': dryRun, env, 'exclude-flows': excludeFlows, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'ignore-sha-check': ignoreShaCheck, 'include-tags': includeTags, 'ios-device': iOSDevice, 'ios-version': iOSVersion, json, 'json-file-name': jsonFileName, 'maestro-version': maestroVersion, metadata, mitmHost, mitmPath, 'moropo-v1-api-key': moropoApiKey, name, orientation, quiet, report, retry, 'runner-type': runnerType, 'x86-arch': x86Arch, ...rest } = flags;
74
+ let { 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey: apiKeyFlag, apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, 'artifacts-path': artifactsPath, 'junit-path': junitPath, 'allure-path': allurePath, 'html-path': htmlPath, async, config: configFile, debug, 'device-locale': deviceLocale, 'download-artifacts': downloadArtifacts, 'dry-run': dryRun, env, 'exclude-flows': excludeFlows, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'ignore-sha-check': ignoreShaCheck, 'include-tags': includeTags, 'ios-device': iOSDevice, 'ios-version': iOSVersion, json, 'json-file-name': jsonFileName, 'maestro-version': maestroVersion, metadata, mitmHost, mitmPath, 'moropo-v1-api-key': moropoApiKey, name, orientation, quiet, report, retry, 'runner-type': runnerType, ...rest } = flags;
75
75
  // Resolve "latest" maestro version to actual version
76
76
  const resolvedMaestroVersion = (0, constants_1.resolveMaestroVersion)(maestroVersion);
77
77
  // Store debug flag for use in catch block
@@ -170,7 +170,6 @@ class Cloud extends core_1.Command {
170
170
  core_1.ux.action.status = 'Extracting tests...';
171
171
  }
172
172
  // Extract zip file
173
- const StreamZip = streamZip;
174
173
  // eslint-disable-next-line new-cap
175
174
  const zip = new StreamZip.async({ file: zipPath });
176
175
  await zip.extract(null, moropoDir);
@@ -222,7 +221,6 @@ class Cloud extends core_1.Command {
222
221
  }
223
222
  if (debug) {
224
223
  this.log(`DEBUG: API URL: ${apiUrl}`);
225
- this.log(`DEBUG: API Key provided: ${apiKey ? 'Yes' : 'No'}`);
226
224
  }
227
225
  if (retry && retry > 2) {
228
226
  this.log("Retries are now free of charge but limited to 2. If you're test is still failing after 2 retries, please ask for help on Discord.");
@@ -238,11 +236,8 @@ class Cloud extends core_1.Command {
238
236
  if (runnerType === 'gpu1') {
239
237
  this.log('Note: runnerType gpu1 is Android-only and requires contacting support to enable. Without support enablement, your runner type will revert to default.');
240
238
  }
241
- const additionalAppBinaryIds = nonFlatAdditionalAppBinaryIds?.flat();
242
- const additionalAppFiles = nonFlatAdditionalAppFiles?.flat();
243
239
  const { firstFile, secondFile } = args;
244
240
  let finalBinaryId = appBinaryId;
245
- let finalAdditionalBinaryIds = additionalAppBinaryIds;
246
241
  const finalAppFile = appFile ?? firstFile;
247
242
  let flowFile = flows ?? secondFile;
248
243
  if (debug) {
@@ -251,8 +246,6 @@ class Cloud extends core_1.Command {
251
246
  this.log(`DEBUG: App binary ID: ${appBinaryId || 'not provided'}`);
252
247
  this.log(`DEBUG: App file: ${finalAppFile || 'not provided'}`);
253
248
  this.log(`DEBUG: Flow file: ${flowFile || 'not provided'}`);
254
- this.log(`DEBUG: Additional app binary IDs: ${additionalAppBinaryIds?.join(', ') || 'none'}`);
255
- this.log(`DEBUG: Additional app files: ${additionalAppFiles?.join(', ') || 'none'}`);
256
249
  }
257
250
  if (appBinaryId) {
258
251
  if (secondFile) {
@@ -370,13 +363,10 @@ class Cloud extends core_1.Command {
370
363
  await (0, methods_1.verifyAppZip)(finalAppFile);
371
364
  }
372
365
  }
373
- if (debug && additionalAppFiles?.length) {
374
- this.log(`DEBUG: Verifying additional app files: ${additionalAppFiles.join(', ')}`);
375
- }
376
- await (0, methods_1.verifyAdditionalAppFiles)(additionalAppFiles);
377
366
  const flagLogs = [];
367
+ const sensitiveFlags = new Set(['api-key', 'apiKey', 'moropo-v1-api-key']);
378
368
  for (const [k, v] of Object.entries(flags)) {
379
- if (v && v.toString().length > 0) {
369
+ if (v && v.toString().length > 0 && !sensitiveFlags.has(k)) {
380
370
  flagLogs.push(`${k}: ${v}`);
381
371
  }
382
372
  }
@@ -402,9 +392,6 @@ class Cloud extends core_1.Command {
402
392
  Submitting new job
403
393
  → Flow(s): ${flowFile}
404
394
  → App: ${appBinaryId || finalAppFile}
405
- ${additionalAppBinaryIds.length > 0 || additionalAppFiles.length > 0
406
- ? `→ Additional app(s): ${additionalAppBinaryIds} ${additionalAppFiles}`
407
- : ''}
408
395
 
409
396
  With options
410
397
  → ${flagLogs.join(`
@@ -440,21 +427,6 @@ class Cloud extends core_1.Command {
440
427
  this.log(`DEBUG: Binary uploaded with ID: ${binaryId}`);
441
428
  }
442
429
  }
443
- let uploadedBinaryIds = [];
444
- if (additionalAppFiles?.length) {
445
- if (debug) {
446
- this.log(`DEBUG: Uploading additional binary files: ${additionalAppFiles.join(', ')}`);
447
- }
448
- uploadedBinaryIds = await (0, methods_1.uploadBinaries)(additionalAppFiles, apiUrl, apiKey, ignoreShaCheck, !json);
449
- finalAdditionalBinaryIds = [
450
- ...finalAdditionalBinaryIds,
451
- ...uploadedBinaryIds,
452
- ];
453
- if (debug) {
454
- this.log(`DEBUG: Additional binaries uploaded with IDs: ${uploadedBinaryIds.join(', ')}`);
455
- this.log(`DEBUG: Final additional binary IDs: ${finalAdditionalBinaryIds.join(', ')}`);
456
- }
457
- }
458
430
  const testFormData = new FormData();
459
431
  // eslint-disable-next-line unicorn/no-array-reduce
460
432
  const envObject = (env ?? []).reduce((acc, cur) => {
@@ -526,16 +498,8 @@ class Cloud extends core_1.Command {
526
498
  report,
527
499
  showCrosshairs: flags['show-crosshairs'],
528
500
  skipChromeOnboarding: flags['skip-chrome-onboarding'],
529
- uploadedBinaryIds,
530
501
  version: this.config.version,
531
- x86Arch,
532
502
  };
533
- if (finalAdditionalBinaryIds?.length > 0) {
534
- config.additionalAppBinaryIds = finalAdditionalBinaryIds;
535
- }
536
- if (uploadedBinaryIds?.length > 0) {
537
- config.uploadedBinaryIds = uploadedBinaryIds;
538
- }
539
503
  testFormData.set('config', JSON.stringify(config));
540
504
  if (Object.keys(metadataObject).length > 0) {
541
505
  const metadataPayload = { userMetadata: metadataObject };
@@ -691,33 +655,9 @@ class Cloud extends core_1.Command {
691
655
  this.warn('Failed to download artifacts');
692
656
  }
693
657
  }
694
- // Handle report download separately if --report junit is specified
695
- if (report === 'junit') {
696
- try {
697
- if (debug) {
698
- this.log('DEBUG: Downloading JUNIT report');
699
- }
700
- const reportPath = path.resolve(process.cwd(), junitPath || 'report.xml');
701
- await api_gateway_1.ApiGateway.downloadReport(apiUrl, apiKey, results[0].test_upload_id, reportPath);
702
- this.log('\n');
703
- this.log(`JUNIT test report has been downloaded to ${reportPath}`);
704
- }
705
- catch (error) {
706
- if (debug) {
707
- this.log(`DEBUG: Error downloading JUNIT report: ${error}`);
708
- }
709
- const errorMessage = error instanceof Error ? error.message : String(error);
710
- this.warn(`Failed to download JUNIT report: ${errorMessage}`);
711
- if (errorMessage.includes('404')) {
712
- this.warn('No JUNIT reports found for this upload. Make sure your tests were run with --report junit flag.');
713
- }
714
- else if (errorMessage.includes('EACCES') || errorMessage.includes('EPERM')) {
715
- this.warn('Permission denied. Check write permissions for the current directory.');
716
- }
717
- else if (errorMessage.includes('ENOENT')) {
718
- this.warn('Directory does not exist. Make sure you have write access to the current directory.');
719
- }
720
- }
658
+ // Handle report downloads based on --report flag
659
+ if (report && ['allure', 'html', 'junit'].includes(report)) {
660
+ await this.handleReportDownloads(report, apiUrl, apiKey, results[0].test_upload_id, junitPath, allurePath, htmlPath, debug);
721
661
  }
722
662
  const resultsWithoutEarlierTries = updatedResults.filter((result) => {
723
663
  const originalTryId = result.retry_of || result.id;
@@ -818,5 +758,65 @@ class Cloud extends core_1.Command {
818
758
  }
819
759
  }
820
760
  }
761
+ /**
762
+ * Handle downloading reports based on the report type specified
763
+ * @param reportType The type of report to download ('junit', 'allure', or 'html')
764
+ * @param apiUrl The base URL for the API server
765
+ * @param apiKey The API key for authentication
766
+ * @param uploadId The unique identifier for the test upload
767
+ * @param junitPath Optional file path where the JUnit report should be saved
768
+ * @param allurePath Optional file path where the Allure report should be saved
769
+ * @param htmlPath Optional file path where the HTML report should be saved
770
+ * @param debug Whether debug logging is enabled
771
+ * @returns Promise that resolves when all reports have been downloaded
772
+ */
773
+ // eslint-disable-next-line max-params
774
+ async handleReportDownloads(reportType, apiUrl, apiKey, uploadId, junitPath, allurePath, htmlPath, debug) {
775
+ const downloadReport = async (type, filePath) => {
776
+ try {
777
+ if (debug) {
778
+ this.log(`DEBUG: Downloading ${type.toUpperCase()} report`);
779
+ }
780
+ await api_gateway_1.ApiGateway.downloadReportGeneric(apiUrl, apiKey, uploadId, type, filePath);
781
+ this.log(`${type.toUpperCase()} test report has been downloaded to ${filePath}`);
782
+ }
783
+ catch (error) {
784
+ if (debug) {
785
+ this.log(`DEBUG: Error downloading ${type.toUpperCase()} report: ${error}`);
786
+ }
787
+ const errorMessage = error instanceof Error ? error.message : String(error);
788
+ this.warn(`Failed to download ${type.toUpperCase()} report: ${errorMessage}`);
789
+ if (errorMessage.includes('404')) {
790
+ this.warn(`No ${type.toUpperCase()} reports found for this upload. Make sure your tests generated results.`);
791
+ }
792
+ else if (errorMessage.includes('EACCES') || errorMessage.includes('EPERM')) {
793
+ this.warn('Permission denied. Check write permissions for the current directory.');
794
+ }
795
+ else if (errorMessage.includes('ENOENT')) {
796
+ this.warn('Directory does not exist. Make sure you have write access to the current directory.');
797
+ }
798
+ }
799
+ };
800
+ switch (reportType) {
801
+ case 'junit': {
802
+ const reportPath = path.resolve(process.cwd(), junitPath || 'report.xml');
803
+ await downloadReport('junit', reportPath);
804
+ break;
805
+ }
806
+ case 'allure': {
807
+ const reportPath = path.resolve(process.cwd(), allurePath || 'report.html');
808
+ await downloadReport('allure', reportPath);
809
+ break;
810
+ }
811
+ case 'html': {
812
+ const htmlReportPath = path.resolve(process.cwd(), htmlPath || 'report.html');
813
+ await downloadReport('html', htmlReportPath);
814
+ break;
815
+ }
816
+ default: {
817
+ this.warn(`Unknown report type: ${reportType}`);
818
+ }
819
+ }
820
+ }
821
821
  }
822
822
  exports.default = Cloud;
@@ -3,8 +3,6 @@ export declare const DEFAULT_MAESTRO_VERSION = "1.41.0";
3
3
  export declare const getLatestMaestroVersion: () => string;
4
4
  export declare const resolveMaestroVersion: (version?: string) => string;
5
5
  export declare const flags: {
6
- 'additional-app-binary-ids': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
7
- 'additional-app-files': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
8
6
  'android-api-level': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
9
7
  'android-device': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
10
8
  apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -13,6 +11,8 @@ export declare const flags: {
13
11
  'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
14
12
  'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
15
13
  'junit-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
14
+ 'allure-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
15
+ 'html-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
16
16
  async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
17
17
  config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
18
18
  debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -44,5 +44,4 @@ export declare const flags: {
44
44
  'runner-type': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
45
45
  'show-crosshairs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
46
46
  'skip-chrome-onboarding': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
47
- 'x86-arch': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
48
47
  };
package/dist/constants.js CHANGED
@@ -27,20 +27,6 @@ const resolveMaestroVersion = (version) => {
27
27
  };
28
28
  exports.resolveMaestroVersion = resolveMaestroVersion;
29
29
  exports.flags = {
30
- 'additional-app-binary-ids': core_1.Flags.string({
31
- default: [],
32
- description: 'The ID of the additional app binary(s) previously uploaded to devicecloud.dev to install before execution',
33
- multiple: true,
34
- multipleNonGreedy: true,
35
- parse: (input) => input.split(','),
36
- }),
37
- 'additional-app-files': core_1.Flags.file({
38
- default: [],
39
- description: 'Additional app binary(s) to install before execution',
40
- multiple: true,
41
- multipleNonGreedy: true,
42
- parse: (input) => input.split(','),
43
- }),
44
30
  'android-api-level': core_1.Flags.string({
45
31
  description: '[Android only] Android API level to run your flow against',
46
32
  options: Object.values(device_types_1.EAndroidApiLevels),
@@ -75,6 +61,14 @@ exports.flags = {
75
61
  dependsOn: ['report'],
76
62
  description: 'Custom file path for downloaded JUnit report (default: ./report.xml)',
77
63
  }),
64
+ 'allure-path': core_1.Flags.string({
65
+ dependsOn: ['report'],
66
+ description: 'Custom file path for downloaded Allure report (default: ./report.html)',
67
+ }),
68
+ 'html-path': core_1.Flags.string({
69
+ dependsOn: ['report'],
70
+ description: 'Custom file path for downloaded HTML report (default: ./report.html)',
71
+ }),
78
72
  async: core_1.Flags.boolean({
79
73
  description: 'Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)',
80
74
  }),
@@ -169,10 +163,12 @@ exports.flags = {
169
163
  }),
170
164
  mitmHost: core_1.Flags.string({
171
165
  description: 'used for mitmproxy support, enterprise only, contact support if interested',
166
+ hidden: true,
172
167
  }),
173
168
  mitmPath: core_1.Flags.string({
174
169
  dependsOn: ['mitmHost'],
175
170
  description: 'used for mitmproxy support, enterprise only, contact support if interested',
171
+ hidden: true,
176
172
  }),
177
173
  'moropo-v1-api-key': core_1.Flags.string({
178
174
  description: 'API key for Moropo v1 integration',
@@ -183,7 +179,7 @@ exports.flags = {
183
179
  }),
184
180
  orientation: core_1.Flags.string({
185
181
  description: '[Android only] The orientation of the device to run your flow against in degrees',
186
- options: ['0', '90', '180', '270'],
182
+ options: ['0', '90'],
187
183
  }),
188
184
  quiet: core_1.Flags.boolean({
189
185
  char: 'q',
@@ -192,8 +188,8 @@ exports.flags = {
192
188
  }),
193
189
  report: core_1.Flags.string({
194
190
  aliases: ['format'],
195
- description: 'Runs Maestro with the --format flag, this will generate a report in the specified format',
196
- options: ['junit', 'html'],
191
+ description: 'Generate and download test reports in the specified format. Use "allure" for a complete HTML report.',
192
+ options: ['allure', 'junit', 'html'],
197
193
  }),
198
194
  retry: core_1.Flags.integer({
199
195
  description: 'Automatically retry the run up to the number of times specified (same as pressing retry in the UI) - this is free of charge',
@@ -211,8 +207,4 @@ exports.flags = {
211
207
  default: false,
212
208
  description: '[Android only] Skip Chrome browser onboarding screens when running tests',
213
209
  }),
214
- 'x86-arch': core_1.Flags.boolean({
215
- default: false,
216
- description: '[iOS only, experimental] Run your flow against x86 architecture simulator instead of arm64',
217
- }),
218
210
  };
@@ -1,10 +1,16 @@
1
1
  import { TAppMetadata } from '../types';
2
2
  export declare const ApiGateway: {
3
+ /**
4
+ * Standardized error handling for API responses
5
+ * @param res - The fetch response object
6
+ * @param operation - Description of the operation that failed
7
+ * @returns Never returns, always throws
8
+ */
9
+ handleApiError(res: Response, operation: string): Promise<never>;
3
10
  checkForExistingUpload(baseUrl: string, apiKey: string, sha: string): Promise<{
4
11
  appBinaryId: string;
5
12
  exists: boolean;
6
13
  }>;
7
- downloadReport(baseUrl: string, apiKey: string, uploadId: string, reportPath?: string): Promise<void>;
8
14
  downloadArtifactsZip(baseUrl: string, apiKey: string, uploadId: string, results: "ALL" | "FAILED", artifactsPath?: string): Promise<void>;
9
15
  finaliseUpload(baseUrl: string, apiKey: string, id: string, metadata: TAppMetadata, path: string, sha: string): Promise<Record<string, never>>;
10
16
  getBinaryUploadUrl(baseUrl: string, apiKey: string, platform: "android" | "ios"): Promise<{
@@ -33,4 +39,14 @@ export declare const ApiGateway: {
33
39
  message?: string;
34
40
  results?: import("../types/schema.types").components["schemas"]["IDBResult"][];
35
41
  }>;
42
+ /**
43
+ * Generic report download method that handles both junit and allure reports
44
+ * @param baseUrl - API base URL
45
+ * @param apiKey - API key for authentication
46
+ * @param uploadId - Upload ID to download report for
47
+ * @param reportType - Type of report to download ('junit' or 'allure')
48
+ * @param reportPath - Optional custom path for the downloaded report
49
+ * @returns Promise that resolves when download is complete
50
+ */
51
+ downloadReportGeneric(baseUrl: string, apiKey: string, uploadId: string, reportType: "allure" | "html" | "junit", reportPath?: string): Promise<void>;
36
52
  };
@@ -1,9 +1,50 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ApiGateway = void 0;
4
- const fs = require("node:fs/promises");
5
4
  const path = require("node:path");
6
5
  exports.ApiGateway = {
6
+ /**
7
+ * Standardized error handling for API responses
8
+ * @param res - The fetch response object
9
+ * @param operation - Description of the operation that failed
10
+ * @returns Never returns, always throws
11
+ */
12
+ async handleApiError(res, operation) {
13
+ const errorText = await res.text();
14
+ let userMessage;
15
+ // Parse common API error formats
16
+ try {
17
+ const errorData = JSON.parse(errorText);
18
+ userMessage = errorData.message || errorData.error || errorText;
19
+ }
20
+ catch {
21
+ userMessage = errorText;
22
+ }
23
+ // Add context and improve readability
24
+ switch (res.status) {
25
+ case 400: {
26
+ throw new Error(`Invalid request: ${userMessage}`);
27
+ }
28
+ case 401: {
29
+ throw new Error(`Authentication failed. Please check your API key.`);
30
+ }
31
+ case 403: {
32
+ throw new Error(`Access denied. ${userMessage}`);
33
+ }
34
+ case 404: {
35
+ throw new Error(`Resource not found. ${userMessage}`);
36
+ }
37
+ case 429: {
38
+ throw new Error(`Rate limit exceeded. Please try again later.`);
39
+ }
40
+ case 500: {
41
+ throw new Error(`Server error occurred. Please try again or contact support.`);
42
+ }
43
+ default: {
44
+ throw new Error(`${operation} failed: ${userMessage} (HTTP ${res.status})`);
45
+ }
46
+ }
47
+ },
7
48
  async checkForExistingUpload(baseUrl, apiKey, sha) {
8
49
  const res = await fetch(`${baseUrl}/uploads/checkForExistingUpload`, {
9
50
  body: JSON.stringify({ sha }),
@@ -15,25 +56,6 @@ exports.ApiGateway = {
15
56
  });
16
57
  return res.json();
17
58
  },
18
- async downloadReport(baseUrl, apiKey, uploadId, reportPath) {
19
- const finalReportPath = reportPath || path.resolve(process.cwd(), `report-${uploadId}.xml`);
20
- const url = `${baseUrl}/results/${uploadId}/report`;
21
- const res = await fetch(url, {
22
- headers: {
23
- 'x-app-api-key': apiKey,
24
- },
25
- method: 'GET',
26
- });
27
- if (!res.ok) {
28
- const errorText = await res.text();
29
- if (res.status === 404) {
30
- throw new Error(`Upload ID '${uploadId}' not found or no results available for this upload`);
31
- }
32
- throw new Error(`Failed to download report: ${res.status} ${errorText}`);
33
- }
34
- const buffer = await res.arrayBuffer();
35
- await fs.writeFile(finalReportPath, Buffer.from(buffer));
36
- },
37
59
  async downloadArtifactsZip(baseUrl, apiKey, uploadId, results, artifactsPath = './artifacts.zip') {
38
60
  const res = await fetch(`${baseUrl}/results/${uploadId}/download`, {
39
61
  body: JSON.stringify({ results }),
@@ -44,7 +66,7 @@ exports.ApiGateway = {
44
66
  method: 'POST',
45
67
  });
46
68
  if (!res.ok) {
47
- throw new Error(await res.text());
69
+ await this.handleApiError(res, 'Failed to download artifacts');
48
70
  }
49
71
  // Handle tilde expansion for home directory
50
72
  if (artifactsPath.startsWith('~/') || artifactsPath === '~') {
@@ -87,7 +109,7 @@ exports.ApiGateway = {
87
109
  method: 'POST',
88
110
  });
89
111
  if (!res.ok) {
90
- throw new Error(await res.text());
112
+ await this.handleApiError(res, 'Failed to finalize upload');
91
113
  }
92
114
  return res.json();
93
115
  },
@@ -101,7 +123,7 @@ exports.ApiGateway = {
101
123
  method: 'POST',
102
124
  });
103
125
  if (!res.ok) {
104
- throw new Error(await res.text());
126
+ await this.handleApiError(res, 'Failed to get upload URL');
105
127
  }
106
128
  return res.json();
107
129
  },
@@ -111,7 +133,7 @@ exports.ApiGateway = {
111
133
  headers: { 'x-app-api-key': apiKey },
112
134
  });
113
135
  if (!res.ok) {
114
- throw new Error(await res.text());
136
+ await this.handleApiError(res, 'Failed to get results');
115
137
  }
116
138
  return res.json();
117
139
  },
@@ -129,7 +151,7 @@ exports.ApiGateway = {
129
151
  },
130
152
  });
131
153
  if (!response.ok) {
132
- throw new Error(`Failed to fetch status: ${response.statusText}`);
154
+ await this.handleApiError(response, 'Failed to get upload status');
133
155
  }
134
156
  return response.json();
135
157
  },
@@ -142,8 +164,89 @@ exports.ApiGateway = {
142
164
  method: 'POST',
143
165
  });
144
166
  if (!res.ok) {
145
- throw new Error(await res.text());
167
+ await this.handleApiError(res, 'Failed to upload test flows');
146
168
  }
147
169
  return res.json();
148
170
  },
171
+ /**
172
+ * Generic report download method that handles both junit and allure reports
173
+ * @param baseUrl - API base URL
174
+ * @param apiKey - API key for authentication
175
+ * @param uploadId - Upload ID to download report for
176
+ * @param reportType - Type of report to download ('junit' or 'allure')
177
+ * @param reportPath - Optional custom path for the downloaded report
178
+ * @returns Promise that resolves when download is complete
179
+ */
180
+ async downloadReportGeneric(baseUrl, apiKey, uploadId, reportType, reportPath) {
181
+ // Define endpoint and default filename mappings
182
+ const config = {
183
+ junit: {
184
+ endpoint: `/results/${uploadId}/report`,
185
+ defaultFilename: `report-${uploadId}.xml`,
186
+ notFoundMessage: `Upload ID '${uploadId}' not found or no results available for this upload`,
187
+ errorPrefix: 'Failed to download report',
188
+ },
189
+ allure: {
190
+ endpoint: `/allure/${uploadId}/download`,
191
+ defaultFilename: `report-${uploadId}.html`,
192
+ notFoundMessage: `Upload ID '${uploadId}' not found or no Allure report available for this upload`,
193
+ errorPrefix: 'Failed to download Allure report',
194
+ },
195
+ html: {
196
+ endpoint: `/results/${uploadId}/html-report`,
197
+ defaultFilename: `report-${uploadId}.html`,
198
+ notFoundMessage: `Upload ID '${uploadId}' not found or no HTML report available for this upload`,
199
+ errorPrefix: 'Failed to download HTML report',
200
+ },
201
+ };
202
+ const { endpoint, defaultFilename, notFoundMessage, errorPrefix } = config[reportType];
203
+ const finalReportPath = reportPath || path.resolve(process.cwd(), defaultFilename);
204
+ const url = `${baseUrl}${endpoint}`;
205
+ // Make the download request
206
+ const res = await fetch(url, {
207
+ headers: {
208
+ 'x-app-api-key': apiKey,
209
+ },
210
+ method: 'GET',
211
+ });
212
+ if (!res.ok) {
213
+ const errorText = await res.text();
214
+ if (res.status === 404) {
215
+ throw new Error(notFoundMessage);
216
+ }
217
+ throw new Error(`${errorPrefix}: ${res.status} ${errorText}`);
218
+ }
219
+ // Handle tilde expansion for home directory (applies to all report types)
220
+ let expandedPath = finalReportPath;
221
+ if (finalReportPath.startsWith('~/') || finalReportPath === '~') {
222
+ expandedPath = finalReportPath.replace(/^~/,
223
+ // eslint-disable-next-line unicorn/prefer-module
224
+ require('node:os').homedir());
225
+ }
226
+ // Create directory structure if it doesn't exist
227
+ // eslint-disable-next-line unicorn/prefer-module
228
+ const { dirname } = require('node:path');
229
+ // eslint-disable-next-line unicorn/prefer-module
230
+ const { createWriteStream, mkdirSync } = require('node:fs');
231
+ // eslint-disable-next-line unicorn/prefer-module
232
+ const { finished } = require('node:stream/promises');
233
+ // eslint-disable-next-line unicorn/prefer-module
234
+ const { Readable } = require('node:stream');
235
+ const directory = dirname(expandedPath);
236
+ if (directory !== '.') {
237
+ try {
238
+ mkdirSync(directory, { recursive: true });
239
+ }
240
+ catch (error) {
241
+ // Ignore EEXIST errors (directory already exists)
242
+ if (error.code !== 'EEXIST') {
243
+ throw error;
244
+ }
245
+ }
246
+ }
247
+ // Write the file using streaming for better memory efficiency
248
+ // Use 'w' flag to overwrite existing files instead of failing
249
+ const fileStream = createWriteStream(expandedPath, { flags: 'w' });
250
+ await finished(Readable.fromWeb(res.body).pipe(fileStream));
251
+ },
149
252
  };
package/dist/methods.d.ts CHANGED
@@ -8,8 +8,6 @@ export declare const extractAppMetadataAndroid: (appFilePath: string) => Promise
8
8
  export declare const extractAppMetadataIosZip: (appFilePath: string) => Promise<TAppMetadata>;
9
9
  export declare const extractAppMetadataIos: (appFolderPath: string) => Promise<TAppMetadata>;
10
10
  export declare const uploadBinary: (filePath: string, apiUrl: string, apiKey: string, ignoreShaCheck?: boolean, log?: boolean) => Promise<string>;
11
- export declare const uploadBinaries: (finalAppFiles: string[], apiUrl: string, apiKey: string, ignoreShaCheck?: boolean, log?: boolean) => Promise<string[]>;
12
- export declare const verifyAdditionalAppFiles: (appFiles: string[] | undefined) => Promise<void>;
13
11
  /**
14
12
  * Writes JSON data to a file with error handling
15
13
  * @param filePath - Path to the output JSON file
package/dist/methods.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatDurationSeconds = exports.writeJSONFile = exports.verifyAdditionalAppFiles = exports.uploadBinaries = exports.uploadBinary = exports.extractAppMetadataIos = exports.extractAppMetadataIosZip = exports.extractAppMetadataAndroid = exports.verifyAppZip = exports.compressFilesFromRelativePath = exports.compressFolderToBlob = exports.toBuffer = void 0;
3
+ exports.formatDurationSeconds = exports.writeJSONFile = exports.uploadBinary = exports.extractAppMetadataIos = exports.extractAppMetadataIosZip = exports.extractAppMetadataAndroid = exports.verifyAppZip = exports.compressFilesFromRelativePath = exports.compressFolderToBlob = exports.toBuffer = void 0;
4
4
  const core_1 = require("@oclif/core");
5
5
  // required polyfill for node 18
6
6
  const file_1 = require("@web-std/file");
@@ -201,21 +201,6 @@ const uploadBinary = async (filePath, apiUrl, apiKey, ignoreShaCheck = false, lo
201
201
  return id;
202
202
  };
203
203
  exports.uploadBinary = uploadBinary;
204
- const uploadBinaries = async (finalAppFiles, apiUrl, apiKey, ignoreShaCheck = false, log = true) => Promise.all(finalAppFiles.map((f) => (0, exports.uploadBinary)(f, apiUrl, apiKey, ignoreShaCheck, log)));
205
- exports.uploadBinaries = uploadBinaries;
206
- const verifyAdditionalAppFiles = async (appFiles) => {
207
- if (appFiles?.length) {
208
- if (!appFiles.every((f) => ['apk', '.app', '.zip'].some((ext) => f.endsWith(ext)))) {
209
- throw new Error('App file must be a .apk for android or .app/.zip file for iOS');
210
- }
211
- await Promise.all(appFiles.map(async (f) => {
212
- if (f.endsWith('.zip')) {
213
- await (0, exports.verifyAppZip)(f);
214
- }
215
- }));
216
- }
217
- };
218
- exports.verifyAdditionalAppFiles = verifyAdditionalAppFiles;
219
204
  async function getFileHashFromFile(file) {
220
205
  return new Promise((resolve, reject) => {
221
206
  const hash = (0, node_crypto_1.createHash)('sha256');
@@ -23,7 +23,8 @@ export declare enum EAndroidDevices {
23
23
  export declare enum EiOSVersions {
24
24
  'eighteen' = "18",
25
25
  'seventeen' = "17",
26
- 'sixteen' = "16"
26
+ 'sixteen' = "16",
27
+ 'twentySix' = "26"
27
28
  }
28
29
  export declare enum EAndroidApiLevels {
29
30
  'thirty' = "30",
@@ -30,6 +30,7 @@ var EiOSVersions;
30
30
  EiOSVersions["eighteen"] = "18";
31
31
  EiOSVersions["seventeen"] = "17";
32
32
  EiOSVersions["sixteen"] = "16";
33
+ EiOSVersions["twentySix"] = "26";
33
34
  })(EiOSVersions || (exports.EiOSVersions = EiOSVersions = {}));
34
35
  var EAndroidApiLevels;
35
36
  (function (EAndroidApiLevels) {
@@ -3,9 +3,6 @@
3
3
  * Do not make direct changes to the file.
4
4
  */
5
5
  export interface paths {
6
- "/admin/cleanupOldUploads": {
7
- post: operations["AdminController_cleanupOldUploads"];
8
- };
9
6
  "/uploads/binary": {
10
7
  post: operations["UploadsController_createBinary"];
11
8
  };
@@ -39,15 +36,36 @@ export interface paths {
39
36
  "/results/{uploadId}/download": {
40
37
  post: operations["ResultsController_getTestRunArtifacts"];
41
38
  };
42
- "/results": {
43
- post: operations["ResultsController_createResult"];
44
- };
45
39
  "/results/notify/{uploadId}": {
46
40
  post: operations["ResultsController_notifyTestRunComplete"];
47
41
  };
42
+ "/results/{uploadId}/report": {
43
+ get: operations["ResultsController_downloadReport"];
44
+ };
45
+ "/results/{uploadId}/html-report": {
46
+ get: operations["ResultsController_downloadHtmlReport"];
47
+ };
48
48
  "/results/compatibility/data": {
49
49
  get: operations["ResultsController_getCompatibilityData"];
50
50
  };
51
+ "/allure/{uploadId}/download": {
52
+ /**
53
+ * Download Allure report as HTML
54
+ * @description Downloads a single-file Allure report as HTML containing all test results. Report is generated once and stored in Supabase Storage for subsequent downloads.
55
+ */
56
+ get: operations["AllureController_downloadAllureReport"];
57
+ };
58
+ "/webhooks": {
59
+ get: operations["WebhooksController_getWebhook"];
60
+ post: operations["WebhooksController_setWebhook"];
61
+ delete: operations["WebhooksController_deleteWebhook"];
62
+ };
63
+ "/webhooks/regenerate-secret": {
64
+ post: operations["WebhooksController_regenerateWebhookSecret"];
65
+ };
66
+ "/webhooks/test": {
67
+ post: operations["WebhooksController_testWebhook"];
68
+ };
51
69
  "/org/increase_credits": {
52
70
  post: operations["OrgController_paddleTransactionCompleted"];
53
71
  };
@@ -60,12 +78,6 @@ export interface paths {
60
78
  "/org/accept-invite": {
61
79
  post: operations["OrgController_acceptInvite"];
62
80
  };
63
- "/public/stats": {
64
- get: operations["StatsController_getPublicStats"];
65
- };
66
- "/moropo/run-scheduled-jobs": {
67
- get: operations["MoropoController_runScheduledJobs"];
68
- };
69
81
  "/frontend/check-domain-saml": {
70
82
  post: operations["FrontendController_checkDomainSaml"];
71
83
  };
@@ -137,7 +149,7 @@ export interface components {
137
149
  apiUrl?: string;
138
150
  appFile?: string;
139
151
  /** @enum {string} */
140
- iOSVersion?: "16" | "17" | "18";
152
+ iOSVersion?: "16" | "17" | "18" | "26";
141
153
  /** @enum {string} */
142
154
  iOSDevice?: "iphone-14" | "iphone-14-pro" | "iphone-15" | "iphone-15-pro" | "iphone-16" | "iphone-16-plus" | "iphone-16-pro" | "iphone-16-pro-max" | "ipad-pro-6th-gen";
143
155
  platform?: string;
@@ -182,32 +194,6 @@ export interface components {
182
194
  export type $defs = Record<string, never>;
183
195
  export type external = Record<string, never>;
184
196
  export interface operations {
185
- AdminController_cleanupOldUploads: {
186
- parameters: {
187
- query?: {
188
- /** @description If true, shows what would be deleted without actually deleting */
189
- dryRun?: boolean;
190
- };
191
- };
192
- responses: {
193
- /** @description Cleanup of old uploads has been triggered. */
194
- 201: {
195
- content: {
196
- "application/json": {
197
- message?: string;
198
- deletedCount?: number;
199
- uploads?: ({
200
- id?: string;
201
- uploadType?: string;
202
- createdAt?: string;
203
- lastUsedDate?: string | null;
204
- })[];
205
- dryRun?: boolean;
206
- };
207
- };
208
- };
209
- };
210
- };
211
197
  UploadsController_createBinary: {
212
198
  parameters: {
213
199
  header: {
@@ -445,19 +431,25 @@ export interface operations {
445
431
  };
446
432
  };
447
433
  };
448
- ResultsController_createResult: {
434
+ ResultsController_notifyTestRunComplete: {
449
435
  parameters: {
450
436
  header: {
451
437
  "x-app-api-key": string;
452
438
  };
439
+ path: {
440
+ uploadId: string;
441
+ };
453
442
  };
454
443
  responses: {
444
+ /** @description Send results summary email. */
455
445
  201: {
456
- content: never;
446
+ content: {
447
+ "application/json": string;
448
+ };
457
449
  };
458
450
  };
459
451
  };
460
- ResultsController_notifyTestRunComplete: {
452
+ ResultsController_downloadReport: {
461
453
  parameters: {
462
454
  header: {
463
455
  "x-app-api-key": string;
@@ -467,8 +459,26 @@ export interface operations {
467
459
  };
468
460
  };
469
461
  responses: {
470
- /** @description Send results summary email. */
471
- 201: {
462
+ /** @description Download combined JUNIT test report (report.xml) for the upload */
463
+ 200: {
464
+ content: {
465
+ "application/json": string;
466
+ };
467
+ };
468
+ };
469
+ };
470
+ ResultsController_downloadHtmlReport: {
471
+ parameters: {
472
+ header: {
473
+ "x-app-api-key": string;
474
+ };
475
+ path: {
476
+ uploadId: string;
477
+ };
478
+ };
479
+ responses: {
480
+ /** @description Download combined HTML test report (report.html) for the upload */
481
+ 200: {
472
482
  content: {
473
483
  "application/json": string;
474
484
  };
@@ -497,6 +507,141 @@ export interface operations {
497
507
  };
498
508
  };
499
509
  };
510
+ /**
511
+ * Download Allure report as HTML
512
+ * @description Downloads a single-file Allure report as HTML containing all test results. Report is generated once and stored in Supabase Storage for subsequent downloads.
513
+ */
514
+ AllureController_downloadAllureReport: {
515
+ parameters: {
516
+ header: {
517
+ "x-app-api-key": string;
518
+ };
519
+ path: {
520
+ /** @description The upload ID to generate Allure report for */
521
+ uploadId: string;
522
+ };
523
+ };
524
+ responses: {
525
+ /** @description Allure report HTML file download */
526
+ 200: {
527
+ content: {
528
+ "text/html": string;
529
+ };
530
+ };
531
+ /** @description Upload not found or no results available */
532
+ 404: {
533
+ content: never;
534
+ };
535
+ };
536
+ };
537
+ WebhooksController_getWebhook: {
538
+ parameters: {
539
+ query?: {
540
+ /** @description Set to true to return full secret instead of masked version */
541
+ show_secret?: boolean;
542
+ };
543
+ header: {
544
+ "x-app-api-key": string;
545
+ };
546
+ };
547
+ responses: {
548
+ /** @description Current webhook configuration */
549
+ 200: {
550
+ content: {
551
+ "application/json": {
552
+ webhook_url?: string;
553
+ /** @description Full secret (only when show_secret=true) */
554
+ secret_key?: string;
555
+ /** @description Masked secret (default) */
556
+ secret_key_masked?: string;
557
+ /** Format: date-time */
558
+ created_at?: string;
559
+ /** Format: date-time */
560
+ updated_at?: string;
561
+ };
562
+ };
563
+ };
564
+ };
565
+ };
566
+ WebhooksController_setWebhook: {
567
+ parameters: {
568
+ header: {
569
+ "x-app-api-key": string;
570
+ };
571
+ };
572
+ requestBody: {
573
+ content: {
574
+ "application/json": {
575
+ /**
576
+ * Format: uri
577
+ * @example https://api.example.com/webhook
578
+ */
579
+ url: string;
580
+ };
581
+ };
582
+ };
583
+ responses: {
584
+ /** @description Webhook URL set successfully */
585
+ 201: {
586
+ content: {
587
+ "application/json": Record<string, never>;
588
+ };
589
+ };
590
+ };
591
+ };
592
+ WebhooksController_deleteWebhook: {
593
+ parameters: {
594
+ header: {
595
+ "x-app-api-key": string;
596
+ };
597
+ };
598
+ responses: {
599
+ /** @description Webhook configuration deleted successfully */
600
+ 200: {
601
+ content: {
602
+ "application/json": Record<string, never>;
603
+ };
604
+ };
605
+ };
606
+ };
607
+ WebhooksController_regenerateWebhookSecret: {
608
+ parameters: {
609
+ header: {
610
+ "x-app-api-key": string;
611
+ };
612
+ };
613
+ responses: {
614
+ /** @description Webhook secret regenerated successfully */
615
+ 201: {
616
+ content: {
617
+ "application/json": Record<string, never>;
618
+ };
619
+ };
620
+ };
621
+ };
622
+ WebhooksController_testWebhook: {
623
+ parameters: {
624
+ header: {
625
+ "x-app-api-key": string;
626
+ };
627
+ };
628
+ requestBody: {
629
+ content: {
630
+ "application/json": {
631
+ /** Format: uri */
632
+ url?: string;
633
+ };
634
+ };
635
+ };
636
+ responses: {
637
+ /** @description Test webhook sent successfully */
638
+ 201: {
639
+ content: {
640
+ "application/json": Record<string, never>;
641
+ };
642
+ };
643
+ };
644
+ };
500
645
  OrgController_paddleTransactionCompleted: {
501
646
  parameters: {
502
647
  header: {
@@ -583,41 +728,6 @@ export interface operations {
583
728
  };
584
729
  };
585
730
  };
586
- StatsController_getPublicStats: {
587
- responses: {
588
- 200: {
589
- content: never;
590
- };
591
- };
592
- };
593
- StatsController_sendDailyReport: {
594
- responses: {
595
- 201: {
596
- content: never;
597
- };
598
- };
599
- };
600
- StatsController_reportSlowTestsDev: {
601
- responses: {
602
- 201: {
603
- content: never;
604
- };
605
- };
606
- };
607
- StatsController_reportSlowTestsProd: {
608
- responses: {
609
- 201: {
610
- content: never;
611
- };
612
- };
613
- };
614
- MoropoController_runScheduledJobs: {
615
- responses: {
616
- 200: {
617
- content: never;
618
- };
619
- };
620
- };
621
731
  FrontendController_checkDomainSaml: {
622
732
  /** @description Domain to check for SAML configuration */
623
733
  requestBody: {
@@ -25,22 +25,6 @@
25
25
  "allowNo": false,
26
26
  "type": "boolean"
27
27
  },
28
- "additional-app-binary-ids": {
29
- "description": "The ID of the additional app binary(s) previously uploaded to devicecloud.dev to install before execution",
30
- "name": "additional-app-binary-ids",
31
- "default": [],
32
- "hasDynamicHelp": false,
33
- "multiple": true,
34
- "type": "option"
35
- },
36
- "additional-app-files": {
37
- "description": "Additional app binary(s) to install before execution",
38
- "name": "additional-app-files",
39
- "default": [],
40
- "hasDynamicHelp": false,
41
- "multiple": true,
42
- "type": "option"
43
- },
44
28
  "android-api-level": {
45
29
  "description": "[Android only] Android API level to run your flow against",
46
30
  "name": "android-api-level",
@@ -134,6 +118,26 @@
134
118
  "multiple": false,
135
119
  "type": "option"
136
120
  },
121
+ "allure-path": {
122
+ "dependsOn": [
123
+ "report"
124
+ ],
125
+ "description": "Custom file path for downloaded Allure report (default: ./report.html)",
126
+ "name": "allure-path",
127
+ "hasDynamicHelp": false,
128
+ "multiple": false,
129
+ "type": "option"
130
+ },
131
+ "html-path": {
132
+ "dependsOn": [
133
+ "report"
134
+ ],
135
+ "description": "Custom file path for downloaded HTML report (default: ./report.html)",
136
+ "name": "html-path",
137
+ "hasDynamicHelp": false,
138
+ "multiple": false,
139
+ "type": "option"
140
+ },
137
141
  "async": {
138
142
  "description": "Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)",
139
143
  "name": "async",
@@ -263,7 +267,8 @@
263
267
  "options": [
264
268
  "18",
265
269
  "17",
266
- "16"
270
+ "16",
271
+ "26"
267
272
  ],
268
273
  "type": "option"
269
274
  },
@@ -318,6 +323,7 @@
318
323
  },
319
324
  "mitmHost": {
320
325
  "description": "used for mitmproxy support, enterprise only, contact support if interested",
326
+ "hidden": true,
321
327
  "name": "mitmHost",
322
328
  "hasDynamicHelp": false,
323
329
  "multiple": false,
@@ -328,6 +334,7 @@
328
334
  "mitmHost"
329
335
  ],
330
336
  "description": "used for mitmproxy support, enterprise only, contact support if interested",
337
+ "hidden": true,
331
338
  "name": "mitmPath",
332
339
  "hasDynamicHelp": false,
333
340
  "multiple": false,
@@ -355,9 +362,7 @@
355
362
  "multiple": false,
356
363
  "options": [
357
364
  "0",
358
- "90",
359
- "180",
360
- "270"
365
+ "90"
361
366
  ],
362
367
  "type": "option"
363
368
  },
@@ -372,11 +377,12 @@
372
377
  "aliases": [
373
378
  "format"
374
379
  ],
375
- "description": "Runs Maestro with the --format flag, this will generate a report in the specified format",
380
+ "description": "Generate and download test reports in the specified format. Use \"allure\" for a complete HTML report.",
376
381
  "name": "report",
377
382
  "hasDynamicHelp": false,
378
383
  "multiple": false,
379
384
  "options": [
385
+ "allure",
380
386
  "junit",
381
387
  "html"
382
388
  ],
@@ -414,12 +420,6 @@
414
420
  "name": "skip-chrome-onboarding",
415
421
  "allowNo": false,
416
422
  "type": "boolean"
417
- },
418
- "x86-arch": {
419
- "description": "[iOS only, experimental] Run your flow against x86 architecture simulator instead of arm64",
420
- "name": "x86-arch",
421
- "allowNo": false,
422
- "type": "boolean"
423
423
  }
424
424
  },
425
425
  "hasDynamicHelp": false,
@@ -579,5 +579,5 @@
579
579
  ]
580
580
  }
581
581
  },
582
- "version": "4.0.4"
582
+ "version": "4.1.1"
583
583
  }
package/package.json CHANGED
@@ -72,7 +72,7 @@
72
72
  "type": "git",
73
73
  "url": "https://devicecloud.dev"
74
74
  },
75
- "version": "4.0.4",
75
+ "version": "4.1.1",
76
76
  "bugs": {
77
77
  "url": "https://discord.gg/gm3mJwcNw8"
78
78
  },
@@ -89,7 +89,7 @@
89
89
  "dcd": "./bin/dev.js",
90
90
  "prod": "./bin/run.js",
91
91
  "build": "shx rm -rf dist && tsc -b",
92
- "lint": "NODE_OPTIONS='--no-deprecation' eslint . --ext .ts",
92
+ "lint": "eslint . --ext .ts",
93
93
  "version": "oclif readme && git add README.md",
94
94
  "test": "node scripts/test-runner.mjs"
95
95
  }