@devicecloud.dev/dcd 4.2.0 → 4.2.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/methods.js CHANGED
@@ -115,7 +115,11 @@ const uploadBinary = async (config) => {
115
115
  return uploadId;
116
116
  }
117
117
  catch (error) {
118
+ if (log) {
119
+ core_1.ux.action.stop(styling_1.colors.error('✗ Failed'));
120
+ }
118
121
  if (debug) {
122
+ console.error('[DEBUG] === BINARY UPLOAD FAILED ===');
119
123
  console.error('[DEBUG] Binary upload failed:', error);
120
124
  console.error(`[DEBUG] Error type: ${error instanceof Error ? error.name : typeof error}`);
121
125
  console.error(`[DEBUG] Error message: ${error instanceof Error ? error.message : String(error)}`);
@@ -124,6 +128,14 @@ const uploadBinary = async (config) => {
124
128
  }
125
129
  console.error(`[DEBUG] Failed after ${Date.now() - startTime}ms`);
126
130
  }
131
+ // Add helpful context for common errors
132
+ if (error instanceof Error) {
133
+ if (error.name === 'NetworkError') {
134
+ throw error; // NetworkError already has detailed troubleshooting info
135
+ }
136
+ // Re-throw with original message
137
+ throw error;
138
+ }
127
139
  throw error;
128
140
  }
129
141
  };
@@ -144,6 +156,31 @@ async function prepareFileForUpload(filePath, debug, startTime) {
144
156
  if (debug) {
145
157
  console.log('[DEBUG] Compressing .app folder to zip...');
146
158
  }
159
+ // Validate that the .app directory exists before attempting to compress
160
+ // Without this check, archiver silently creates an empty 22-byte zip for non-existent paths
161
+ try {
162
+ await (0, promises_1.access)(filePath);
163
+ }
164
+ catch {
165
+ // Provide helpful error message for common quoting issues
166
+ const hasQuotes = filePath.includes("'") || filePath.includes('"');
167
+ const errorMessage = [
168
+ `App folder not found: ${filePath}`,
169
+ '',
170
+ hasQuotes
171
+ ? 'Note: Your path contains quote characters. If the folder name has spaces, ensure quotes wrap the entire path:'
172
+ : 'Note: If your folder name contains spaces, wrap the entire path in quotes:',
173
+ hasQuotes
174
+ ? ` ❌ Wrong: --app-file=./path/'My App Name.app' (quotes become literal characters)`
175
+ : ` Example: --app-file="./path/My App Name.app"`,
176
+ hasQuotes
177
+ ? ` ✅ Right: --app-file="./path/My App Name.app" (quotes processed by shell)`
178
+ : '',
179
+ ]
180
+ .filter(Boolean)
181
+ .join('\n');
182
+ throw new Error(errorMessage);
183
+ }
147
184
  const zippedAppBlob = await (0, exports.compressFolderToBlob)(filePath);
148
185
  file = new File([zippedAppBlob], filePath + '.zip');
149
186
  if (debug) {
@@ -225,9 +262,18 @@ async function checkExistingUpload(apiUrl, apiKey, sha, debug) {
225
262
  }
226
263
  catch (error) {
227
264
  if (debug) {
228
- console.error('[DEBUG] SHA check failed (continuing with upload):', error);
265
+ console.error('[DEBUG] === SHA CHECK FAILED ===');
266
+ console.error('[DEBUG] Continuing with upload despite SHA check failure');
229
267
  console.error(`[DEBUG] Error type: ${error instanceof Error ? error.name : typeof error}`);
230
268
  console.error(`[DEBUG] Error message: ${error instanceof Error ? error.message : String(error)}`);
269
+ if (error instanceof Error && error.stack) {
270
+ console.error(`[DEBUG] Stack trace:\n${error.stack}`);
271
+ }
272
+ }
273
+ else if (error instanceof Error && error.name === 'NetworkError') {
274
+ // Even without debug, show a warning for network errors
275
+ console.warn('\nWarning: Failed to check for existing binary upload (network error).');
276
+ console.warn('Continuing with new upload...\n');
231
277
  }
232
278
  return { exists: false };
233
279
  }
@@ -350,21 +396,37 @@ async function requestUploadPaths(apiUrl, apiKey, filePath, fileSize, debug) {
350
396
  console.log(`[DEBUG] Target endpoint: ${apiUrl}/uploads/getBinaryUploadUrl`);
351
397
  console.log(`[DEBUG] Platform: ${platform}`);
352
398
  }
353
- const urlRequestStartTime = Date.now();
354
- const { id, tempPath, finalPath, b2 } = await api_gateway_1.ApiGateway.getBinaryUploadUrl(apiUrl, apiKey, platform, fileSize);
355
- if (debug) {
356
- const hasStrategy = b2 && typeof b2 === 'object' && 'strategy' in b2;
357
- console.log(`[DEBUG] Upload URL request completed in ${Date.now() - urlRequestStartTime}ms`);
358
- console.log(`[DEBUG] Upload ID: ${id}`);
359
- console.log(`[DEBUG] Temp path (TUS upload): ${tempPath}`);
360
- console.log(`[DEBUG] Final path (after finalize): ${finalPath}`);
361
- console.log(`[DEBUG] Backblaze upload URL provided: ${Boolean(b2)}`);
362
- if (hasStrategy)
363
- console.log(`[DEBUG] Backblaze strategy: ${b2.strategy}`);
364
- }
365
- if (!tempPath)
366
- throw new Error('No upload path provided by API');
367
- return { b2, finalPath, id, tempPath };
399
+ try {
400
+ const urlRequestStartTime = Date.now();
401
+ const { id, tempPath, finalPath, b2 } = await api_gateway_1.ApiGateway.getBinaryUploadUrl(apiUrl, apiKey, platform, fileSize);
402
+ if (debug) {
403
+ const hasStrategy = b2 && typeof b2 === 'object' && 'strategy' in b2;
404
+ console.log(`[DEBUG] Upload URL request completed in ${Date.now() - urlRequestStartTime}ms`);
405
+ console.log(`[DEBUG] Upload ID: ${id}`);
406
+ console.log(`[DEBUG] Temp path (TUS upload): ${tempPath}`);
407
+ console.log(`[DEBUG] Final path (after finalize): ${finalPath}`);
408
+ console.log(`[DEBUG] Backblaze upload URL provided: ${Boolean(b2)}`);
409
+ if (hasStrategy)
410
+ console.log(`[DEBUG] Backblaze strategy: ${b2.strategy}`);
411
+ }
412
+ if (!tempPath)
413
+ throw new Error('No upload path provided by API');
414
+ return { b2, finalPath, id, tempPath };
415
+ }
416
+ catch (error) {
417
+ if (debug) {
418
+ console.error('[DEBUG] === FAILED TO GET UPLOAD URL ===');
419
+ console.error(`[DEBUG] Error: ${error instanceof Error ? error.message : String(error)}`);
420
+ }
421
+ // Add context to the error
422
+ if (error instanceof Error) {
423
+ if (error.name === 'NetworkError') {
424
+ throw new Error(`Failed to request upload URL from API.\n\n${error.message}`);
425
+ }
426
+ throw new Error(`Failed to request upload URL: ${error.message}`);
427
+ }
428
+ throw error;
429
+ }
368
430
  }
369
431
  /**
370
432
  * Extracts metadata from the binary file
@@ -529,10 +591,25 @@ async function uploadToBackblaze(uploadUrl, authorizationToken, fileName, file,
529
591
  }
530
592
  catch (error) {
531
593
  if (debug) {
594
+ console.error('[DEBUG] === BACKBLAZE UPLOAD EXCEPTION ===');
532
595
  console.error('[DEBUG] Backblaze upload exception:', error);
596
+ console.error(`[DEBUG] Error type: ${error instanceof Error ? error.name : typeof error}`);
597
+ console.error(`[DEBUG] Error message: ${error instanceof Error ? error.message : String(error)}`);
598
+ if (error instanceof Error && error.stack) {
599
+ console.error(`[DEBUG] Stack trace:\n${error.stack}`);
600
+ }
601
+ }
602
+ // Provide more specific error messages for common network errors
603
+ if (error instanceof TypeError && error.message === 'fetch failed') {
604
+ if (debug) {
605
+ console.error('[DEBUG] Network error detected - could be DNS, connection, or SSL issue');
606
+ }
607
+ console.warn('Warning: Backblaze upload failed due to network error');
608
+ }
609
+ else {
610
+ // Don't throw - we don't want Backblaze failures to block the primary upload
611
+ console.warn(`Warning: Backblaze upload failed: ${error instanceof Error ? error.message : String(error)}`);
533
612
  }
534
- // Don't throw - we don't want Backblaze failures to block the primary upload
535
- console.warn(`Warning: Backblaze upload failed: ${error instanceof Error ? error.message : String(error)}`);
536
613
  return false;
537
614
  }
538
615
  }
@@ -608,30 +685,50 @@ async function uploadLargeFileToBackblaze(config) {
608
685
  if (debug) {
609
686
  console.log(`[DEBUG] Uploading part ${partNumber}/${uploadPartUrls.length} (${(partLength / 1024 / 1024).toFixed(2)} MB, SHA1: ${sha1Hex})`);
610
687
  }
611
- const response = await fetch(uploadPartUrls[i].uploadUrl, {
612
- body: new Uint8Array(partBuffer),
613
- headers: {
614
- Authorization: uploadPartUrls[i].authorizationToken,
615
- 'Content-Length': partLength.toString(),
616
- 'X-Bz-Content-Sha1': sha1Hex,
617
- 'X-Bz-Part-Number': partNumber.toString(),
618
- },
619
- method: 'POST',
620
- });
621
- if (!response.ok) {
622
- const errorText = await response.text();
623
- if (debug) {
624
- console.error(`[DEBUG] Part ${partNumber} upload failed with status ${response.status}: ${errorText}`);
688
+ try {
689
+ const response = await fetch(uploadPartUrls[i].uploadUrl, {
690
+ body: new Uint8Array(partBuffer),
691
+ headers: {
692
+ Authorization: uploadPartUrls[i].authorizationToken,
693
+ 'Content-Length': partLength.toString(),
694
+ 'X-Bz-Content-Sha1': sha1Hex,
695
+ 'X-Bz-Part-Number': partNumber.toString(),
696
+ },
697
+ method: 'POST',
698
+ });
699
+ if (!response.ok) {
700
+ const errorText = await response.text();
701
+ if (debug) {
702
+ console.error(`[DEBUG] Part ${partNumber} upload failed with status ${response.status}: ${errorText}`);
703
+ }
704
+ throw new Error(`Part ${partNumber} upload failed with status ${response.status}`);
705
+ }
706
+ }
707
+ catch (error) {
708
+ if (error instanceof TypeError && error.message === 'fetch failed') {
709
+ if (debug) {
710
+ console.error(`[DEBUG] Network error uploading part ${partNumber} - could be DNS, connection, or SSL issue`);
711
+ }
712
+ throw new Error(`Part ${partNumber} upload failed due to network error`);
625
713
  }
626
- throw new Error(`Part ${partNumber} upload failed with status ${response.status}`);
714
+ throw error;
627
715
  }
628
716
  if (debug) {
629
717
  console.log(`[DEBUG] Part ${partNumber}/${uploadPartUrls.length} uploaded successfully`);
630
718
  }
631
719
  }
720
+ // Validate all parts were uploaded
721
+ if (partSha1Array.length !== uploadPartUrls.length) {
722
+ const errorMsg = `Part count mismatch: uploaded ${partSha1Array.length} parts but expected ${uploadPartUrls.length}`;
723
+ if (debug) {
724
+ console.error(`[DEBUG] ${errorMsg}`);
725
+ }
726
+ throw new Error(errorMsg);
727
+ }
632
728
  // Finish the large file upload
633
729
  if (debug) {
634
730
  console.log('[DEBUG] Finishing large file upload...');
731
+ console.log(`[DEBUG] Finalizing ${partSha1Array.length} parts with fileId: ${fileId}`);
635
732
  }
636
733
  await api_gateway_1.ApiGateway.finishLargeFile(apiUrl, apiKey, fileId, partSha1Array);
637
734
  if (debug) {
@@ -641,10 +738,22 @@ async function uploadLargeFileToBackblaze(config) {
641
738
  }
642
739
  catch (error) {
643
740
  if (debug) {
741
+ console.error('[DEBUG] === BACKBLAZE LARGE FILE UPLOAD EXCEPTION ===');
644
742
  console.error('[DEBUG] Large file upload exception:', error);
743
+ console.error(`[DEBUG] Error type: ${error instanceof Error ? error.name : typeof error}`);
744
+ console.error(`[DEBUG] Error message: ${error instanceof Error ? error.message : String(error)}`);
745
+ if (error instanceof Error && error.stack) {
746
+ console.error(`[DEBUG] Stack trace:\n${error.stack}`);
747
+ }
748
+ }
749
+ // Provide more specific error messages
750
+ if (error instanceof Error && error.message.includes('network error')) {
751
+ console.warn('Warning: Backblaze large file upload failed due to network error');
752
+ }
753
+ else {
754
+ // Don't throw - we don't want Backblaze failures to block the primary upload
755
+ console.warn(`Warning: Backblaze large file upload failed: ${error instanceof Error ? error.message : String(error)}`);
645
756
  }
646
- // Don't throw - we don't want Backblaze failures to block the primary upload
647
- console.warn(`Warning: Backblaze large file upload failed: ${error instanceof Error ? error.message : String(error)}`);
648
757
  return false;
649
758
  }
650
759
  }
@@ -255,6 +255,15 @@ async function plan(options) {
255
255
  if (debug) {
256
256
  console.log(`[DEBUG] Sequential flows resolved: ${flowsToRunInSequence.length} flow(s)`);
257
257
  }
258
+ // Validate that all specified flows were found
259
+ if (workspaceConfig.executionOrder?.flowsOrder &&
260
+ workspaceConfig.executionOrder.flowsOrder.length > 0 &&
261
+ flowsToRunInSequence.length === 0) {
262
+ throw new Error(`executionOrder specified ${workspaceConfig.executionOrder.flowsOrder.length} flow(s) but none were found.\n\n` +
263
+ `Expected flows: ${workspaceConfig.executionOrder.flowsOrder.join(', ')}\n` +
264
+ `Available flow names: ${Object.keys(pathsByName).join(', ')}\n\n` +
265
+ `Hint: Flow names come from either the 'name' field in the flow config or the filename without extension.`);
266
+ }
258
267
  const normalFlows = allFlows
259
268
  .filter((flow) => !flowsToRunInSequence.includes(flow))
260
269
  .sort((a, b) => a.localeCompare(b));
@@ -27,10 +27,8 @@ function getFlowsToRunInSequence(paths, flowOrder, debug = false) {
27
27
  }
28
28
  if (namesInOrder.length === 0) {
29
29
  const notFound = [...orderSet].filter((item) => !availableNames.includes(item));
30
- if (debug) {
31
- console.log(`[DEBUG] getFlowsToRunInSequence: No flows matched, not found: [${notFound.join(', ')}]`);
32
- }
33
- return [];
30
+ throw new Error(`Could not find flows specified in executionOrder.flowsOrder: ${notFound.join(', ')}\n\n` +
31
+ `Available flow names:\n${availableNames.join('\n')}`);
34
32
  }
35
33
  const result = [...orderSet].filter((item) => namesInOrder.includes(item));
36
34
  if (result.length === 0) {
@@ -47,12 +45,10 @@ function getFlowsToRunInSequence(paths, flowOrder, debug = false) {
47
45
  return resolvedPaths;
48
46
  }
49
47
  else {
50
- if (debug) {
51
- console.log('[DEBUG] getFlowsToRunInSequence: Order does not match, returning []');
52
- console.log(`[DEBUG] Expected order: [${flowOrder.slice(0, result.length).join(', ')}]`);
53
- console.log(`[DEBUG] Actual result: [${result.join(', ')}]`);
54
- }
55
- return [];
48
+ throw new Error(`Flow order mismatch in executionOrder.flowsOrder.\n\n` +
49
+ `Expected order: [${flowOrder.slice(0, result.length).join(', ')}]\n` +
50
+ `Actual order: [${result.join(', ')}]\n\n` +
51
+ `Please ensure flows are specified in the correct order.`);
56
52
  }
57
53
  }
58
54
  function isFlowFile(filePath) {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TestSubmissionService = void 0;
4
+ const node_crypto_1 = require("node:crypto");
4
5
  const path = require("node:path");
5
6
  const methods_1 = require("../methods");
6
7
  const mimeTypeLookupByExtension = {
@@ -50,10 +51,14 @@ class TestSubmissionService {
50
51
  ]),
51
52
  ], commonRoot);
52
53
  this.logDebug(debug, logger, `[DEBUG] Compressed file size: ${buffer.length} bytes`);
54
+ // Calculate SHA-256 hash of the flow ZIP
55
+ const sha = (0, node_crypto_1.createHash)('sha256').update(buffer).digest('hex');
56
+ this.logDebug(debug, logger, `[DEBUG] Flow ZIP SHA-256: ${sha}`);
53
57
  const blob = new Blob([buffer], {
54
58
  type: mimeTypeLookupByExtension.zip,
55
59
  });
56
60
  testFormData.set('file', blob, 'flowFile.zip');
61
+ testFormData.set('sha', sha);
57
62
  testFormData.set('appBinaryId', appBinaryId);
58
63
  testFormData.set('testFileNames', JSON.stringify(this.normalizePaths(testFileNames, commonRoot)));
59
64
  testFormData.set('flowMetadata', JSON.stringify(this.normalizePathMap(flowMetadata, commonRoot)));
@@ -31,6 +31,7 @@ export declare enum EAndroidApiLevels {
31
31
  'thirtyFive' = "35",
32
32
  'thirtyFour' = "34",
33
33
  'thirtyOne' = "31",
34
+ 'thirtySix' = "36",
34
35
  'thirtyThree' = "33",
35
36
  'thirtyTwo' = "32",
36
37
  'twentyNine' = "29"
@@ -38,6 +38,7 @@ var EAndroidApiLevels;
38
38
  EAndroidApiLevels["thirtyFive"] = "35";
39
39
  EAndroidApiLevels["thirtyFour"] = "34";
40
40
  EAndroidApiLevels["thirtyOne"] = "31";
41
+ EAndroidApiLevels["thirtySix"] = "36";
41
42
  EAndroidApiLevels["thirtyThree"] = "33";
42
43
  EAndroidApiLevels["thirtyTwo"] = "32";
43
44
  EAndroidApiLevels["twentyNine"] = "29";
@@ -538,7 +538,7 @@ export interface components {
538
538
  */
539
539
  file: string;
540
540
  /** @enum {string} */
541
- androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35";
541
+ androidApiLevel?: "29" | "30" | "31" | "32" | "33" | "34" | "35" | "36";
542
542
  /** @enum {string} */
543
543
  androidDevice?: "pixel-6" | "pixel-6-pro" | "pixel-7" | "pixel-7-pro" | "generic-tablet";
544
544
  apiKey?: string;