dashcam 1.3.12-beta → 1.3.14-beta

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/bin/dashcam.js CHANGED
@@ -404,55 +404,94 @@ program
404
404
  const logFile = path.join(process.cwd(), '.dashcam', 'recording.log');
405
405
 
406
406
  console.log('Stopping recording...');
407
+ logger.debug('Active status before stop:', activeStatus);
407
408
 
408
409
  try {
410
+ logger.debug('Calling stopActiveRecording...');
409
411
  const result = await processManager.stopActiveRecording();
410
412
 
411
413
  if (!result) {
412
414
  console.log('Failed to stop recording');
415
+ logger.error('stopActiveRecording returned null/false');
413
416
  process.exit(1);
414
417
  }
415
418
 
416
419
  console.log('Recording stopped successfully');
420
+ logger.debug('Stop result:', result);
417
421
 
418
422
  // Wait for upload to complete (background process handles this)
419
423
  logger.debug('Waiting for background upload to complete...');
420
- console.log('Uploading recording...');
424
+ console.log('Checking if background process uploaded...');
421
425
 
422
426
  // Wait up to 2 minutes for upload result to appear
423
427
  const maxWaitForUpload = 120000; // 2 minutes
424
428
  const startWaitForUpload = Date.now();
425
429
  let uploadResult = null;
430
+ let checkCount = 0;
426
431
 
427
432
  while (!uploadResult && (Date.now() - startWaitForUpload) < maxWaitForUpload) {
428
433
  uploadResult = processManager.readUploadResult();
434
+ checkCount++;
435
+
429
436
  if (!uploadResult) {
437
+ // Log every 10 seconds to show progress
438
+ if (checkCount % 10 === 0) {
439
+ const elapsed = Math.round((Date.now() - startWaitForUpload) / 1000);
440
+ logger.debug(`Still waiting for background upload... (${elapsed}s elapsed)`);
441
+ console.log(`Waiting for background upload... (${elapsed}s)`);
442
+ }
430
443
  await new Promise(resolve => setTimeout(resolve, 1000)); // Check every second
431
444
  }
432
445
  }
433
446
 
434
- logger.debug('Upload result read attempt', { found: !!uploadResult, shareLink: uploadResult?.shareLink });
447
+ logger.debug('Upload result read attempt', {
448
+ found: !!uploadResult,
449
+ shareLink: uploadResult?.shareLink,
450
+ checksPerformed: checkCount,
451
+ timeElapsed: Math.round((Date.now() - startWaitForUpload) / 1000) + 's'
452
+ });
435
453
 
436
454
  if (uploadResult && uploadResult.shareLink) {
437
455
  console.log('Watch your recording:', uploadResult.shareLink);
456
+ logger.info('Background process upload succeeded');
438
457
  // Clean up the result file now that we've read it
439
458
  processManager.cleanup();
440
459
  process.exit(0);
441
460
  }
442
461
 
462
+ logger.debug('No upload result from background process, checking files...');
463
+
443
464
  // Check if files still exist - if not, background process already uploaded
444
- const filesExist = fs.existsSync(result.outputPath) &&
445
- (!result.gifPath || fs.existsSync(result.gifPath)) &&
446
- (!result.snapshotPath || fs.existsSync(result.snapshotPath));
465
+ const videoExists = fs.existsSync(result.outputPath);
466
+ const gifExists = !result.gifPath || fs.existsSync(result.gifPath);
467
+ const snapshotExists = !result.snapshotPath || fs.existsSync(result.snapshotPath);
468
+
469
+ logger.debug('File existence check:', {
470
+ video: videoExists,
471
+ gif: gifExists,
472
+ snapshot: snapshotExists,
473
+ outputPath: result.outputPath,
474
+ gifPath: result.gifPath,
475
+ snapshotPath: result.snapshotPath
476
+ });
477
+
478
+ const filesExist = videoExists && gifExists && snapshotExists;
447
479
 
448
480
  if (!filesExist) {
449
- console.log('Recording uploaded by background process');
450
- logger.info('Files were cleaned up by background process');
481
+ console.log('Recording appears to be uploaded by background process (files deleted)');
482
+ logger.info('Files were cleaned up by background process, assuming upload succeeded');
451
483
  process.exit(0);
452
484
  }
453
485
 
454
486
  // Always attempt to upload - let upload function find project if needed
455
- console.log('Uploading recording...');
487
+ console.log('No upload result found, uploading from foreground process...');
488
+ logger.debug('Starting foreground upload with metadata:', {
489
+ title: activeStatus?.options?.title,
490
+ project: activeStatus?.options?.project,
491
+ duration: result.duration,
492
+ outputPath: result.outputPath
493
+ });
494
+
456
495
  try {
457
496
  const uploadResult = await upload(result.outputPath, {
458
497
  title: activeStatus?.options?.title,
@@ -467,12 +506,23 @@ program
467
506
  });
468
507
 
469
508
  console.log('Watch your recording:', uploadResult.shareLink);
509
+ logger.info('Foreground upload succeeded');
470
510
  } catch (uploadError) {
471
511
  console.error('Upload failed:', uploadError.message);
512
+ logger.error('Upload error details:', {
513
+ message: uploadError.message,
514
+ stack: uploadError.stack,
515
+ code: uploadError.code,
516
+ statusCode: uploadError.response?.statusCode
517
+ });
472
518
  console.log('Recording saved locally:', result.outputPath);
473
519
  }
474
520
  } catch (error) {
475
521
  console.error('Failed to stop recording:', error.message);
522
+ logger.error('Stop recording error details:', {
523
+ message: error.message,
524
+ stack: error.stack
525
+ });
476
526
  process.exit(1);
477
527
  }
478
528
 
@@ -10,7 +10,6 @@ const __dirname = path.dirname(__filename);
10
10
 
11
11
  // Use a fixed directory in the user's home directory for cross-process communication
12
12
  const PROCESS_DIR = path.join(os.homedir(), '.dashcam-cli');
13
- const PID_FILE = path.join(PROCESS_DIR, 'recording.pid');
14
13
  const STATUS_FILE = path.join(PROCESS_DIR, 'status.json');
15
14
  const RESULT_FILE = path.join(PROCESS_DIR, 'upload-result.json');
16
15
 
@@ -52,6 +51,22 @@ class ProcessManager {
52
51
  }
53
52
  }
54
53
 
54
+ markStatusCompleted(completionData = {}) {
55
+ try {
56
+ const status = this.readStatus();
57
+ if (status) {
58
+ fs.writeFileSync(STATUS_FILE, JSON.stringify({
59
+ ...status,
60
+ isRecording: false,
61
+ completedAt: Date.now(),
62
+ ...completionData
63
+ }, null, 2));
64
+ }
65
+ } catch (error) {
66
+ logger.error('Failed to mark status as completed', { error });
67
+ }
68
+ }
69
+
55
70
  writeUploadResult(result) {
56
71
  try {
57
72
  logger.info('Writing upload result to file', { path: RESULT_FILE, shareLink: result.shareLink });
@@ -82,24 +97,6 @@ class ProcessManager {
82
97
  }
83
98
  }
84
99
 
85
- writePid(pid = process.pid) {
86
- try {
87
- fs.writeFileSync(PID_FILE, pid.toString());
88
- } catch (error) {
89
- logger.error('Failed to write PID file', { error });
90
- }
91
- }
92
-
93
- readPid() {
94
- try {
95
- if (!fs.existsSync(PID_FILE)) return null;
96
- const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim());
97
- return isNaN(pid) ? null : pid;
98
- } catch (error) {
99
- return null;
100
- }
101
- }
102
-
103
100
  isProcessRunning(pid) {
104
101
  if (!pid) return false;
105
102
  try {
@@ -111,16 +108,17 @@ class ProcessManager {
111
108
  }
112
109
 
113
110
  isRecordingActive() {
114
- const pid = this.readPid();
115
111
  const status = this.readStatus();
116
112
 
117
- if (!pid || !this.isProcessRunning(pid)) {
118
- // Clean up but preserve upload result in case the background process just finished uploading
119
- this.cleanup({ preserveResult: true });
113
+ if (!status || !status.pid || !this.isProcessRunning(status.pid)) {
114
+ // Mark as completed if process is dead but status exists
115
+ if (status && status.isRecording) {
116
+ this.markStatusCompleted({ reason: 'process_not_running' });
117
+ }
120
118
  return false;
121
119
  }
122
120
 
123
- return status && status.isRecording;
121
+ return status.isRecording;
124
122
  }
125
123
 
126
124
  getActiveStatus() {
@@ -131,8 +129,7 @@ class ProcessManager {
131
129
  cleanup(options = {}) {
132
130
  const { preserveResult = false } = options;
133
131
  try {
134
- if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
135
- if (fs.existsSync(STATUS_FILE)) fs.unlinkSync(STATUS_FILE);
132
+ // Only delete result file, keep status file for history
136
133
  if (!preserveResult && fs.existsSync(RESULT_FILE)) fs.unlinkSync(RESULT_FILE);
137
134
  } catch (error) {
138
135
  logger.error('Failed to cleanup process files', { error });
@@ -149,16 +146,16 @@ class ProcessManager {
149
146
 
150
147
  try {
151
148
  const status = this.readStatus();
152
- const pid = this.readPid();
153
149
 
154
150
  if (!status || !status.isRecording) {
155
151
  logger.warn('No active recording found');
156
152
  return false;
157
153
  }
158
154
 
155
+ const pid = status.pid;
159
156
  if (!pid || !this.isProcessRunning(pid)) {
160
157
  logger.warn('Background process not running');
161
- this.cleanup({ preserveResult: true });
158
+ this.markStatusCompleted({ reason: 'process_already_stopped' });
162
159
  return false;
163
160
  }
164
161
 
@@ -193,6 +190,12 @@ class ProcessManager {
193
190
 
194
191
  logger.info('Background process stopped');
195
192
 
193
+ // Mark status as completed
194
+ this.markStatusCompleted({
195
+ reason: 'stopped_by_user',
196
+ duration: Date.now() - status.startTime
197
+ });
198
+
196
199
  // Return a minimal result indicating success
197
200
  // The upload will be handled by the stop command checking the result file
198
201
  return {
@@ -312,11 +315,8 @@ class ProcessManager {
312
315
  logger.info('Recording stopped successfully during graceful exit');
313
316
  } catch (error) {
314
317
  logger.error('Failed to stop recording during graceful exit', { error });
315
- this.cleanup(); // Fallback cleanup
318
+ this.markStatusCompleted({ reason: 'graceful_exit_error' });
316
319
  }
317
- } else {
318
- // Just cleanup if no recording is active
319
- this.cleanup();
320
320
  }
321
321
 
322
322
  process.exit(0);
package/lib/uploader.js CHANGED
@@ -110,6 +110,11 @@ class Uploader {
110
110
  total: progress.total,
111
111
  speedMBps: speedMBps.toFixed(2)
112
112
  });
113
+
114
+ // Also output to console for user feedback
115
+ if (fileType === 'video') {
116
+ console.log(`Uploading video: ${percent}%`);
117
+ }
113
118
  }
114
119
 
115
120
  // Call progress callback if registered
@@ -125,6 +130,7 @@ class Uploader {
125
130
  const uploadDuration = (Date.now() - upload.startTime) / 1000;
126
131
 
127
132
  if (extension !== 'png') {
133
+ console.log(`Uploaded ${fileType} successfully (${uploadDuration.toFixed(1)}s)`);
128
134
  logger.info(`Successfully uploaded ${fileType}`, {
129
135
  key: result.Key,
130
136
  location: result.Location,
@@ -143,10 +149,14 @@ class Uploader {
143
149
  logExit();
144
150
  return result;
145
151
  } catch (error) {
152
+ console.error(`Failed to upload ${fileType}: ${error.message}`);
146
153
  logger.error('Upload error:', {
147
154
  fileType,
148
155
  file: path.basename(file),
149
156
  error: error.message,
157
+ code: error.code,
158
+ statusCode: error.$metadata?.httpStatusCode,
159
+ requestId: error.$metadata?.requestId,
150
160
  stack: error.stack
151
161
  });
152
162
 
@@ -254,6 +264,7 @@ export async function upload(filePath, metadata = {}) {
254
264
 
255
265
  logger.verbose('Creating replay with config', replayConfig);
256
266
 
267
+ console.log('Creating replay on server...');
257
268
  logger.info('Creating replay', replayConfig);
258
269
 
259
270
  // Create the replay first
@@ -261,6 +272,7 @@ export async function upload(filePath, metadata = {}) {
261
272
 
262
273
  let newReplay;
263
274
  try {
275
+ logger.debug('Sending replay creation request...');
264
276
  newReplay = await got.post('https://testdriver-api.onrender.com/api/v1/replay', {
265
277
  headers: {
266
278
  Authorization: `Bearer ${token}`
@@ -269,16 +281,19 @@ export async function upload(filePath, metadata = {}) {
269
281
  timeout: 30000
270
282
  }).json();
271
283
 
284
+ console.log('Replay created successfully');
272
285
  logger.info('Replay created successfully', {
273
286
  replayId: newReplay.replay.id,
274
287
  shareKey: newReplay.replay.shareKey,
275
288
  shareLink: newReplay.replay.shareLink
276
289
  });
277
290
  } catch (error) {
291
+ console.error('Failed to create replay on server');
278
292
  logger.error('Failed to create replay', {
279
293
  status: error.response?.statusCode,
280
294
  statusText: error.response?.statusMessage,
281
295
  body: error.response?.body,
296
+ message: error.message,
282
297
  replayConfig: replayConfig
283
298
  });
284
299
  throw error;
@@ -310,8 +325,10 @@ export async function upload(filePath, metadata = {}) {
310
325
  }
311
326
 
312
327
  logger.verbose('Getting STS credentials for replay', { replayId: newReplay.replay.id });
328
+ console.log('Getting upload credentials...');
313
329
  const sts = await auth.getStsCredentials(replayData);
314
330
 
331
+ console.log('Starting file uploads...');
315
332
  logger.verbose('STS credentials received', {
316
333
  hasVideo: !!sts.video,
317
334
  hasImage: !!sts.image,
@@ -342,6 +359,7 @@ export async function upload(filePath, metadata = {}) {
342
359
  }
343
360
 
344
361
  logger.info('Starting asset uploads', { totalUploads: promises.length });
362
+ console.log(`Uploading ${promises.length} file(s)...`);
345
363
 
346
364
  // Process and upload logs if available
347
365
  if (metadata.logs && metadata.logs.length > 0) {
@@ -420,6 +438,8 @@ export async function upload(filePath, metadata = {}) {
420
438
  });
421
439
  }
422
440
 
441
+ logger.debug('Waiting for all uploads to complete...');
442
+ console.log('Finalizing uploads...');
423
443
  await Promise.all(promises);
424
444
 
425
445
  // Clean up uploaded files after all uploads complete successfully
@@ -436,13 +456,27 @@ export async function upload(filePath, metadata = {}) {
436
456
 
437
457
  // Publish the replay (like the desktop app does)
438
458
  logger.debug('Publishing replay...');
439
- await got.post('https://testdriver-api.onrender.com/api/v1/replay/publish', {
440
- headers: {
441
- Authorization: `Bearer ${token}`
442
- },
443
- json: { id: newReplay.replay.id },
444
- timeout: 30000
445
- }).json();
459
+ console.log('Publishing replay...');
460
+
461
+ try {
462
+ await got.post('https://testdriver-api.onrender.com/api/v1/replay/publish', {
463
+ headers: {
464
+ Authorization: `Bearer ${token}`
465
+ },
466
+ json: { id: newReplay.replay.id },
467
+ timeout: 30000
468
+ }).json();
469
+
470
+ console.log('Replay published successfully');
471
+ } catch (error) {
472
+ console.error('Failed to publish replay:', error.message);
473
+ logger.error('Publish error:', {
474
+ message: error.message,
475
+ statusCode: error.response?.statusCode,
476
+ body: error.response?.body
477
+ });
478
+ throw error;
479
+ }
446
480
 
447
481
  logger.info('Upload process completed successfully', {
448
482
  replayId: newReplay.replay.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.3.12-beta",
3
+ "version": "1.3.14-beta",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/dashcam.js",
6
6
  "bin": {