@devicecloud.dev/dcd 4.1.3 → 4.1.5

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.
@@ -1,4 +1,16 @@
1
1
  import { Command } from '@oclif/core';
2
+ /**
3
+ * Primary CLI command for executing tests on DeviceCloud.
4
+ * Orchestrates the complete test workflow:
5
+ * - Binary upload with SHA deduplication
6
+ * - Flow file analysis and dependency resolution
7
+ * - Device compatibility validation
8
+ * - Test submission with parallel execution
9
+ * - Real-time result polling with 5-second intervals
10
+ * - Artifact download (reports, videos, logs)
11
+ *
12
+ * Replaces `maestro cloud` with DeviceCloud-specific functionality.
13
+ */
2
14
  export default class Cloud extends Command {
3
15
  static args: {
4
16
  firstFile: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
@@ -50,12 +62,30 @@ export default class Cloud extends Command {
50
62
  apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
51
63
  apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
52
64
  };
65
+ /** Service for device/OS compatibility validation */
53
66
  private deviceValidationService;
67
+ /** Service for Moropo test framework integration */
54
68
  private moropoService;
69
+ /** Service for downloading test reports and artifacts */
55
70
  private reportDownloadService;
71
+ /** Service for polling test results with 5-second intervals */
56
72
  private resultsPollingService;
73
+ /** Service for submitting tests to the API */
57
74
  private testSubmissionService;
75
+ /**
76
+ * Check for CLI updates and notify user if outdated
77
+ * Compares current version with latest npm release
78
+ * @returns Promise that resolves when version check is complete
79
+ */
58
80
  private versionCheck;
81
+ /** Service for CLI version checking */
59
82
  private versionService;
83
+ /**
84
+ * Main command execution entry point
85
+ * Orchestrates the complete test submission and monitoring workflow
86
+ * @returns Promise that resolves when command execution is complete
87
+ * @throws RunFailedError if tests fail
88
+ * @throws Error for infrastructure or configuration errors
89
+ */
60
90
  run(): Promise<any>;
61
91
  }
@@ -23,6 +23,18 @@ process.on('warning', (warning) => {
23
23
  // Ignore punycode deprecation warnings
24
24
  }
25
25
  });
26
+ /**
27
+ * Primary CLI command for executing tests on DeviceCloud.
28
+ * Orchestrates the complete test workflow:
29
+ * - Binary upload with SHA deduplication
30
+ * - Flow file analysis and dependency resolution
31
+ * - Device compatibility validation
32
+ * - Test submission with parallel execution
33
+ * - Real-time result polling with 5-second intervals
34
+ * - Artifact download (reports, videos, logs)
35
+ *
36
+ * Replaces `maestro cloud` with DeviceCloud-specific functionality.
37
+ */
26
38
  class Cloud extends core_1.Command {
27
39
  static args = {
28
40
  firstFile: core_1.Args.string({
@@ -40,11 +52,21 @@ class Cloud extends core_1.Command {
40
52
  static enableJsonFlag = true;
41
53
  static examples = ['<%= config.bin %> <%= command.id %>'];
42
54
  static flags = constants_1.flags;
55
+ /** Service for device/OS compatibility validation */
43
56
  deviceValidationService = new device_validation_service_1.DeviceValidationService();
57
+ /** Service for Moropo test framework integration */
44
58
  moropoService = new moropo_service_1.MoropoService();
59
+ /** Service for downloading test reports and artifacts */
45
60
  reportDownloadService = new report_download_service_1.ReportDownloadService();
61
+ /** Service for polling test results with 5-second intervals */
46
62
  resultsPollingService = new results_polling_service_1.ResultsPollingService();
63
+ /** Service for submitting tests to the API */
47
64
  testSubmissionService = new test_submission_service_1.TestSubmissionService();
65
+ /**
66
+ * Check for CLI updates and notify user if outdated
67
+ * Compares current version with latest npm release
68
+ * @returns Promise that resolves when version check is complete
69
+ */
48
70
  versionCheck = async () => {
49
71
  const latestVersion = await this.versionService.checkLatestCliVersion();
50
72
  if (latestVersion &&
@@ -57,7 +79,15 @@ class Cloud extends core_1.Command {
57
79
  `);
58
80
  }
59
81
  };
82
+ /** Service for CLI version checking */
60
83
  versionService = new version_service_1.VersionService();
84
+ /**
85
+ * Main command execution entry point
86
+ * Orchestrates the complete test submission and monitoring workflow
87
+ * @returns Promise that resolves when command execution is complete
88
+ * @throws RunFailedError if tests fail
89
+ * @throws Error for infrastructure or configuration errors
90
+ */
61
91
  async run() {
62
92
  let output = null;
63
93
  // Store debug flag outside try/catch to access it in catch block
@@ -186,7 +216,14 @@ class Cloud extends core_1.Command {
186
216
  if (debug) {
187
217
  this.log('DEBUG: Generating execution plan...');
188
218
  }
189
- executionPlan = await (0, execution_plan_service_1.plan)(flowFile, includeTags.flat(), excludeTags.flat(), excludeFlows.flat(), configFile, debug);
219
+ executionPlan = await (0, execution_plan_service_1.plan)({
220
+ input: flowFile,
221
+ includeTags: includeTags.flat(),
222
+ excludeTags: excludeTags.flat(),
223
+ excludeFlows: excludeFlows.flat(),
224
+ configFile,
225
+ debug,
226
+ });
190
227
  if (debug) {
191
228
  this.log(`DEBUG: Execution plan generated`);
192
229
  this.log(`DEBUG: Total flow files: ${executionPlan.totalFlowFiles}`);
@@ -395,7 +432,7 @@ class Cloud extends core_1.Command {
395
432
  quiet,
396
433
  uploadId: results[0].test_upload_id,
397
434
  })
398
- .catch((error) => {
435
+ .catch(async (error) => {
399
436
  if (error instanceof results_polling_service_1.RunFailedError) {
400
437
  // Handle failed test run
401
438
  const jsonOutput = error.result;
@@ -406,11 +443,38 @@ class Cloud extends core_1.Command {
406
443
  if (json) {
407
444
  output = jsonOutput;
408
445
  }
446
+ // Download artifacts and reports even when tests fail
447
+ if (downloadArtifacts) {
448
+ await this.reportDownloadService.downloadArtifacts({
449
+ apiKey,
450
+ apiUrl,
451
+ artifactsPath,
452
+ debug,
453
+ downloadType: downloadArtifacts,
454
+ logger: this.log.bind(this),
455
+ uploadId: results[0].test_upload_id,
456
+ warnLogger: this.warn.bind(this),
457
+ });
458
+ }
459
+ if (report && ['allure', 'html', 'junit'].includes(report)) {
460
+ await this.reportDownloadService.downloadReports({
461
+ allurePath,
462
+ apiKey,
463
+ apiUrl,
464
+ debug,
465
+ htmlPath,
466
+ junitPath,
467
+ logger: this.log.bind(this),
468
+ reportType: report,
469
+ uploadId: results[0].test_upload_id,
470
+ warnLogger: this.warn.bind(this),
471
+ });
472
+ }
409
473
  throw new Error('RUN_FAILED');
410
474
  }
411
475
  throw error;
412
476
  });
413
- // Handle successful completion
477
+ // Handle successful completion - download artifacts and reports
414
478
  if (downloadArtifacts) {
415
479
  await this.reportDownloadService.downloadArtifacts({
416
480
  apiKey,
@@ -44,7 +44,7 @@ exports.executionFlags = {
44
44
  }),
45
45
  'runner-type': core_1.Flags.string({
46
46
  default: 'default',
47
- description: '[experimental] The type of runner to use - note: anything other than default will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information. gpu1 is Android-only and requires contacting support to enable, otherwise reverts to default.',
48
- options: ['default', 'm4', 'm1', 'gpu1'],
47
+ description: '[experimental] The type of runner to use - note: anything other than default will incur premium pricing tiers, see https://docs.devicecloud.dev/reference/runner-type for more information. gpu1 and cpu1 are Android-only and require contacting support to enable, otherwise reverts to default.',
48
+ options: ['default', 'm4', 'm1', 'gpu1', 'cpu1'],
49
49
  }),
50
50
  };
@@ -14,14 +14,14 @@ export declare const ApiGateway: {
14
14
  downloadArtifactsZip(baseUrl: string, apiKey: string, uploadId: string, results: "ALL" | "FAILED", artifactsPath?: string): Promise<void>;
15
15
  finaliseUpload(baseUrl: string, apiKey: string, id: string, metadata: TAppMetadata, path: string, sha: string): Promise<Record<string, never>>;
16
16
  getBinaryUploadUrl(baseUrl: string, apiKey: string, platform: "android" | "ios"): Promise<{
17
- id: string;
18
17
  message: string;
19
18
  path: string;
20
19
  token: string;
20
+ id: string;
21
21
  }>;
22
22
  getResultsForUpload(baseUrl: string, apiKey: string, uploadId: string): Promise<{
23
- results?: import("../types/generated/schema.types").components["schemas"]["TResultResponse"][];
24
23
  statusCode?: number;
24
+ results?: import("../types/generated/schema.types").components["schemas"]["TResultResponse"][];
25
25
  }>;
26
26
  getUploadStatus(baseUrl: string, apiKey: string, options: {
27
27
  name?: string;
@@ -95,7 +95,7 @@ exports.ApiGateway = {
95
95
  }
96
96
  }
97
97
  }
98
- const fileStream = createWriteStream(artifactsPath, { flags: 'wx' });
98
+ const fileStream = createWriteStream(artifactsPath, { flags: 'w' });
99
99
  await finished(Readable.fromWeb(res.body).pipe(fileStream));
100
100
  },
101
101
  // eslint-disable-next-line max-params
@@ -14,6 +14,7 @@ export declare class DeviceValidationService {
14
14
  * @param googlePlay Whether Google Play services are enabled
15
15
  * @param compatibilityData Compatibility data from API
16
16
  * @param options Validation options
17
+ * @returns void
17
18
  * @throws Error if device/API level combination is not supported
18
19
  */
19
20
  validateAndroidDevice(androidApiLevel: string | undefined, androidDevice: string | undefined, googlePlay: boolean, compatibilityData: CompatibilityData, options?: DeviceValidationOptions): void;
@@ -23,6 +24,7 @@ export declare class DeviceValidationService {
23
24
  * @param iOSDevice iOS device model to validate
24
25
  * @param compatibilityData Compatibility data from API
25
26
  * @param options Validation options
27
+ * @returns void
26
28
  * @throws Error if device/version combination is not supported
27
29
  */
28
30
  validateiOSDevice(iOSVersion: string | undefined, iOSDevice: string | undefined, compatibilityData: CompatibilityData, options?: DeviceValidationOptions): void;
@@ -12,6 +12,7 @@ class DeviceValidationService {
12
12
  * @param googlePlay Whether Google Play services are enabled
13
13
  * @param compatibilityData Compatibility data from API
14
14
  * @param options Validation options
15
+ * @returns void
15
16
  * @throws Error if device/API level combination is not supported
16
17
  */
17
18
  validateAndroidDevice(androidApiLevel, androidDevice, googlePlay, compatibilityData, options = {}) {
@@ -45,6 +46,7 @@ class DeviceValidationService {
45
46
  * @param iOSDevice iOS device model to validate
46
47
  * @param compatibilityData Compatibility data from API
47
48
  * @param options Validation options
49
+ * @returns void
48
50
  * @throws Error if device/version combination is not supported
49
51
  */
50
52
  validateiOSDevice(iOSVersion, iOSDevice, compatibilityData, options = {}) {
@@ -1,3 +1,4 @@
1
+ /** Email notification configuration */
1
2
  interface INotificationsConfig {
2
3
  email?: {
3
4
  enabled?: boolean;
@@ -5,6 +6,7 @@ interface INotificationsConfig {
5
6
  recipients?: string[];
6
7
  };
7
8
  }
9
+ /** Workspace configuration from config.yaml */
8
10
  interface IWorkspaceConfig {
9
11
  excludeTags?: null | string[];
10
12
  executionOrder?: IExecutionOrder | null;
@@ -13,13 +15,25 @@ interface IWorkspaceConfig {
13
15
  local?: ILocal | null;
14
16
  notifications?: INotificationsConfig;
15
17
  }
18
+ /** Local execution configuration */
16
19
  interface ILocal {
17
20
  deterministicOrder: boolean | null;
18
21
  }
22
+ /** Sequential execution configuration */
19
23
  interface IExecutionOrder {
20
24
  continueOnFailure: boolean;
21
25
  flowsOrder: string[];
22
26
  }
27
+ /** Options for execution plan generation */
28
+ export interface PlanOptions {
29
+ configFile?: string;
30
+ debug?: boolean;
31
+ excludeFlows?: string[];
32
+ excludeTags?: string[];
33
+ includeTags?: string[];
34
+ input: string;
35
+ }
36
+ /** Execution plan containing all flows to run with metadata and dependencies */
23
37
  export interface IExecutionPlan {
24
38
  allExcludeTags?: null | string[];
25
39
  allIncludeTags?: null | string[];
@@ -31,9 +45,26 @@ export interface IExecutionPlan {
31
45
  totalFlowFiles: number;
32
46
  workspaceConfig?: IWorkspaceConfig;
33
47
  }
48
+ /** Flow sequence configuration for ordered execution */
34
49
  interface IFlowSequence {
35
50
  continueOnFailure?: boolean;
36
51
  flows: string[];
37
52
  }
38
- export declare function plan(input: string, includeTags: string[], excludeTags: string[], excludeFlows?: string[], configFile?: string, debug?: boolean): Promise<IExecutionPlan>;
53
+ /**
54
+ * Generate execution plan for test flows
55
+ *
56
+ * Handles:
57
+ * - Single file or directory input
58
+ * - Workspace configuration (config.yaml)
59
+ * - Flow inclusion/exclusion patterns
60
+ * - Tag-based filtering (include/exclude)
61
+ * - Dependency resolution (runFlow, scripts, media)
62
+ * - Sequential execution ordering
63
+ * - DeviceCloud-specific overrides
64
+ *
65
+ * @param options - Plan generation options
66
+ * @returns Complete execution plan with flows, dependencies, and metadata
67
+ * @throws Error if input path doesn't exist, no flows found, or dependencies missing
68
+ */
69
+ export declare function plan(options: PlanOptions): Promise<IExecutionPlan>;
39
70
  export {};
@@ -5,6 +5,13 @@ const glob_1 = require("glob");
5
5
  const fs = require("node:fs");
6
6
  const path = require("node:path");
7
7
  const execution_plan_utils_1 = require("./execution-plan.utils");
8
+ /**
9
+ * Recursively check and resolve all dependencies for a flow file
10
+ * Includes runFlow references, JavaScript scripts, and media files
11
+ * @param input - Path to flow file to check
12
+ * @returns Array of all dependency file paths (deduplicated)
13
+ * @throws Error if any referenced files are missing
14
+ */
8
15
  async function checkDependencies(input) {
9
16
  const checkedDependencies = [];
10
17
  const uncheckedDependencies = [input];
@@ -35,12 +42,24 @@ async function checkDependencies(input) {
35
42
  }
36
43
  return checkedDependencies;
37
44
  }
45
+ /**
46
+ * Filter flow files based on exclude patterns
47
+ * @param unfilteredFlowFiles - All discovered flow files
48
+ * @param excludeFlows - Patterns to exclude
49
+ * @returns Filtered array of flow file paths
50
+ */
38
51
  function filterFlowFiles(unfilteredFlowFiles, excludeFlows) {
39
52
  if (excludeFlows) {
40
53
  return unfilteredFlowFiles.filter((file) => !excludeFlows.some((flow) => path.normalize(file).startsWith(path.normalize(path.resolve(flow)))));
41
54
  }
42
55
  return unfilteredFlowFiles;
43
56
  }
57
+ /**
58
+ * Load workspace configuration from config.yaml/yml if present
59
+ * @param input - Input directory path
60
+ * @param unfilteredFlowFiles - List of discovered flow files
61
+ * @returns Workspace configuration object (empty if no config file found)
62
+ */
44
63
  function getWorkspaceConfig(input, unfilteredFlowFiles) {
45
64
  const possibleConfigPaths = new Set([path.join(input, 'config.yaml'), path.join(input, 'config.yml')].map((p) => path.normalize(p)));
46
65
  const configFilePath = unfilteredFlowFiles.find((file) => possibleConfigPaths.has(path.normalize(file)));
@@ -49,6 +68,12 @@ function getWorkspaceConfig(input, unfilteredFlowFiles) {
49
68
  : {};
50
69
  return config;
51
70
  }
71
+ /**
72
+ * Extract DeviceCloud-specific override environment variables
73
+ * Looks for env vars prefixed with DEVICECLOUD_OVERRIDE_
74
+ * @param config - Flow configuration object
75
+ * @returns Object containing override key-value pairs
76
+ */
52
77
  function extractDeviceCloudOverrides(config) {
53
78
  if (!config || !config.env || typeof config.env !== 'object') {
54
79
  return {};
@@ -64,7 +89,24 @@ function extractDeviceCloudOverrides(config) {
64
89
  }
65
90
  return overrides;
66
91
  }
67
- async function plan(input, includeTags, excludeTags, excludeFlows, configFile, debug = false) {
92
+ /**
93
+ * Generate execution plan for test flows
94
+ *
95
+ * Handles:
96
+ * - Single file or directory input
97
+ * - Workspace configuration (config.yaml)
98
+ * - Flow inclusion/exclusion patterns
99
+ * - Tag-based filtering (include/exclude)
100
+ * - Dependency resolution (runFlow, scripts, media)
101
+ * - Sequential execution ordering
102
+ * - DeviceCloud-specific overrides
103
+ *
104
+ * @param options - Plan generation options
105
+ * @returns Complete execution plan with flows, dependencies, and metadata
106
+ * @throws Error if input path doesn't exist, no flows found, or dependencies missing
107
+ */
108
+ async function plan(options) {
109
+ const { input, includeTags = [], excludeTags = [], excludeFlows, configFile, debug = false, } = options;
68
110
  const normalizedInput = path.normalize(input);
69
111
  const flowMetadata = {};
70
112
  if (!fs.existsSync(normalizedInput)) {
@@ -17,4 +17,9 @@ export declare class MoropoService {
17
17
  * @returns Path to the extracted Moropo tests directory
18
18
  */
19
19
  downloadAndExtract(options: MoropoDownloadOptions): Promise<string>;
20
+ private createConfigFile;
21
+ private downloadZipFile;
22
+ private extractZipFile;
23
+ private logDebug;
24
+ private showProgress;
20
25
  }
@@ -18,10 +18,8 @@ class MoropoService {
18
18
  */
19
19
  async downloadAndExtract(options) {
20
20
  const { apiKey, branchName = 'main', debug = false, quiet = false, json = false, logger } = options;
21
- if (debug && logger) {
22
- logger('DEBUG: Moropo v1 API key detected, downloading tests from Moropo API');
23
- logger(`DEBUG: Using branch name: ${branchName}`);
24
- }
21
+ this.logDebug(debug, logger, 'DEBUG: Moropo v1 API key detected, downloading tests from Moropo API');
22
+ this.logDebug(debug, logger, `DEBUG: Using branch name: ${branchName}`);
25
23
  try {
26
24
  if (!quiet && !json) {
27
25
  core_1.ux.action.start('Downloading Moropo tests', 'Initializing', {
@@ -38,76 +36,83 @@ class MoropoService {
38
36
  if (!response.ok) {
39
37
  throw new Error(`Failed to download Moropo tests: ${response.statusText}`);
40
38
  }
41
- const contentLength = response.headers.get('content-length');
42
- const totalSize = contentLength
43
- ? Number.parseInt(contentLength, 10)
44
- : 0;
45
- let downloadedSize = 0;
46
39
  const moropoDir = path.join(os.tmpdir(), `moropo-tests-${Date.now()}`);
47
- if (debug && logger) {
48
- logger(`DEBUG: Extracting Moropo tests to: ${moropoDir}`);
49
- }
40
+ this.logDebug(debug, logger, `DEBUG: Extracting Moropo tests to: ${moropoDir}`);
50
41
  // Create moropo directory if it doesn't exist
51
42
  if (!fs.existsSync(moropoDir)) {
52
43
  fs.mkdirSync(moropoDir, { recursive: true });
53
44
  }
54
- // Write zip file to moropo directory
45
+ // Download zip file
55
46
  const zipPath = path.join(moropoDir, 'moropo-tests.zip');
56
- const fileStream = fs.createWriteStream(zipPath);
57
- const reader = response.body?.getReader();
58
- if (!reader) {
59
- throw new Error('Failed to get response reader');
60
- }
61
- let readerResult = await reader.read();
62
- while (!readerResult.done) {
63
- const { value } = readerResult;
64
- downloadedSize += value.length;
65
- if (!quiet && !json && totalSize) {
66
- const progress = Math.round((downloadedSize / totalSize) * 100);
67
- core_1.ux.action.status = `Downloading: ${progress}%`;
68
- }
69
- fileStream.write(value);
70
- readerResult = await reader.read();
71
- }
72
- fileStream.end();
73
- await new Promise((resolve) => {
74
- fileStream.on('finish', () => {
75
- resolve();
76
- });
77
- });
78
- if (!quiet && !json) {
79
- core_1.ux.action.status = 'Extracting tests...';
80
- }
47
+ await this.downloadZipFile(response, zipPath, { quiet, json });
48
+ this.showProgress(quiet, json, 'Extracting tests...');
81
49
  // Extract zip file
82
- // eslint-disable-next-line new-cap
83
- const zip = new StreamZip.async({ file: zipPath });
84
- await zip.extract(null, moropoDir);
85
- await zip.close();
86
- // Delete zip file after extraction
87
- fs.unlinkSync(zipPath);
50
+ await this.extractZipFile(zipPath, moropoDir);
88
51
  if (!quiet && !json) {
89
52
  core_1.ux.action.stop('completed');
90
53
  }
91
- if (debug && logger) {
92
- logger('DEBUG: Successfully extracted Moropo tests');
93
- }
54
+ this.logDebug(debug, logger, 'DEBUG: Successfully extracted Moropo tests');
94
55
  // Create config.yaml file
95
- const configPath = path.join(moropoDir, 'config.yaml');
96
- fs.writeFileSync(configPath, 'flows:\n- ./**/*.yaml\n- ./*.yaml\n');
97
- if (debug && logger) {
98
- logger('DEBUG: Created config.yaml file');
99
- }
56
+ this.createConfigFile(moropoDir);
57
+ this.logDebug(debug, logger, 'DEBUG: Created config.yaml file');
100
58
  return moropoDir;
101
59
  }
102
60
  catch (error) {
103
61
  if (!quiet && !json) {
104
62
  core_1.ux.action.stop('failed');
105
63
  }
106
- if (debug && logger) {
107
- logger(`DEBUG: Error downloading/extracting Moropo tests: ${error}`);
108
- }
64
+ this.logDebug(debug, logger, `DEBUG: Error downloading/extracting Moropo tests: ${error}`);
109
65
  throw new Error(`Failed to download/extract Moropo tests: ${error}`);
110
66
  }
111
67
  }
68
+ createConfigFile(moropoDir) {
69
+ const configPath = path.join(moropoDir, 'config.yaml');
70
+ fs.writeFileSync(configPath, 'flows:\n- ./**/*.yaml\n- ./*.yaml\n');
71
+ }
72
+ async downloadZipFile(response, zipPath, options) {
73
+ const { quiet, json } = options;
74
+ const contentLength = response.headers.get('content-length');
75
+ const totalSize = contentLength ? Number.parseInt(contentLength, 10) : 0;
76
+ let downloadedSize = 0;
77
+ const fileStream = fs.createWriteStream(zipPath);
78
+ const reader = response.body?.getReader();
79
+ if (!reader) {
80
+ throw new Error('Failed to get response reader');
81
+ }
82
+ let readerResult = await reader.read();
83
+ while (!readerResult.done) {
84
+ const { value } = readerResult;
85
+ downloadedSize += value.length;
86
+ if (!quiet && !json && totalSize) {
87
+ const progress = Math.round((downloadedSize / totalSize) * 100);
88
+ core_1.ux.action.status = `Downloading: ${progress}%`;
89
+ }
90
+ fileStream.write(value);
91
+ readerResult = await reader.read();
92
+ }
93
+ fileStream.end();
94
+ await new Promise((resolve) => {
95
+ fileStream.on('finish', () => {
96
+ resolve();
97
+ });
98
+ });
99
+ }
100
+ async extractZipFile(zipPath, extractPath) {
101
+ // eslint-disable-next-line new-cap
102
+ const zip = new StreamZip.async({ file: zipPath });
103
+ await zip.extract(null, extractPath);
104
+ await zip.close();
105
+ fs.unlinkSync(zipPath);
106
+ }
107
+ logDebug(debug, logger, message) {
108
+ if (debug && logger) {
109
+ logger(message);
110
+ }
111
+ }
112
+ showProgress(quiet, json, message) {
113
+ if (!quiet && !json) {
114
+ core_1.ux.action.status = message;
115
+ }
116
+ }
112
117
  }
113
118
  exports.MoropoService = MoropoService;
@@ -23,11 +23,13 @@ export declare class ReportDownloadService {
23
23
  /**
24
24
  * Download test artifacts as a zip file
25
25
  * @param options Download configuration
26
+ * @returns Promise that resolves when download is complete
26
27
  */
27
28
  downloadArtifacts(options: ArtifactsDownloadOptions): Promise<void>;
28
29
  /**
29
30
  * Handle downloading reports based on the report type specified
30
31
  * @param options Report download configuration
32
+ * @returns Promise that resolves when download is complete
31
33
  */
32
34
  downloadReports(options: ReportDownloadOptions): Promise<void>;
33
35
  /**
@@ -35,6 +37,7 @@ export declare class ReportDownloadService {
35
37
  * @param type Report type to download
36
38
  * @param filePath Path where report should be saved
37
39
  * @param options Download configuration
40
+ * @returns Promise that resolves when download is complete
38
41
  */
39
42
  private downloadReport;
40
43
  }
@@ -10,6 +10,7 @@ class ReportDownloadService {
10
10
  /**
11
11
  * Download test artifacts as a zip file
12
12
  * @param options Download configuration
13
+ * @returns Promise that resolves when download is complete
13
14
  */
14
15
  async downloadArtifacts(options) {
15
16
  const { apiUrl, apiKey, uploadId, downloadType, artifactsPath = './artifacts.zip', debug = false, logger, warnLogger, } = options;
@@ -35,6 +36,7 @@ class ReportDownloadService {
35
36
  /**
36
37
  * Handle downloading reports based on the report type specified
37
38
  * @param options Report download configuration
39
+ * @returns Promise that resolves when download is complete
38
40
  */
39
41
  async downloadReports(options) {
40
42
  const { reportType, junitPath, allurePath, htmlPath, warnLogger, ...downloadOptions } = options;
@@ -75,6 +77,7 @@ class ReportDownloadService {
75
77
  * @param type Report type to download
76
78
  * @param filePath Path where report should be saved
77
79
  * @param options Download configuration
80
+ * @returns Promise that resolves when download is complete
78
81
  */
79
82
  async downloadReport(type, filePath, options) {
80
83
  const { apiUrl, apiKey, uploadId, debug = false, logger, warnLogger } = options;
@@ -41,5 +41,11 @@ export declare class ResultsPollingService {
41
41
  * @returns Promise that resolves with final test results or rejects if tests fail
42
42
  */
43
43
  pollUntilComplete(results: TestResult[], options: PollingOptions): Promise<PollingResult>;
44
+ private buildPollingResult;
45
+ private calculateStatusSummary;
46
+ private displayFinalResults;
47
+ private filterLatestResults;
48
+ private handlePollingError;
49
+ private updateDisplayStatus;
44
50
  }
45
51
  export {};