dashcam 1.3.13-beta → 1.3.15-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.
@@ -36,13 +36,41 @@ logger.info('Background recording process started', {
36
36
  // Write status file
37
37
  function writeStatus(status) {
38
38
  try {
39
- fs.writeFileSync(STATUS_FILE, JSON.stringify({
39
+ const statusData = {
40
40
  ...status,
41
41
  timestamp: Date.now(),
42
- pid: process.pid
43
- }, null, 2));
42
+ pid: process.pid,
43
+ platform: process.platform
44
+ };
45
+
46
+ logger.debug('Background process writing status file', {
47
+ statusFile: STATUS_FILE,
48
+ pid: statusData.pid,
49
+ isRecording: statusData.isRecording,
50
+ platform: statusData.platform
51
+ });
52
+
53
+ fs.writeFileSync(STATUS_FILE, JSON.stringify(statusData, null, 2));
54
+
55
+ // Verify it was written
56
+ if (fs.existsSync(STATUS_FILE)) {
57
+ logger.debug('Status file written and verified', { statusFile: STATUS_FILE });
58
+
59
+ // Read it back to verify content
60
+ const writtenContent = fs.readFileSync(STATUS_FILE, 'utf8');
61
+ logger.debug('Status file content verification', {
62
+ contentLength: writtenContent.length,
63
+ parseable: true
64
+ });
65
+ } else {
66
+ logger.error('Status file does not exist after write!', { statusFile: STATUS_FILE });
67
+ }
44
68
  } catch (error) {
45
- logger.error('Failed to write status file', { error });
69
+ logger.error('Failed to write status file in background process', {
70
+ error: error.message,
71
+ stack: error.stack,
72
+ statusFile: STATUS_FILE
73
+ });
46
74
  }
47
75
  }
48
76
 
package/bin/dashcam.js CHANGED
@@ -395,8 +395,39 @@ program
395
395
  // Enable verbose logging for stop command
396
396
  setVerbose(true);
397
397
 
398
- if (!processManager.isRecordingActive()) {
398
+ logger.debug('Stop command invoked', {
399
+ platform: process.platform,
400
+ cwd: process.cwd(),
401
+ pid: process.pid,
402
+ processDir: require('os').homedir() + '/.dashcam-cli'
403
+ });
404
+
405
+ const isActive = processManager.isRecordingActive();
406
+ logger.debug('Recording active check result', { isActive });
407
+
408
+ if (!isActive) {
399
409
  console.log('No active recording to stop');
410
+ logger.warn('Stop command called but no active recording found', {
411
+ platform: process.platform,
412
+ statusFile: require('path').join(require('os').homedir(), '.dashcam-cli', 'status.json'),
413
+ statusFileExists: require('fs').existsSync(require('path').join(require('os').homedir(), '.dashcam-cli', 'status.json'))
414
+ });
415
+
416
+ // Try to read and display status file for debugging
417
+ try {
418
+ const statusPath = require('path').join(require('os').homedir(), '.dashcam-cli', 'status.json');
419
+ if (require('fs').existsSync(statusPath)) {
420
+ const statusContent = require('fs').readFileSync(statusPath, 'utf8');
421
+ logger.debug('Status file contents', { content: statusContent });
422
+ console.log('Status file exists but recording not detected as active');
423
+ console.log('Status file location:', statusPath);
424
+ } else {
425
+ console.log('Status file does not exist');
426
+ }
427
+ } catch (err) {
428
+ logger.error('Failed to read status file for debugging', { error: err.message });
429
+ }
430
+
400
431
  process.exit(0);
401
432
  }
402
433
 
@@ -404,55 +435,94 @@ program
404
435
  const logFile = path.join(process.cwd(), '.dashcam', 'recording.log');
405
436
 
406
437
  console.log('Stopping recording...');
438
+ logger.debug('Active status before stop:', activeStatus);
407
439
 
408
440
  try {
441
+ logger.debug('Calling stopActiveRecording...');
409
442
  const result = await processManager.stopActiveRecording();
410
443
 
411
444
  if (!result) {
412
445
  console.log('Failed to stop recording');
446
+ logger.error('stopActiveRecording returned null/false');
413
447
  process.exit(1);
414
448
  }
415
449
 
416
450
  console.log('Recording stopped successfully');
451
+ logger.debug('Stop result:', result);
417
452
 
418
453
  // Wait for upload to complete (background process handles this)
419
454
  logger.debug('Waiting for background upload to complete...');
420
- console.log('Uploading recording...');
455
+ console.log('Checking if background process uploaded...');
421
456
 
422
457
  // Wait up to 2 minutes for upload result to appear
423
458
  const maxWaitForUpload = 120000; // 2 minutes
424
459
  const startWaitForUpload = Date.now();
425
460
  let uploadResult = null;
461
+ let checkCount = 0;
426
462
 
427
463
  while (!uploadResult && (Date.now() - startWaitForUpload) < maxWaitForUpload) {
428
464
  uploadResult = processManager.readUploadResult();
465
+ checkCount++;
466
+
429
467
  if (!uploadResult) {
468
+ // Log every 10 seconds to show progress
469
+ if (checkCount % 10 === 0) {
470
+ const elapsed = Math.round((Date.now() - startWaitForUpload) / 1000);
471
+ logger.debug(`Still waiting for background upload... (${elapsed}s elapsed)`);
472
+ console.log(`Waiting for background upload... (${elapsed}s)`);
473
+ }
430
474
  await new Promise(resolve => setTimeout(resolve, 1000)); // Check every second
431
475
  }
432
476
  }
433
477
 
434
- logger.debug('Upload result read attempt', { found: !!uploadResult, shareLink: uploadResult?.shareLink });
478
+ logger.debug('Upload result read attempt', {
479
+ found: !!uploadResult,
480
+ shareLink: uploadResult?.shareLink,
481
+ checksPerformed: checkCount,
482
+ timeElapsed: Math.round((Date.now() - startWaitForUpload) / 1000) + 's'
483
+ });
435
484
 
436
485
  if (uploadResult && uploadResult.shareLink) {
437
486
  console.log('Watch your recording:', uploadResult.shareLink);
487
+ logger.info('Background process upload succeeded');
438
488
  // Clean up the result file now that we've read it
439
489
  processManager.cleanup();
440
490
  process.exit(0);
441
491
  }
442
492
 
493
+ logger.debug('No upload result from background process, checking files...');
494
+
443
495
  // 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));
496
+ const videoExists = fs.existsSync(result.outputPath);
497
+ const gifExists = !result.gifPath || fs.existsSync(result.gifPath);
498
+ const snapshotExists = !result.snapshotPath || fs.existsSync(result.snapshotPath);
499
+
500
+ logger.debug('File existence check:', {
501
+ video: videoExists,
502
+ gif: gifExists,
503
+ snapshot: snapshotExists,
504
+ outputPath: result.outputPath,
505
+ gifPath: result.gifPath,
506
+ snapshotPath: result.snapshotPath
507
+ });
508
+
509
+ const filesExist = videoExists && gifExists && snapshotExists;
447
510
 
448
511
  if (!filesExist) {
449
- console.log('Recording uploaded by background process');
450
- logger.info('Files were cleaned up by background process');
512
+ console.log('Recording appears to be uploaded by background process (files deleted)');
513
+ logger.info('Files were cleaned up by background process, assuming upload succeeded');
451
514
  process.exit(0);
452
515
  }
453
516
 
454
517
  // Always attempt to upload - let upload function find project if needed
455
- console.log('Uploading recording...');
518
+ console.log('No upload result found, uploading from foreground process...');
519
+ logger.debug('Starting foreground upload with metadata:', {
520
+ title: activeStatus?.options?.title,
521
+ project: activeStatus?.options?.project,
522
+ duration: result.duration,
523
+ outputPath: result.outputPath
524
+ });
525
+
456
526
  try {
457
527
  const uploadResult = await upload(result.outputPath, {
458
528
  title: activeStatus?.options?.title,
@@ -467,12 +537,23 @@ program
467
537
  });
468
538
 
469
539
  console.log('Watch your recording:', uploadResult.shareLink);
540
+ logger.info('Foreground upload succeeded');
470
541
  } catch (uploadError) {
471
542
  console.error('Upload failed:', uploadError.message);
543
+ logger.error('Upload error details:', {
544
+ message: uploadError.message,
545
+ stack: uploadError.stack,
546
+ code: uploadError.code,
547
+ statusCode: uploadError.response?.statusCode
548
+ });
472
549
  console.log('Recording saved locally:', result.outputPath);
473
550
  }
474
551
  } catch (error) {
475
552
  console.error('Failed to stop recording:', error.message);
553
+ logger.error('Stop recording error details:', {
554
+ message: error.message,
555
+ stack: error.stack
556
+ });
476
557
  process.exit(1);
477
558
  }
478
559
 
@@ -30,27 +30,86 @@ class ProcessManager {
30
30
 
31
31
  writeStatus(status) {
32
32
  try {
33
- fs.writeFileSync(STATUS_FILE, JSON.stringify({
33
+ const statusData = {
34
34
  ...status,
35
35
  timestamp: Date.now(),
36
36
  pid: process.pid
37
- }, null, 2));
37
+ };
38
+
39
+ logger.debug('Writing status file', {
40
+ statusFile: STATUS_FILE,
41
+ pid: statusData.pid,
42
+ isRecording: statusData.isRecording,
43
+ platform: process.platform
44
+ });
45
+
46
+ fs.writeFileSync(STATUS_FILE, JSON.stringify(statusData, null, 2));
47
+
48
+ // Verify it was written
49
+ if (fs.existsSync(STATUS_FILE)) {
50
+ logger.debug('Status file written successfully');
51
+ } else {
52
+ logger.error('Status file does not exist after write!');
53
+ }
38
54
  } catch (error) {
39
- logger.error('Failed to write status file', { error });
55
+ logger.error('Failed to write status file', {
56
+ error: error.message,
57
+ stack: error.stack,
58
+ statusFile: STATUS_FILE
59
+ });
40
60
  }
41
61
  }
42
62
 
43
63
  readStatus() {
44
64
  try {
45
- if (!fs.existsSync(STATUS_FILE)) return null;
65
+ logger.debug('Reading status file', {
66
+ statusFile: STATUS_FILE,
67
+ exists: fs.existsSync(STATUS_FILE)
68
+ });
69
+
70
+ if (!fs.existsSync(STATUS_FILE)) {
71
+ logger.debug('Status file does not exist');
72
+ return null;
73
+ }
74
+
46
75
  const data = fs.readFileSync(STATUS_FILE, 'utf8');
47
- return JSON.parse(data);
76
+ const status = JSON.parse(data);
77
+
78
+ logger.debug('Status file read successfully', {
79
+ pid: status.pid,
80
+ isRecording: status.isRecording,
81
+ timestamp: status.timestamp,
82
+ startTime: status.startTime,
83
+ outputPath: status.outputPath
84
+ });
85
+
86
+ return status;
48
87
  } catch (error) {
49
- logger.error('Failed to read status file', { error });
88
+ logger.error('Failed to read status file', {
89
+ error: error.message,
90
+ stack: error.stack,
91
+ statusFile: STATUS_FILE
92
+ });
50
93
  return null;
51
94
  }
52
95
  }
53
96
 
97
+ markStatusCompleted(completionData = {}) {
98
+ try {
99
+ const status = this.readStatus();
100
+ if (status) {
101
+ fs.writeFileSync(STATUS_FILE, JSON.stringify({
102
+ ...status,
103
+ isRecording: false,
104
+ completedAt: Date.now(),
105
+ ...completionData
106
+ }, null, 2));
107
+ }
108
+ } catch (error) {
109
+ logger.error('Failed to mark status as completed', { error });
110
+ }
111
+ }
112
+
54
113
  writeUploadResult(result) {
55
114
  try {
56
115
  logger.info('Writing upload result to file', { path: RESULT_FILE, shareLink: result.shareLink });
@@ -82,24 +141,77 @@ class ProcessManager {
82
141
  }
83
142
 
84
143
  isProcessRunning(pid) {
85
- if (!pid) return false;
144
+ if (!pid) {
145
+ logger.debug('isProcessRunning: no PID provided');
146
+ return false;
147
+ }
148
+
86
149
  try {
87
150
  process.kill(pid, 0); // Signal 0 just checks if process exists
151
+ logger.debug('Process is running', { pid });
88
152
  return true;
89
153
  } catch (error) {
154
+ logger.debug('Process is not running', {
155
+ pid,
156
+ error: error.code,
157
+ platform: process.platform
158
+ });
90
159
  return false;
91
160
  }
92
161
  }
93
162
 
94
163
  isRecordingActive() {
164
+ logger.debug('Checking if recording is active...', {
165
+ statusFile: STATUS_FILE,
166
+ processDir: PROCESS_DIR,
167
+ platform: process.platform
168
+ });
169
+
95
170
  const status = this.readStatus();
96
171
 
97
- if (!status || !status.pid || !this.isProcessRunning(status.pid)) {
98
- // Clean up but preserve upload result in case the background process just finished uploading
99
- this.cleanup({ preserveResult: true });
172
+ logger.debug('Status check result', {
173
+ hasStatus: !!status,
174
+ hasPid: !!(status && status.pid),
175
+ isRecording: status ? status.isRecording : null,
176
+ statusPid: status ? status.pid : null,
177
+ currentPid: process.pid
178
+ });
179
+
180
+ if (!status) {
181
+ logger.debug('No status found - recording not active');
100
182
  return false;
101
183
  }
102
184
 
185
+ if (!status.pid) {
186
+ logger.debug('Status has no PID - marking as completed');
187
+ this.markStatusCompleted({ reason: 'no_pid_in_status' });
188
+ return false;
189
+ }
190
+
191
+ const processRunning = this.isProcessRunning(status.pid);
192
+ logger.debug('Process running check', {
193
+ pid: status.pid,
194
+ isRunning: processRunning
195
+ });
196
+
197
+ if (!processRunning) {
198
+ logger.debug('Process not running - marking as completed', {
199
+ pid: status.pid,
200
+ wasRecording: status.isRecording
201
+ });
202
+
203
+ // Mark as completed if process is dead but status exists
204
+ if (status.isRecording) {
205
+ this.markStatusCompleted({ reason: 'process_not_running' });
206
+ }
207
+ return false;
208
+ }
209
+
210
+ logger.debug('Recording active status', {
211
+ isRecording: status.isRecording,
212
+ pid: status.pid
213
+ });
214
+
103
215
  return status.isRecording;
104
216
  }
105
217
 
@@ -111,7 +223,7 @@ class ProcessManager {
111
223
  cleanup(options = {}) {
112
224
  const { preserveResult = false } = options;
113
225
  try {
114
- if (fs.existsSync(STATUS_FILE)) fs.unlinkSync(STATUS_FILE);
226
+ // Only delete result file, keep status file for history
115
227
  if (!preserveResult && fs.existsSync(RESULT_FILE)) fs.unlinkSync(RESULT_FILE);
116
228
  } catch (error) {
117
229
  logger.error('Failed to cleanup process files', { error });
@@ -137,7 +249,7 @@ class ProcessManager {
137
249
  const pid = status.pid;
138
250
  if (!pid || !this.isProcessRunning(pid)) {
139
251
  logger.warn('Background process not running');
140
- this.cleanup({ preserveResult: true });
252
+ this.markStatusCompleted({ reason: 'process_already_stopped' });
141
253
  return false;
142
254
  }
143
255
 
@@ -172,6 +284,12 @@ class ProcessManager {
172
284
 
173
285
  logger.info('Background process stopped');
174
286
 
287
+ // Mark status as completed
288
+ this.markStatusCompleted({
289
+ reason: 'stopped_by_user',
290
+ duration: Date.now() - status.startTime
291
+ });
292
+
175
293
  // Return a minimal result indicating success
176
294
  // The upload will be handled by the stop command checking the result file
177
295
  return {
@@ -291,11 +409,8 @@ class ProcessManager {
291
409
  logger.info('Recording stopped successfully during graceful exit');
292
410
  } catch (error) {
293
411
  logger.error('Failed to stop recording during graceful exit', { error });
294
- this.cleanup(); // Fallback cleanup
412
+ this.markStatusCompleted({ reason: 'graceful_exit_error' });
295
413
  }
296
- } else {
297
- // Just cleanup if no recording is active
298
- this.cleanup();
299
414
  }
300
415
 
301
416
  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.13-beta",
3
+ "version": "1.3.15-beta",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/dashcam.js",
6
6
  "bin": {