@devicecloud.dev/dcd 0.0.1-alpha.9 → 0.0.2
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 +6 -3
- package/dist/commands/cloud.js +265 -142
- package/dist/plan.d.ts +1 -0
- package/dist/plan.js +2 -1
- package/oclif.manifest.json +58 -22
- package/package.json +2 -5
package/dist/commands/cloud.d.ts
CHANGED
|
@@ -8,18 +8,21 @@ export default class Cloud extends Command {
|
|
|
8
8
|
static examples: string[];
|
|
9
9
|
static flags: {
|
|
10
10
|
'android-api-level': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
-
'
|
|
12
|
-
|
|
11
|
+
'android-device': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
apiKey: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
|
+
apiUrl: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
13
14
|
'app-binary-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
14
15
|
'app-file': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
15
16
|
arm64: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
16
|
-
async: import("@oclif/core/lib/interfaces").
|
|
17
|
+
async: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
17
18
|
env: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
18
19
|
'exclude-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
19
20
|
flows: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
20
21
|
'google-play': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
21
22
|
'include-tags': import("@oclif/core/lib/interfaces").OptionFlag<string[], import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
|
+
'ios-device': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
22
24
|
'ios-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
25
|
+
name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
23
26
|
orientation: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
24
27
|
};
|
|
25
28
|
run(): Promise<void>;
|
package/dist/commands/cloud.js
CHANGED
|
@@ -59,27 +59,51 @@ const compressDir = async (sourceDir) => {
|
|
|
59
59
|
archive.on('error', (err) => {
|
|
60
60
|
throw err;
|
|
61
61
|
});
|
|
62
|
-
archive.directory(sourceDir,
|
|
62
|
+
archive.directory(sourceDir, '.', (data) => {
|
|
63
|
+
if (data.name.split('/')[0] === 'node_modules')
|
|
64
|
+
return false;
|
|
63
65
|
if (PERMITTED_EXTENSIONS.has(data.name.split('.').pop())) {
|
|
64
66
|
return data;
|
|
65
67
|
}
|
|
66
68
|
return false;
|
|
67
69
|
});
|
|
68
70
|
const buffer = await toBuffer(archive);
|
|
69
|
-
return buffer;
|
|
70
|
-
};
|
|
71
|
-
const compressFile = async (sourceFile) => {
|
|
72
|
-
const archive = archiver('zip', {
|
|
73
|
-
zlib: { level: 9 },
|
|
74
|
-
});
|
|
75
|
-
archive.on('error', (err) => {
|
|
76
|
-
throw err;
|
|
77
|
-
});
|
|
78
|
-
archive.file(sourceFile, { name: sourceFile });
|
|
79
|
-
const buffer = await toBuffer(archive);
|
|
80
71
|
// await writeFile('./my-zip.zip', buffer);
|
|
81
72
|
return buffer;
|
|
82
73
|
};
|
|
74
|
+
// const compressFile = async (sourceFile: string) => {
|
|
75
|
+
// const archive = archiver('zip', {
|
|
76
|
+
// zlib: { level: 9 },
|
|
77
|
+
// });
|
|
78
|
+
// archive.on('error', (err) => {
|
|
79
|
+
// throw err;
|
|
80
|
+
// });
|
|
81
|
+
// console.log(sourceFile, sourceFile.split('/').pop());
|
|
82
|
+
// archive.file(sourceFile.includes('/') ? sourceFile : './' + sourceFile, {
|
|
83
|
+
// name: sourceFile.split('/').pop()!,
|
|
84
|
+
// });
|
|
85
|
+
// const buffer = await toBuffer(archive);
|
|
86
|
+
// await writeFile('./my-file-zip.zip', buffer);
|
|
87
|
+
// return buffer;
|
|
88
|
+
// };
|
|
89
|
+
var EiOSDevices;
|
|
90
|
+
(function (EiOSDevices) {
|
|
91
|
+
EiOSDevices["ipad-pro-6th-gen"] = "ipad-pro-6th-gen";
|
|
92
|
+
EiOSDevices["iphone-12"] = "iphone-12";
|
|
93
|
+
EiOSDevices["iphone-12-mini"] = "iphone-12-mini";
|
|
94
|
+
EiOSDevices["iphone-12-pro-max"] = "iphone-12-pro-max";
|
|
95
|
+
EiOSDevices["iphone-13"] = "iphone-13";
|
|
96
|
+
EiOSDevices["iphone-13-mini"] = "iphone-13-mini";
|
|
97
|
+
EiOSDevices["iphone-13-pro-max"] = "iphone-13-pro-max";
|
|
98
|
+
EiOSDevices["iphone-14"] = "iphone-14";
|
|
99
|
+
EiOSDevices["iphone-14-plus"] = "iphone-14-plus";
|
|
100
|
+
EiOSDevices["iphone-14-pro"] = "iphone-14-pro";
|
|
101
|
+
EiOSDevices["iphone-14-pro-max"] = "iphone-14-pro-max";
|
|
102
|
+
EiOSDevices["iphone-15"] = "iphone-15";
|
|
103
|
+
EiOSDevices["iphone-15-plus"] = "iphone-15-plus";
|
|
104
|
+
EiOSDevices["iphone-15-pro"] = "iphone-15-pro";
|
|
105
|
+
EiOSDevices["iphone-15-pro-max"] = "iphone-15-pro-max";
|
|
106
|
+
})(EiOSDevices || (EiOSDevices = {}));
|
|
83
107
|
class Cloud extends core_1.Command {
|
|
84
108
|
static args = {
|
|
85
109
|
firstFile: core_1.Args.string({
|
|
@@ -97,16 +121,26 @@ class Cloud extends core_1.Command {
|
|
|
97
121
|
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
98
122
|
static flags = {
|
|
99
123
|
'android-api-level': core_1.Flags.integer({
|
|
100
|
-
aliases: ['android-api-level'],
|
|
101
124
|
description: '[Android only] Android API level to run your flow against',
|
|
102
125
|
options: ['32', '33', '34'],
|
|
103
126
|
}),
|
|
104
|
-
'
|
|
127
|
+
'android-device': core_1.Flags.string({
|
|
128
|
+
description: '[Android only] Android device to run your flow against',
|
|
129
|
+
options: [
|
|
130
|
+
'pixel-6',
|
|
131
|
+
'pixel-6a',
|
|
132
|
+
'pixel-6-pro',
|
|
133
|
+
'pixel-7',
|
|
134
|
+
'pixel-7-pro',
|
|
135
|
+
'generic-tablet',
|
|
136
|
+
],
|
|
137
|
+
}),
|
|
138
|
+
apiKey: core_1.Flags.string({
|
|
105
139
|
aliases: ['api-key'],
|
|
106
140
|
description: 'API key',
|
|
107
141
|
}),
|
|
108
|
-
|
|
109
|
-
aliases: ['api-url'],
|
|
142
|
+
apiUrl: core_1.Flags.string({
|
|
143
|
+
aliases: ['api-url', 'apiURL'],
|
|
110
144
|
default: 'https://api.devicecloud.dev',
|
|
111
145
|
description: 'API base URL',
|
|
112
146
|
hidden: true,
|
|
@@ -123,10 +157,8 @@ class Cloud extends core_1.Command {
|
|
|
123
157
|
default: false,
|
|
124
158
|
description: '[Android only] Run your flow against arm64 devices',
|
|
125
159
|
}),
|
|
126
|
-
async: core_1.Flags.
|
|
127
|
-
default: 'true',
|
|
160
|
+
async: core_1.Flags.boolean({
|
|
128
161
|
description: 'Wait for the results of the run',
|
|
129
|
-
options: ['true', 'false'],
|
|
130
162
|
}),
|
|
131
163
|
env: core_1.Flags.file({
|
|
132
164
|
char: 'e',
|
|
@@ -155,10 +187,32 @@ class Cloud extends core_1.Command {
|
|
|
155
187
|
multiple: true,
|
|
156
188
|
parse: (input) => input.split(','),
|
|
157
189
|
}),
|
|
190
|
+
'ios-device': core_1.Flags.string({
|
|
191
|
+
description: '[iOS only] iOS device to run your flow against',
|
|
192
|
+
options: [
|
|
193
|
+
'iphone-12',
|
|
194
|
+
'iphone-12-mini',
|
|
195
|
+
'iphone-12-pro-max',
|
|
196
|
+
'iphone-13',
|
|
197
|
+
'iphone-13-mini',
|
|
198
|
+
'iphone-13-pro-max',
|
|
199
|
+
'iphone-14',
|
|
200
|
+
'iphone-14-plus',
|
|
201
|
+
'iphone-14-pro',
|
|
202
|
+
'iphone-14-pro-max',
|
|
203
|
+
'iphone-15',
|
|
204
|
+
'iphone-15-plus',
|
|
205
|
+
'iphone-15-pro',
|
|
206
|
+
'iphone-15-pro-max',
|
|
207
|
+
'ipad-pro-6th-gen',
|
|
208
|
+
],
|
|
209
|
+
}),
|
|
158
210
|
'ios-version': core_1.Flags.string({
|
|
159
|
-
aliases: ['ios-version'],
|
|
160
211
|
description: '[iOS only] iOS version to run your flow against',
|
|
161
|
-
options: ['16
|
|
212
|
+
options: ['15', '16', '17'],
|
|
213
|
+
}),
|
|
214
|
+
name: core_1.Flags.string({
|
|
215
|
+
description: 'A custom name for your upload (useful for tagging commits etc)',
|
|
162
216
|
}),
|
|
163
217
|
orientation: core_1.Flags.string({
|
|
164
218
|
description: '[Android only] The orientation of the device to run your flow against in degrees',
|
|
@@ -166,137 +220,206 @@ class Cloud extends core_1.Command {
|
|
|
166
220
|
}),
|
|
167
221
|
};
|
|
168
222
|
async run() {
|
|
169
|
-
const { args, flags } = await this.parse(Cloud);
|
|
170
|
-
const { 'api-key': apiKey, 'api-url': apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, arm64, async, env, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'include-tags': includeTags, orientation, ...rest } = flags;
|
|
171
|
-
if (arm64) {
|
|
172
|
-
(0, cli_ux_1.info)('Contact hello@devicecloud.dev to enquire about arm64 devices');
|
|
173
|
-
(0, cli_ux_1.exit)();
|
|
174
|
-
}
|
|
175
|
-
const { firstFile, secondFile } = args;
|
|
176
|
-
let finalBinaryId = appBinaryId;
|
|
177
|
-
const finalAppFile = appFile ?? firstFile;
|
|
178
|
-
let flowFile = flows ?? secondFile;
|
|
179
|
-
if (appBinaryId) {
|
|
180
|
-
if (secondFile) {
|
|
181
|
-
throw new Error('You cannot provide both an appBinaryId and a binary file');
|
|
182
|
-
}
|
|
183
|
-
flowFile = flows ?? firstFile;
|
|
184
|
-
}
|
|
185
|
-
if (!flowFile) {
|
|
186
|
-
throw new Error('You must provide a flow file');
|
|
187
|
-
}
|
|
188
|
-
let testFileNames = [];
|
|
189
|
-
let continueOnFailure = true;
|
|
190
|
-
let sequentialFlows = [];
|
|
191
|
-
if (!flowFile?.endsWith('.yaml') &&
|
|
192
|
-
!flowFile?.endsWith('.yml') &&
|
|
193
|
-
!flowFile?.endsWith('/')) {
|
|
194
|
-
flowFile += '/';
|
|
195
|
-
}
|
|
196
223
|
try {
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
catch (error) {
|
|
203
|
-
console.error(error);
|
|
204
|
-
}
|
|
205
|
-
if (!appBinaryId) {
|
|
206
|
-
if (!(flowFile && finalAppFile)) {
|
|
207
|
-
throw new Error('You must provide a flow file and an app binary id');
|
|
224
|
+
const { args, flags } = await this.parse(Cloud);
|
|
225
|
+
const { 'android-api-level': androidApiLevel, 'android-device': androidDevice, apiKey, apiUrl, 'app-binary-id': appBinaryId, 'app-file': appFile, arm64, async, env, 'exclude-tags': excludeTags, flows, 'google-play': googlePlay, 'include-tags': includeTags, 'ios-device': iOSDevice, 'ios-version': iOSVersion, name, orientation, ...rest } = flags;
|
|
226
|
+
if (arm64) {
|
|
227
|
+
(0, cli_ux_1.info)('Contact hello@devicecloud.dev to enquire about arm64 devices');
|
|
228
|
+
(0, cli_ux_1.exit)();
|
|
208
229
|
}
|
|
209
|
-
|
|
210
|
-
|
|
230
|
+
const { firstFile, secondFile } = args;
|
|
231
|
+
let finalBinaryId = appBinaryId;
|
|
232
|
+
const finalAppFile = appFile ?? firstFile;
|
|
233
|
+
let flowFile = flows ?? secondFile;
|
|
234
|
+
if (appBinaryId) {
|
|
235
|
+
if (secondFile) {
|
|
236
|
+
throw new Error('You cannot provide both an appBinaryId and a binary file');
|
|
237
|
+
}
|
|
238
|
+
flowFile = flows ?? firstFile;
|
|
211
239
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
240
|
+
if (!flowFile) {
|
|
241
|
+
throw new Error('You must provide a flow file');
|
|
242
|
+
}
|
|
243
|
+
if (iOSVersion) {
|
|
244
|
+
const iOSCompatibilityLookup = {
|
|
245
|
+
'ipad-pro-6th-gen': ['16', '17'],
|
|
246
|
+
'iphone-12': ['15', '16', '17'],
|
|
247
|
+
'iphone-12-mini': ['15', '16', '17'],
|
|
248
|
+
'iphone-12-pro-max': ['15', '16', '17'],
|
|
249
|
+
'iphone-13': ['15', '16', '17'],
|
|
250
|
+
'iphone-13-mini': ['15', '16', '17'],
|
|
251
|
+
'iphone-13-pro-max': ['15', '16', '17'],
|
|
252
|
+
'iphone-14': ['16', '17'],
|
|
253
|
+
'iphone-14-plus': ['16', '17'],
|
|
254
|
+
'iphone-14-pro': ['16', '17'],
|
|
255
|
+
'iphone-14-pro-max': ['16', '17'],
|
|
256
|
+
'iphone-15': ['17'],
|
|
257
|
+
'iphone-15-plus': ['17'],
|
|
258
|
+
'iphone-15-pro': ['17'],
|
|
259
|
+
'iphone-15-pro-max': ['17'],
|
|
260
|
+
};
|
|
261
|
+
const iOSDeviceID = iOSDevice || 'iphone-14';
|
|
262
|
+
const supportediOSVersions = iOSCompatibilityLookup[iOSDeviceID];
|
|
263
|
+
if (!supportediOSVersions.includes(iOSVersion)) {
|
|
264
|
+
throw new Error(`${iOSDeviceID} only supports these iOS versions: ${supportediOSVersions.join(',')}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
let testFileNames = [];
|
|
268
|
+
let continueOnFailure = true;
|
|
269
|
+
let sequentialFlows = [];
|
|
270
|
+
if (!flowFile?.endsWith('.yaml') &&
|
|
271
|
+
!flowFile?.endsWith('.yml') &&
|
|
272
|
+
!flowFile?.endsWith('/')) {
|
|
273
|
+
flowFile += '/';
|
|
274
|
+
}
|
|
275
|
+
try {
|
|
276
|
+
const executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat());
|
|
277
|
+
testFileNames = executionPlan.flowsToRun;
|
|
278
|
+
continueOnFailure = executionPlan.sequence?.continueOnFailure ?? true;
|
|
279
|
+
sequentialFlows = executionPlan.sequence?.flows ?? [];
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
console.error(error);
|
|
283
|
+
}
|
|
284
|
+
if (!appBinaryId) {
|
|
285
|
+
if (!(flowFile && finalAppFile)) {
|
|
286
|
+
throw new Error('You must provide a flow file and an app binary id');
|
|
287
|
+
}
|
|
288
|
+
if (!finalAppFile.endsWith('.apk') && !finalAppFile.endsWith('.zip')) {
|
|
289
|
+
throw new Error('App file must be a .apk or .zip file');
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const flagLogs = [];
|
|
293
|
+
for (const [k, v] of Object.entries(flags)) {
|
|
294
|
+
if (v && v.toString().length > 0) {
|
|
295
|
+
flagLogs.push(`${k}: ${v}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
this.log(`
|
|
299
|
+
|
|
300
|
+
Submitting new job
|
|
301
|
+
→ Flow(s): ${flowFile}
|
|
302
|
+
→ App: ${appBinaryId || finalAppFile}
|
|
303
|
+
|
|
304
|
+
With options
|
|
305
|
+
→ ${flagLogs.join(`
|
|
306
|
+
→ `)}
|
|
307
|
+
|
|
308
|
+
`);
|
|
309
|
+
if (!finalBinaryId) {
|
|
310
|
+
core_1.ux.action.start('Uploading binary', 'Initializing', { stdout: true });
|
|
311
|
+
const binaryFormData = new FormData();
|
|
312
|
+
const binaryBlob = new Blob([await (0, promises_1.readFile)(finalAppFile)], {
|
|
313
|
+
type: mimeTypeLookupByExtension[finalAppFile.split('.').pop()],
|
|
314
|
+
});
|
|
315
|
+
binaryFormData.set('file', binaryBlob, finalAppFile);
|
|
316
|
+
const options = {
|
|
317
|
+
body: binaryFormData,
|
|
318
|
+
headers: { 'x-app-api-key': apiKey },
|
|
319
|
+
};
|
|
320
|
+
core_1.ux.action.status = `Uploading`;
|
|
321
|
+
const { binaryId, message } = await typeSafePost(apiUrl, '/uploads/binary', options);
|
|
322
|
+
if (!binaryId)
|
|
323
|
+
throw new Error(message);
|
|
324
|
+
core_1.ux.action.stop(`\nBinary uploaded with id: ${binaryId}`);
|
|
325
|
+
finalBinaryId = binaryId;
|
|
326
|
+
}
|
|
327
|
+
const testFormData = new FormData();
|
|
328
|
+
// eslint-disable-next-line unicorn/no-array-reduce
|
|
329
|
+
const envObject = (env ?? []).reduce((acc, cur) => {
|
|
330
|
+
const [key, value] = cur.split('=');
|
|
331
|
+
acc[key] = value;
|
|
332
|
+
return acc;
|
|
333
|
+
}, {});
|
|
334
|
+
const path = flowFile.split('/').slice(0, -1).join('/');
|
|
335
|
+
const buffer = await compressDir(path?.length ? path : './');
|
|
336
|
+
// const buffer =
|
|
337
|
+
// totalFlowFiles > 1 || flowFile?.endsWith('/')
|
|
338
|
+
// ? await compressDir(
|
|
339
|
+
// flowFile!.split('/').slice(0, -1).join('/') ?? '.',
|
|
340
|
+
// )
|
|
341
|
+
// : await compressFile(flowFile!);
|
|
342
|
+
const blob = new Blob([buffer], {
|
|
343
|
+
type: mimeTypeLookupByExtension.zip,
|
|
219
344
|
});
|
|
220
|
-
|
|
345
|
+
testFormData.set('file', blob, 'flowFile.zip');
|
|
346
|
+
testFormData.set('appBinaryId', finalBinaryId);
|
|
347
|
+
testFormData.set('testFileNames', JSON.stringify(testFileNames));
|
|
348
|
+
testFormData.set('sequentialFlows', JSON.stringify(sequentialFlows));
|
|
349
|
+
testFormData.set('env', JSON.stringify(envObject));
|
|
350
|
+
testFormData.set('googlePlay', googlePlay ? 'true' : 'false');
|
|
351
|
+
testFormData.set('config', JSON.stringify({ continueOnFailure, orientation }));
|
|
352
|
+
if (androidApiLevel) {
|
|
353
|
+
testFormData.set('androidApiLevel', androidApiLevel.toString());
|
|
354
|
+
}
|
|
355
|
+
if (androidDevice) {
|
|
356
|
+
testFormData.set('androidDevice', androidDevice.toString());
|
|
357
|
+
}
|
|
358
|
+
if (iOSVersion) {
|
|
359
|
+
testFormData.set('iOSVersion', iOSVersion.toString());
|
|
360
|
+
}
|
|
361
|
+
if (iOSDevice) {
|
|
362
|
+
testFormData.set('iOSDevice', iOSDevice.toString());
|
|
363
|
+
}
|
|
364
|
+
if (name) {
|
|
365
|
+
testFormData.set('name', name.toString());
|
|
366
|
+
}
|
|
367
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
368
|
+
if (value) {
|
|
369
|
+
testFormData.set(key, value);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
221
372
|
const options = {
|
|
222
|
-
body:
|
|
373
|
+
body: testFormData,
|
|
223
374
|
headers: { 'x-app-api-key': apiKey },
|
|
224
375
|
};
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
return acc;
|
|
238
|
-
}, {});
|
|
239
|
-
const buffer = [...sequentialFlows, ...testFileNames]?.length > 1
|
|
240
|
-
? await compressDir(flowFile.split('/').slice(0, -1).join('/'))
|
|
241
|
-
: await compressFile(flowFile);
|
|
242
|
-
const blob = new Blob([buffer], {
|
|
243
|
-
type: mimeTypeLookupByExtension.zip,
|
|
244
|
-
});
|
|
245
|
-
testFormData.set('file', blob, 'flowFile.zip');
|
|
246
|
-
testFormData.set('appBinaryId', finalBinaryId);
|
|
247
|
-
testFormData.set('testFileNames', JSON.stringify(testFileNames));
|
|
248
|
-
testFormData.set('sequentialFlows', JSON.stringify(sequentialFlows));
|
|
249
|
-
testFormData.set('env', JSON.stringify(envObject));
|
|
250
|
-
testFormData.set('googlePlay', googlePlay ? 'true' : 'false');
|
|
251
|
-
testFormData.set('config', JSON.stringify({ continueOnFailure, orientation }));
|
|
252
|
-
for (const [key, value] of Object.entries(rest)) {
|
|
253
|
-
if (value) {
|
|
254
|
-
testFormData.set(key, value);
|
|
376
|
+
const { message, results } = await typeSafePost(apiUrl, '/uploads/flow', options);
|
|
377
|
+
if (!results?.length)
|
|
378
|
+
(0, errors_1.error)('No tests created: ' + message);
|
|
379
|
+
(0, cli_ux_1.info)(`\nCreated ${results.length} tests: ${results
|
|
380
|
+
.map((r) => r.test_file_name)
|
|
381
|
+
.join(', ')}\n`);
|
|
382
|
+
(0, cli_ux_1.info)('Run triggered, you can access the results at:');
|
|
383
|
+
const url = `https://console.devicecloud.dev/results/${results[0].test_upload_id}/${results[0].id}`;
|
|
384
|
+
core_1.ux.url(url, url);
|
|
385
|
+
if (async) {
|
|
386
|
+
(0, cli_ux_1.info)('Not waiting for results as async flag is set to true');
|
|
387
|
+
(0, cli_ux_1.exit)(0);
|
|
255
388
|
}
|
|
389
|
+
// poll for the run status every 5 seconds
|
|
390
|
+
core_1.ux.action.start('Waiting for results', 'Initializing', { stdout: true });
|
|
391
|
+
(0, cli_ux_1.info)('\nYou can safely close this terminal and the tests will continue\n');
|
|
392
|
+
const intervalId = setInterval(async () => {
|
|
393
|
+
const { results: updatedResults } = await typeSafeGet(apiUrl, `/results/${results[0].test_upload_id}`, {
|
|
394
|
+
headers: { 'x-app-api-key': apiKey },
|
|
395
|
+
});
|
|
396
|
+
if (!updatedResults) {
|
|
397
|
+
clearInterval(intervalId);
|
|
398
|
+
(0, errors_1.error)('No results found');
|
|
399
|
+
}
|
|
400
|
+
core_1.ux.action.status = '\nStatus Test\n─────────── ───────────────';
|
|
401
|
+
for (const { status, test_file_name: test } of updatedResults) {
|
|
402
|
+
core_1.ux.action.status += `\n${status.padEnd(10, ' ')} ${test}`;
|
|
403
|
+
}
|
|
404
|
+
if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
|
|
405
|
+
core_1.ux.action.stop('completed');
|
|
406
|
+
(0, cli_ux_1.info)('\n');
|
|
407
|
+
(0, cli_ux_1.table)(updatedResults, {
|
|
408
|
+
status: { get: (row) => row.status },
|
|
409
|
+
test: { get: (row) => row.test_file_name },
|
|
410
|
+
}, { printLine: this.log.bind(this) });
|
|
411
|
+
(0, cli_ux_1.info)('\n');
|
|
412
|
+
clearInterval(intervalId);
|
|
413
|
+
if (updatedResults.some((result) => result.status === 'FAILED')) {
|
|
414
|
+
(0, cli_ux_1.exit)(1);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}, 5000);
|
|
256
418
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
};
|
|
261
|
-
const { message, results } = await typeSafePost(apiUrl, '/uploads/flow', options);
|
|
262
|
-
if (!results?.length)
|
|
263
|
-
(0, errors_1.error)('No tests created: ' + message);
|
|
264
|
-
(0, cli_ux_1.info)(`\nCreated ${results.length} tests: ${results
|
|
265
|
-
.map((r) => r.test_file_name)
|
|
266
|
-
.join(', ')}\n`);
|
|
267
|
-
(0, cli_ux_1.info)('Run triggered, you can access the results at:');
|
|
268
|
-
(0, cli_ux_1.info)(`https://console.devicecloud.dev/results/${results[0].test_upload_id}/${results[0].id}/\n`);
|
|
269
|
-
if (async === 'false') {
|
|
270
|
-
(0, cli_ux_1.info)('Not waiting for results as async flag is set to false');
|
|
271
|
-
(0, cli_ux_1.exit)(0);
|
|
419
|
+
catch (error) {
|
|
420
|
+
console.error(error);
|
|
421
|
+
(0, cli_ux_1.exit)(1);
|
|
272
422
|
}
|
|
273
|
-
// poll for the run status every 5 seconds
|
|
274
|
-
core_1.ux.action.start('Waiting for results', 'Initializing', { stdout: true });
|
|
275
|
-
(0, cli_ux_1.info)('You can safely close this terminal and the tests will continue\n');
|
|
276
|
-
const intervalId = setInterval(async () => {
|
|
277
|
-
const { results: updatedResults } = await typeSafeGet(apiUrl, `/results/${results[0].test_upload_id}`, {
|
|
278
|
-
headers: { 'x-app-api-key': apiKey },
|
|
279
|
-
});
|
|
280
|
-
if (!updatedResults) {
|
|
281
|
-
clearInterval(intervalId);
|
|
282
|
-
(0, errors_1.error)('No results found');
|
|
283
|
-
}
|
|
284
|
-
core_1.ux.action.status = `\nStatus | Test
|
|
285
|
-
────────── ─────────── `;
|
|
286
|
-
for (const { status, test_file_name: test } of updatedResults) {
|
|
287
|
-
core_1.ux.action.status += `\n${status.padEnd(10, ' ')} | ${test}`;
|
|
288
|
-
}
|
|
289
|
-
if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
|
|
290
|
-
core_1.ux.action.stop('completed');
|
|
291
|
-
(0, cli_ux_1.info)('\n');
|
|
292
|
-
(0, cli_ux_1.table)(updatedResults, {
|
|
293
|
-
status: { get: (row) => row.status },
|
|
294
|
-
test: { get: (row) => row.test_file_name },
|
|
295
|
-
}, { printLine: this.log.bind(this) });
|
|
296
|
-
(0, cli_ux_1.info)('\n');
|
|
297
|
-
clearInterval(intervalId);
|
|
298
|
-
}
|
|
299
|
-
}, 5000);
|
|
300
423
|
}
|
|
301
424
|
}
|
|
302
425
|
exports.default = Cloud;
|
package/dist/plan.d.ts
CHANGED
package/dist/plan.js
CHANGED
|
@@ -68,7 +68,7 @@ async function plan(input, includeTags, excludeTags) {
|
|
|
68
68
|
throw new Error(`Flow path does not exist: ${path.resolve(input)}`);
|
|
69
69
|
}
|
|
70
70
|
if (fs.lstatSync(input).isFile()) {
|
|
71
|
-
return { flowsToRun: [input] };
|
|
71
|
+
return { flowsToRun: [input.split('/').pop() ?? input], totalFlowFiles: 1 };
|
|
72
72
|
}
|
|
73
73
|
let unfilteredFlowFiles = await walk(input, isFlowFile);
|
|
74
74
|
if (unfilteredFlowFiles.length === 0) {
|
|
@@ -147,6 +147,7 @@ async function plan(input, includeTags, excludeTags) {
|
|
|
147
147
|
continueOnFailure: workspaceConfig.executionOrder?.continueOnFailure,
|
|
148
148
|
flows: flowsToRunInSequence,
|
|
149
149
|
},
|
|
150
|
+
totalFlowFiles: unfilteredFlowFiles.length,
|
|
150
151
|
};
|
|
151
152
|
}
|
|
152
153
|
exports.plan = plan;
|
package/oclif.manifest.json
CHANGED
|
@@ -20,9 +20,6 @@
|
|
|
20
20
|
],
|
|
21
21
|
"flags": {
|
|
22
22
|
"android-api-level": {
|
|
23
|
-
"aliases": [
|
|
24
|
-
"android-api-level"
|
|
25
|
-
],
|
|
26
23
|
"description": "[Android only] Android API level to run your flow against",
|
|
27
24
|
"name": "android-api-level",
|
|
28
25
|
"hasDynamicHelp": false,
|
|
@@ -34,23 +31,39 @@
|
|
|
34
31
|
],
|
|
35
32
|
"type": "option"
|
|
36
33
|
},
|
|
37
|
-
"
|
|
34
|
+
"android-device": {
|
|
35
|
+
"description": "[Android only] Android device to run your flow against",
|
|
36
|
+
"name": "android-device",
|
|
37
|
+
"hasDynamicHelp": false,
|
|
38
|
+
"multiple": false,
|
|
39
|
+
"options": [
|
|
40
|
+
"pixel-6",
|
|
41
|
+
"pixel-6a",
|
|
42
|
+
"pixel-6-pro",
|
|
43
|
+
"pixel-7",
|
|
44
|
+
"pixel-7-pro",
|
|
45
|
+
"generic-tablet"
|
|
46
|
+
],
|
|
47
|
+
"type": "option"
|
|
48
|
+
},
|
|
49
|
+
"apiKey": {
|
|
38
50
|
"aliases": [
|
|
39
51
|
"api-key"
|
|
40
52
|
],
|
|
41
53
|
"description": "API key",
|
|
42
|
-
"name": "
|
|
54
|
+
"name": "apiKey",
|
|
43
55
|
"hasDynamicHelp": false,
|
|
44
56
|
"multiple": false,
|
|
45
57
|
"type": "option"
|
|
46
58
|
},
|
|
47
|
-
"
|
|
59
|
+
"apiUrl": {
|
|
48
60
|
"aliases": [
|
|
49
|
-
"api-url"
|
|
61
|
+
"api-url",
|
|
62
|
+
"apiURL"
|
|
50
63
|
],
|
|
51
64
|
"description": "API base URL",
|
|
52
65
|
"hidden": true,
|
|
53
|
-
"name": "
|
|
66
|
+
"name": "apiUrl",
|
|
54
67
|
"default": "https://api.devicecloud.dev",
|
|
55
68
|
"hasDynamicHelp": false,
|
|
56
69
|
"multiple": false,
|
|
@@ -85,14 +98,8 @@
|
|
|
85
98
|
"async": {
|
|
86
99
|
"description": "Wait for the results of the run",
|
|
87
100
|
"name": "async",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"multiple": false,
|
|
91
|
-
"options": [
|
|
92
|
-
"true",
|
|
93
|
-
"false"
|
|
94
|
-
],
|
|
95
|
-
"type": "option"
|
|
101
|
+
"allowNo": false,
|
|
102
|
+
"type": "boolean"
|
|
96
103
|
},
|
|
97
104
|
"env": {
|
|
98
105
|
"char": "e",
|
|
@@ -140,20 +147,49 @@
|
|
|
140
147
|
"multiple": true,
|
|
141
148
|
"type": "option"
|
|
142
149
|
},
|
|
143
|
-
"ios-
|
|
144
|
-
"
|
|
145
|
-
|
|
150
|
+
"ios-device": {
|
|
151
|
+
"description": "[iOS only] iOS device to run your flow against",
|
|
152
|
+
"name": "ios-device",
|
|
153
|
+
"hasDynamicHelp": false,
|
|
154
|
+
"multiple": false,
|
|
155
|
+
"options": [
|
|
156
|
+
"iphone-12",
|
|
157
|
+
"iphone-12-mini",
|
|
158
|
+
"iphone-12-pro-max",
|
|
159
|
+
"iphone-13",
|
|
160
|
+
"iphone-13-mini",
|
|
161
|
+
"iphone-13-pro-max",
|
|
162
|
+
"iphone-14",
|
|
163
|
+
"iphone-14-plus",
|
|
164
|
+
"iphone-14-pro",
|
|
165
|
+
"iphone-14-pro-max",
|
|
166
|
+
"iphone-15",
|
|
167
|
+
"iphone-15-plus",
|
|
168
|
+
"iphone-15-pro",
|
|
169
|
+
"iphone-15-pro-max",
|
|
170
|
+
"ipad-pro-6th-gen"
|
|
146
171
|
],
|
|
172
|
+
"type": "option"
|
|
173
|
+
},
|
|
174
|
+
"ios-version": {
|
|
147
175
|
"description": "[iOS only] iOS version to run your flow against",
|
|
148
176
|
"name": "ios-version",
|
|
149
177
|
"hasDynamicHelp": false,
|
|
150
178
|
"multiple": false,
|
|
151
179
|
"options": [
|
|
152
|
-
"
|
|
153
|
-
"
|
|
180
|
+
"15",
|
|
181
|
+
"16",
|
|
182
|
+
"17"
|
|
154
183
|
],
|
|
155
184
|
"type": "option"
|
|
156
185
|
},
|
|
186
|
+
"name": {
|
|
187
|
+
"description": "A custom name for your upload (useful for tagging commits etc)",
|
|
188
|
+
"name": "name",
|
|
189
|
+
"hasDynamicHelp": false,
|
|
190
|
+
"multiple": false,
|
|
191
|
+
"type": "option"
|
|
192
|
+
},
|
|
157
193
|
"orientation": {
|
|
158
194
|
"description": "[Android only] The orientation of the device to run your flow against in degrees",
|
|
159
195
|
"name": "orientation",
|
|
@@ -184,5 +220,5 @@
|
|
|
184
220
|
]
|
|
185
221
|
}
|
|
186
222
|
},
|
|
187
|
-
"version": "0.0.
|
|
223
|
+
"version": "0.0.2"
|
|
188
224
|
}
|
package/package.json
CHANGED
|
@@ -53,10 +53,7 @@
|
|
|
53
53
|
"commands": "./dist/commands",
|
|
54
54
|
"plugins": [
|
|
55
55
|
"@oclif/plugin-help",
|
|
56
|
-
"@oclif/plugin-not-found"
|
|
57
|
-
"@oclif/plugin-update",
|
|
58
|
-
"@oclif/plugin-warn-if-update-available",
|
|
59
|
-
"@oclif/plugin-autocomplete"
|
|
56
|
+
"@oclif/plugin-not-found"
|
|
60
57
|
]
|
|
61
58
|
},
|
|
62
59
|
"private": false,
|
|
@@ -75,7 +72,7 @@
|
|
|
75
72
|
"test": "mocha --forbid-only \"test/**/*.test.ts\"",
|
|
76
73
|
"version": "oclif readme && git add README.md"
|
|
77
74
|
},
|
|
78
|
-
"version": "0.0.
|
|
75
|
+
"version": "0.0.2",
|
|
79
76
|
"bugs": {
|
|
80
77
|
"url": "https://discord.gg/GzZBHcUJ"
|
|
81
78
|
},
|