@devicecloud.dev/dcd 3.2.1 → 3.2.3

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.
@@ -50,6 +50,7 @@ export default class Cloud extends Command {
50
50
  'ignore-sha-check': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
51
51
  'ios-device': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
52
52
  'ios-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
53
+ 'x86-arch': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
53
54
  'maestro-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
54
55
  name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
55
56
  orientation: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -38,6 +38,15 @@ var EAndroidDevices;
38
38
  EAndroidDevices["pixel-7"] = "pixel-7";
39
39
  EAndroidDevices["pixel-7-pro"] = "pixel-7-pro";
40
40
  })(EAndroidDevices || (exports.EAndroidDevices = EAndroidDevices = {}));
41
+ // Suppress punycode deprecation warning (caused by whatwg, supabase dependancy)
42
+ process.removeAllListeners('warning');
43
+ process.on('warning', (warning) => {
44
+ if (warning.name === 'DeprecationWarning' &&
45
+ warning.message.includes('punycode')) {
46
+ return;
47
+ }
48
+ console.warn(warning);
49
+ });
41
50
  class Cloud extends core_1.Command {
42
51
  static args = {
43
52
  firstFile: core_1.Args.string({
@@ -62,7 +71,7 @@ class Cloud extends core_1.Command {
62
71
  }
63
72
  await (0, methods_1.versionCheck)(this.config.version);
64
73
  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: 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;
74
+ 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, 'x86-arch': x86Arch, ...rest } = flags;
66
75
  const apiKey = apiKeyFlag || process.env.DEVICE_CLOUD_API_KEY;
67
76
  if (!apiKey)
68
77
  throw new Error('You must provide an API key via --api-key flag or DEVICE_CLOUD_API_KEY environment variable');
@@ -121,9 +130,7 @@ class Cloud extends core_1.Command {
121
130
  executionPlan = await (0, plan_1.plan)(flowFile, includeTags.flat(), excludeTags.flat(), excludeFlows.flat());
122
131
  }
123
132
  catch (error) {
124
- console.error(error);
125
- // eslint-disable-next-line unicorn/no-process-exit
126
- process.exit(2);
133
+ throw error;
127
134
  }
128
135
  const { allExcludeTags, allIncludeTags, flowsToRun: testFileNames, referencedFiles, sequence, workspaceConfig, } = executionPlan;
129
136
  const pathsShortestToLongest = [
@@ -219,6 +226,7 @@ class Cloud extends core_1.Command {
219
226
  deviceLocale,
220
227
  maestroVersion,
221
228
  orientation,
229
+ x86Arch,
222
230
  report,
223
231
  raw: JSON.stringify(raw),
224
232
  uploadedBinaryIds,
@@ -255,91 +263,95 @@ class Cloud extends core_1.Command {
255
263
  const { message, results } = await (0, methods_1.typeSafePost)(apiUrl, '/uploads/flow', options);
256
264
  if (!results?.length)
257
265
  (0, errors_1.error)('No tests created: ' + message);
258
- (0, cli_ux_1.info)(`\nCreated ${results.length} tests: ${results
266
+ this.log(`\nCreated ${results.length} tests: ${results
259
267
  .map((r) => r.test_file_name)
260
268
  .sort((a, b) => a.localeCompare(b))
261
269
  .join(', ')}\n`);
262
- (0, cli_ux_1.info)('Run triggered, you can access the results at:');
270
+ this.log('Run triggered, you can access the results at:');
263
271
  const url = `https://console.devicecloud.dev/results?upload=${results[0].test_upload_id}&result=${results[0].id}`;
264
272
  core_1.ux.url(url, url);
265
273
  if (async) {
266
- (0, cli_ux_1.info)('Not waiting for results as async flag is set to true');
267
- (0, cli_ux_1.exit)(0);
274
+ this.log('Not waiting for results as async flag is set to true');
275
+ return;
268
276
  }
269
277
  // poll for the run status every 5 seconds
270
278
  core_1.ux.action.start('Waiting for results', 'Initializing', { stdout: true });
271
- (0, cli_ux_1.info)('\nYou can safely close this terminal and the tests will continue\n');
279
+ this.log('\nYou can safely close this terminal and the tests will continue\n');
272
280
  let sequentialPollFaillures = 0;
273
- const intervalId = setInterval(async () => {
274
- try {
275
- const { results: updatedResults } = await (0, methods_1.typeSafeGet)(apiUrl, `/results/${results[0].test_upload_id}`, {
276
- headers: { 'x-app-api-key': apiKey },
277
- });
278
- if (!updatedResults) {
279
- throw new Error('no results');
280
- }
281
- if (!quiet) {
282
- core_1.ux.action.status =
283
- '\nStatus Test\n─────────── ───────────────';
284
- for (const { retry_of: isRetry, status, test_file_name: test, } of updatedResults) {
285
- core_1.ux.action.status += `\n${status.padEnd(10, ' ')} ${test} ${isRetry ? '(retry)' : ''}`;
281
+ await new Promise((resolve, reject) => {
282
+ const intervalId = setInterval(async () => {
283
+ try {
284
+ const { results: updatedResults } = await (0, methods_1.typeSafeGet)(apiUrl, `/results/${results[0].test_upload_id}`, {
285
+ headers: { 'x-app-api-key': apiKey },
286
+ });
287
+ if (!updatedResults) {
288
+ throw new Error('no results');
286
289
  }
287
- }
288
- if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
289
- core_1.ux.action.stop('completed');
290
- (0, cli_ux_1.info)('\n');
291
- (0, cli_ux_1.table)(updatedResults, {
292
- status: { get: (row) => row.status },
293
- test: {
294
- get: (row) => `${row.test_file_name} ${row.retry_of ? '(retry)' : ''}`,
295
- },
296
- }, { printLine: this.log.bind(this) });
297
- (0, cli_ux_1.info)('\n');
298
- (0, cli_ux_1.info)('Run completed, you can access the results at:');
299
- core_1.ux.url(url, url);
300
- (0, cli_ux_1.info)('\n');
301
- clearInterval(intervalId);
302
- if (downloadArtifacts) {
303
- try {
304
- await (0, methods_1.typeSafePostDownload)(apiUrl, `/results/${results[0].test_upload_id}/download`, {
305
- body: JSON.stringify({ results: downloadArtifacts }),
306
- headers: {
307
- 'content-type': 'application/json',
308
- 'x-app-api-key': apiKey,
309
- },
310
- });
311
- (0, cli_ux_1.info)('\n');
312
- (0, cli_ux_1.info)('Test artifacts have been downloaded to ./artifacts.zip');
313
- }
314
- catch {
315
- this.warn('Failed to download artifacts');
290
+ if (!quiet) {
291
+ core_1.ux.action.status =
292
+ '\nStatus Test\n─────────── ───────────────';
293
+ for (const { retry_of: isRetry, status, test_file_name: test, } of updatedResults) {
294
+ core_1.ux.action.status += `\n${status.padEnd(10, ' ')} ${test} ${isRetry ? '(retry)' : ''}`;
316
295
  }
317
296
  }
318
- const resultsWithoutEarlierTries = updatedResults.filter((result) => {
319
- const beenRetried = updatedResults.find((r) => r.retry_of === result.id);
320
- return !beenRetried;
321
- });
322
- if (resultsWithoutEarlierTries.some((result) => result.status === 'FAILED')) {
323
- // eslint-disable-next-line unicorn/no-process-exit
324
- process.exit(2);
297
+ if (updatedResults.every((result) => !['PENDING', 'RUNNING'].includes(result.status))) {
298
+ core_1.ux.action.stop('completed');
299
+ this.log('\n');
300
+ (0, cli_ux_1.table)(updatedResults, {
301
+ status: { get: (row) => row.status },
302
+ test: {
303
+ get: (row) => `${row.test_file_name} ${row.retry_of ? '(retry)' : ''}`,
304
+ },
305
+ }, { printLine: this.log.bind(this) });
306
+ this.log('\n');
307
+ this.log('Run completed, you can access the results at:');
308
+ core_1.ux.url(url, url);
309
+ this.log('\n');
310
+ clearInterval(intervalId);
311
+ if (downloadArtifacts) {
312
+ try {
313
+ await (0, methods_1.typeSafePostDownload)(apiUrl, `/results/${results[0].test_upload_id}/download`, {
314
+ body: JSON.stringify({ results: downloadArtifacts }),
315
+ headers: {
316
+ 'content-type': 'application/json',
317
+ 'x-app-api-key': apiKey,
318
+ },
319
+ });
320
+ this.log('\n');
321
+ this.log('Test artifacts have been downloaded to ./artifacts.zip');
322
+ }
323
+ catch {
324
+ this.warn('Failed to download artifacts');
325
+ }
326
+ }
327
+ const resultsWithoutEarlierTries = updatedResults.filter((result) => {
328
+ const beenRetried = updatedResults.find((r) => r.retry_of === result.id);
329
+ return !beenRetried;
330
+ });
331
+ if (resultsWithoutEarlierTries.some((result) => result.status === 'FAILED')) {
332
+ reject(new Error('RUN_FAILED'));
333
+ }
334
+ sequentialPollFaillures = 0;
335
+ resolve();
325
336
  }
326
- sequentialPollFaillures = 0;
327
337
  }
328
- }
329
- catch {
330
- sequentialPollFaillures++;
331
- if (sequentialPollFaillures > 5) {
332
- // dropped poll requests shouldn't err user CI
333
- clearInterval(intervalId);
334
- throw new Error('unable to fetch results');
338
+ catch (error) {
339
+ sequentialPollFaillures++;
340
+ if (sequentialPollFaillures > 5) {
341
+ // dropped poll requests shouldn't err user CI
342
+ clearInterval(intervalId);
343
+ throw new Error('unable to fetch results after 5 attempts');
344
+ }
345
+ this.log('unable to fetch results, trying again...');
335
346
  }
336
- (0, cli_ux_1.info)('unable to fetch results, trying again...');
337
- }
338
- }, 5000);
347
+ }, 5000);
348
+ });
339
349
  }
340
350
  catch (error) {
341
- console.error(error);
342
- (0, cli_ux_1.exit)(1);
351
+ if (error instanceof Error && error.message === 'RUN_FAILED') {
352
+ this.exit(2);
353
+ }
354
+ this.error(error, { exit: 1 });
343
355
  }
344
356
  }
345
357
  }
@@ -20,6 +20,7 @@ export declare const flags: {
20
20
  'ignore-sha-check': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
21
  'ios-device': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
22
  'ios-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
23
+ 'x86-arch': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
23
24
  'maestro-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
24
25
  name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
25
26
  orientation: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
package/dist/constants.js CHANGED
@@ -50,7 +50,7 @@ exports.flags = {
50
50
  description: 'App binary to run your flows against',
51
51
  }),
52
52
  async: core_1.Flags.boolean({
53
- description: 'Wait for the results of the run',
53
+ description: 'Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)',
54
54
  }),
55
55
  'device-locale': core_1.Flags.string({
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',
@@ -122,6 +122,10 @@ exports.flags = {
122
122
  description: '[iOS only] iOS version to run your flow against',
123
123
  options: ['16', '17', '18'],
124
124
  }),
125
+ 'x86-arch': core_1.Flags.boolean({
126
+ description: '[iOS only, experimental] Run your flow against x86 architecture simulator instead of arm64',
127
+ default: false,
128
+ }),
125
129
  'maestro-version': core_1.Flags.string({
126
130
  aliases: ['maestroVersion'],
127
131
  default: '1.39.5',
package/dist/methods.d.ts CHANGED
@@ -14,10 +14,10 @@ export declare const typeSafeGet: <T extends keyof paths>(baseUrl: string, path:
14
14
  body?: FormData;
15
15
  headers?: HeadersInit;
16
16
  }) => Promise<paths[T]["get"]["responses"]["200"]["content"]["application/json"]>;
17
- export declare const toBuffer: (archive: archiver.Archiver) => Promise<Buffer>;
18
- export declare const compressDir: (sourceDir: string) => Promise<Buffer>;
17
+ export declare const toBuffer: (archive: archiver.Archiver) => Promise<Buffer<ArrayBuffer>>;
18
+ export declare const compressDir: (sourceDir: string) => Promise<Buffer<ArrayBuffer>>;
19
19
  export declare const compressFolderToBlob: (sourceDir: string) => Promise<Blob>;
20
- export declare const compressFilesFromRelativePath: (path: string, files: string[], commonRoot: string) => Promise<Buffer>;
20
+ export declare const compressFilesFromRelativePath: (path: string, files: string[], commonRoot: string) => Promise<Buffer<ArrayBuffer>>;
21
21
  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>;
package/dist/methods.js CHANGED
@@ -33,7 +33,7 @@ const versionCheck = async (currentVersion) => {
33
33
  const versionResponseJson = await versionResponse.json();
34
34
  const latestVersion = versionResponseJson.version;
35
35
  if (latestVersion !== currentVersion) {
36
- core_1.ux.warn(`
36
+ console.log(`
37
37
  -------------------
38
38
  A new version of the devicecloud.dev CLI is available: ${latestVersion}
39
39
  Run 'npm install -g @devicecloud.dev/dcd@latest' to update to the latest version
@@ -272,16 +272,16 @@ const uploadBinary = async (filePath, apiUrl, apiKey, ignoreShaCheck = false) =>
272
272
  // this needs to made nicer by using envs or maybe fetching the keys from the getSignedURL call
273
273
  const SB = {
274
274
  dev: {
275
- SUPABASE_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxibXNvd2VodGp3bnFsdXJwZW1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDkyMTg0ODcsImV4cCI6MjAyNDc5NDQ4N30.zeLTMAuZ_WwYvGdeP0kdvL_Zrs-RQee5APPyxmWq7qQ',
275
+ SUPABASE_PUBLIC_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxibXNvd2VodGp3bnFsdXJwZW1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDkyMTg0ODcsImV4cCI6MjAyNDc5NDQ4N30.zeLTMAuZ_WwYvGdeP0kdvL_Zrs-RQee5APPyxmWq7qQ',
276
276
  SUPABASE_URL: 'https://lbmsowehtjwnqlurpemb.supabase.co',
277
277
  },
278
278
  prod: {
279
- SUPABASE_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBneWRucGhiaW1ldGluc2dma2JvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDc1OTQzNDYsImV4cCI6MjAyMzE3MDM0Nn0.hAYOMFxxwX1exkQkY9xyQJGC_GhGnyogkj2N-kBkMI8',
279
+ SUPABASE_PUBLIC_KEY: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBneWRucGhiaW1ldGluc2dma2JvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDc1OTQzNDYsImV4cCI6MjAyMzE3MDM0Nn0.hAYOMFxxwX1exkQkY9xyQJGC_GhGnyogkj2N-kBkMI8',
280
280
  SUPABASE_URL: 'https://pgydnphbimetinsgfkbo.supabase.co',
281
281
  },
282
282
  };
283
- const { SUPABASE_KEY, SUPABASE_URL } = SB[apiUrl === 'https://api.devicecloud.dev' ? 'prod' : 'dev'];
284
- const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_KEY);
283
+ const { SUPABASE_PUBLIC_KEY, SUPABASE_URL } = SB[apiUrl === 'https://api.devicecloud.dev' ? 'prod' : 'dev'];
284
+ const supabase = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_PUBLIC_KEY);
285
285
  const uploadToUrl = await supabase.storage
286
286
  .from('organizations')
287
287
  .uploadToSignedUrl(path, token, file);
@@ -109,7 +109,7 @@
109
109
  "type": "option"
110
110
  },
111
111
  "async": {
112
- "description": "Wait for the results of the run",
112
+ "description": "Immediately return (exit code 0) from the command without waiting for the results of the run (useful for saving CI minutes)",
113
113
  "name": "async",
114
114
  "allowNo": false,
115
115
  "type": "boolean"
@@ -227,6 +227,12 @@
227
227
  ],
228
228
  "type": "option"
229
229
  },
230
+ "x86-arch": {
231
+ "description": "[iOS only, experimental] Run your flow against x86 architecture simulator instead of arm64",
232
+ "name": "x86-arch",
233
+ "allowNo": false,
234
+ "type": "boolean"
235
+ },
230
236
  "maestro-version": {
231
237
  "aliases": [
232
238
  "maestroVersion"
@@ -323,5 +329,5 @@
323
329
  ]
324
330
  }
325
331
  },
326
- "version": "3.2.1"
332
+ "version": "3.2.3"
327
333
  }
package/package.json CHANGED
@@ -80,7 +80,7 @@
80
80
  "test": "mocha --forbid-only \"test/**/*.test.ts\"",
81
81
  "version": "oclif readme && git add README.md"
82
82
  },
83
- "version": "3.2.1",
83
+ "version": "3.2.3",
84
84
  "bugs": {
85
85
  "url": "https://discord.gg/gm3mJwcNw8"
86
86
  },