@devicecloud.dev/dcd 4.1.7 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/cloud.d.ts +0 -1
- package/dist/commands/cloud.js +45 -39
- package/dist/commands/upload.js +8 -1
- package/dist/config/flags/device.flags.d.ts +0 -1
- package/dist/config/flags/device.flags.js +0 -4
- package/dist/constants.d.ts +0 -1
- package/dist/gateways/api-gateway.d.ts +13 -18
- package/dist/gateways/api-gateway.js +19 -3
- package/dist/gateways/supabase-gateway.d.ts +28 -0
- package/dist/gateways/supabase-gateway.js +156 -33
- package/dist/methods.d.ts +10 -1
- package/dist/methods.js +206 -134
- package/dist/services/device-validation.service.js +7 -7
- package/dist/services/moropo.service.js +6 -6
- package/dist/services/report-download.service.js +4 -4
- package/dist/services/results-polling.service.js +13 -13
- package/dist/services/test-submission.service.d.ts +0 -1
- package/dist/services/test-submission.service.js +8 -9
- package/dist/services/version.service.js +4 -4
- package/dist/types/generated/schema.types.d.ts +117 -23
- package/dist/types/schema.types.d.ts +113 -6
- package/oclif.manifest.json +1 -7
- package/package.json +4 -2
package/dist/methods.js
CHANGED
|
@@ -77,7 +77,8 @@ const verifyAppZip = async (zipPath) => {
|
|
|
77
77
|
zip.close();
|
|
78
78
|
};
|
|
79
79
|
exports.verifyAppZip = verifyAppZip;
|
|
80
|
-
const uploadBinary = async (
|
|
80
|
+
const uploadBinary = async (config) => {
|
|
81
|
+
const { filePath, apiUrl, apiKey, ignoreShaCheck = false, log = true, debug = false } = config;
|
|
81
82
|
if (log) {
|
|
82
83
|
core_1.ux.action.start(styling_1.colors.bold('Checking and uploading binary'), styling_1.colors.dim('Initializing'), {
|
|
83
84
|
stdout: true,
|
|
@@ -107,7 +108,7 @@ const uploadBinary = async (filePath, apiUrl, apiKey, ignoreShaCheck = false, lo
|
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
// Perform the upload
|
|
110
|
-
const uploadId = await performUpload(
|
|
111
|
+
const uploadId = await performUpload({ apiKey, apiUrl, debug, file, filePath, sha, startTime });
|
|
111
112
|
if (log) {
|
|
112
113
|
core_1.ux.action.stop(styling_1.colors.success('\n✓ Binary uploaded with ID: ') + (0, styling_1.formatId)(uploadId));
|
|
113
114
|
}
|
|
@@ -232,162 +233,240 @@ async function checkExistingUpload(apiUrl, apiKey, sha, debug) {
|
|
|
232
233
|
}
|
|
233
234
|
}
|
|
234
235
|
/**
|
|
235
|
-
*
|
|
236
|
-
* @param
|
|
237
|
-
* @param
|
|
238
|
-
* @param
|
|
239
|
-
* @param
|
|
240
|
-
* @
|
|
241
|
-
* @param debug Whether debug logging is enabled
|
|
242
|
-
* @param startTime Timestamp when upload started
|
|
243
|
-
* @returns Promise resolving to upload ID
|
|
236
|
+
* Uploads file to Supabase using resumable uploads
|
|
237
|
+
* @param env - Environment (dev or prod)
|
|
238
|
+
* @param tempPath - Temporary staging path for upload
|
|
239
|
+
* @param file - File to upload
|
|
240
|
+
* @param debug - Enable debug logging
|
|
241
|
+
* @returns Upload result with success status and any error
|
|
244
242
|
*/
|
|
245
|
-
async function
|
|
243
|
+
async function uploadToSupabase(env, tempPath, file, debug) {
|
|
246
244
|
if (debug) {
|
|
247
|
-
console.log(
|
|
248
|
-
console.log(`[DEBUG]
|
|
249
|
-
console.log(`[DEBUG] Platform: ${filePath?.endsWith('.apk') ? 'android' : 'ios'}`);
|
|
250
|
-
}
|
|
251
|
-
const urlRequestStartTime = Date.now();
|
|
252
|
-
const { id, message, path, token, b2 } = await api_gateway_1.ApiGateway.getBinaryUploadUrl(apiUrl, apiKey, filePath?.endsWith('.apk') ? 'android' : 'ios', file.size);
|
|
253
|
-
if (debug) {
|
|
254
|
-
console.log(`[DEBUG] Upload URL request completed in ${Date.now() - urlRequestStartTime}ms`);
|
|
255
|
-
console.log(`[DEBUG] Upload ID: ${id}`);
|
|
256
|
-
console.log(`[DEBUG] Upload path: ${path}`);
|
|
257
|
-
console.log(`[DEBUG] Backblaze upload URL provided: ${Boolean(b2)}`);
|
|
258
|
-
if (b2) {
|
|
259
|
-
console.log(`[DEBUG] Backblaze strategy: ${b2.strategy}`);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
if (!path)
|
|
263
|
-
throw new Error(message);
|
|
264
|
-
// Extract app metadata using the service
|
|
265
|
-
if (debug) {
|
|
266
|
-
console.log('[DEBUG] Extracting app metadata...');
|
|
267
|
-
}
|
|
268
|
-
const metadataExtractor = new metadata_extractor_service_1.MetadataExtractorService();
|
|
269
|
-
const metadata = await metadataExtractor.extract(filePath);
|
|
270
|
-
if (!metadata) {
|
|
271
|
-
throw new Error(`Failed to extract metadata from ${filePath}. Supported formats: .apk, .app, .zip`);
|
|
272
|
-
}
|
|
273
|
-
if (debug) {
|
|
274
|
-
console.log(`[DEBUG] Metadata extracted: ${JSON.stringify(metadata)}`);
|
|
275
|
-
}
|
|
276
|
-
const env = apiUrl === 'https://api.devicecloud.dev' ? 'prod' : 'dev';
|
|
277
|
-
let supabaseSuccess = false;
|
|
278
|
-
let backblazeSuccess = false;
|
|
279
|
-
let lastError = null;
|
|
280
|
-
// Try Supabase upload first
|
|
281
|
-
if (debug) {
|
|
282
|
-
console.log(`[DEBUG] Uploading to Supabase storage (${env})...`);
|
|
245
|
+
console.log(`[DEBUG] Uploading to Supabase storage (${env}) using resumable uploads...`);
|
|
246
|
+
console.log(`[DEBUG] Staging path: ${tempPath}`);
|
|
283
247
|
console.log(`[DEBUG] File size: ${(file.size / 1024 / 1024).toFixed(2)} MB`);
|
|
284
248
|
}
|
|
285
249
|
try {
|
|
286
250
|
const uploadStartTime = Date.now();
|
|
287
|
-
await supabase_gateway_1.SupabaseGateway.
|
|
288
|
-
supabaseSuccess = true;
|
|
251
|
+
await supabase_gateway_1.SupabaseGateway.uploadResumable(env, tempPath, file, debug);
|
|
289
252
|
if (debug) {
|
|
290
253
|
const uploadDuration = Date.now() - uploadStartTime;
|
|
291
|
-
const
|
|
292
|
-
|
|
254
|
+
const uploadDurationSeconds = uploadDuration / 1000;
|
|
255
|
+
const uploadSpeed = (file.size / 1024 / 1024) / uploadDurationSeconds;
|
|
256
|
+
console.log(`[DEBUG] Supabase resumable upload completed in ${uploadDurationSeconds.toFixed(2)}s (${uploadDuration}ms)`);
|
|
293
257
|
console.log(`[DEBUG] Average upload speed: ${uploadSpeed.toFixed(2)} MB/s`);
|
|
294
258
|
}
|
|
259
|
+
return { error: null, success: true };
|
|
295
260
|
}
|
|
296
261
|
catch (error) {
|
|
297
|
-
|
|
262
|
+
const uploadError = error instanceof Error ? error : new Error(String(error));
|
|
298
263
|
if (debug) {
|
|
299
|
-
console.error(`[DEBUG] === SUPABASE UPLOAD FAILED ===`);
|
|
300
|
-
console.error(`[DEBUG] Error message: ${
|
|
301
|
-
console.error(`[DEBUG] Error name: ${
|
|
302
|
-
if (
|
|
303
|
-
console.error(`[DEBUG] Error stack:\n${
|
|
264
|
+
console.error(`[DEBUG] === SUPABASE RESUMABLE UPLOAD FAILED ===`);
|
|
265
|
+
console.error(`[DEBUG] Error message: ${uploadError.message}`);
|
|
266
|
+
console.error(`[DEBUG] Error name: ${uploadError.name}`);
|
|
267
|
+
if (uploadError.stack) {
|
|
268
|
+
console.error(`[DEBUG] Error stack:\n${uploadError.stack}`);
|
|
304
269
|
}
|
|
305
|
-
console.error(`[DEBUG]
|
|
270
|
+
console.error(`[DEBUG] Staging path: ${tempPath}`);
|
|
306
271
|
console.error(`[DEBUG] File size: ${file.size} bytes`);
|
|
307
272
|
console.log('[DEBUG] Will attempt Backblaze fallback if available...');
|
|
308
273
|
}
|
|
274
|
+
return { error: uploadError, success: false };
|
|
309
275
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Handles Backblaze upload with appropriate strategy
|
|
279
|
+
* @param config - Configuration object for Backblaze upload
|
|
280
|
+
* @returns Upload result with success status and any error
|
|
281
|
+
*/
|
|
282
|
+
async function handleBackblazeUpload(config) {
|
|
283
|
+
const { b2, apiUrl, apiKey, finalPath, file, filePath, debug, supabaseSuccess } = config;
|
|
284
|
+
if (!b2) {
|
|
285
|
+
if (debug && !supabaseSuccess) {
|
|
286
|
+
console.log('[DEBUG] Backblaze not configured, cannot fallback');
|
|
319
287
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
288
|
+
return { error: null, success: false };
|
|
289
|
+
}
|
|
290
|
+
if (debug) {
|
|
291
|
+
console.log(supabaseSuccess ? '[DEBUG] Starting Backblaze backup upload...' : '[DEBUG] Starting Backblaze fallback upload...');
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const b2UploadStartTime = Date.now();
|
|
295
|
+
let backblazeSuccess = false;
|
|
296
|
+
if (b2.strategy === 'simple' && b2.simple) {
|
|
297
|
+
const simple = b2.simple;
|
|
298
|
+
backblazeSuccess = await uploadToBackblaze(simple.uploadUrl, simple.authorizationToken, `organizations/${finalPath}`, file, debug);
|
|
299
|
+
}
|
|
300
|
+
else if (b2.strategy === 'large' && b2.large) {
|
|
301
|
+
const large = b2.large;
|
|
302
|
+
backblazeSuccess = await uploadLargeFileToBackblaze({
|
|
303
|
+
apiKey,
|
|
304
|
+
apiUrl,
|
|
305
|
+
debug,
|
|
306
|
+
fileId: large.fileId,
|
|
307
|
+
fileName: `organizations/${finalPath}`,
|
|
308
|
+
fileObject: file,
|
|
309
|
+
filePath,
|
|
310
|
+
fileSize: file.size,
|
|
311
|
+
uploadPartUrls: large.uploadPartUrls,
|
|
312
|
+
});
|
|
339
313
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
const
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
console.error(`[DEBUG] Error name: ${b2Error.name}`);
|
|
347
|
-
if (b2Error.stack) {
|
|
348
|
-
console.error(`[DEBUG] Error stack:\n${b2Error.stack}`);
|
|
349
|
-
}
|
|
350
|
-
console.error(`[DEBUG] Upload strategy: ${b2.strategy}`);
|
|
351
|
-
}
|
|
352
|
-
backblazeSuccess = false;
|
|
353
|
-
// Only update lastError if Supabase also failed
|
|
354
|
-
if (!supabaseSuccess) {
|
|
355
|
-
lastError = b2Error;
|
|
356
|
-
}
|
|
314
|
+
if (debug) {
|
|
315
|
+
const duration = Date.now() - b2UploadStartTime;
|
|
316
|
+
const durationSeconds = duration / 1000;
|
|
317
|
+
console.log(backblazeSuccess
|
|
318
|
+
? `[DEBUG] Backblaze upload completed successfully in ${durationSeconds.toFixed(2)}s (${duration}ms)`
|
|
319
|
+
: `[DEBUG] Backblaze upload failed after ${durationSeconds.toFixed(2)}s (${duration}ms)`);
|
|
357
320
|
}
|
|
321
|
+
return { error: null, success: backblazeSuccess };
|
|
358
322
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
// Check if at least one upload succeeded
|
|
363
|
-
if (!supabaseSuccess && !backblazeSuccess) {
|
|
323
|
+
catch (error) {
|
|
324
|
+
const b2Error = error instanceof Error ? error : new Error(String(error));
|
|
364
325
|
if (debug) {
|
|
365
|
-
console.error(`[DEBUG] ===
|
|
366
|
-
console.error(`[DEBUG]
|
|
367
|
-
console.error(`[DEBUG]
|
|
368
|
-
if (
|
|
369
|
-
console.error(`[DEBUG]
|
|
370
|
-
console.error(`[DEBUG] - Message: ${lastError.message}`);
|
|
371
|
-
console.error(`[DEBUG] - Name: ${lastError.name}`);
|
|
372
|
-
console.error(`[DEBUG] - Stack: ${lastError.stack}`);
|
|
326
|
+
console.error(`[DEBUG] === UNEXPECTED BACKBLAZE UPLOAD ERROR ===`);
|
|
327
|
+
console.error(`[DEBUG] Error message: ${b2Error.message}`);
|
|
328
|
+
console.error(`[DEBUG] Error name: ${b2Error.name}`);
|
|
329
|
+
if (b2Error.stack) {
|
|
330
|
+
console.error(`[DEBUG] Error stack:\n${b2Error.stack}`);
|
|
373
331
|
}
|
|
332
|
+
console.error(`[DEBUG] Upload strategy: ${b2.strategy}`);
|
|
374
333
|
}
|
|
375
|
-
|
|
334
|
+
return { error: b2Error, success: false };
|
|
376
335
|
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Requests upload URL and paths from API
|
|
339
|
+
* @param apiUrl - API base URL
|
|
340
|
+
* @param apiKey - API authentication key
|
|
341
|
+
* @param filePath - Path to the file being uploaded
|
|
342
|
+
* @param fileSize - Size of the file in bytes
|
|
343
|
+
* @param debug - Enable debug logging
|
|
344
|
+
* @returns Promise resolving to upload paths and configuration
|
|
345
|
+
*/
|
|
346
|
+
async function requestUploadPaths(apiUrl, apiKey, filePath, fileSize, debug) {
|
|
347
|
+
const platform = filePath?.endsWith('.apk') ? 'android' : 'ios';
|
|
377
348
|
if (debug) {
|
|
378
|
-
console.log(
|
|
379
|
-
|
|
380
|
-
|
|
349
|
+
console.log('[DEBUG] Requesting upload URL...');
|
|
350
|
+
console.log(`[DEBUG] Target endpoint: ${apiUrl}/uploads/getBinaryUploadUrl`);
|
|
351
|
+
console.log(`[DEBUG] Platform: ${platform}`);
|
|
352
|
+
}
|
|
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 };
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Extracts metadata from the binary file
|
|
371
|
+
* @param filePath - Path to the binary file
|
|
372
|
+
* @param debug - Enable debug logging
|
|
373
|
+
* @returns Promise resolving to extracted metadata containing appId and platform
|
|
374
|
+
*/
|
|
375
|
+
async function extractBinaryMetadata(filePath, debug) {
|
|
376
|
+
if (debug)
|
|
377
|
+
console.log('[DEBUG] Extracting app metadata...');
|
|
378
|
+
const metadataExtractor = new metadata_extractor_service_1.MetadataExtractorService();
|
|
379
|
+
const metadata = await metadataExtractor.extract(filePath);
|
|
380
|
+
if (!metadata) {
|
|
381
|
+
throw new Error(`Failed to extract metadata from ${filePath}. Supported formats: .apk, .app, .zip`);
|
|
382
|
+
}
|
|
383
|
+
if (debug)
|
|
384
|
+
console.log(`[DEBUG] Metadata extracted: ${JSON.stringify(metadata)}`);
|
|
385
|
+
return metadata;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Validates upload results and throws if all uploads failed
|
|
389
|
+
* @param supabaseSuccess - Whether Supabase upload succeeded
|
|
390
|
+
* @param backblazeSuccess - Whether Backblaze upload succeeded
|
|
391
|
+
* @param lastError - Last error encountered during uploads
|
|
392
|
+
* @param b2 - Backblaze configuration
|
|
393
|
+
* @param debug - Enable debug logging
|
|
394
|
+
* @returns void - throws error if all uploads failed
|
|
395
|
+
*/
|
|
396
|
+
function validateUploadResults(supabaseSuccess, backblazeSuccess, lastError, b2, debug) {
|
|
397
|
+
if (supabaseSuccess || backblazeSuccess) {
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
if (debug) {
|
|
401
|
+
console.error(`[DEBUG] === ALL UPLOADS FAILED ===`);
|
|
402
|
+
console.error(`[DEBUG] Supabase upload: FAILED`);
|
|
403
|
+
console.error(`[DEBUG] Backblaze upload: ${b2 ? 'FAILED' : 'NOT CONFIGURED'}`);
|
|
404
|
+
if (lastError) {
|
|
405
|
+
console.error(`[DEBUG] Final error details:`);
|
|
406
|
+
console.error(`[DEBUG] - Message: ${lastError.message}`);
|
|
407
|
+
console.error(`[DEBUG] - Name: ${lastError.name}`);
|
|
408
|
+
console.error(`[DEBUG] - Stack: ${lastError.stack}`);
|
|
381
409
|
}
|
|
382
410
|
}
|
|
411
|
+
throw new Error(`All uploads failed. ${lastError ? `Last error: ${JSON.stringify({ message: lastError.message, name: lastError.name, stack: lastError.stack })}` : 'No upload targets available.'}`);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Performs the actual file upload
|
|
415
|
+
* @param config Configuration object for the upload
|
|
416
|
+
* @returns Promise resolving to upload ID
|
|
417
|
+
*/
|
|
418
|
+
async function performUpload(config) {
|
|
419
|
+
const { filePath, apiUrl, apiKey, file, sha, debug, startTime } = config;
|
|
420
|
+
// Request upload URL and paths
|
|
421
|
+
const { id, tempPath, finalPath, b2 } = await requestUploadPaths(apiUrl, apiKey, filePath, file.size, debug);
|
|
422
|
+
// Extract app metadata
|
|
423
|
+
const metadata = await extractBinaryMetadata(filePath, debug);
|
|
424
|
+
const env = apiUrl === 'https://api.devicecloud.dev' ? 'prod' : 'dev';
|
|
425
|
+
// Upload to Supabase
|
|
426
|
+
const supabaseResult = await uploadToSupabase(env, tempPath, file, debug);
|
|
427
|
+
let lastError = supabaseResult.error;
|
|
428
|
+
// Upload to Backblaze
|
|
429
|
+
const backblazeResult = await handleBackblazeUpload({
|
|
430
|
+
apiKey,
|
|
431
|
+
apiUrl,
|
|
432
|
+
b2: b2,
|
|
433
|
+
debug,
|
|
434
|
+
file,
|
|
435
|
+
filePath,
|
|
436
|
+
finalPath,
|
|
437
|
+
supabaseSuccess: supabaseResult.success,
|
|
438
|
+
});
|
|
439
|
+
// Update lastError if Supabase also failed
|
|
440
|
+
if (!supabaseResult.success && backblazeResult.error) {
|
|
441
|
+
lastError = backblazeResult.error;
|
|
442
|
+
}
|
|
443
|
+
// Validate results
|
|
444
|
+
validateUploadResults(supabaseResult.success, backblazeResult.success, lastError, b2, debug);
|
|
445
|
+
// Log upload summary
|
|
383
446
|
if (debug) {
|
|
447
|
+
const hasWarning = !supabaseResult.success && backblazeResult.success;
|
|
448
|
+
console.log(`[DEBUG] Upload summary - Supabase: ${supabaseResult.success ? '✓' : '✗'}, Backblaze: ${backblazeResult.success ? '✓' : '✗'}`);
|
|
384
449
|
console.log('[DEBUG] Finalizing upload...');
|
|
385
450
|
console.log(`[DEBUG] Target endpoint: ${apiUrl}/uploads/finaliseUpload`);
|
|
386
|
-
console.log(`[DEBUG]
|
|
387
|
-
console.log(`[DEBUG]
|
|
451
|
+
console.log(`[DEBUG] Uploaded to staging path: ${tempPath}`);
|
|
452
|
+
console.log(`[DEBUG] API will move to final path: ${finalPath}`);
|
|
453
|
+
console.log(`[DEBUG] Supabase upload status: ${supabaseResult.success ? 'SUCCESS' : 'FAILED'}`);
|
|
454
|
+
console.log(`[DEBUG] Backblaze upload status: ${backblazeResult.success ? 'SUCCESS' : 'FAILED'}`);
|
|
455
|
+
if (hasWarning)
|
|
456
|
+
console.log('[DEBUG] ⚠ Warning: File only exists in Backblaze (Supabase failed)');
|
|
388
457
|
}
|
|
458
|
+
// Finalize upload
|
|
389
459
|
const finalizeStartTime = Date.now();
|
|
390
|
-
await api_gateway_1.ApiGateway.finaliseUpload(
|
|
460
|
+
await api_gateway_1.ApiGateway.finaliseUpload({
|
|
461
|
+
apiKey,
|
|
462
|
+
backblazeSuccess: backblazeResult.success,
|
|
463
|
+
baseUrl: apiUrl,
|
|
464
|
+
id,
|
|
465
|
+
metadata,
|
|
466
|
+
path: tempPath,
|
|
467
|
+
sha: sha,
|
|
468
|
+
supabaseSuccess: supabaseResult.success,
|
|
469
|
+
});
|
|
391
470
|
if (debug) {
|
|
392
471
|
console.log(`[DEBUG] Upload finalization completed in ${Date.now() - finalizeStartTime}ms`);
|
|
393
472
|
console.log(`[DEBUG] Total upload time: ${Date.now() - startTime}ms`);
|
|
@@ -494,18 +573,11 @@ async function readFileObjectChunk(file, start, end) {
|
|
|
494
573
|
/**
|
|
495
574
|
* Upload large file to Backblaze using multi-part upload with streaming (for files >= 5MB)
|
|
496
575
|
* Uses file streaming to avoid loading entire file into memory, preventing OOM errors on large files
|
|
497
|
-
* @param
|
|
498
|
-
* @param apiKey - API key for authentication
|
|
499
|
-
* @param fileId - Backblaze file ID
|
|
500
|
-
* @param uploadPartUrls - Array of upload URLs for each part
|
|
501
|
-
* @param fileName - Name/path of the file in Backblaze
|
|
502
|
-
* @param filePath - Local file path to upload from (used when fileObject is not provided)
|
|
503
|
-
* @param fileSize - Total size of the file in bytes
|
|
504
|
-
* @param debug - Whether debug logging is enabled
|
|
505
|
-
* @param fileObject - Optional File/Blob object to upload from (used for .app bundles that are compressed in memory)
|
|
576
|
+
* @param config - Configuration object for the large file upload
|
|
506
577
|
* @returns Promise that resolves when upload completes or fails gracefully
|
|
507
578
|
*/
|
|
508
|
-
async function uploadLargeFileToBackblaze(
|
|
579
|
+
async function uploadLargeFileToBackblaze(config) {
|
|
580
|
+
const { apiUrl, apiKey, fileId, uploadPartUrls, filePath, fileSize, debug, fileObject } = config;
|
|
509
581
|
try {
|
|
510
582
|
const partSha1Array = [];
|
|
511
583
|
// Calculate part size (divide file evenly across all parts)
|
|
@@ -34,10 +34,10 @@ class DeviceValidationService {
|
|
|
34
34
|
throw new Error(`${androidDeviceID} ${googlePlay ? '(Play Store) ' : ''}only supports these Android API levels: ${supportedAndroidVersions.join(', ')}`);
|
|
35
35
|
}
|
|
36
36
|
if (debug && logger) {
|
|
37
|
-
logger(`DEBUG
|
|
38
|
-
logger(`DEBUG
|
|
39
|
-
logger(`DEBUG
|
|
40
|
-
logger(`DEBUG
|
|
37
|
+
logger(`[DEBUG] Android device: ${androidDeviceID}`);
|
|
38
|
+
logger(`[DEBUG] Android API level: ${version}`);
|
|
39
|
+
logger(`[DEBUG] Google Play enabled: ${googlePlay}`);
|
|
40
|
+
logger(`[DEBUG] Supported Android versions: ${supportedAndroidVersions.join(', ')}`);
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
/**
|
|
@@ -65,9 +65,9 @@ class DeviceValidationService {
|
|
|
65
65
|
throw new Error(`${iOSDeviceID} only supports these iOS versions: ${supportediOSVersions.join(', ')}`);
|
|
66
66
|
}
|
|
67
67
|
if (debug && logger) {
|
|
68
|
-
logger(`DEBUG
|
|
69
|
-
logger(`DEBUG
|
|
70
|
-
logger(`DEBUG
|
|
68
|
+
logger(`[DEBUG] iOS device: ${iOSDeviceID}`);
|
|
69
|
+
logger(`[DEBUG] iOS version: ${version}`);
|
|
70
|
+
logger(`[DEBUG] Supported iOS versions: ${supportediOSVersions.join(', ')}`);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
}
|
|
@@ -18,8 +18,8 @@ class MoropoService {
|
|
|
18
18
|
*/
|
|
19
19
|
async downloadAndExtract(options) {
|
|
20
20
|
const { apiKey, branchName = 'main', debug = false, quiet = false, json = false, logger } = options;
|
|
21
|
-
this.logDebug(debug, logger, 'DEBUG
|
|
22
|
-
this.logDebug(debug, logger, `DEBUG
|
|
21
|
+
this.logDebug(debug, logger, '[DEBUG] Moropo v1 API key detected, downloading tests from Moropo API');
|
|
22
|
+
this.logDebug(debug, logger, `[DEBUG] Using branch name: ${branchName}`);
|
|
23
23
|
try {
|
|
24
24
|
if (!quiet && !json) {
|
|
25
25
|
core_1.ux.action.start('Downloading Moropo tests', 'Initializing', {
|
|
@@ -37,7 +37,7 @@ class MoropoService {
|
|
|
37
37
|
throw new Error(`Failed to download Moropo tests: ${response.statusText}`);
|
|
38
38
|
}
|
|
39
39
|
const moropoDir = path.join(os.tmpdir(), `moropo-tests-${Date.now()}`);
|
|
40
|
-
this.logDebug(debug, logger, `DEBUG
|
|
40
|
+
this.logDebug(debug, logger, `[DEBUG] Extracting Moropo tests to: ${moropoDir}`);
|
|
41
41
|
// Create moropo directory if it doesn't exist
|
|
42
42
|
if (!fs.existsSync(moropoDir)) {
|
|
43
43
|
fs.mkdirSync(moropoDir, { recursive: true });
|
|
@@ -51,17 +51,17 @@ class MoropoService {
|
|
|
51
51
|
if (!quiet && !json) {
|
|
52
52
|
core_1.ux.action.stop('completed');
|
|
53
53
|
}
|
|
54
|
-
this.logDebug(debug, logger, 'DEBUG
|
|
54
|
+
this.logDebug(debug, logger, '[DEBUG] Successfully extracted Moropo tests');
|
|
55
55
|
// Create config.yaml file
|
|
56
56
|
this.createConfigFile(moropoDir);
|
|
57
|
-
this.logDebug(debug, logger, 'DEBUG
|
|
57
|
+
this.logDebug(debug, logger, '[DEBUG] Created config.yaml file');
|
|
58
58
|
return moropoDir;
|
|
59
59
|
}
|
|
60
60
|
catch (error) {
|
|
61
61
|
if (!quiet && !json) {
|
|
62
62
|
core_1.ux.action.stop('failed');
|
|
63
63
|
}
|
|
64
|
-
this.logDebug(debug, logger, `DEBUG
|
|
64
|
+
this.logDebug(debug, logger, `[DEBUG] Error downloading/extracting Moropo tests: ${error}`);
|
|
65
65
|
throw new Error(`Failed to download/extract Moropo tests: ${error}`);
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -16,7 +16,7 @@ class ReportDownloadService {
|
|
|
16
16
|
const { apiUrl, apiKey, uploadId, downloadType, artifactsPath = './artifacts.zip', debug = false, logger, warnLogger, } = options;
|
|
17
17
|
try {
|
|
18
18
|
if (debug && logger) {
|
|
19
|
-
logger(`DEBUG
|
|
19
|
+
logger(`[DEBUG] Downloading artifacts: ${downloadType}`);
|
|
20
20
|
}
|
|
21
21
|
await api_gateway_1.ApiGateway.downloadArtifactsZip(apiUrl, apiKey, uploadId, downloadType, artifactsPath);
|
|
22
22
|
if (logger) {
|
|
@@ -26,7 +26,7 @@ class ReportDownloadService {
|
|
|
26
26
|
}
|
|
27
27
|
catch (error) {
|
|
28
28
|
if (debug && logger) {
|
|
29
|
-
logger(`DEBUG
|
|
29
|
+
logger(`[DEBUG] Error downloading artifacts: ${error}`);
|
|
30
30
|
}
|
|
31
31
|
if (warnLogger) {
|
|
32
32
|
warnLogger('Failed to download artifacts');
|
|
@@ -83,7 +83,7 @@ class ReportDownloadService {
|
|
|
83
83
|
const { apiUrl, apiKey, uploadId, debug = false, logger, warnLogger } = options;
|
|
84
84
|
try {
|
|
85
85
|
if (debug && logger) {
|
|
86
|
-
logger(`DEBUG
|
|
86
|
+
logger(`[DEBUG] Downloading ${type.toUpperCase()} report`);
|
|
87
87
|
}
|
|
88
88
|
await api_gateway_1.ApiGateway.downloadReportGeneric(apiUrl, apiKey, uploadId, type, filePath);
|
|
89
89
|
if (logger) {
|
|
@@ -92,7 +92,7 @@ class ReportDownloadService {
|
|
|
92
92
|
}
|
|
93
93
|
catch (error) {
|
|
94
94
|
if (debug && logger) {
|
|
95
|
-
logger(`DEBUG
|
|
95
|
+
logger(`[DEBUG] Error downloading ${type.toUpperCase()} report: ${error}`);
|
|
96
96
|
}
|
|
97
97
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
98
98
|
if (warnLogger) {
|
|
@@ -37,7 +37,7 @@ class ResultsPollingService {
|
|
|
37
37
|
let sequentialPollFailures = 0;
|
|
38
38
|
let previousSummary = '';
|
|
39
39
|
if (debug && logger) {
|
|
40
|
-
logger(`DEBUG
|
|
40
|
+
logger(`[DEBUG] Starting polling loop for results`);
|
|
41
41
|
}
|
|
42
42
|
// Poll in a loop until all tests complete
|
|
43
43
|
// eslint-disable-next-line no-constant-condition
|
|
@@ -186,16 +186,16 @@ class ResultsPollingService {
|
|
|
186
186
|
*/
|
|
187
187
|
async fetchAndLogResults(apiUrl, apiKey, uploadId, debug, logger) {
|
|
188
188
|
if (debug && logger) {
|
|
189
|
-
logger(`DEBUG
|
|
189
|
+
logger(`[DEBUG] Polling for results: ${uploadId}`);
|
|
190
190
|
}
|
|
191
191
|
const { results: updatedResults } = await api_gateway_1.ApiGateway.getResultsForUpload(apiUrl, apiKey, uploadId);
|
|
192
192
|
if (!updatedResults) {
|
|
193
193
|
throw new Error('no results');
|
|
194
194
|
}
|
|
195
195
|
if (debug && logger) {
|
|
196
|
-
logger(`DEBUG
|
|
196
|
+
logger(`[DEBUG] Poll received ${updatedResults.length} results`);
|
|
197
197
|
for (const result of updatedResults) {
|
|
198
|
-
logger(`DEBUG
|
|
198
|
+
logger(`[DEBUG] Result status: ${result.test_file_name} - ${result.status}`);
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
return updatedResults;
|
|
@@ -216,39 +216,39 @@ class ResultsPollingService {
|
|
|
216
216
|
async handleCompletedTests(updatedResults, options) {
|
|
217
217
|
const { uploadId, consoleUrl, json, debug, logger } = options;
|
|
218
218
|
if (debug && logger) {
|
|
219
|
-
logger(`DEBUG
|
|
219
|
+
logger(`[DEBUG] All tests completed, stopping poll`);
|
|
220
220
|
}
|
|
221
221
|
this.displayFinalResults(updatedResults, consoleUrl, json, logger);
|
|
222
222
|
const output = this.buildPollingResult(updatedResults, uploadId, consoleUrl);
|
|
223
223
|
if (output.status === 'FAILED') {
|
|
224
224
|
if (debug && logger) {
|
|
225
|
-
logger(`DEBUG
|
|
225
|
+
logger(`[DEBUG] Some tests failed, returning failed status`);
|
|
226
226
|
}
|
|
227
227
|
throw new RunFailedError(output);
|
|
228
228
|
}
|
|
229
229
|
if (debug && logger) {
|
|
230
|
-
logger(`DEBUG
|
|
230
|
+
logger(`[DEBUG] All tests passed, returning success status`);
|
|
231
231
|
}
|
|
232
232
|
return output;
|
|
233
233
|
}
|
|
234
234
|
async handlePollingError(error, sequentialPollFailures, debug, logger) {
|
|
235
235
|
if (debug && logger) {
|
|
236
|
-
logger(`DEBUG
|
|
237
|
-
logger(`DEBUG
|
|
236
|
+
logger(`[DEBUG] Error polling for results: ${error}`);
|
|
237
|
+
logger(`[DEBUG] Sequential poll failures: ${sequentialPollFailures}`);
|
|
238
238
|
}
|
|
239
239
|
if (sequentialPollFailures > this.MAX_SEQUENTIAL_FAILURES) {
|
|
240
240
|
if (debug && logger) {
|
|
241
|
-
logger('DEBUG
|
|
241
|
+
logger('[DEBUG] Checking internet connectivity...');
|
|
242
242
|
}
|
|
243
243
|
const connectivityCheck = await (0, connectivity_1.checkInternetConnectivity)();
|
|
244
244
|
if (debug && logger) {
|
|
245
|
-
logger(`DEBUG
|
|
245
|
+
logger(`[DEBUG] ${connectivityCheck.message}`);
|
|
246
246
|
for (const result of connectivityCheck.endpointResults) {
|
|
247
247
|
if (result.success) {
|
|
248
|
-
logger(`DEBUG
|
|
248
|
+
logger(`[DEBUG] ✓ ${result.endpoint} - ${result.statusCode} (${result.latencyMs}ms)`);
|
|
249
249
|
}
|
|
250
250
|
else {
|
|
251
|
-
logger(`DEBUG
|
|
251
|
+
logger(`[DEBUG] ✗ ${result.endpoint} - ${result.error} (${result.latencyMs}ms)`);
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
}
|