@devicecloud.dev/dcd 3.6.9 → 3.6.11
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 +2 -0
- package/dist/commands/cloud.js +111 -1
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +8 -0
- package/dist/methods.js +10 -1
- package/dist/plan.js +2 -1
- package/oclif.manifest.json +15 -1
- package/package.json +1 -1
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -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;
|
package/dist/commands/cloud.js
CHANGED
|
@@ -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');
|
package/dist/constants.d.ts
CHANGED
|
@@ -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/methods.js
CHANGED
|
@@ -155,9 +155,18 @@ const parseInfoPlist = async (buffer) => {
|
|
|
155
155
|
const extractAppMetadataIosZip = async (appFilePath) => new Promise((resolve, reject) => {
|
|
156
156
|
const zip = new StreamZip({ file: appFilePath });
|
|
157
157
|
zip.on('ready', () => {
|
|
158
|
-
|
|
158
|
+
// Get all entries and sort them by path depth
|
|
159
|
+
const entries = Object.values(zip.entries());
|
|
160
|
+
const sortedEntries = entries.sort((a, b) => {
|
|
161
|
+
const aDepth = a.name.split('/').length;
|
|
162
|
+
const bDepth = b.name.split('/').length;
|
|
163
|
+
return aDepth - bDepth;
|
|
164
|
+
});
|
|
165
|
+
// Find the first Info.plist in the shallowest directory
|
|
166
|
+
const infoPlist = sortedEntries.find((e) => e.name.endsWith('.app/Info.plist'));
|
|
159
167
|
if (!infoPlist) {
|
|
160
168
|
reject(new Error('Failed to find info plist'));
|
|
169
|
+
return;
|
|
161
170
|
}
|
|
162
171
|
const buffer = zip.entryDataSync(infoPlist.name);
|
|
163
172
|
parseInfoPlist(buffer)
|
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];
|
package/oclif.manifest.json
CHANGED
|
@@ -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.
|
|
554
|
+
"version": "3.6.11"
|
|
541
555
|
}
|