@devicecloud.dev/dcd 3.4.4 → 3.5.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 +1 -0
- package/dist/commands/cloud.js +3 -3
- package/dist/commands/status.js +2 -1
- package/dist/commands/upload.d.ts +17 -0
- package/dist/commands/upload.js +57 -0
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +4 -0
- package/dist/methods.d.ts +1 -1
- package/dist/methods.js +22 -2
- package/oclif.manifest.json +78 -1
- package/package.json +1 -1
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -41,6 +41,7 @@ export default class Cloud extends Command {
|
|
|
41
41
|
async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
42
42
|
'device-locale': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
43
43
|
'download-artifacts': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
44
|
+
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
44
45
|
env: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
45
46
|
'exclude-flows': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
46
47
|
'exclude-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
package/dist/commands/cloud.js
CHANGED
|
@@ -80,7 +80,7 @@ class Cloud extends core_1.Command {
|
|
|
80
80
|
let output = null;
|
|
81
81
|
try {
|
|
82
82
|
const { args, flags, raw } = await this.parse(Cloud);
|
|
83
|
-
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, async, '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, ...rest } = flags;
|
|
83
|
+
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, '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, ...rest } = flags;
|
|
84
84
|
// If in JSON mode, temporarily intercept stdout to suppress the warning
|
|
85
85
|
if (json) {
|
|
86
86
|
const originalStdoutWrite = process.stdout.write;
|
|
@@ -375,9 +375,9 @@ class Cloud extends core_1.Command {
|
|
|
375
375
|
'content-type': 'application/json',
|
|
376
376
|
'x-app-api-key': apiKey,
|
|
377
377
|
},
|
|
378
|
-
});
|
|
378
|
+
}, artifactsPath);
|
|
379
379
|
this.log('\n');
|
|
380
|
-
this.log(
|
|
380
|
+
this.log(`Test artifacts have been downloaded to ${artifactsPath || './artifacts.zip'}`);
|
|
381
381
|
}
|
|
382
382
|
catch {
|
|
383
383
|
this.warn('Failed to download artifacts');
|
package/dist/commands/status.js
CHANGED
|
@@ -41,7 +41,8 @@ class Status extends core_1.Command {
|
|
|
41
41
|
}
|
|
42
42
|
async run() {
|
|
43
43
|
const { flags } = await this.parse(Status);
|
|
44
|
-
const { apiUrl, apiKey, name, 'upload-id': uploadId, json } = flags;
|
|
44
|
+
const { apiUrl, apiKey: apiKeyFlag, name, 'upload-id': uploadId, json, } = flags;
|
|
45
|
+
const apiKey = apiKeyFlag || process.env.DEVICE_CLOUD_API_KEY;
|
|
45
46
|
if (!apiKey) {
|
|
46
47
|
this.error('API Key is required. Please provide it via --api-key flag or DEVICE_CLOUD_API_KEY environment variable.');
|
|
47
48
|
return;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class Upload extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
appFile: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
10
|
+
apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
'ignore-sha-check': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
static enableJsonFlag: boolean;
|
|
14
|
+
run(): Promise<{
|
|
15
|
+
appBinaryId: string;
|
|
16
|
+
} | undefined>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const constants_1 = require("../constants");
|
|
5
|
+
const methods_1 = require("../methods");
|
|
6
|
+
class Upload extends core_1.Command {
|
|
7
|
+
static args = {
|
|
8
|
+
appFile: core_1.Args.string({
|
|
9
|
+
description: 'The binary file to upload (e.g. test.apk for android or test.app/.zip for ios)',
|
|
10
|
+
required: true,
|
|
11
|
+
name: 'App file',
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
static description = 'Upload an app binary to devicecloud.dev';
|
|
15
|
+
static examples = [
|
|
16
|
+
'<%= config.bin %> <%= command.id %> path/to/app.apk',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> path/to/app.zip --api-key YOUR_API_KEY',
|
|
18
|
+
];
|
|
19
|
+
static flags = {
|
|
20
|
+
apiKey: constants_1.flags.apiKey,
|
|
21
|
+
apiUrl: constants_1.flags.apiUrl,
|
|
22
|
+
'ignore-sha-check': constants_1.flags['ignore-sha-check'],
|
|
23
|
+
};
|
|
24
|
+
static enableJsonFlag = true;
|
|
25
|
+
async run() {
|
|
26
|
+
try {
|
|
27
|
+
const { args, flags } = await this.parse(Upload);
|
|
28
|
+
const { appFile } = args;
|
|
29
|
+
const { apiKey: apiKeyFlag, apiUrl, 'ignore-sha-check': ignoreShaCheck, json, } = flags;
|
|
30
|
+
const apiKey = apiKeyFlag || process.env.DEVICE_CLOUD_API_KEY;
|
|
31
|
+
if (!apiKey) {
|
|
32
|
+
throw new Error('You must provide an API key via --api-key flag or DEVICE_CLOUD_API_KEY environment variable');
|
|
33
|
+
}
|
|
34
|
+
if (!['apk', '.app', '.zip'].some((ext) => appFile.endsWith(ext))) {
|
|
35
|
+
throw new Error('App file must be a .apk for android or .app/.zip file for iOS');
|
|
36
|
+
}
|
|
37
|
+
if (appFile.endsWith('.zip')) {
|
|
38
|
+
await (0, methods_1.verifyAppZip)(appFile);
|
|
39
|
+
}
|
|
40
|
+
this.log(`
|
|
41
|
+
Uploading app binary
|
|
42
|
+
→ File: ${appFile}
|
|
43
|
+
`);
|
|
44
|
+
const appBinaryId = await (0, methods_1.uploadBinary)(appFile, apiUrl, apiKey, ignoreShaCheck, !json);
|
|
45
|
+
if (json) {
|
|
46
|
+
return { appBinaryId };
|
|
47
|
+
}
|
|
48
|
+
this.log(`\nUpload complete. Binary ID: ${appBinaryId}\n`);
|
|
49
|
+
this.log(`You can use this Binary ID in subsequent test runs with:`);
|
|
50
|
+
this.log(`dcd cloud --app-binary-id ${appBinaryId} path/to/flow.yaml\n`);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.error(error, { exit: 1 });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.default = Upload;
|
package/dist/constants.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export declare const flags: {
|
|
|
11
11
|
async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
12
|
'device-locale': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
13
|
'download-artifacts': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
|
+
'artifacts-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
15
|
env: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
16
|
'exclude-flows': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
16
17
|
'exclude-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
package/dist/constants.js
CHANGED
|
@@ -59,6 +59,10 @@ exports.flags = {
|
|
|
59
59
|
description: 'Download a zip containing the logs, screenshots and videos for each result in this run. You will debited a $0.01 egress fee for each result. Use --download-artifacts=FAILED for failures only or --download-artifacts=ALL for every result.',
|
|
60
60
|
options: ['ALL', 'FAILED'],
|
|
61
61
|
}),
|
|
62
|
+
'artifacts-path': core_1.Flags.string({
|
|
63
|
+
description: 'Custom file path for downloaded artifacts (default: ./artifacts.zip)',
|
|
64
|
+
dependsOn: ['download-artifacts'],
|
|
65
|
+
}),
|
|
62
66
|
env: core_1.Flags.file({
|
|
63
67
|
char: 'e',
|
|
64
68
|
description: 'One or more environment variables to inject into your flows',
|
package/dist/methods.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export declare const typeSafePost: <T extends keyof paths>(baseUrl: string, path
|
|
|
8
8
|
export declare const typeSafePostDownload: (baseUrl: string, path: string, init?: {
|
|
9
9
|
body?: BodyInit;
|
|
10
10
|
headers?: HeadersInit;
|
|
11
|
-
}) => Promise<void>;
|
|
11
|
+
}, artifactsPath?: string) => Promise<void>;
|
|
12
12
|
export declare const typeSafeGet: <T extends keyof paths>(baseUrl: string, path: string, init?: {
|
|
13
13
|
body?: FormData;
|
|
14
14
|
headers?: HeadersInit;
|
package/dist/methods.js
CHANGED
|
@@ -18,6 +18,8 @@ const StreamZip = require("node-stream-zip");
|
|
|
18
18
|
const plist_1 = require("plist");
|
|
19
19
|
const node_crypto_1 = require("node:crypto");
|
|
20
20
|
const path = require("path");
|
|
21
|
+
const node_path_1 = require("node:path");
|
|
22
|
+
const os = require("node:os");
|
|
21
23
|
const cloud_1 = require("./commands/cloud");
|
|
22
24
|
const PERMITTED_EXTENSIONS = new Set([
|
|
23
25
|
'yml',
|
|
@@ -40,7 +42,7 @@ const typeSafePost = async (baseUrl, path, init) => {
|
|
|
40
42
|
return res.json();
|
|
41
43
|
};
|
|
42
44
|
exports.typeSafePost = typeSafePost;
|
|
43
|
-
const typeSafePostDownload = async (baseUrl, path, init) => {
|
|
45
|
+
const typeSafePostDownload = async (baseUrl, path, init, artifactsPath) => {
|
|
44
46
|
const res = await fetch(baseUrl + path, {
|
|
45
47
|
...init,
|
|
46
48
|
method: 'POST',
|
|
@@ -48,7 +50,25 @@ const typeSafePostDownload = async (baseUrl, path, init) => {
|
|
|
48
50
|
if (!res.ok) {
|
|
49
51
|
throw new Error(await res.text());
|
|
50
52
|
}
|
|
51
|
-
|
|
53
|
+
let outputPath = artifactsPath || './artifacts.zip';
|
|
54
|
+
// Handle tilde expansion for home directory
|
|
55
|
+
if (outputPath.startsWith('~/') || outputPath === '~') {
|
|
56
|
+
outputPath = outputPath.replace(/^~(?=$|\/|\\)/, os.homedir());
|
|
57
|
+
}
|
|
58
|
+
// Create directory structure if it doesn't exist
|
|
59
|
+
const directory = (0, node_path_1.dirname)(outputPath);
|
|
60
|
+
if (directory !== '.') {
|
|
61
|
+
try {
|
|
62
|
+
(0, node_fs_1.mkdirSync)(directory, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
// Ignore if directory already exists
|
|
66
|
+
if (error.code !== 'EEXIST') {
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const fileStream = (0, node_fs_1.createWriteStream)(outputPath, { flags: 'wx' });
|
|
52
72
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
53
73
|
await (0, promises_2.finished)(node_stream_1.Readable.fromWeb(res.body).pipe(fileStream));
|
|
54
74
|
};
|
package/oclif.manifest.json
CHANGED
|
@@ -138,6 +138,16 @@
|
|
|
138
138
|
],
|
|
139
139
|
"type": "option"
|
|
140
140
|
},
|
|
141
|
+
"artifacts-path": {
|
|
142
|
+
"dependsOn": [
|
|
143
|
+
"download-artifacts"
|
|
144
|
+
],
|
|
145
|
+
"description": "Custom file path for downloaded artifacts (default: ./artifacts.zip)",
|
|
146
|
+
"name": "artifacts-path",
|
|
147
|
+
"hasDynamicHelp": false,
|
|
148
|
+
"multiple": false,
|
|
149
|
+
"type": "option"
|
|
150
|
+
},
|
|
141
151
|
"env": {
|
|
142
152
|
"char": "e",
|
|
143
153
|
"description": "One or more environment variables to inject into your flows",
|
|
@@ -414,7 +424,74 @@
|
|
|
414
424
|
"commands",
|
|
415
425
|
"status.js"
|
|
416
426
|
]
|
|
427
|
+
},
|
|
428
|
+
"upload": {
|
|
429
|
+
"aliases": [],
|
|
430
|
+
"args": {
|
|
431
|
+
"appFile": {
|
|
432
|
+
"description": "The binary file to upload (e.g. test.apk for android or test.app/.zip for ios)",
|
|
433
|
+
"name": "appFile",
|
|
434
|
+
"required": true
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
"description": "Upload an app binary to devicecloud.dev",
|
|
438
|
+
"examples": [
|
|
439
|
+
"<%= config.bin %> <%= command.id %> path/to/app.apk",
|
|
440
|
+
"<%= config.bin %> <%= command.id %> path/to/app.zip --api-key YOUR_API_KEY"
|
|
441
|
+
],
|
|
442
|
+
"flags": {
|
|
443
|
+
"json": {
|
|
444
|
+
"description": "Format output as json.",
|
|
445
|
+
"helpGroup": "GLOBAL",
|
|
446
|
+
"name": "json",
|
|
447
|
+
"allowNo": false,
|
|
448
|
+
"type": "boolean"
|
|
449
|
+
},
|
|
450
|
+
"apiKey": {
|
|
451
|
+
"aliases": [
|
|
452
|
+
"api-key"
|
|
453
|
+
],
|
|
454
|
+
"description": "API key for devicecloud.dev (find this in the console UI). You can also set the DEVICE_CLOUD_API_KEY environment variable.",
|
|
455
|
+
"name": "apiKey",
|
|
456
|
+
"hasDynamicHelp": false,
|
|
457
|
+
"multiple": false,
|
|
458
|
+
"type": "option"
|
|
459
|
+
},
|
|
460
|
+
"apiUrl": {
|
|
461
|
+
"aliases": [
|
|
462
|
+
"api-url",
|
|
463
|
+
"apiURL"
|
|
464
|
+
],
|
|
465
|
+
"description": "API base URL",
|
|
466
|
+
"hidden": true,
|
|
467
|
+
"name": "apiUrl",
|
|
468
|
+
"default": "https://api.devicecloud.dev",
|
|
469
|
+
"hasDynamicHelp": false,
|
|
470
|
+
"multiple": false,
|
|
471
|
+
"type": "option"
|
|
472
|
+
},
|
|
473
|
+
"ignore-sha-check": {
|
|
474
|
+
"description": "Ignore the sha hash check and upload the binary regardless of whether it already exists (not recommended)",
|
|
475
|
+
"name": "ignore-sha-check",
|
|
476
|
+
"allowNo": false,
|
|
477
|
+
"type": "boolean"
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
"hasDynamicHelp": false,
|
|
481
|
+
"hiddenAliases": [],
|
|
482
|
+
"id": "upload",
|
|
483
|
+
"pluginAlias": "@devicecloud.dev/dcd",
|
|
484
|
+
"pluginName": "@devicecloud.dev/dcd",
|
|
485
|
+
"pluginType": "core",
|
|
486
|
+
"strict": true,
|
|
487
|
+
"enableJsonFlag": true,
|
|
488
|
+
"isESM": false,
|
|
489
|
+
"relativePath": [
|
|
490
|
+
"dist",
|
|
491
|
+
"commands",
|
|
492
|
+
"upload.js"
|
|
493
|
+
]
|
|
417
494
|
}
|
|
418
495
|
},
|
|
419
|
-
"version": "3.
|
|
496
|
+
"version": "3.5.1"
|
|
420
497
|
}
|