@devicecloud.dev/dcd 3.6.9 → 3.6.10

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.
@@ -66,6 +66,8 @@ export default class Cloud extends Command {
66
66
  report: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
67
67
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
68
68
  'json-file': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
69
+ 'moropo-v1-api-key': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
70
+ 'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
69
71
  };
70
72
  static enableJsonFlag: boolean;
71
73
  private versionCheck;
@@ -6,6 +6,9 @@ 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
+ const os = require("os");
11
+ const StreamZip = require("node-stream-zip");
9
12
  const constants_1 = require("../constants");
10
13
  const methods_1 = require("../methods");
11
14
  const plan_1 = require("../plan");
@@ -96,7 +99,7 @@ class Cloud extends core_1.Command {
96
99
  let jsonFile = false;
97
100
  try {
98
101
  const { args, flags, raw } = await this.parse(Cloud);
99
- 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, mitmHost, mitmPath, ...rest } = flags;
102
+ 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, mitmHost, mitmPath, 'moropo-v1-api-key': moropoApiKey, 'dry-run': dryRun, ...rest } = flags;
100
103
  // Store debug flag for use in catch block
101
104
  debugFlag = debug === true;
102
105
  jsonFile = flags['json-file'] === true;
@@ -125,6 +128,96 @@ class Cloud extends core_1.Command {
125
128
  throw new Error(`You are using node version ${major}. DCD requires node version 18 or later`);
126
129
  }
127
130
  await this.versionCheck();
131
+ // Download and expand Moropo zip if API key is present
132
+ if (moropoApiKey) {
133
+ if (debug) {
134
+ this.log('DEBUG: Moropo v1 API key detected, downloading tests from Moropo API');
135
+ }
136
+ const branchName = process.env.GITHUB_REF_NAME || 'main';
137
+ if (debug) {
138
+ this.log(`DEBUG: Using branch name: ${branchName}`);
139
+ }
140
+ try {
141
+ if (!quiet && !json) {
142
+ core_1.ux.action.start('Downloading Moropo tests', 'Initializing', {
143
+ stdout: true,
144
+ });
145
+ }
146
+ const response = await fetch('https://api.moropo.com/tests', {
147
+ headers: {
148
+ accept: 'application/zip',
149
+ 'x-app-api-key': moropoApiKey,
150
+ 'x-branch-name': branchName,
151
+ },
152
+ });
153
+ if (!response.ok) {
154
+ throw new Error(`Failed to download Moropo tests: ${response.statusText}`);
155
+ }
156
+ const contentLength = response.headers.get('content-length');
157
+ const totalSize = contentLength ? parseInt(contentLength, 10) : 0;
158
+ let downloadedSize = 0;
159
+ const moropoDir = path.join(os.tmpdir(), `moropo-tests-${Date.now()}`);
160
+ if (debug) {
161
+ this.log(`DEBUG: Extracting Moropo tests to: ${moropoDir}`);
162
+ }
163
+ // Create moropo directory if it doesn't exist
164
+ if (!fs.existsSync(moropoDir)) {
165
+ fs.mkdirSync(moropoDir, { recursive: true });
166
+ }
167
+ // Write zip file to moropo directory
168
+ const zipPath = path.join(moropoDir, 'moropo-tests.zip');
169
+ const fileStream = fs.createWriteStream(zipPath);
170
+ const reader = response.body?.getReader();
171
+ if (!reader) {
172
+ throw new Error('Failed to get response reader');
173
+ }
174
+ while (true) {
175
+ const { done, value } = await reader.read();
176
+ if (done)
177
+ break;
178
+ downloadedSize += value.length;
179
+ if (!quiet && !json && totalSize) {
180
+ const progress = Math.round((downloadedSize / totalSize) * 100);
181
+ core_1.ux.action.status = `Downloading: ${progress}%`;
182
+ }
183
+ fileStream.write(value);
184
+ }
185
+ fileStream.end();
186
+ await new Promise((resolve) => fileStream.on('finish', resolve));
187
+ if (!quiet && !json) {
188
+ core_1.ux.action.status = 'Extracting tests...';
189
+ }
190
+ // Extract zip file
191
+ const zip = new StreamZip.async({ file: zipPath });
192
+ await zip.extract(null, moropoDir);
193
+ await zip.close();
194
+ // Delete zip file after extraction
195
+ fs.unlinkSync(zipPath);
196
+ if (!quiet && !json) {
197
+ core_1.ux.action.stop('completed');
198
+ }
199
+ if (debug) {
200
+ this.log('DEBUG: Successfully extracted Moropo tests');
201
+ }
202
+ // Create config.yaml file
203
+ const configPath = path.join(moropoDir, 'config.yaml');
204
+ fs.writeFileSync(configPath, 'flows:\n- ./**/*.yaml\n- ./*.yaml\n');
205
+ if (debug) {
206
+ this.log('DEBUG: Created config.yaml file');
207
+ }
208
+ // Update flows to point to the extracted directory
209
+ flows = moropoDir;
210
+ }
211
+ catch (error) {
212
+ if (!quiet && !json) {
213
+ core_1.ux.action.stop('failed');
214
+ }
215
+ if (debug) {
216
+ this.log(`DEBUG: Error downloading/extracting Moropo tests: ${error}`);
217
+ }
218
+ throw new Error(`Failed to download/extract Moropo tests: ${error}`);
219
+ }
220
+ }
128
221
  const apiKey = apiKeyFlag || process.env.DEVICE_CLOUD_API_KEY;
129
222
  if (!apiKey)
130
223
  throw new Error('You must provide an API key via --api-key flag or DEVICE_CLOUD_API_KEY environment variable');
@@ -300,6 +393,23 @@ class Cloud extends core_1.Command {
300
393
  → `)}
301
394
 
302
395
  `);
396
+ if (dryRun) {
397
+ this.log('\nDry run mode - no tests were actually triggered\n');
398
+ this.log('The following tests would have been run:');
399
+ this.log('─────────────────────────────────────────');
400
+ for (const test of testFileNames) {
401
+ this.log(`• ${test}`);
402
+ }
403
+ if (sequentialFlows.length > 0) {
404
+ this.log('\nSequential flows:');
405
+ this.log('────────────────');
406
+ for (const flow of sequentialFlows) {
407
+ this.log(`• ${flow}`);
408
+ }
409
+ }
410
+ this.log('\n');
411
+ return;
412
+ }
303
413
  if (!finalBinaryId) {
304
414
  if (!finalAppFile)
305
415
  throw new Error('You must provide either an app binary id or an app file');
@@ -36,6 +36,8 @@ export declare const flags: {
36
36
  report: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
37
37
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
38
38
  'json-file': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
39
+ 'moropo-v1-api-key': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
40
+ 'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
39
41
  };
40
42
  export declare const iOSCompatibilityLookup: {
41
43
  [k in EiOSDevices]: string[];
package/dist/constants.js CHANGED
@@ -200,6 +200,14 @@ exports.flags = {
200
200
  description: 'Write JSON output to a file with name <run_name>_dcd.json or <upload_id>_dcd.json if no name is provided - note: will always provide exit code 0',
201
201
  required: false,
202
202
  }),
203
+ 'moropo-v1-api-key': core_1.Flags.string({
204
+ description: 'API key for Moropo v1 integration',
205
+ required: false,
206
+ }),
207
+ 'dry-run': core_1.Flags.boolean({
208
+ description: 'Simulate the run without actually triggering the upload/test, useful for debugging workflow issues.',
209
+ default: false,
210
+ }),
203
211
  };
204
212
  exports.iOSCompatibilityLookup = {
205
213
  'ipad-pro-6th-gen': ['16', '17', '18'],
package/dist/plan.js CHANGED
@@ -118,7 +118,6 @@ async function plan(input, includeTags, excludeTags, excludeFlows, configFile) {
118
118
  acc[filePath] = config;
119
119
  return acc;
120
120
  }, {});
121
- const allFiles = await Promise.all(unfilteredFlowFiles.map((filePath) => checkDependencies(filePath))).then((results) => [...new Set(results.flat())]);
122
121
  const allIncludeTags = [
123
122
  ...includeTags,
124
123
  ...(workspaceConfig.includeTags || []),
@@ -138,6 +137,8 @@ async function plan(input, includeTags, excludeTags, excludeFlows, configFile) {
138
137
  if (allFlows.length === 0) {
139
138
  throw new Error(`Include / Exclude tags did not match any Flows:\n\nInclude Tags:\n${allIncludeTags.join('\n')}\n\nExclude Tags:\n${allExcludeTags.join('\n')}`);
140
139
  }
140
+ // Check dependencies only for the filtered flows
141
+ const allFiles = await Promise.all(allFlows.map((filePath) => checkDependencies(filePath))).then((results) => [...new Set(results.flat())]);
141
142
  // eslint-disable-next-line unicorn/no-array-reduce
142
143
  const pathsByName = allFlows.reduce((acc, filePath) => {
143
144
  const config = configPerFlowFile[filePath];
@@ -378,6 +378,20 @@
378
378
  "required": false,
379
379
  "allowNo": false,
380
380
  "type": "boolean"
381
+ },
382
+ "moropo-v1-api-key": {
383
+ "description": "API key for Moropo v1 integration",
384
+ "name": "moropo-v1-api-key",
385
+ "required": false,
386
+ "hasDynamicHelp": false,
387
+ "multiple": false,
388
+ "type": "option"
389
+ },
390
+ "dry-run": {
391
+ "description": "Simulate the run without actually triggering the upload/test, useful for debugging workflow issues.",
392
+ "name": "dry-run",
393
+ "allowNo": false,
394
+ "type": "boolean"
381
395
  }
382
396
  },
383
397
  "hasDynamicHelp": false,
@@ -537,5 +551,5 @@
537
551
  ]
538
552
  }
539
553
  },
540
- "version": "3.6.9"
554
+ "version": "3.6.10"
541
555
  }
package/package.json CHANGED
@@ -79,7 +79,7 @@
79
79
  "prepare": "yarn build",
80
80
  "version": "oclif readme && git add README.md"
81
81
  },
82
- "version": "3.6.9",
82
+ "version": "3.6.10",
83
83
  "bugs": {
84
84
  "url": "https://discord.gg/gm3mJwcNw8"
85
85
  },