@devicecloud.dev/dcd 5.0.0-beta.0 → 5.0.0-beta.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.
Files changed (101) hide show
  1. package/README.md +35 -0
  2. package/dist/commands/artifacts.d.ts +28 -28
  3. package/dist/commands/artifacts.js +20 -23
  4. package/dist/commands/cloud.d.ts +57 -57
  5. package/dist/commands/cloud.js +173 -186
  6. package/dist/commands/list.d.ts +22 -22
  7. package/dist/commands/list.js +36 -38
  8. package/dist/commands/live.js +134 -127
  9. package/dist/commands/login.d.ts +11 -11
  10. package/dist/commands/login.js +46 -44
  11. package/dist/commands/logout.js +16 -18
  12. package/dist/commands/status.d.ts +11 -11
  13. package/dist/commands/status.js +45 -43
  14. package/dist/commands/switch-org.d.ts +7 -7
  15. package/dist/commands/switch-org.js +19 -21
  16. package/dist/commands/upgrade.js +29 -31
  17. package/dist/commands/upload.d.ts +10 -10
  18. package/dist/commands/upload.js +42 -43
  19. package/dist/commands/whoami.js +17 -20
  20. package/dist/config/environments.js +6 -12
  21. package/dist/config/flags/api.flags.js +1 -4
  22. package/dist/config/flags/binary.flags.js +1 -4
  23. package/dist/config/flags/device.flags.js +6 -9
  24. package/dist/config/flags/environment.flags.js +1 -4
  25. package/dist/config/flags/execution.flags.js +1 -4
  26. package/dist/config/flags/github.flags.js +1 -4
  27. package/dist/config/flags/output.flags.js +1 -4
  28. package/dist/constants.js +15 -18
  29. package/dist/gateways/api-gateway.d.ts +31 -6
  30. package/dist/gateways/api-gateway.js +70 -16
  31. package/dist/gateways/cli-auth-gateway.d.ts +1 -1
  32. package/dist/gateways/cli-auth-gateway.js +3 -6
  33. package/dist/gateways/realtime-gateway.d.ts +32 -0
  34. package/dist/gateways/realtime-gateway.js +103 -0
  35. package/dist/gateways/supabase-gateway.d.ts +1 -1
  36. package/dist/gateways/supabase-gateway.js +10 -14
  37. package/dist/index.js +41 -38
  38. package/dist/mcp/context.d.ts +33 -0
  39. package/dist/mcp/context.js +33 -0
  40. package/dist/mcp/helpers.d.ts +16 -0
  41. package/dist/mcp/helpers.js +34 -0
  42. package/dist/mcp/index.d.ts +2 -0
  43. package/dist/mcp/index.js +24 -0
  44. package/dist/mcp/server.d.ts +7 -0
  45. package/dist/mcp/server.js +27 -0
  46. package/dist/mcp/tools/download-artifacts.d.ts +11 -0
  47. package/dist/mcp/tools/download-artifacts.js +84 -0
  48. package/dist/mcp/tools/get-status.d.ts +7 -0
  49. package/dist/mcp/tools/get-status.js +39 -0
  50. package/dist/mcp/tools/list-devices.d.ts +7 -0
  51. package/dist/mcp/tools/list-devices.js +27 -0
  52. package/dist/mcp/tools/list-runs.d.ts +3 -0
  53. package/dist/mcp/tools/list-runs.js +60 -0
  54. package/dist/mcp/tools/run-cloud-test.d.ts +14 -0
  55. package/dist/mcp/tools/run-cloud-test.js +233 -0
  56. package/dist/methods.d.ts +32 -1
  57. package/dist/methods.js +125 -66
  58. package/dist/services/device-validation.service.d.ts +1 -1
  59. package/dist/services/device-validation.service.js +1 -5
  60. package/dist/services/execution-plan.service.js +14 -17
  61. package/dist/services/execution-plan.utils.js +15 -23
  62. package/dist/services/flow-paths.d.ts +17 -0
  63. package/dist/services/flow-paths.js +52 -0
  64. package/dist/services/metadata-extractor.service.js +22 -25
  65. package/dist/services/moropo.service.js +18 -20
  66. package/dist/services/report-download.service.d.ts +1 -1
  67. package/dist/services/report-download.service.js +5 -9
  68. package/dist/services/results-polling.service.d.ts +18 -3
  69. package/dist/services/results-polling.service.js +195 -108
  70. package/dist/services/telemetry.service.d.ts +10 -1
  71. package/dist/services/telemetry.service.js +40 -18
  72. package/dist/services/test-submission.service.d.ts +21 -4
  73. package/dist/services/test-submission.service.js +51 -34
  74. package/dist/services/version.service.d.ts +1 -1
  75. package/dist/services/version.service.js +1 -5
  76. package/dist/types/domain/auth.types.d.ts +8 -0
  77. package/dist/types/domain/auth.types.js +1 -2
  78. package/dist/types/domain/device.types.js +8 -11
  79. package/dist/types/domain/live.types.js +1 -2
  80. package/dist/types/generated/schema.types.js +1 -2
  81. package/dist/types/index.d.ts +2 -2
  82. package/dist/types/index.js +2 -18
  83. package/dist/types.js +1 -2
  84. package/dist/utils/auth.d.ts +1 -1
  85. package/dist/utils/auth.js +27 -28
  86. package/dist/utils/ci.d.ts +12 -0
  87. package/dist/utils/ci.js +39 -0
  88. package/dist/utils/cli.js +18 -27
  89. package/dist/utils/compatibility.d.ts +1 -1
  90. package/dist/utils/compatibility.js +5 -7
  91. package/dist/utils/config-store.js +33 -43
  92. package/dist/utils/connectivity.js +1 -4
  93. package/dist/utils/expo.js +15 -21
  94. package/dist/utils/orgs.js +8 -12
  95. package/dist/utils/paths.js +2 -5
  96. package/dist/utils/progress.js +2 -5
  97. package/dist/utils/styling.d.ts +35 -37
  98. package/dist/utils/styling.js +52 -86
  99. package/dist/utils/ui.d.ts +41 -0
  100. package/dist/utils/ui.js +95 -0
  101. package/package.json +27 -24
package/dist/methods.js CHANGED
@@ -1,20 +1,17 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatDurationSeconds = exports.writeJSONFile = exports.uploadBinary = exports.verifyAppZip = exports.compressFilesFromRelativePath = void 0;
4
- const progress_1 = require("./utils/progress");
5
- const node_crypto_1 = require("node:crypto");
6
- const node_fs_1 = require("node:fs");
7
- const promises_1 = require("node:fs/promises");
8
- const os = require("node:os");
9
- const path = require("node:path");
10
- const promises_2 = require("node:stream/promises");
11
- const StreamZip = require("node-stream-zip");
12
- const yazl = require("yazl");
13
- const environments_1 = require("./config/environments");
14
- const api_gateway_1 = require("./gateways/api-gateway");
15
- const supabase_gateway_1 = require("./gateways/supabase-gateway");
16
- const metadata_extractor_service_1 = require("./services/metadata-extractor.service");
17
- const styling_1 = require("./utils/styling");
1
+ import { ux } from './utils/progress.js';
2
+ import { createHash } from 'node:crypto';
3
+ import { createReadStream, createWriteStream, mkdirSync, readdirSync, writeFileSync, } from 'node:fs';
4
+ import { access, mkdtemp, readFile, rm, stat, writeFile } from 'node:fs/promises';
5
+ import * as os from 'node:os';
6
+ import * as path from 'node:path';
7
+ import { pipeline } from 'node:stream/promises';
8
+ import StreamZip from 'node-stream-zip';
9
+ import * as yazl from 'yazl';
10
+ import { inferEnvFromApiUrl } from './config/environments.js';
11
+ import { ApiError, ApiGateway } from './gateways/api-gateway.js';
12
+ import { SupabaseGateway } from './gateways/supabase-gateway.js';
13
+ import { MetadataExtractorService } from './services/metadata-extractor.service.js';
14
+ import { colors, formatId } from './utils/styling.js';
18
15
  const mimeTypeLookupByExtension = {
19
16
  apk: 'application/vnd.android.package-archive',
20
17
  yaml: 'application/x-yaml',
@@ -44,7 +41,7 @@ function toZipEntryName(relativePath) {
44
41
  async function compressFolderToTempZip(sourceDir) {
45
42
  const zipfile = new yazl.ZipFile();
46
43
  const rootName = path.basename(sourceDir);
47
- const entries = (0, node_fs_1.readdirSync)(sourceDir, {
44
+ const entries = readdirSync(sourceDir, {
48
45
  recursive: true,
49
46
  withFileTypes: true,
50
47
  });
@@ -55,13 +52,13 @@ async function compressFolderToTempZip(sourceDir) {
55
52
  const relativePath = path.relative(sourceDir, absolutePath);
56
53
  zipfile.addFile(absolutePath, toZipEntryName(path.join(rootName, relativePath)));
57
54
  }
58
- const tempDir = await (0, promises_1.mkdtemp)(path.join(os.tmpdir(), 'dcd-app-zip-'));
55
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), 'dcd-app-zip-'));
59
56
  const zipPath = path.join(tempDir, `${rootName}.zip`);
60
57
  zipfile.end();
61
- await (0, promises_2.pipeline)(zipfile.outputStream, (0, node_fs_1.createWriteStream)(zipPath));
58
+ await pipeline(zipfile.outputStream, createWriteStream(zipPath));
62
59
  return { tempDir, zipPath };
63
60
  }
64
- const compressFilesFromRelativePath = async (basePath, files, commonRoot) => {
61
+ export const compressFilesFromRelativePath = async (basePath, files, commonRoot) => {
65
62
  const zipfile = new yazl.ZipFile();
66
63
  for (const file of files) {
67
64
  // Anchored prefix strip — replace() would remove the first occurrence
@@ -70,8 +67,7 @@ const compressFilesFromRelativePath = async (basePath, files, commonRoot) => {
70
67
  }
71
68
  return zipToBuffer(zipfile);
72
69
  };
73
- exports.compressFilesFromRelativePath = compressFilesFromRelativePath;
74
- const verifyAppZip = async (zipPath) => {
70
+ export const verifyAppZip = async (zipPath) => {
75
71
  // eslint-disable-next-line import/namespace, new-cap
76
72
  const zip = await new StreamZip.async({
77
73
  file: zipPath,
@@ -96,11 +92,10 @@ const verifyAppZip = async (zipPath) => {
96
92
  zip.close();
97
93
  }
98
94
  };
99
- exports.verifyAppZip = verifyAppZip;
100
- const uploadBinary = async (config) => {
95
+ export const uploadBinary = async (config) => {
101
96
  const { filePath, apiUrl, auth, ignoreShaCheck = false, log = true, debug = false } = config;
102
97
  if (log) {
103
- progress_1.ux.action.start(styling_1.colors.bold('Checking and uploading binary'), styling_1.colors.dim('Initializing'), {
98
+ ux.action.start(colors.bold('Checking and uploading binary'), colors.dim('Initializing'), {
104
99
  stdout: true,
105
100
  });
106
101
  }
@@ -122,8 +117,8 @@ const uploadBinary = async (config) => {
122
117
  const { exists, binaryId } = await checkExistingUpload(apiUrl, auth, sha, debug);
123
118
  if (exists && binaryId) {
124
119
  if (log) {
125
- progress_1.ux.info(styling_1.colors.dim('SHA hash matches existing binary with ID: ') + (0, styling_1.formatId)(binaryId) + styling_1.colors.dim(', skipping upload. Force upload with --ignore-sha-check'));
126
- progress_1.ux.action.stop(styling_1.colors.info('Skipping upload'));
120
+ ux.info(colors.dim('SHA hash matches existing binary with ID: ') + formatId(binaryId) + colors.dim(', skipping upload. Force upload with --ignore-sha-check'));
121
+ ux.action.stop(colors.info('Skipping upload'));
127
122
  }
128
123
  return binaryId;
129
124
  }
@@ -131,13 +126,13 @@ const uploadBinary = async (config) => {
131
126
  // Perform the upload
132
127
  const uploadId = await performUpload({ auth, apiUrl, debug, filePath, sha, source, startTime });
133
128
  if (log) {
134
- progress_1.ux.action.stop(styling_1.colors.success('\n✓ Binary uploaded with ID: ') + (0, styling_1.formatId)(uploadId));
129
+ ux.action.stop(colors.success('\n✓ Binary uploaded with ID: ') + formatId(uploadId));
135
130
  }
136
131
  return uploadId;
137
132
  }
138
133
  catch (error) {
139
134
  if (log) {
140
- progress_1.ux.action.stop(styling_1.colors.error('✗ Failed'));
135
+ ux.action.stop(colors.error('✗ Failed'));
141
136
  }
142
137
  if (debug) {
143
138
  console.error('[DEBUG] === BINARY UPLOAD FAILED ===');
@@ -153,11 +148,10 @@ const uploadBinary = async (config) => {
153
148
  }
154
149
  finally {
155
150
  if (source?.cleanupDir) {
156
- await (0, promises_1.rm)(source.cleanupDir, { recursive: true, force: true }).catch(() => { });
151
+ await rm(source.cleanupDir, { recursive: true, force: true }).catch(() => { });
157
152
  }
158
153
  }
159
154
  };
160
- exports.uploadBinary = uploadBinary;
161
155
  /**
162
156
  * Prepares a file for upload: .app directories are zipped to a temp file on
163
157
  * disk; everything else is described in place. Nothing is read into memory.
@@ -178,7 +172,7 @@ async function prepareFileForUpload(filePath, debug, startTime) {
178
172
  // Validate that the .app directory exists before attempting to compress —
179
173
  // zipping a non-existent path silently produces an empty 22-byte zip.
180
174
  try {
181
- await (0, promises_1.access)(filePath);
175
+ await access(filePath);
182
176
  }
183
177
  catch {
184
178
  // Provide helpful error message for common quoting issues
@@ -201,7 +195,7 @@ async function prepareFileForUpload(filePath, debug, startTime) {
201
195
  throw new Error(errorMessage);
202
196
  }
203
197
  const { tempDir, zipPath } = await compressFolderToTempZip(filePath);
204
- const { size } = await (0, promises_1.stat)(zipPath);
198
+ const { size } = await stat(zipPath);
205
199
  source = {
206
200
  contentType: 'application/zip',
207
201
  cleanupDir: tempDir,
@@ -214,7 +208,7 @@ async function prepareFileForUpload(filePath, debug, startTime) {
214
208
  }
215
209
  }
216
210
  else {
217
- const { size } = await (0, promises_1.stat)(filePath);
211
+ const { size } = await stat(filePath);
218
212
  if (debug) {
219
213
  console.log(`[DEBUG] File size: ${(size / 1024 / 1024).toFixed(2)} MB`);
220
214
  }
@@ -276,7 +270,7 @@ async function checkExistingUpload(apiUrl, auth, sha, debug) {
276
270
  console.log(`[DEBUG] Target endpoint: ${apiUrl}/uploads/checkForExistingUpload`);
277
271
  }
278
272
  const shaCheckStartTime = Date.now();
279
- const { appBinaryId, exists } = await api_gateway_1.ApiGateway.checkForExistingUpload(apiUrl, auth, sha);
273
+ const { appBinaryId, exists } = await ApiGateway.checkForExistingUpload(apiUrl, auth, sha);
280
274
  if (debug) {
281
275
  console.log(`[DEBUG] SHA check completed in ${Date.now() - shaCheckStartTime}ms`);
282
276
  console.log(`[DEBUG] Existing binary found: ${exists}`);
@@ -289,7 +283,7 @@ async function checkExistingUpload(apiUrl, auth, sha, debug) {
289
283
  catch (error) {
290
284
  // Invalid credentials will fail every subsequent request — surface now
291
285
  // rather than after the user has waited through a potentially huge upload.
292
- if (error instanceof api_gateway_1.ApiError && (error.status === 401 || error.status === 403)) {
286
+ if (error instanceof ApiError && (error.status === 401 || error.status === 403)) {
293
287
  throw error;
294
288
  }
295
289
  if (debug) {
@@ -325,7 +319,7 @@ async function uploadToSupabase(env, tempPath, source, debug) {
325
319
  }
326
320
  try {
327
321
  const uploadStartTime = Date.now();
328
- await supabase_gateway_1.SupabaseGateway.uploadResumable(env, tempPath, source, debug);
322
+ await SupabaseGateway.uploadResumable(env, tempPath, source, debug);
329
323
  if (debug) {
330
324
  const uploadDuration = Date.now() - uploadStartTime;
331
325
  const uploadDurationSeconds = uploadDuration / 1000;
@@ -428,7 +422,7 @@ async function requestUploadPaths(apiUrl, auth, filePath, fileSize, debug) {
428
422
  }
429
423
  try {
430
424
  const urlRequestStartTime = Date.now();
431
- const { id, tempPath, finalPath, b2 } = await api_gateway_1.ApiGateway.getBinaryUploadUrl(apiUrl, auth, platform, fileSize);
425
+ const { id, tempPath, finalPath, b2 } = await ApiGateway.getBinaryUploadUrl(apiUrl, auth, platform, fileSize);
432
426
  if (debug) {
433
427
  const hasStrategy = b2 && typeof b2 === 'object' && 'strategy' in b2;
434
428
  console.log(`[DEBUG] Upload URL request completed in ${Date.now() - urlRequestStartTime}ms`);
@@ -451,9 +445,11 @@ async function requestUploadPaths(apiUrl, auth, filePath, fileSize, debug) {
451
445
  // Add context to the error
452
446
  if (error instanceof Error) {
453
447
  if (error.name === 'NetworkError') {
454
- throw new Error(`Failed to request upload URL from API.\n\n${error.message}`);
448
+ throw new Error(`Failed to request upload URL from API.\n\n${error.message}`, { cause: error });
455
449
  }
456
- throw new Error(`Failed to request upload URL: ${error.message}`);
450
+ throw new Error(`Failed to request upload URL: ${error.message}`, {
451
+ cause: error,
452
+ });
457
453
  }
458
454
  throw error;
459
455
  }
@@ -467,7 +463,7 @@ async function requestUploadPaths(apiUrl, auth, filePath, fileSize, debug) {
467
463
  async function extractBinaryMetadata(filePath, debug) {
468
464
  if (debug)
469
465
  console.log('[DEBUG] Extracting app metadata...');
470
- const metadataExtractor = new metadata_extractor_service_1.MetadataExtractorService();
466
+ const metadataExtractor = new MetadataExtractorService();
471
467
  const metadata = await metadataExtractor.extract(filePath);
472
468
  if (!metadata) {
473
469
  throw new Error(`Failed to extract metadata from ${filePath}. Supported formats: .apk, .app, .zip`);
@@ -513,7 +509,7 @@ async function performUpload(config) {
513
509
  const { id, tempPath, finalPath, b2 } = await requestUploadPaths(apiUrl, auth, filePath, source.size, debug);
514
510
  // Extract app metadata
515
511
  const metadata = await extractBinaryMetadata(filePath, debug);
516
- const env = (0, environments_1.inferEnvFromApiUrl)(apiUrl);
512
+ const env = inferEnvFromApiUrl(apiUrl);
517
513
  // Upload to Backblaze first (primary)
518
514
  const backblazeResult = await handleBackblazeUpload({
519
515
  auth,
@@ -525,11 +521,10 @@ async function performUpload(config) {
525
521
  });
526
522
  let lastError = backblazeResult.error;
527
523
  // Always upload to Supabase (re-enabled as always-on alongside Backblaze)
528
- let supabaseResult = { error: null, success: false };
529
524
  if (debug) {
530
525
  console.log('[DEBUG] Uploading to Supabase...');
531
526
  }
532
- supabaseResult = await uploadToSupabase(env, tempPath, source, debug);
527
+ const supabaseResult = await uploadToSupabase(env, tempPath, source, debug);
533
528
  if (!supabaseResult.success && supabaseResult.error) {
534
529
  lastError = supabaseResult.error;
535
530
  }
@@ -549,7 +544,7 @@ async function performUpload(config) {
549
544
  }
550
545
  // Finalize upload
551
546
  const finalizeStartTime = Date.now();
552
- await api_gateway_1.ApiGateway.finaliseUpload({
547
+ await ApiGateway.finaliseUpload({
553
548
  auth,
554
549
  backblazeSuccess: backblazeResult.success,
555
550
  baseUrl: apiUrl,
@@ -567,6 +562,70 @@ async function performUpload(config) {
567
562
  }
568
563
  return id;
569
564
  }
565
+ /**
566
+ * Uploads an already-built flow zip directly to storage, mirroring the binary
567
+ * client-direct path: getFlowUploadUrl → upload to storage (Backblaze if
568
+ * offered + TUS to Supabase) → return the storage reference for submitFlowTest.
569
+ *
570
+ * The zip arrives as an in-memory Buffer (from `compressFilesFromRelativePath`);
571
+ * it's written to a temp file so the exact same disk-streaming uploaders the
572
+ * binary path uses can be reused unchanged. `supabaseSuccess`/`backblazeSuccess`
573
+ * are reported honestly based on which uploads succeeded.
574
+ *
575
+ * Errors from `getFlowUploadUrl` propagate untouched so callers can detect a
576
+ * 404 from an older API and fall back to the legacy multipart endpoint.
577
+ */
578
+ export const uploadFlowZip = async (config) => {
579
+ const { apiUrl, auth, buffer, debug = false } = config;
580
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), 'dcd-flow-zip-'));
581
+ const diskPath = path.join(tempDir, 'flowFile.zip');
582
+ try {
583
+ await writeFile(diskPath, buffer);
584
+ const source = {
585
+ contentType: 'application/zip',
586
+ diskPath,
587
+ name: 'flowFile.zip',
588
+ size: buffer.length,
589
+ };
590
+ // Same response shape as getBinaryUploadUrl. A 404 here means the API
591
+ // predates the client-direct flow path — let it propagate so the caller
592
+ // falls back to the multipart endpoint.
593
+ const { id, tempPath, finalPath, b2 } = await ApiGateway.getFlowUploadUrl(apiUrl, auth, buffer.length);
594
+ if (!tempPath)
595
+ throw new Error('No upload path provided by API');
596
+ const env = inferEnvFromApiUrl(apiUrl);
597
+ // Upload to Backblaze first (primary, if configured)
598
+ const backblazeResult = await handleBackblazeUpload({
599
+ auth,
600
+ apiUrl,
601
+ b2: b2,
602
+ debug,
603
+ finalPath,
604
+ source,
605
+ });
606
+ let lastError = backblazeResult.error;
607
+ // Always upload to Supabase (always-on alongside Backblaze)
608
+ const supabaseResult = await uploadToSupabase(env, tempPath, source, debug);
609
+ if (!supabaseResult.success && supabaseResult.error) {
610
+ lastError = supabaseResult.error;
611
+ }
612
+ validateUploadResults(supabaseResult.success, backblazeResult.success, lastError, b2, debug);
613
+ if (debug) {
614
+ console.log(`[DEBUG] Flow zip upload summary - Backblaze: ${backblazeResult.success ? '✓' : '✗'}, Supabase: ${supabaseResult.success ? '✓' : '✗'}`);
615
+ }
616
+ return {
617
+ backblazeSuccess: backblazeResult.success,
618
+ bytes: buffer.length,
619
+ id,
620
+ path: tempPath,
621
+ supabaseSuccess: supabaseResult.success,
622
+ useTus: true,
623
+ };
624
+ }
625
+ finally {
626
+ await rm(tempDir, { recursive: true, force: true }).catch(() => { });
627
+ }
628
+ };
570
629
  /**
571
630
  * Upload file to Backblaze using signed URL (simple upload for files < 100MB)
572
631
  * @param uploadUrl - Backblaze upload URL
@@ -582,9 +641,9 @@ async function uploadToBackblaze(uploadUrl, authorizationToken, fileName, source
582
641
  // get the multi-part path), so one transient buffer here is bounded.
583
642
  // S3 pre-signed PUTs reject chunked transfer encoding, which rules out a
584
643
  // plain stream body.
585
- const body = await (0, promises_1.readFile)(source.diskPath);
644
+ const body = await readFile(source.diskPath);
586
645
  // Calculate SHA1 hash for Backblaze (B2 requires SHA1, not SHA256)
587
- const sha1 = (0, node_crypto_1.createHash)('sha1');
646
+ const sha1 = createHash('sha1');
588
647
  sha1.update(body);
589
648
  const sha1Hex = sha1.digest('hex');
590
649
  // Detect if this is an S3 pre-signed URL (authorization token is empty)
@@ -660,7 +719,7 @@ async function uploadToBackblaze(uploadUrl, authorizationToken, fileName, source
660
719
  async function readFileChunk(filePath, start, end) {
661
720
  return new Promise((resolve, reject) => {
662
721
  const chunks = [];
663
- const stream = (0, node_fs_1.createReadStream)(filePath, { start, end: end - 1 }); // end is inclusive in createReadStream
722
+ const stream = createReadStream(filePath, { start, end: end - 1 }); // end is inclusive in createReadStream
664
723
  stream.on('data', (chunk) => {
665
724
  chunks.push(chunk);
666
725
  });
@@ -678,7 +737,7 @@ async function readFileChunk(filePath, start, end) {
678
737
  * @returns SHA1 hash as hex string
679
738
  */
680
739
  function calculateSha1(buffer) {
681
- const sha1 = (0, node_crypto_1.createHash)('sha1');
740
+ const sha1 = createHash('sha1');
682
741
  sha1.update(buffer);
683
742
  return sha1.digest('hex');
684
743
  }
@@ -716,7 +775,9 @@ async function uploadPartToBackblaze(config) {
716
775
  if (debug) {
717
776
  console.error(`[DEBUG] Network error uploading part ${partNumber} - could be DNS, connection, or SSL issue`);
718
777
  }
719
- throw new Error(`Part ${partNumber} upload failed due to network error`);
778
+ throw new Error(`Part ${partNumber} upload failed due to network error`, {
779
+ cause: error,
780
+ });
720
781
  }
721
782
  throw error;
722
783
  }
@@ -790,7 +851,7 @@ async function uploadLargeFileToBackblaze(config) {
790
851
  console.log('[DEBUG] Finishing large file upload...');
791
852
  console.log(`[DEBUG] Finalizing ${partSha1Array.length} parts with fileId: ${fileId}`);
792
853
  }
793
- await api_gateway_1.ApiGateway.finishLargeFile(apiUrl, auth, fileId, partSha1Array);
854
+ await ApiGateway.finishLargeFile(apiUrl, auth, fileId, partSha1Array);
794
855
  if (debug)
795
856
  console.log('[DEBUG] Large file upload completed successfully');
796
857
  return true;
@@ -801,8 +862,8 @@ async function uploadLargeFileToBackblaze(config) {
801
862
  }
802
863
  }
803
864
  async function getFileHashFromPath(filePath) {
804
- const hash = (0, node_crypto_1.createHash)('sha256');
805
- for await (const chunk of (0, node_fs_1.createReadStream)(filePath)) {
865
+ const hash = createHash('sha256');
866
+ for await (const chunk of createReadStream(filePath)) {
806
867
  hash.update(chunk);
807
868
  }
808
869
  return hash.digest('hex');
@@ -814,37 +875,36 @@ async function getFileHashFromPath(filePath) {
814
875
  * @param logger - Logger object with log and warn methods
815
876
  * @returns true if successful, false if an error occurred
816
877
  */
817
- const writeJSONFile = (filePath, data, logger) => {
878
+ export const writeJSONFile = (filePath, data, logger) => {
818
879
  try {
819
880
  const directory = path.dirname(filePath);
820
881
  if (directory !== '.') {
821
- (0, node_fs_1.mkdirSync)(directory, { recursive: true });
882
+ mkdirSync(directory, { recursive: true });
822
883
  }
823
- (0, node_fs_1.writeFileSync)(filePath, JSON.stringify(data, null, 2));
824
- logger.log(styling_1.colors.dim('JSON output written to: ') + styling_1.colors.highlight(path.resolve(filePath)));
884
+ writeFileSync(filePath, JSON.stringify(data, null, 2));
885
+ logger.log(colors.dim('JSON output written to: ') + colors.highlight(path.resolve(filePath)));
825
886
  }
826
887
  catch (error) {
827
888
  const errorMessage = error instanceof Error ? error.message : String(error);
828
889
  const isPermissionError = errorMessage.includes('EACCES') || errorMessage.includes('EPERM');
829
890
  const isNoSuchFileError = errorMessage.includes('ENOENT');
830
- logger.warn(styling_1.colors.error(`Failed to write JSON output to file: ${filePath}`));
891
+ logger.warn(colors.error(`Failed to write JSON output to file: ${filePath}`));
831
892
  if (isPermissionError) {
832
- logger.warn(styling_1.colors.dim(' Permission denied - check file/directory write permissions'));
833
- logger.warn(styling_1.colors.dim(' Try running with appropriate permissions or choose a different output location'));
893
+ logger.warn(colors.dim(' Permission denied - check file/directory write permissions'));
894
+ logger.warn(colors.dim(' Try running with appropriate permissions or choose a different output location'));
834
895
  }
835
896
  else if (isNoSuchFileError) {
836
- logger.warn(styling_1.colors.dim(' Directory does not exist - create the directory first or choose an existing path'));
897
+ logger.warn(colors.dim(' Directory does not exist - create the directory first or choose an existing path'));
837
898
  }
838
- logger.warn(styling_1.colors.dim(' Error details: ') + errorMessage);
899
+ logger.warn(colors.dim(' Error details: ') + errorMessage);
839
900
  }
840
901
  };
841
- exports.writeJSONFile = writeJSONFile;
842
902
  /**
843
903
  * Formats duration in seconds into a human readable string
844
904
  * @param durationSeconds - Duration in seconds
845
905
  * @returns Formatted duration string (e.g. "2m 30s" or "45s")
846
906
  */
847
- const formatDurationSeconds = (durationSeconds) => {
907
+ export const formatDurationSeconds = (durationSeconds) => {
848
908
  const minutes = Math.floor(durationSeconds / 60);
849
909
  const seconds = durationSeconds % 60;
850
910
  if (minutes > 0) {
@@ -852,4 +912,3 @@ const formatDurationSeconds = (durationSeconds) => {
852
912
  }
853
913
  return `${durationSeconds}s`;
854
914
  };
855
- exports.formatDurationSeconds = formatDurationSeconds;
@@ -1,4 +1,4 @@
1
- import { CompatibilityData } from '../utils/compatibility';
1
+ import { CompatibilityData } from '../utils/compatibility.js';
2
2
  export interface DeviceValidationOptions {
3
3
  debug?: boolean;
4
4
  logger?: (message: string) => void;
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DeviceValidationService = void 0;
4
1
  /**
5
2
  * Service for validating device configurations against compatibility data
6
3
  */
7
- class DeviceValidationService {
4
+ export class DeviceValidationService {
8
5
  /**
9
6
  * Validate Android device configuration
10
7
  * @param androidApiLevel Android API level to validate
@@ -91,4 +88,3 @@ class DeviceValidationService {
91
88
  }
92
89
  }
93
90
  }
94
- exports.DeviceValidationService = DeviceValidationService;
@@ -1,9 +1,6 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.plan = plan;
4
- const fs = require("node:fs");
5
- const path = require("node:path");
6
- const execution_plan_utils_1 = require("./execution-plan.utils");
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { getFlowsToRunInSequence, isFlowFile, processDependencies, readDirectory, readTestYamlFileAsJson, readYamlFileAsJson, } from './execution-plan.utils.js';
7
4
  /**
8
5
  * Recursively check and resolve all dependencies for a flow file
9
6
  * Includes runFlow references, JavaScript scripts, and media files
@@ -16,8 +13,8 @@ async function checkDependencies(input) {
16
13
  const uncheckedDependencies = [input];
17
14
  while (uncheckedDependencies.length > 0) {
18
15
  const fileToCheck = uncheckedDependencies.shift();
19
- const { config, testSteps } = (0, execution_plan_utils_1.readTestYamlFileAsJson)(fileToCheck);
20
- const { allErrors, allFiles } = (0, execution_plan_utils_1.processDependencies)({
16
+ const { config, testSteps } = readTestYamlFileAsJson(fileToCheck);
17
+ const { allErrors, allFiles } = processDependencies({
21
18
  config,
22
19
  input: fileToCheck,
23
20
  testSteps,
@@ -27,7 +24,7 @@ async function checkDependencies(input) {
27
24
  allErrors.join('\n'));
28
25
  }
29
26
  for (const file of allFiles) {
30
- if (!(0, execution_plan_utils_1.isFlowFile)(file)) {
27
+ if (!isFlowFile(file)) {
31
28
  // js/media files don't have dependencies
32
29
  checkedDependencies.push(file);
33
30
  }
@@ -63,7 +60,7 @@ function getWorkspaceConfig(input, unfilteredFlowFiles) {
63
60
  const possibleConfigPaths = new Set([path.join(input, 'config.yaml'), path.join(input, 'config.yml')].map((p) => path.normalize(p)));
64
61
  const configFilePath = unfilteredFlowFiles.find((file) => possibleConfigPaths.has(path.normalize(file)));
65
62
  const config = configFilePath
66
- ? (0, execution_plan_utils_1.readYamlFileAsJson)(configFilePath)
63
+ ? readYamlFileAsJson(configFilePath)
67
64
  : {};
68
65
  return config;
69
66
  }
@@ -99,7 +96,7 @@ async function planSingleFile(normalizedInput, configFile) {
99
96
  normalizedInput.endsWith('config.yml')) {
100
97
  throw new Error('If using config.yaml, pass the workspace folder path, not the config file or a custom path via --config');
101
98
  }
102
- const { config } = (0, execution_plan_utils_1.readTestYamlFileAsJson)(normalizedInput);
99
+ const { config } = readTestYamlFileAsJson(normalizedInput);
103
100
  const flowMetadata = {};
104
101
  const flowOverrides = {};
105
102
  if (config) {
@@ -112,7 +109,7 @@ async function planSingleFile(normalizedInput, configFile) {
112
109
  if (!fs.existsSync(configFilePath)) {
113
110
  throw new Error(`Config file does not exist: ${configFilePath}`);
114
111
  }
115
- workspaceConfig = (0, execution_plan_utils_1.readYamlFileAsJson)(configFilePath);
112
+ workspaceConfig = readYamlFileAsJson(configFilePath);
116
113
  }
117
114
  const checkedDependancies = await checkDependencies(normalizedInput);
118
115
  return {
@@ -193,7 +190,7 @@ function resolveSequentialFlows(workspaceConfig, pathsByName, debug) {
193
190
  if (debug && flowOrder !== normalizedFlowOrder) {
194
191
  console.log(`[DEBUG] Stripping trailing extension: "${flowOrder}" -> "${normalizedFlowOrder}"`);
195
192
  }
196
- return (0, execution_plan_utils_1.getFlowsToRunInSequence)(pathsByName, [normalizedFlowOrder], debug);
193
+ return getFlowsToRunInSequence(pathsByName, [normalizedFlowOrder], debug);
197
194
  })),
198
195
  ];
199
196
  if (debug) {
@@ -225,7 +222,7 @@ function resolveSequentialFlows(workspaceConfig, pathsByName, debug) {
225
222
  * @returns Complete execution plan with flows, dependencies, and metadata
226
223
  * @throws Error if input path doesn't exist, no flows found, or dependencies missing
227
224
  */
228
- async function plan(options) {
225
+ export async function plan(options) {
229
226
  const { input, includeTags = [], excludeTags = [], excludeFlows, configFile, debug = false, } = options;
230
227
  const normalizedInput = path.normalize(input);
231
228
  const flowMetadata = {};
@@ -235,7 +232,7 @@ async function plan(options) {
235
232
  if (fs.lstatSync(normalizedInput).isFile()) {
236
233
  return planSingleFile(normalizedInput, configFile);
237
234
  }
238
- let unfilteredFlowFiles = await (0, execution_plan_utils_1.readDirectory)(normalizedInput, execution_plan_utils_1.isFlowFile);
235
+ let unfilteredFlowFiles = await readDirectory(normalizedInput, isFlowFile);
239
236
  if (unfilteredFlowFiles.length === 0) {
240
237
  throw new Error(`Flow directory does not contain any Flow files: ${path.resolve(normalizedInput)}`);
241
238
  }
@@ -246,7 +243,7 @@ async function plan(options) {
246
243
  if (!fs.existsSync(configFilePath)) {
247
244
  throw new Error(`Config file does not exist: ${configFilePath}`);
248
245
  }
249
- workspaceConfig = (0, execution_plan_utils_1.readYamlFileAsJson)(configFilePath);
246
+ workspaceConfig = readYamlFileAsJson(configFilePath);
250
247
  }
251
248
  else {
252
249
  workspaceConfig = getWorkspaceConfig(normalizedInput, unfilteredFlowFiles);
@@ -260,7 +257,7 @@ async function plan(options) {
260
257
  }
261
258
  // eslint-disable-next-line unicorn/no-array-reduce
262
259
  const configPerFlowFile = unfilteredFlowFiles.reduce((acc, filePath) => {
263
- const { config } = (0, execution_plan_utils_1.readTestYamlFileAsJson)(filePath);
260
+ const { config } = readTestYamlFileAsJson(filePath);
264
261
  acc[filePath] = config;
265
262
  return acc;
266
263
  }, {});
@@ -1,14 +1,8 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.processDependencies = exports.checkIfFilesExistInWorkspace = exports.readTestYamlFileAsJson = exports.readYamlFileAsJson = void 0;
4
- exports.getFlowsToRunInSequence = getFlowsToRunInSequence;
5
- exports.isFlowFile = isFlowFile;
6
- exports.readDirectory = readDirectory;
7
- const yaml = require("js-yaml");
8
- const fs = require("node:fs");
9
- const path = require("node:path");
1
+ import * as yaml from 'js-yaml';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
10
4
  const commandsThatRequireFiles = new Set(['addMedia', 'runFlow', 'runScript']);
11
- function getFlowsToRunInSequence(paths, flowOrder, debug = false) {
5
+ export function getFlowsToRunInSequence(paths, flowOrder, debug = false) {
12
6
  if (flowOrder.length === 0) {
13
7
  if (debug) {
14
8
  console.log('[DEBUG] getFlowsToRunInSequence: flowOrder is empty, returning []');
@@ -33,7 +27,7 @@ function getFlowsToRunInSequence(paths, flowOrder, debug = false) {
33
27
  }
34
28
  return namesInOrder.map((name) => paths[name]);
35
29
  }
36
- function isFlowFile(filePath) {
30
+ export function isFlowFile(filePath) {
37
31
  // Exclude files inside .app bundles
38
32
  // Check if any directory in the path ends with .app
39
33
  const pathParts = filePath.split(path.sep);
@@ -44,7 +38,7 @@ function isFlowFile(filePath) {
44
38
  }
45
39
  return filePath.endsWith('.yaml') || filePath.endsWith('.yml');
46
40
  }
47
- const readYamlFileAsJson = (filePath) => {
41
+ export const readYamlFileAsJson = (filePath) => {
48
42
  try {
49
43
  const normalizedPath = path.normalize(filePath);
50
44
  const yamlText = fs.readFileSync(normalizedPath, 'utf8');
@@ -61,11 +55,12 @@ const readYamlFileAsJson = (filePath) => {
61
55
  return result;
62
56
  }
63
57
  catch (error) {
64
- throw new Error(`Error parsing YAML file ${filePath}: ${error}`);
58
+ throw new Error(`Error parsing YAML file ${filePath}: ${error}`, {
59
+ cause: error,
60
+ });
65
61
  }
66
62
  };
67
- exports.readYamlFileAsJson = readYamlFileAsJson;
68
- const readTestYamlFileAsJson = (filePath) => {
63
+ export const readTestYamlFileAsJson = (filePath) => {
69
64
  try {
70
65
  const normalizedPath = path.normalize(filePath);
71
66
  const yamlText = fs.readFileSync(normalizedPath, 'utf8');
@@ -88,11 +83,10 @@ const readTestYamlFileAsJson = (filePath) => {
88
83
  catch (error) {
89
84
  const message = `Error parsing YAML file ${filePath}: ${error}`;
90
85
  console.error(message);
91
- throw new Error(message);
86
+ throw new Error(message, { cause: error });
92
87
  }
93
88
  };
94
- exports.readTestYamlFileAsJson = readTestYamlFileAsJson;
95
- async function readDirectory(dir, filterFunction) {
89
+ export async function readDirectory(dir, filterFunction) {
96
90
  const readDirResult = await fs.promises.readdir(dir);
97
91
  const files = await Promise.all(readDirResult.map(async (file) => {
98
92
  const filePath = path.join(dir, file);
@@ -108,7 +102,7 @@ async function readDirectory(dir, filterFunction) {
108
102
  }));
109
103
  return files.flat().filter(Boolean);
110
104
  }
111
- const checkIfFilesExistInWorkspace = (commandName, command, absoluteFilePath) => {
105
+ export const checkIfFilesExistInWorkspace = (commandName, command, absoluteFilePath) => {
112
106
  const errors = [];
113
107
  const files = [];
114
108
  const directory = path.dirname(absoluteFilePath);
@@ -136,7 +130,6 @@ const checkIfFilesExistInWorkspace = (commandName, command, absoluteFilePath) =>
136
130
  processFilePath(x.file);
137
131
  return { errors, files };
138
132
  };
139
- exports.checkIfFilesExistInWorkspace = checkIfFilesExistInWorkspace;
140
133
  const checkFile = (filePath) => {
141
134
  if (!fs.existsSync(filePath))
142
135
  return `non-existent file`;
@@ -149,7 +142,7 @@ const checkStepsArray = (steps, absoluteFilePath) => {
149
142
  continue;
150
143
  for (const [commandName, commandValue] of Object.entries(command)) {
151
144
  if (commandsThatRequireFiles.has(commandName)) {
152
- const { errors: newErrors, files: newFiles } = (0, exports.checkIfFilesExistInWorkspace)(commandName, commandValue, path.normalize(absoluteFilePath));
145
+ const { errors: newErrors, files: newFiles } = checkIfFilesExistInWorkspace(commandName, commandValue, path.normalize(absoluteFilePath));
153
146
  errors = [...errors, ...newErrors];
154
147
  files = [...files, ...newFiles];
155
148
  }
@@ -164,7 +157,7 @@ const checkStepsArray = (steps, absoluteFilePath) => {
164
157
  }
165
158
  return { errors, files };
166
159
  };
167
- const processDependencies = ({ config, input, testSteps, }) => {
160
+ export const processDependencies = ({ config, input, testSteps, }) => {
168
161
  let allErrors = [];
169
162
  let allFiles = [];
170
163
  const { onFlowComplete, onFlowStart } = config ?? {};
@@ -186,4 +179,3 @@ const processDependencies = ({ config, input, testSteps, }) => {
186
179
  }
187
180
  return { allErrors, allFiles };
188
181
  };
189
- exports.processDependencies = processDependencies;