@devicecloud.dev/dcd 3.7.11 → 4.0.0
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 +88 -54
- 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.d.ts → supabase-gateway.d.ts} +2 -2
- package/dist/gateways/{SupabaseGateway.js → supabase-gateway.js} +7 -4
- package/dist/methods.d.ts +2 -2
- package/dist/methods.js +21 -22
- package/dist/plan.d.ts +1 -1
- package/dist/plan.js +5 -5
- 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/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, 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'}`);
|
|
@@ -437,9 +436,19 @@ class Cloud extends core_1.Command {
|
|
|
437
436
|
acc[key] = value.join('=');
|
|
438
437
|
return acc;
|
|
439
438
|
}, {});
|
|
439
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
440
|
+
const metadataObject = (metadata ?? []).reduce((acc, cur) => {
|
|
441
|
+
const [key, ...value] = cur.split('=');
|
|
442
|
+
// handle case where value includes an equals sign
|
|
443
|
+
acc[key] = value.join('=');
|
|
444
|
+
return acc;
|
|
445
|
+
}, {});
|
|
440
446
|
if (debug && Object.keys(envObject).length > 0) {
|
|
441
447
|
this.log(`DEBUG: Environment variables: ${JSON.stringify(envObject)}`);
|
|
442
448
|
}
|
|
449
|
+
if (debug && Object.keys(metadataObject).length > 0) {
|
|
450
|
+
this.log(`DEBUG: User metadata: ${JSON.stringify(metadataObject)}`);
|
|
451
|
+
}
|
|
443
452
|
if (debug) {
|
|
444
453
|
this.log(`DEBUG: Compressing files from path: ${flowFile}`);
|
|
445
454
|
}
|
|
@@ -459,6 +468,10 @@ class Cloud extends core_1.Command {
|
|
|
459
468
|
type: exports.mimeTypeLookupByExtension.zip,
|
|
460
469
|
});
|
|
461
470
|
testFormData.set('file', blob, 'flowFile.zip');
|
|
471
|
+
// finalBinaryId should always be defined after validation - fail fast if not
|
|
472
|
+
if (!finalBinaryId) {
|
|
473
|
+
throw new Error('Internal error: finalBinaryId should be defined after validation');
|
|
474
|
+
}
|
|
462
475
|
testFormData.set('appBinaryId', finalBinaryId);
|
|
463
476
|
testFormData.set('testFileNames', JSON.stringify(testFileNames.map((t) => t.replaceAll(commonRoot, '.').split(path.sep).join('/'))));
|
|
464
477
|
testFormData.set('flowMetadata', JSON.stringify(Object.fromEntries(Object.entries(flowMetadata).map(([key, value]) => [
|
|
@@ -474,17 +487,17 @@ class Cloud extends core_1.Command {
|
|
|
474
487
|
autoRetriesRemaining: retry,
|
|
475
488
|
continueOnFailure,
|
|
476
489
|
deviceLocale,
|
|
477
|
-
maestroVersion,
|
|
490
|
+
maestroVersion: resolvedMaestroVersion,
|
|
491
|
+
mitmHost,
|
|
492
|
+
mitmPath,
|
|
478
493
|
orientation,
|
|
479
|
-
x86Arch,
|
|
480
|
-
report,
|
|
481
494
|
raw: JSON.stringify(raw),
|
|
495
|
+
report,
|
|
496
|
+
showCrosshairs: flags['show-crosshairs'],
|
|
497
|
+
skipChromeOnboarding: flags['skip-chrome-onboarding'],
|
|
482
498
|
uploadedBinaryIds,
|
|
483
499
|
version: this.config.version,
|
|
484
|
-
|
|
485
|
-
showCrosshairs: flags['show-crosshairs'],
|
|
486
|
-
mitmHost,
|
|
487
|
-
mitmPath,
|
|
500
|
+
x86Arch,
|
|
488
501
|
};
|
|
489
502
|
if (finalAdditionalBinaryIds?.length > 0) {
|
|
490
503
|
config.additionalAppBinaryIds = finalAdditionalBinaryIds;
|
|
@@ -493,6 +506,13 @@ class Cloud extends core_1.Command {
|
|
|
493
506
|
config.uploadedBinaryIds = uploadedBinaryIds;
|
|
494
507
|
}
|
|
495
508
|
testFormData.set('config', JSON.stringify(config));
|
|
509
|
+
if (Object.keys(metadataObject).length > 0) {
|
|
510
|
+
const metadataPayload = { userMetadata: metadataObject };
|
|
511
|
+
testFormData.set('metadata', JSON.stringify(metadataPayload));
|
|
512
|
+
if (debug) {
|
|
513
|
+
this.log(`DEBUG: Sending metadata to API: ${JSON.stringify(metadataPayload)}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
496
516
|
if (androidApiLevel)
|
|
497
517
|
testFormData.set('androidApiLevel', androidApiLevel.toString());
|
|
498
518
|
if (androidDevice)
|
|
@@ -515,7 +535,7 @@ class Cloud extends core_1.Command {
|
|
|
515
535
|
if (debug) {
|
|
516
536
|
this.log(`DEBUG: Submitting flow upload request to ${apiUrl}/uploads/flow`);
|
|
517
537
|
}
|
|
518
|
-
const { message, results } = await
|
|
538
|
+
const { message, results } = await api_gateway_1.ApiGateway.uploadFlow(apiUrl, apiKey, testFormData);
|
|
519
539
|
if (debug) {
|
|
520
540
|
this.log(`DEBUG: Flow upload response received`);
|
|
521
541
|
this.log(`DEBUG: Message: ${message}`);
|
|
@@ -539,16 +559,16 @@ class Cloud extends core_1.Command {
|
|
|
539
559
|
this.log(`DEBUG: Async flag is set, not waiting for results`);
|
|
540
560
|
}
|
|
541
561
|
const jsonOutput = {
|
|
542
|
-
uploadId: results[0].test_upload_id,
|
|
543
562
|
consoleUrl: url,
|
|
544
563
|
status: 'PENDING',
|
|
545
564
|
tests: results.map((r) => ({
|
|
546
565
|
name: r.test_file_name,
|
|
547
566
|
status: r.status,
|
|
548
567
|
})),
|
|
568
|
+
uploadId: results[0].test_upload_id,
|
|
549
569
|
};
|
|
550
570
|
if (flags['json-file']) {
|
|
551
|
-
const jsonFilePath = this.getJsonOutputPath(
|
|
571
|
+
const jsonFilePath = this.getJsonOutputPath(results[0].test_upload_id, jsonFileName);
|
|
552
572
|
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
553
573
|
}
|
|
554
574
|
if (json) {
|
|
@@ -574,7 +594,7 @@ class Cloud extends core_1.Command {
|
|
|
574
594
|
if (debug) {
|
|
575
595
|
this.log(`DEBUG: Polling for results: ${results[0].test_upload_id}`);
|
|
576
596
|
}
|
|
577
|
-
const { results: updatedResults } = await
|
|
597
|
+
const { results: updatedResults } = await api_gateway_1.ApiGateway.getResultsForUpload(apiUrl, apiKey, results[0].test_upload_id);
|
|
578
598
|
if (!updatedResults) {
|
|
579
599
|
throw new Error('no results');
|
|
580
600
|
}
|
|
@@ -606,10 +626,11 @@ class Cloud extends core_1.Command {
|
|
|
606
626
|
},
|
|
607
627
|
duration: {
|
|
608
628
|
get: (row) => row.duration_seconds
|
|
609
|
-
? (0, methods_1.formatDurationSeconds)(row.duration_seconds)
|
|
610
|
-
: '',
|
|
629
|
+
? (0, methods_1.formatDurationSeconds)(Number(row.duration_seconds))
|
|
630
|
+
: '-',
|
|
611
631
|
},
|
|
612
632
|
...(hasFailedTests && {
|
|
633
|
+
// eslint-disable-next-line camelcase
|
|
613
634
|
fail_reason: {
|
|
614
635
|
get: (row) => row.status === 'FAILED' && row.fail_reason
|
|
615
636
|
? row.fail_reason
|
|
@@ -628,7 +649,7 @@ class Cloud extends core_1.Command {
|
|
|
628
649
|
if (debug) {
|
|
629
650
|
this.log(`DEBUG: Downloading artifacts: ${downloadArtifacts}`);
|
|
630
651
|
}
|
|
631
|
-
await
|
|
652
|
+
await api_gateway_1.ApiGateway.downloadArtifactsZip(apiUrl, apiKey, results[0].test_upload_id, downloadArtifacts, artifactsPath);
|
|
632
653
|
this.log('\n');
|
|
633
654
|
this.log(`Test artifacts have been downloaded to ${artifactsPath || './artifacts.zip'}`);
|
|
634
655
|
}
|
|
@@ -649,20 +670,20 @@ class Cloud extends core_1.Command {
|
|
|
649
670
|
this.log(`DEBUG: Some tests failed, returning failed status`);
|
|
650
671
|
}
|
|
651
672
|
const jsonOutput = {
|
|
652
|
-
uploadId: results[0].test_upload_id,
|
|
653
673
|
consoleUrl: url,
|
|
654
674
|
status: 'FAILED',
|
|
655
675
|
tests: resultsWithoutEarlierTries.map((r) => ({
|
|
656
676
|
name: r.test_file_name,
|
|
657
677
|
status: r.status,
|
|
658
|
-
|
|
659
|
-
|
|
678
|
+
durationSeconds: r.duration_seconds,
|
|
679
|
+
failReason: r.status === 'FAILED'
|
|
660
680
|
? r.fail_reason || 'No reason provided'
|
|
661
681
|
: undefined,
|
|
662
682
|
})),
|
|
683
|
+
uploadId: results[0].test_upload_id,
|
|
663
684
|
};
|
|
664
685
|
if (flags['json-file']) {
|
|
665
|
-
const jsonFilePath = this.getJsonOutputPath(
|
|
686
|
+
const jsonFilePath = this.getJsonOutputPath(results[0].test_upload_id, jsonFileName);
|
|
666
687
|
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
667
688
|
}
|
|
668
689
|
if (json) {
|
|
@@ -675,20 +696,20 @@ class Cloud extends core_1.Command {
|
|
|
675
696
|
this.log(`DEBUG: All tests passed, returning success status`);
|
|
676
697
|
}
|
|
677
698
|
const jsonOutput = {
|
|
678
|
-
uploadId: results[0].test_upload_id,
|
|
679
699
|
consoleUrl: url,
|
|
680
700
|
status: 'PASSED',
|
|
681
701
|
tests: resultsWithoutEarlierTries.map((r) => ({
|
|
682
702
|
name: r.test_file_name,
|
|
683
703
|
status: r.status,
|
|
684
|
-
|
|
685
|
-
|
|
704
|
+
durationSeconds: r.duration_seconds,
|
|
705
|
+
failReason: r.status === 'FAILED'
|
|
686
706
|
? r.fail_reason || 'No reason provided'
|
|
687
707
|
: undefined,
|
|
688
708
|
})),
|
|
709
|
+
uploadId: results[0].test_upload_id,
|
|
689
710
|
};
|
|
690
711
|
if (flags['json-file']) {
|
|
691
|
-
const jsonFilePath = this.getJsonOutputPath(
|
|
712
|
+
const jsonFilePath = this.getJsonOutputPath(results[0].test_upload_id, jsonFileName);
|
|
692
713
|
(0, methods_1.writeJSONFile)(jsonFilePath, jsonOutput, this);
|
|
693
714
|
}
|
|
694
715
|
if (json) {
|
|
@@ -733,9 +754,22 @@ class Cloud extends core_1.Command {
|
|
|
733
754
|
}
|
|
734
755
|
finally {
|
|
735
756
|
if (output) {
|
|
757
|
+
// eslint-disable-next-line no-unsafe-finally
|
|
736
758
|
return output;
|
|
737
759
|
}
|
|
738
760
|
}
|
|
739
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* Generate the JSON output file path based on upload ID or custom filename
|
|
764
|
+
* @param uploadId - Upload ID to use if custom filename is not provided
|
|
765
|
+
* @param jsonFileName - Optional custom filename (can include relative path)
|
|
766
|
+
* @returns Path to the JSON output file
|
|
767
|
+
*/
|
|
768
|
+
getJsonOutputPath(uploadId, jsonFileName) {
|
|
769
|
+
if (jsonFileName) {
|
|
770
|
+
return jsonFileName;
|
|
771
|
+
}
|
|
772
|
+
return `${uploadId}_dcd.json`;
|
|
773
|
+
}
|
|
740
774
|
}
|
|
741
775
|
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 {};
|