@devicecloud.dev/dcd 3.1.1-alpha.1 → 3.2.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 +5 -4
- package/dist/constants.d.ts +1 -0
- package/dist/constants.js +5 -2
- package/dist/methods.d.ts +2 -2
- package/dist/methods.js +54 -24
- package/dist/plan.js +3 -1
- package/oclif.manifest.json +9 -3
- package/package.json +1 -1
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -47,6 +47,7 @@ export default class Cloud extends Command {
|
|
|
47
47
|
flows: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
48
48
|
'google-play': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
49
49
|
'include-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
50
|
+
'ignore-sha-check': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
50
51
|
'ios-device': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
51
52
|
'ios-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
52
53
|
'maestro-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
package/dist/commands/cloud.js
CHANGED
|
@@ -62,9 +62,10 @@ class Cloud extends core_1.Command {
|
|
|
62
62
|
}
|
|
63
63
|
await (0, methods_1.versionCheck)(this.config.version);
|
|
64
64
|
const { args, flags, raw } = await this.parse(Cloud);
|
|
65
|
-
const { 'additional-app-binary-ids': nonFlatAdditionalAppBinaryIds, 'additional-app-files': nonFlatAdditionalAppFiles, 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey, 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, 'ios-device': iOSDevice, 'ios-version': iOSVersion, 'maestro-version': maestroVersion, name, orientation, quiet, retry, report, ...rest } = flags;
|
|
65
|
+
const { '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, ...rest } = flags;
|
|
66
|
+
const apiKey = apiKeyFlag || process.env.DEVICE_CLOUD_API_KEY;
|
|
66
67
|
if (!apiKey)
|
|
67
|
-
throw new Error('You must provide an API key');
|
|
68
|
+
throw new Error('You must provide an API key via --api-key flag or DEVICE_CLOUD_API_KEY environment variable');
|
|
68
69
|
if (retry) {
|
|
69
70
|
if (retry < 0)
|
|
70
71
|
throw new Error('Retry must be a positive number');
|
|
@@ -173,12 +174,12 @@ class Cloud extends core_1.Command {
|
|
|
173
174
|
if (!finalBinaryId) {
|
|
174
175
|
if (!finalAppFile)
|
|
175
176
|
throw new Error('You must provide either an app binary id or an app file');
|
|
176
|
-
const binaryId = await (0, methods_1.uploadBinary)(finalAppFile, apiUrl, apiKey);
|
|
177
|
+
const binaryId = await (0, methods_1.uploadBinary)(finalAppFile, apiUrl, apiKey, ignoreShaCheck);
|
|
177
178
|
finalBinaryId = binaryId;
|
|
178
179
|
}
|
|
179
180
|
let uploadedBinaryIds = [];
|
|
180
181
|
if (additionalAppFiles?.length) {
|
|
181
|
-
uploadedBinaryIds = await (0, methods_1.uploadBinaries)(additionalAppFiles, apiUrl, apiKey);
|
|
182
|
+
uploadedBinaryIds = await (0, methods_1.uploadBinaries)(additionalAppFiles, apiUrl, apiKey, ignoreShaCheck);
|
|
182
183
|
finalAdditionalBinaryIds = [
|
|
183
184
|
...finalAdditionalBinaryIds,
|
|
184
185
|
...uploadedBinaryIds,
|
package/dist/constants.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export declare const flags: {
|
|
|
17
17
|
flows: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
18
|
'google-play': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
19
19
|
'include-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
|
+
'ignore-sha-check': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
20
21
|
'ios-device': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
21
22
|
'ios-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
22
23
|
'maestro-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
package/dist/constants.js
CHANGED
|
@@ -33,7 +33,7 @@ exports.flags = {
|
|
|
33
33
|
}),
|
|
34
34
|
apiKey: core_1.Flags.string({
|
|
35
35
|
aliases: ['api-key'],
|
|
36
|
-
description: 'API key',
|
|
36
|
+
description: 'API key for devicecloud.dev (find this in the console UI). You can also set the DEVICE_CLOUD_API_KEY environment variable.',
|
|
37
37
|
}),
|
|
38
38
|
apiUrl: core_1.Flags.string({
|
|
39
39
|
aliases: ['api-url', 'apiURL'],
|
|
@@ -56,7 +56,7 @@ exports.flags = {
|
|
|
56
56
|
description: 'Locale that will be set to a device, ISO-639-1 code and uppercase ISO-3166-1 code e.g. "de_DE" for Germany',
|
|
57
57
|
}),
|
|
58
58
|
'download-artifacts': core_1.Flags.string({
|
|
59
|
-
description: '
|
|
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
62
|
env: core_1.Flags.file({
|
|
@@ -96,6 +96,9 @@ exports.flags = {
|
|
|
96
96
|
multipleNonGreedy: true,
|
|
97
97
|
parse: (input) => input.split(','),
|
|
98
98
|
}),
|
|
99
|
+
'ignore-sha-check': core_1.Flags.boolean({
|
|
100
|
+
description: 'Ignore the sha hash check and upload the binary regardless of whether it already exists (not recommended)',
|
|
101
|
+
}),
|
|
99
102
|
'ios-device': core_1.Flags.string({
|
|
100
103
|
description: '[iOS only] iOS device to run your flow against',
|
|
101
104
|
options: [
|
package/dist/methods.d.ts
CHANGED
|
@@ -22,6 +22,6 @@ export declare const verifyAppZip: (zipPath: string) => Promise<void>;
|
|
|
22
22
|
export declare const extractAppMetadataAndroid: (appFilePath: string) => Promise<TAppMetadata>;
|
|
23
23
|
export declare const extractAppMetadataIosZip: (appFilePath: string) => Promise<TAppMetadata>;
|
|
24
24
|
export declare const extractAppMetadataIos: (appFolderPath: string) => Promise<TAppMetadata>;
|
|
25
|
-
export declare const uploadBinary: (filePath: string, apiUrl: string, apiKey: string) => Promise<string>;
|
|
26
|
-
export declare const uploadBinaries: (finalAppFiles: string[], apiUrl: string, apiKey: string) => Promise<string[]>;
|
|
25
|
+
export declare const uploadBinary: (filePath: string, apiUrl: string, apiKey: string, ignoreShaCheck?: boolean) => Promise<string>;
|
|
26
|
+
export declare const uploadBinaries: (finalAppFiles: string[], apiUrl: string, apiKey: string, ignoreShaCheck?: boolean) => Promise<string[]>;
|
|
27
27
|
export declare const verifyAdditionalAppFiles: (appFiles: string[] | undefined) => Promise<void>;
|
package/dist/methods.js
CHANGED
|
@@ -17,7 +17,6 @@ const promises_2 = require("node:stream/promises");
|
|
|
17
17
|
const StreamZip = require("node-stream-zip");
|
|
18
18
|
const plist_1 = require("plist");
|
|
19
19
|
const node_crypto_1 = require("node:crypto");
|
|
20
|
-
const node_fs_2 = require("node:fs");
|
|
21
20
|
const cloud_1 = require("./commands/cloud");
|
|
22
21
|
const PERMITTED_EXTENSIONS = new Set([
|
|
23
22
|
'yml',
|
|
@@ -207,19 +206,10 @@ const extractAppMetadataIos = async (appFolderPath) => {
|
|
|
207
206
|
return { appId, platform: 'ios' };
|
|
208
207
|
};
|
|
209
208
|
exports.extractAppMetadataIos = extractAppMetadataIos;
|
|
210
|
-
const uploadBinary = async (filePath, apiUrl, apiKey) => {
|
|
211
|
-
core_1.ux.action.start('
|
|
212
|
-
|
|
213
|
-
body: JSON.stringify({
|
|
214
|
-
platform: filePath?.endsWith('.apk') ? 'android' : 'ios',
|
|
215
|
-
}),
|
|
216
|
-
headers: {
|
|
217
|
-
'content-type': 'application/json',
|
|
218
|
-
'x-app-api-key': apiKey,
|
|
219
|
-
},
|
|
209
|
+
const uploadBinary = async (filePath, apiUrl, apiKey, ignoreShaCheck = false) => {
|
|
210
|
+
core_1.ux.action.start('Checking and uploading binary', 'Initializing', {
|
|
211
|
+
stdout: true,
|
|
220
212
|
});
|
|
221
|
-
if (!path)
|
|
222
|
-
throw new Error(message);
|
|
223
213
|
let file;
|
|
224
214
|
if (filePath?.endsWith('.app')) {
|
|
225
215
|
const zippedAppBlob = await (0, exports.compressFolderToBlob)(filePath);
|
|
@@ -233,14 +223,41 @@ const uploadBinary = async (filePath, apiUrl, apiKey) => {
|
|
|
233
223
|
}
|
|
234
224
|
let sha = undefined;
|
|
235
225
|
try {
|
|
236
|
-
sha = await
|
|
237
|
-
console.log('File hash', sha);
|
|
226
|
+
sha = await getFileHashFromFile(file);
|
|
238
227
|
}
|
|
239
228
|
catch (e) {
|
|
240
|
-
console.
|
|
241
|
-
|
|
242
|
-
|
|
229
|
+
console.warn('Warning: Failed to get file hash', e);
|
|
230
|
+
}
|
|
231
|
+
if (!ignoreShaCheck && sha) {
|
|
232
|
+
try {
|
|
233
|
+
const { appBinaryId, exists } = await (0, exports.typeSafePost)(apiUrl, '/uploads/checkForExistingUpload', {
|
|
234
|
+
body: JSON.stringify({ sha }),
|
|
235
|
+
headers: {
|
|
236
|
+
'content-type': 'application/json',
|
|
237
|
+
'x-app-api-key': apiKey,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
if (exists) {
|
|
241
|
+
core_1.ux.info(`sha hash matches existing binary with id: ${appBinaryId}, skipping upload. Force upload with --ignore-sha-check`);
|
|
242
|
+
core_1.ux.action.stop(`Skipping upload.`);
|
|
243
|
+
return appBinaryId;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
// ignore error
|
|
248
|
+
}
|
|
243
249
|
}
|
|
250
|
+
const { id, message, path, token } = await (0, exports.typeSafePost)(apiUrl, '/uploads/getBinaryUploadUrl', {
|
|
251
|
+
body: JSON.stringify({
|
|
252
|
+
platform: filePath?.endsWith('.apk') ? 'android' : 'ios',
|
|
253
|
+
}),
|
|
254
|
+
headers: {
|
|
255
|
+
'content-type': 'application/json',
|
|
256
|
+
'x-app-api-key': apiKey,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
if (!path)
|
|
260
|
+
throw new Error(message);
|
|
244
261
|
let metadata;
|
|
245
262
|
try {
|
|
246
263
|
metadata = filePath?.endsWith('.apk')
|
|
@@ -283,7 +300,7 @@ const uploadBinary = async (filePath, apiUrl, apiKey) => {
|
|
|
283
300
|
return id;
|
|
284
301
|
};
|
|
285
302
|
exports.uploadBinary = uploadBinary;
|
|
286
|
-
const uploadBinaries = async (finalAppFiles, apiUrl, apiKey) => Promise.all(finalAppFiles.map((f) => (0, exports.uploadBinary)(f, apiUrl, apiKey)));
|
|
303
|
+
const uploadBinaries = async (finalAppFiles, apiUrl, apiKey, ignoreShaCheck = false) => Promise.all(finalAppFiles.map((f) => (0, exports.uploadBinary)(f, apiUrl, apiKey, ignoreShaCheck)));
|
|
287
304
|
exports.uploadBinaries = uploadBinaries;
|
|
288
305
|
const verifyAdditionalAppFiles = async (appFiles) => {
|
|
289
306
|
if (appFiles?.length) {
|
|
@@ -298,12 +315,25 @@ const verifyAdditionalAppFiles = async (appFiles) => {
|
|
|
298
315
|
}
|
|
299
316
|
};
|
|
300
317
|
exports.verifyAdditionalAppFiles = verifyAdditionalAppFiles;
|
|
301
|
-
async function
|
|
318
|
+
async function getFileHashFromFile(file) {
|
|
302
319
|
return new Promise((resolve, reject) => {
|
|
303
320
|
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
304
|
-
const stream =
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
321
|
+
const stream = file.stream();
|
|
322
|
+
const reader = stream.getReader();
|
|
323
|
+
const processChunks = async () => {
|
|
324
|
+
try {
|
|
325
|
+
while (true) {
|
|
326
|
+
const { done, value } = await reader.read();
|
|
327
|
+
if (done)
|
|
328
|
+
break;
|
|
329
|
+
hash.update(value);
|
|
330
|
+
}
|
|
331
|
+
resolve(hash.digest('hex'));
|
|
332
|
+
}
|
|
333
|
+
catch (err) {
|
|
334
|
+
reject(err);
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
processChunks();
|
|
308
338
|
});
|
|
309
339
|
}
|
package/dist/plan.js
CHANGED
|
@@ -120,10 +120,12 @@ async function plan(input, includeTags, excludeTags, excludeFlows) {
|
|
|
120
120
|
// eslint-disable-next-line unicorn/no-array-reduce
|
|
121
121
|
const pathsByName = allFlows.reduce((acc, filePath) => {
|
|
122
122
|
const config = configPerFlowFile[filePath];
|
|
123
|
-
|
|
123
|
+
const name = config?.name || path.parse(filePath).name;
|
|
124
|
+
acc[name] = filePath;
|
|
124
125
|
return acc;
|
|
125
126
|
}, {});
|
|
126
127
|
const flowsToRunInSequence = workspaceConfig.executionOrder?.flowsOrder
|
|
128
|
+
?.map((flowOrder) => flowOrder.replace('.yaml', '').replace('.yml', '')) // support case where ext is left on
|
|
127
129
|
?.map((flowOrder) => (0, planMethods_1.getFlowsToRunInSequence)(pathsByName, [flowOrder]))
|
|
128
130
|
.flat() || [];
|
|
129
131
|
const normalFlows = allFlows
|
package/oclif.manifest.json
CHANGED
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"aliases": [
|
|
70
70
|
"api-key"
|
|
71
71
|
],
|
|
72
|
-
"description": "API key",
|
|
72
|
+
"description": "API key for devicecloud.dev (find this in the console UI). You can also set the DEVICE_CLOUD_API_KEY environment variable.",
|
|
73
73
|
"name": "apiKey",
|
|
74
74
|
"hasDynamicHelp": false,
|
|
75
75
|
"multiple": false,
|
|
@@ -122,7 +122,7 @@
|
|
|
122
122
|
"type": "option"
|
|
123
123
|
},
|
|
124
124
|
"download-artifacts": {
|
|
125
|
-
"description": "
|
|
125
|
+
"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.",
|
|
126
126
|
"name": "download-artifacts",
|
|
127
127
|
"hasDynamicHelp": false,
|
|
128
128
|
"multiple": false,
|
|
@@ -186,6 +186,12 @@
|
|
|
186
186
|
"multiple": true,
|
|
187
187
|
"type": "option"
|
|
188
188
|
},
|
|
189
|
+
"ignore-sha-check": {
|
|
190
|
+
"description": "Ignore the sha hash check and upload the binary regardless of whether it already exists (not recommended)",
|
|
191
|
+
"name": "ignore-sha-check",
|
|
192
|
+
"allowNo": false,
|
|
193
|
+
"type": "boolean"
|
|
194
|
+
},
|
|
189
195
|
"ios-device": {
|
|
190
196
|
"description": "[iOS only] iOS device to run your flow against",
|
|
191
197
|
"name": "ios-device",
|
|
@@ -317,5 +323,5 @@
|
|
|
317
323
|
]
|
|
318
324
|
}
|
|
319
325
|
},
|
|
320
|
-
"version": "3.
|
|
326
|
+
"version": "3.2.1"
|
|
321
327
|
}
|