@devicecloud.dev/dcd 3.7.12-beta.1 → 4.0.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.
- package/dist/commands/cloud.d.ts +36 -42
- package/dist/commands/cloud.js +110 -55
- package/dist/commands/status.d.ts +13 -13
- package/dist/commands/status.js +66 -58
- package/dist/commands/upload.d.ts +3 -3
- package/dist/commands/upload.js +2 -2
- package/dist/constants.d.ts +33 -27
- package/dist/constants.js +76 -54
- package/dist/gateways/api-gateway.d.ts +35 -0
- package/dist/gateways/{ApiGateway.js → api-gateway.js} +65 -60
- package/dist/gateways/{SupabaseGateway.js → supabase-gateway.js} +1 -1
- package/dist/methods.d.ts +2 -2
- package/dist/methods.js +32 -24
- package/dist/plan.d.ts +2 -1
- package/dist/plan.js +25 -7
- package/dist/planMethods.d.ts +0 -3
- package/dist/planMethods.js +1 -1
- package/dist/types/device.types.d.ts +6 -6
- package/dist/types/device.types.js +6 -6
- package/dist/types/schema.types.d.ts +669 -0
- package/dist/types/schema.types.js +6 -0
- package/dist/utils/compatibility.d.ts +1 -1
- package/dist/utils/compatibility.js +2 -2
- package/oclif.manifest.json +110 -91
- package/package.json +18 -17
- package/dist/gateways/ApiGateway.d.ts +0 -35
- /package/dist/gateways/{SupabaseGateway.d.ts → supabase-gateway.d.ts} +0 -0
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -2,68 +2,62 @@ import { Command } from '@oclif/core';
|
|
|
2
2
|
export declare const mimeTypeLookupByExtension: Record<string, string>;
|
|
3
3
|
export default class Cloud extends Command {
|
|
4
4
|
static args: {
|
|
5
|
-
firstFile: import("@oclif/core/lib/interfaces").Arg<string
|
|
6
|
-
secondFile: import("@oclif/core/lib/interfaces").Arg<string
|
|
5
|
+
firstFile: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
6
|
+
secondFile: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
7
7
|
};
|
|
8
8
|
static description: string;
|
|
9
|
+
static enableJsonFlag: boolean;
|
|
9
10
|
static examples: string[];
|
|
10
11
|
static flags: {
|
|
11
12
|
'additional-app-binary-ids': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
13
|
'additional-app-files': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
|
-
'android-api-level': import("@oclif/core/lib/interfaces").OptionFlag<string
|
|
14
|
-
'android-device': import("@oclif/core/lib/interfaces").OptionFlag<string
|
|
15
|
-
|
|
16
|
-
'show-crosshairs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
17
|
-
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
|
+
'android-api-level': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
|
+
'android-device': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
|
+
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
17
|
apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
|
-
'app-binary-id': import("@oclif/core/lib/interfaces").OptionFlag<string
|
|
20
|
-
'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string
|
|
18
|
+
'app-binary-id': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
|
+
'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
+
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
21
|
async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
22
|
-
config: import("@oclif/core/lib/interfaces").OptionFlag<string
|
|
23
|
-
'device-locale': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
|
-
'download-artifacts': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
|
-
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
22
|
+
config: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
26
23
|
debug: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
27
|
-
|
|
24
|
+
'device-locale': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
|
+
'download-artifacts': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
26
|
+
'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
27
|
+
env: import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
28
28
|
'exclude-flows': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
29
29
|
'exclude-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
30
|
-
flows: import("@oclif/core/lib/interfaces").OptionFlag<string
|
|
30
|
+
flows: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
31
31
|
'google-play': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
32
|
-
'include-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
33
32
|
'ignore-sha-check': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
34
|
-
'
|
|
35
|
-
'ios-
|
|
36
|
-
'
|
|
33
|
+
'include-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
34
|
+
'ios-device': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
35
|
+
'ios-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
36
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
37
|
+
'json-file': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
38
|
+
'json-file-name': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
37
39
|
'maestro-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
metadata: import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
41
|
+
mitmHost: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
42
|
+
mitmPath: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
43
|
+
'moropo-v1-api-key': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
44
|
+
name: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
45
|
+
orientation: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
42
46
|
quiet: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
43
|
-
|
|
47
|
+
report: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
48
|
+
retry: import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
44
49
|
'runner-type': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
'
|
|
48
|
-
'moropo-v1-api-key': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
49
|
-
'dry-run': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
50
|
+
'show-crosshairs': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
51
|
+
'skip-chrome-onboarding': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
52
|
+
'x86-arch': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
50
53
|
};
|
|
51
|
-
static enableJsonFlag: boolean;
|
|
52
54
|
private versionCheck;
|
|
55
|
+
run(): Promise<any>;
|
|
53
56
|
/**
|
|
54
|
-
* Generate the JSON output file path based on
|
|
55
|
-
* @param
|
|
56
|
-
* @param
|
|
57
|
+
* Generate the JSON output file path based on upload ID or custom filename
|
|
58
|
+
* @param uploadId - Upload ID to use if custom filename is not provided
|
|
59
|
+
* @param jsonFileName - Optional custom filename (can include relative path)
|
|
57
60
|
* @returns Path to the JSON output file
|
|
58
61
|
*/
|
|
59
62
|
private getJsonOutputPath;
|
|
60
|
-
run(): Promise<{
|
|
61
|
-
uploadId: string;
|
|
62
|
-
consoleUrl: string;
|
|
63
|
-
status: string;
|
|
64
|
-
tests: {
|
|
65
|
-
name: string;
|
|
66
|
-
status: string;
|
|
67
|
-
}[];
|
|
68
|
-
} | undefined>;
|
|
69
63
|
}
|
package/dist/commands/cloud.js
CHANGED
|
@@ -5,15 +5,15 @@ exports.mimeTypeLookupByExtension = void 0;
|
|
|
5
5
|
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
|
-
const path = require("node:path");
|
|
9
8
|
const fs = require("node:fs");
|
|
10
|
-
const os = require("os");
|
|
11
|
-
const
|
|
9
|
+
const os = require("node:os");
|
|
10
|
+
const path = require("node:path");
|
|
11
|
+
const streamZip = require("node-stream-zip");
|
|
12
12
|
const constants_1 = require("../constants");
|
|
13
|
-
const
|
|
13
|
+
const api_gateway_1 = require("../gateways/api-gateway");
|
|
14
14
|
const methods_1 = require("../methods");
|
|
15
15
|
const plan_1 = require("../plan");
|
|
16
|
-
const
|
|
16
|
+
const compatibility_1 = require("../utils/compatibility");
|
|
17
17
|
exports.mimeTypeLookupByExtension = {
|
|
18
18
|
apk: 'application/vnd.android.package-archive',
|
|
19
19
|
yaml: 'application/x-yaml',
|
|
@@ -24,13 +24,9 @@ process.removeAllListeners('warning');
|
|
|
24
24
|
process.on('warning', (warning) => {
|
|
25
25
|
if (warning.name === 'DeprecationWarning' &&
|
|
26
26
|
warning.message.includes('punycode')) {
|
|
27
|
-
|
|
27
|
+
// Ignore punycode deprecation warnings
|
|
28
28
|
}
|
|
29
29
|
});
|
|
30
|
-
const escapeShellValue = (value) => {
|
|
31
|
-
// Escape special characters that could cause shell interpretation issues
|
|
32
|
-
return value.replace(/(["\\'$`!\s\[\]{}()&|;<>*?#^~])/g, '\\$1');
|
|
33
|
-
};
|
|
34
30
|
class Cloud extends core_1.Command {
|
|
35
31
|
static args = {
|
|
36
32
|
firstFile: core_1.Args.string({
|
|
@@ -45,9 +41,9 @@ class Cloud extends core_1.Command {
|
|
|
45
41
|
}),
|
|
46
42
|
};
|
|
47
43
|
static description = `Test a Flow or set of Flows on devicecloud.dev (https://devicecloud.dev)\nProvide your application file and a folder with Maestro flows to run them in parallel on multiple devices in devicecloud.dev\nThe command will block until all analyses have completed`;
|
|
44
|
+
static enableJsonFlag = true;
|
|
48
45
|
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
49
46
|
static flags = constants_1.flags;
|
|
50
|
-
static enableJsonFlag = true;
|
|
51
47
|
versionCheck = async () => {
|
|
52
48
|
const versionResponse = await fetch('https://registry.npmjs.org/@devicecloud.dev/dcd/latest');
|
|
53
49
|
const versionResponseJson = await versionResponse.json();
|
|
@@ -61,15 +57,6 @@ class Cloud extends core_1.Command {
|
|
|
61
57
|
`);
|
|
62
58
|
}
|
|
63
59
|
};
|
|
64
|
-
/**
|
|
65
|
-
* Generate the JSON output file path based on name or upload ID
|
|
66
|
-
* @param name - Optional custom name for the file
|
|
67
|
-
* @param uploadId - Upload ID to use if name is not provided
|
|
68
|
-
* @returns Path to the JSON output file
|
|
69
|
-
*/
|
|
70
|
-
getJsonOutputPath(name, uploadId) {
|
|
71
|
-
return name ? `${name}_dcd.json` : `${uploadId}_dcd.json`;
|
|
72
|
-
}
|
|
73
60
|
async run() {
|
|
74
61
|
let output = null;
|
|
75
62
|
// Store debug flag outside try/catch to access it in catch block
|
|
@@ -77,7 +64,9 @@ class Cloud extends core_1.Command {
|
|
|
77
64
|
let jsonFile = false;
|
|
78
65
|
try {
|
|
79
66
|
const { args, flags, raw } = await this.parse(Cloud);
|
|
80
|
-
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, '
|
|
67
|
+
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, '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;
|
|
68
|
+
// Resolve "latest" maestro version to actual version
|
|
69
|
+
const resolvedMaestroVersion = (0, constants_1.resolveMaestroVersion)(maestroVersion);
|
|
81
70
|
// Store debug flag for use in catch block
|
|
82
71
|
debugFlag = debug === true;
|
|
83
72
|
jsonFile = flags['json-file'] === true;
|
|
@@ -134,7 +123,9 @@ class Cloud extends core_1.Command {
|
|
|
134
123
|
throw new Error(`Failed to download Moropo tests: ${response.statusText}`);
|
|
135
124
|
}
|
|
136
125
|
const contentLength = response.headers.get('content-length');
|
|
137
|
-
const totalSize = contentLength
|
|
126
|
+
const totalSize = contentLength
|
|
127
|
+
? Number.parseInt(contentLength, 10)
|
|
128
|
+
: 0;
|
|
138
129
|
let downloadedSize = 0;
|
|
139
130
|
const moropoDir = path.join(os.tmpdir(), `moropo-tests-${Date.now()}`);
|
|
140
131
|
if (debug) {
|
|
@@ -151,23 +142,29 @@ class Cloud extends core_1.Command {
|
|
|
151
142
|
if (!reader) {
|
|
152
143
|
throw new Error('Failed to get response reader');
|
|
153
144
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
break;
|
|
145
|
+
let readerResult = await reader.read();
|
|
146
|
+
while (!readerResult.done) {
|
|
147
|
+
const { value } = readerResult;
|
|
158
148
|
downloadedSize += value.length;
|
|
159
149
|
if (!quiet && !json && totalSize) {
|
|
160
150
|
const progress = Math.round((downloadedSize / totalSize) * 100);
|
|
161
151
|
core_1.ux.action.status = `Downloading: ${progress}%`;
|
|
162
152
|
}
|
|
163
153
|
fileStream.write(value);
|
|
154
|
+
readerResult = await reader.read();
|
|
164
155
|
}
|
|
165
156
|
fileStream.end();
|
|
166
|
-
await new Promise((resolve) =>
|
|
157
|
+
await new Promise((resolve) => {
|
|
158
|
+
fileStream.on('finish', () => {
|
|
159
|
+
resolve();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
167
162
|
if (!quiet && !json) {
|
|
168
163
|
core_1.ux.action.status = 'Extracting tests...';
|
|
169
164
|
}
|
|
170
165
|
// Extract zip file
|
|
166
|
+
const StreamZip = streamZip;
|
|
167
|
+
// eslint-disable-next-line new-cap
|
|
171
168
|
const zip = new StreamZip.async({ file: zipPath });
|
|
172
169
|
await zip.extract(null, moropoDir);
|
|
173
170
|
await zip.close();
|
|
@@ -258,12 +255,13 @@ class Cloud extends core_1.Command {
|
|
|
258
255
|
}
|
|
259
256
|
if (iOSVersion || iOSDevice) {
|
|
260
257
|
const iOSDeviceID = iOSDevice || 'iphone-14';
|
|
261
|
-
const supportediOSVersions = compatibilityData
|
|
258
|
+
const supportediOSVersions = compatibilityData?.ios?.[iOSDeviceID] || [];
|
|
262
259
|
const version = iOSVersion || '17';
|
|
263
260
|
if (supportediOSVersions.length === 0) {
|
|
264
261
|
throw new Error(`Device ${iOSDeviceID} is not supported. Please check the docs for supported devices: https://docs.devicecloud.dev/getting-started/devices-configuration`);
|
|
265
262
|
}
|
|
266
|
-
if (
|
|
263
|
+
if (Array.isArray(supportediOSVersions) &&
|
|
264
|
+
!supportediOSVersions.includes(version)) {
|
|
267
265
|
throw new Error(`${iOSDeviceID} only supports these iOS versions: ${supportediOSVersions.join(', ')}`);
|
|
268
266
|
}
|
|
269
267
|
if (debug) {
|
|
@@ -277,12 +275,13 @@ class Cloud extends core_1.Command {
|
|
|
277
275
|
const lookup = googlePlay
|
|
278
276
|
? compatibilityData.androidPlay
|
|
279
277
|
: compatibilityData.android;
|
|
280
|
-
const supportedAndroidVersions = lookup[androidDeviceID] || [];
|
|
278
|
+
const supportedAndroidVersions = lookup?.[androidDeviceID] || [];
|
|
281
279
|
const version = androidApiLevel || '34';
|
|
282
280
|
if (supportedAndroidVersions.length === 0) {
|
|
283
281
|
throw new Error(`We don't support that device configuration - please check the docs for supported devices: https://docs.devicecloud.dev/getting-started/devices-configuration`);
|
|
284
282
|
}
|
|
285
|
-
if (
|
|
283
|
+
if (Array.isArray(supportedAndroidVersions) &&
|
|
284
|
+
!supportedAndroidVersions.includes(version)) {
|
|
286
285
|
throw new Error(`${androidDeviceID} ${googlePlay ? '(Play Store) ' : ''}only supports these Android API levels: ${supportedAndroidVersions.join(', ')}`);
|
|
287
286
|
}
|
|
288
287
|
if (debug) {
|
|
@@ -321,7 +320,7 @@ class Cloud extends core_1.Command {
|
|
|
321
320
|
}
|
|
322
321
|
throw error;
|
|
323
322
|
}
|
|
324
|
-
const { allExcludeTags, allIncludeTags, flowsToRun: testFileNames,
|
|
323
|
+
const { allExcludeTags, allIncludeTags, flowMetadata, flowOverrides, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
|
|
325
324
|
if (debug) {
|
|
326
325
|
this.log(`DEBUG: All include tags: ${allIncludeTags?.join(', ') || 'none'}`);
|
|
327
326
|
this.log(`DEBUG: All exclude tags: ${allExcludeTags?.join(', ') || 'none'}`);
|
|
@@ -371,6 +370,23 @@ class Cloud extends core_1.Command {
|
|
|
371
370
|
flagLogs.push(`${k}: ${v}`);
|
|
372
371
|
}
|
|
373
372
|
}
|
|
373
|
+
// Format overrides information
|
|
374
|
+
const overridesEntries = Object.entries(flowOverrides);
|
|
375
|
+
const hasOverrides = overridesEntries.some(([, overrides]) => Object.keys(overrides).length > 0);
|
|
376
|
+
let overridesLog = '';
|
|
377
|
+
if (hasOverrides) {
|
|
378
|
+
overridesLog = '\n With overrides';
|
|
379
|
+
for (const [flowPath, overrides] of overridesEntries) {
|
|
380
|
+
if (Object.keys(overrides).length > 0) {
|
|
381
|
+
const relativePath = flowPath.replace(process.cwd(), '.');
|
|
382
|
+
overridesLog += `\n → ${relativePath}:`;
|
|
383
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
384
|
+
overridesLog += `\n ${key}: ${value}`;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
overridesLog += '\n';
|
|
389
|
+
}
|
|
374
390
|
this.log(`
|
|
375
391
|
|
|
376
392
|
Submitting new job
|
|
@@ -382,7 +398,7 @@ class Cloud extends core_1.Command {
|
|
|
382
398
|
|
|
383
399
|
With options
|
|
384
400
|
→ ${flagLogs.join(`
|
|
385
|
-
→ `)}
|
|
401
|
+
→ `)}${overridesLog}
|
|
386
402
|
|
|
387
403
|
`);
|
|
388
404
|
if (dryRun) {
|
|
@@ -437,9 +453,19 @@ class Cloud extends core_1.Command {
|
|
|
437
453
|
acc[key] = value.join('=');
|
|
438
454
|
return acc;
|
|
439
455
|
}, {});
|
|
456
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
457
|
+
const metadataObject = (metadata ?? []).reduce((acc, cur) => {
|
|
458
|
+
const [key, ...value] = cur.split('=');
|
|
459
|
+
// handle case where value includes an equals sign
|
|
460
|
+
acc[key] = value.join('=');
|
|
461
|
+
return acc;
|
|
462
|
+
}, {});
|
|
440
463
|
if (debug && Object.keys(envObject).length > 0) {
|
|
441
464
|
this.log(`DEBUG: Environment variables: ${JSON.stringify(envObject)}`);
|
|
442
465
|
}
|
|
466
|
+
if (debug && Object.keys(metadataObject).length > 0) {
|
|
467
|
+
this.log(`DEBUG: User metadata: ${JSON.stringify(metadataObject)}`);
|
|
468
|
+
}
|
|
443
469
|
if (debug) {
|
|
444
470
|
this.log(`DEBUG: Compressing files from path: ${flowFile}`);
|
|
445
471
|
}
|
|
@@ -459,12 +485,20 @@ class Cloud extends core_1.Command {
|
|
|
459
485
|
type: exports.mimeTypeLookupByExtension.zip,
|
|
460
486
|
});
|
|
461
487
|
testFormData.set('file', blob, 'flowFile.zip');
|
|
488
|
+
// finalBinaryId should always be defined after validation - fail fast if not
|
|
489
|
+
if (!finalBinaryId) {
|
|
490
|
+
throw new Error('Internal error: finalBinaryId should be defined after validation');
|
|
491
|
+
}
|
|
462
492
|
testFormData.set('appBinaryId', finalBinaryId);
|
|
463
493
|
testFormData.set('testFileNames', JSON.stringify(testFileNames.map((t) => t.replaceAll(commonRoot, '.').split(path.sep).join('/'))));
|
|
464
494
|
testFormData.set('flowMetadata', JSON.stringify(Object.fromEntries(Object.entries(flowMetadata).map(([key, value]) => [
|
|
465
495
|
key.replaceAll(commonRoot, '.').split(path.sep).join('/'),
|
|
466
496
|
value,
|
|
467
497
|
]))));
|
|
498
|
+
testFormData.set('testFileOverrides', JSON.stringify(Object.fromEntries(Object.entries(flowOverrides).map(([key, value]) => [
|
|
499
|
+
key.replaceAll(commonRoot, '.').split(path.sep).join('/'),
|
|
500
|
+
value,
|
|
501
|
+
]))));
|
|
468
502
|
testFormData.set('sequentialFlows', JSON.stringify(sequentialFlows.map((t) => t.replaceAll(commonRoot, '.').split(path.sep).join('/'))));
|
|
469
503
|
testFormData.set('env', JSON.stringify(envObject));
|
|
470
504
|
testFormData.set('googlePlay', googlePlay ? 'true' : 'false');
|
|
@@ -474,17 +508,17 @@ class Cloud extends core_1.Command {
|
|
|
474
508
|
autoRetriesRemaining: retry,
|
|
475
509
|
continueOnFailure,
|
|
476
510
|
deviceLocale,
|
|
477
|
-
maestroVersion,
|
|
511
|
+
maestroVersion: resolvedMaestroVersion,
|
|
512
|
+
mitmHost,
|
|
513
|
+
mitmPath,
|
|
478
514
|
orientation,
|
|
479
|
-
x86Arch,
|
|
480
|
-
report,
|
|
481
515
|
raw: JSON.stringify(raw),
|
|
516
|
+
report,
|
|
517
|
+
showCrosshairs: flags['show-crosshairs'],
|
|
518
|
+
skipChromeOnboarding: flags['skip-chrome-onboarding'],
|
|
482
519
|
uploadedBinaryIds,
|
|
483
520
|
version: this.config.version,
|
|
484
|
-
|
|
485
|
-
showCrosshairs: flags['show-crosshairs'],
|
|
486
|
-
mitmHost,
|
|
487
|
-
mitmPath,
|
|
521
|
+
x86Arch,
|
|
488
522
|
};
|
|
489
523
|
if (finalAdditionalBinaryIds?.length > 0) {
|
|
490
524
|
config.additionalAppBinaryIds = finalAdditionalBinaryIds;
|
|
@@ -493,6 +527,13 @@ class Cloud extends core_1.Command {
|
|
|
493
527
|
config.uploadedBinaryIds = uploadedBinaryIds;
|
|
494
528
|
}
|
|
495
529
|
testFormData.set('config', JSON.stringify(config));
|
|
530
|
+
if (Object.keys(metadataObject).length > 0) {
|
|
531
|
+
const metadataPayload = { userMetadata: metadataObject };
|
|
532
|
+
testFormData.set('metadata', JSON.stringify(metadataPayload));
|
|
533
|
+
if (debug) {
|
|
534
|
+
this.log(`DEBUG: Sending metadata to API: ${JSON.stringify(metadataPayload)}`);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
496
537
|
if (androidApiLevel)
|
|
497
538
|
testFormData.set('androidApiLevel', androidApiLevel.toString());
|
|
498
539
|
if (androidDevice)
|
|
@@ -515,7 +556,7 @@ class Cloud extends core_1.Command {
|
|
|
515
556
|
if (debug) {
|
|
516
557
|
this.log(`DEBUG: Submitting flow upload request to ${apiUrl}/uploads/flow`);
|
|
517
558
|
}
|
|
518
|
-
const { message, results } = await
|
|
559
|
+
const { message, results } = await api_gateway_1.ApiGateway.uploadFlow(apiUrl, apiKey, testFormData);
|
|
519
560
|
if (debug) {
|
|
520
561
|
this.log(`DEBUG: Flow upload response received`);
|
|
521
562
|
this.log(`DEBUG: Message: ${message}`);
|
|
@@ -539,16 +580,16 @@ class Cloud extends core_1.Command {
|
|
|
539
580
|
this.log(`DEBUG: Async flag is set, not waiting for results`);
|
|
540
581
|
}
|
|
541
582
|
const jsonOutput = {
|
|
542
|
-
uploadId: results[0].test_upload_id,
|
|
543
583
|
consoleUrl: url,
|
|
544
584
|
status: 'PENDING',
|
|
545
585
|
tests: results.map((r) => ({
|
|
546
586
|
name: r.test_file_name,
|
|
547
587
|
status: r.status,
|
|
548
588
|
})),
|
|
589
|
+
uploadId: results[0].test_upload_id,
|
|
549
590
|
};
|
|
550
591
|
if (flags['json-file']) {
|
|
551
|
-
const jsonFilePath = this.getJsonOutputPath(
|
|
592
|
+
const jsonFilePath = this.getJsonOutputPath(results[0].test_upload_id, jsonFileName);
|
|
552
593
|
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
553
594
|
}
|
|
554
595
|
if (json) {
|
|
@@ -574,7 +615,7 @@ class Cloud extends core_1.Command {
|
|
|
574
615
|
if (debug) {
|
|
575
616
|
this.log(`DEBUG: Polling for results: ${results[0].test_upload_id}`);
|
|
576
617
|
}
|
|
577
|
-
const { results: updatedResults } = await
|
|
618
|
+
const { results: updatedResults } = await api_gateway_1.ApiGateway.getResultsForUpload(apiUrl, apiKey, results[0].test_upload_id);
|
|
578
619
|
if (!updatedResults) {
|
|
579
620
|
throw new Error('no results');
|
|
580
621
|
}
|
|
@@ -606,10 +647,11 @@ class Cloud extends core_1.Command {
|
|
|
606
647
|
},
|
|
607
648
|
duration: {
|
|
608
649
|
get: (row) => row.duration_seconds
|
|
609
|
-
? (0, methods_1.formatDurationSeconds)(row.duration_seconds)
|
|
610
|
-
: '',
|
|
650
|
+
? (0, methods_1.formatDurationSeconds)(Number(row.duration_seconds))
|
|
651
|
+
: '-',
|
|
611
652
|
},
|
|
612
653
|
...(hasFailedTests && {
|
|
654
|
+
// eslint-disable-next-line camelcase
|
|
613
655
|
fail_reason: {
|
|
614
656
|
get: (row) => row.status === 'FAILED' && row.fail_reason
|
|
615
657
|
? row.fail_reason
|
|
@@ -628,7 +670,7 @@ class Cloud extends core_1.Command {
|
|
|
628
670
|
if (debug) {
|
|
629
671
|
this.log(`DEBUG: Downloading artifacts: ${downloadArtifacts}`);
|
|
630
672
|
}
|
|
631
|
-
await
|
|
673
|
+
await api_gateway_1.ApiGateway.downloadArtifactsZip(apiUrl, apiKey, results[0].test_upload_id, downloadArtifacts, artifactsPath);
|
|
632
674
|
this.log('\n');
|
|
633
675
|
this.log(`Test artifacts have been downloaded to ${artifactsPath || './artifacts.zip'}`);
|
|
634
676
|
}
|
|
@@ -649,20 +691,20 @@ class Cloud extends core_1.Command {
|
|
|
649
691
|
this.log(`DEBUG: Some tests failed, returning failed status`);
|
|
650
692
|
}
|
|
651
693
|
const jsonOutput = {
|
|
652
|
-
uploadId: results[0].test_upload_id,
|
|
653
694
|
consoleUrl: url,
|
|
654
695
|
status: 'FAILED',
|
|
655
696
|
tests: resultsWithoutEarlierTries.map((r) => ({
|
|
656
697
|
name: r.test_file_name,
|
|
657
698
|
status: r.status,
|
|
658
|
-
|
|
659
|
-
|
|
699
|
+
durationSeconds: r.duration_seconds,
|
|
700
|
+
failReason: r.status === 'FAILED'
|
|
660
701
|
? r.fail_reason || 'No reason provided'
|
|
661
702
|
: undefined,
|
|
662
703
|
})),
|
|
704
|
+
uploadId: results[0].test_upload_id,
|
|
663
705
|
};
|
|
664
706
|
if (flags['json-file']) {
|
|
665
|
-
const jsonFilePath = this.getJsonOutputPath(
|
|
707
|
+
const jsonFilePath = this.getJsonOutputPath(results[0].test_upload_id, jsonFileName);
|
|
666
708
|
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
667
709
|
}
|
|
668
710
|
if (json) {
|
|
@@ -675,20 +717,20 @@ class Cloud extends core_1.Command {
|
|
|
675
717
|
this.log(`DEBUG: All tests passed, returning success status`);
|
|
676
718
|
}
|
|
677
719
|
const jsonOutput = {
|
|
678
|
-
uploadId: results[0].test_upload_id,
|
|
679
720
|
consoleUrl: url,
|
|
680
721
|
status: 'PASSED',
|
|
681
722
|
tests: resultsWithoutEarlierTries.map((r) => ({
|
|
682
723
|
name: r.test_file_name,
|
|
683
724
|
status: r.status,
|
|
684
|
-
|
|
685
|
-
|
|
725
|
+
durationSeconds: r.duration_seconds,
|
|
726
|
+
failReason: r.status === 'FAILED'
|
|
686
727
|
? r.fail_reason || 'No reason provided'
|
|
687
728
|
: undefined,
|
|
688
729
|
})),
|
|
730
|
+
uploadId: results[0].test_upload_id,
|
|
689
731
|
};
|
|
690
732
|
if (flags['json-file']) {
|
|
691
|
-
const jsonFilePath = this.getJsonOutputPath(
|
|
733
|
+
const jsonFilePath = this.getJsonOutputPath(results[0].test_upload_id, jsonFileName);
|
|
692
734
|
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
693
735
|
}
|
|
694
736
|
if (json) {
|
|
@@ -733,9 +775,22 @@ class Cloud extends core_1.Command {
|
|
|
733
775
|
}
|
|
734
776
|
finally {
|
|
735
777
|
if (output) {
|
|
778
|
+
// eslint-disable-next-line no-unsafe-finally
|
|
736
779
|
return output;
|
|
737
780
|
}
|
|
738
781
|
}
|
|
739
782
|
}
|
|
783
|
+
/**
|
|
784
|
+
* Generate the JSON output file path based on upload ID or custom filename
|
|
785
|
+
* @param uploadId - Upload ID to use if custom filename is not provided
|
|
786
|
+
* @param jsonFileName - Optional custom filename (can include relative path)
|
|
787
|
+
* @returns Path to the JSON output file
|
|
788
|
+
*/
|
|
789
|
+
getJsonOutputPath(uploadId, jsonFileName) {
|
|
790
|
+
if (jsonFileName) {
|
|
791
|
+
return jsonFileName;
|
|
792
|
+
}
|
|
793
|
+
return `${uploadId}_dcd.json`;
|
|
794
|
+
}
|
|
740
795
|
}
|
|
741
796
|
exports.default = Cloud;
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
2
|
type StatusResponse = {
|
|
3
|
-
|
|
3
|
+
appBinaryId?: string;
|
|
4
|
+
attempts?: number;
|
|
5
|
+
consoleUrl?: string;
|
|
6
|
+
error?: string;
|
|
7
|
+
status: 'CANCELLED' | 'FAILED' | 'PASSED' | 'PENDING' | 'RUNNING';
|
|
4
8
|
tests: {
|
|
5
|
-
name: string;
|
|
6
|
-
status: 'FAILED' | 'PASSED' | 'CANCELLED' | 'PENDING' | 'RUNNING';
|
|
7
9
|
durationSeconds?: number;
|
|
8
10
|
failReason?: string;
|
|
11
|
+
name: string;
|
|
12
|
+
status: 'CANCELLED' | 'FAILED' | 'PASSED' | 'PENDING' | 'RUNNING';
|
|
9
13
|
}[];
|
|
10
14
|
uploadId?: string;
|
|
11
|
-
appBinaryId?: string;
|
|
12
|
-
consoleUrl?: string;
|
|
13
|
-
error?: string;
|
|
14
|
-
attempts?: number;
|
|
15
15
|
};
|
|
16
16
|
export default class Status extends Command {
|
|
17
17
|
static description: string;
|
|
18
|
-
static examples: string[];
|
|
19
18
|
static enableJsonFlag: boolean;
|
|
19
|
+
static examples: string[];
|
|
20
20
|
static flags: {
|
|
21
|
-
|
|
22
|
-
name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
|
-
'upload-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
|
-
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
|
+
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
22
|
apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
24
|
+
name: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
|
+
'upload-id': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
26
26
|
};
|
|
27
|
-
private getStatusSymbol;
|
|
28
27
|
run(): Promise<StatusResponse | void>;
|
|
28
|
+
private getStatusSymbol;
|
|
29
29
|
}
|
|
30
30
|
export {};
|