@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.
@@ -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
- 'api-key': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
- 'api-url': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
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").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
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>;
@@ -59,27 +59,51 @@ const compressDir = async (sourceDir) => {
59
59
  archive.on('error', (err) => {
60
60
  throw err;
61
61
  });
62
- archive.directory(sourceDir, false, (data) => {
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
- 'api-key': core_1.Flags.string({
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
- 'api-url': core_1.Flags.string({
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.string({
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.4', '17.2'],
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 executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat());
198
- testFileNames = executionPlan.flowsToRun;
199
- continueOnFailure = executionPlan.sequence?.continueOnFailure ?? true;
200
- sequentialFlows = executionPlan.sequence?.flows ?? [];
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
- if (!finalAppFile.endsWith('.apk') && !finalAppFile.endsWith('.zip')) {
210
- throw new Error('App file must be a .apk or .zip file');
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
- this.log(`you want to run the flow(s) ${flowFile} against the app ${finalAppFile} with the following flags: ${JSON.stringify(flags)}\n`);
213
- }
214
- if (!finalBinaryId) {
215
- core_1.ux.action.start('Uploading binary', 'Initializing', { stdout: true });
216
- const binaryFormData = new FormData();
217
- const binaryBlob = new Blob([await (0, promises_1.readFile)(finalAppFile)], {
218
- type: mimeTypeLookupByExtension[finalAppFile.split('.').pop()],
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
- binaryFormData.set('file', binaryBlob, finalAppFile);
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: binaryFormData,
373
+ body: testFormData,
223
374
  headers: { 'x-app-api-key': apiKey },
224
375
  };
225
- core_1.ux.action.status = `Uploading`;
226
- const { binaryId, message } = await typeSafePost(apiUrl, '/uploads/binary', options);
227
- if (!binaryId)
228
- throw new Error(message);
229
- core_1.ux.action.stop(`\nBinary uploaded with id: ${binaryId}`);
230
- finalBinaryId = binaryId;
231
- }
232
- const testFormData = new FormData();
233
- // eslint-disable-next-line unicorn/no-array-reduce
234
- const envObject = (env ?? []).reduce((acc, cur) => {
235
- const [key, value] = cur.split('=');
236
- acc[key] = value;
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
- const options = {
258
- body: testFormData,
259
- headers: { 'x-app-api-key': apiKey },
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
@@ -1,6 +1,7 @@
1
1
  interface IExecutionPlan {
2
2
  flowsToRun: string[];
3
3
  sequence?: IFlowSequence | null;
4
+ totalFlowFiles: number;
4
5
  }
5
6
  interface IFlowSequence {
6
7
  continueOnFailure?: boolean;
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;
@@ -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
- "api-key": {
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": "api-key",
54
+ "name": "apiKey",
43
55
  "hasDynamicHelp": false,
44
56
  "multiple": false,
45
57
  "type": "option"
46
58
  },
47
- "api-url": {
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": "api-url",
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
- "default": "true",
89
- "hasDynamicHelp": false,
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-version": {
144
- "aliases": [
145
- "ios-version"
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
- "16.4",
153
- "17.2"
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.1-alpha.9"
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.1-alpha.9",
75
+ "version": "0.0.2",
79
76
  "bugs": {
80
77
  "url": "https://discord.gg/GzZBHcUJ"
81
78
  },