dashcam 1.0.1-beta.4 โ†’ 1.0.1-beta.6

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/lib/logs/index.js CHANGED
@@ -15,7 +15,13 @@ const CLI_CONFIG_FILE = path.join(process.cwd(), '.dashcam', 'cli-config.json');
15
15
 
16
16
  // Simple trim function for CLI (adapted from desktop app)
17
17
  async function trimLogs(groupLogStatuses, startMS, endMS, clientStartDate, clipId) {
18
- logger.info('Trimming logs', { count: groupLogStatuses.length });
18
+ logger.info('Trimming logs', {
19
+ count: groupLogStatuses.length,
20
+ startMS,
21
+ endMS,
22
+ clientStartDate,
23
+ clientStartDateReadable: new Date(clientStartDate).toISOString()
24
+ });
19
25
 
20
26
  const REPLAY_DIR = path.join(os.tmpdir(), 'dashcam', 'recordings');
21
27
 
@@ -36,12 +42,24 @@ async function trimLogs(groupLogStatuses, startMS, endMS, clientStartDate, clipI
36
42
  let events = content;
37
43
 
38
44
  // Convert events to relative time
39
- let relativeEvents = events.map((event) => {
45
+ let relativeEvents = events.map((event, index) => {
46
+ const originalTime = event.time;
40
47
  event.time = parseInt(event.time + '') - startMS;
41
48
  // Check if it's not already relative time
42
49
  if (event.time > 1_000_000_000_000) {
43
50
  // relative time = absolute time - clip start time
44
51
  event.time = event.time - clientStartDate;
52
+ if (index === 0) {
53
+ // Log first event for debugging
54
+ logger.info('First event timestamp conversion', {
55
+ originalTime,
56
+ clientStartDate,
57
+ relativeTime: event.time,
58
+ relativeSeconds: (event.time / 1000).toFixed(2),
59
+ startMS,
60
+ line: event.line ? event.line.substring(0, 50) : 'N/A'
61
+ });
62
+ }
45
63
  }
46
64
  return event;
47
65
  });
@@ -56,16 +74,19 @@ async function trimLogs(groupLogStatuses, startMS, endMS, clientStartDate, clipI
56
74
  });
57
75
 
58
76
  if (status.type === 'cli') {
59
- // Remap logFile indices for CLI logs
60
- let map = {};
61
- filteredEvents = filteredEvents.map((event) => {
62
- let name = map[event.logFile] ?? Object.keys(map).length + 1;
63
- if (!map[event.logFile]) map[event.logFile] = name;
64
- return {
65
- ...event,
66
- logFile: name,
67
- };
77
+ // For CLI logs, keep the file paths in logFile field for UI display
78
+ // No need to remap to numeric indices
79
+
80
+ // Create items array showing unique files and their event counts
81
+ const fileCounts = {};
82
+ filteredEvents.forEach(event => {
83
+ fileCounts[event.logFile] = (fileCounts[event.logFile] || 0) + 1;
68
84
  });
85
+
86
+ status.items = Object.entries(fileCounts).map(([filePath, count]) => ({
87
+ item: filePath,
88
+ count
89
+ }));
69
90
  }
70
91
  } else if (status.type === 'web' && !webHandled) {
71
92
  logger.debug('Found web groupLog, handling all web groupLogs at once');
@@ -243,6 +264,41 @@ class LogsTrackerManager {
243
264
  this.removeCliTrackedPath(filePath);
244
265
  }
245
266
 
267
+ addPipedLog(content) {
268
+ // Find active recording instance
269
+ const activeInstances = Object.values(this.instances);
270
+ if (activeInstances.length === 0) {
271
+ logger.warn('No active recording to add piped content to');
272
+ return;
273
+ }
274
+
275
+ // Add to the most recent active instance (last one)
276
+ const instance = activeInstances[activeInstances.length - 1];
277
+ const timestamp = Date.now();
278
+
279
+ // Write to CLI tracker's temp file
280
+ if (instance.trackers && instance.trackers.cli) {
281
+ const pipedLog = {
282
+ time: timestamp,
283
+ logFile: 'piped-input',
284
+ content: content
285
+ };
286
+
287
+ // Write to the CLI tracker's output file
288
+ try {
289
+ const outputFile = path.join(instance.directory, 'cli-logs.jsonl');
290
+ const logLine = JSON.stringify(pipedLog) + '\n';
291
+ fs.appendFileSync(outputFile, logLine);
292
+ logger.info('Added piped content to recording', {
293
+ recorderId: instance.recorderId,
294
+ contentLength: content.length
295
+ });
296
+ } catch (error) {
297
+ logger.error('Failed to write piped content', { error });
298
+ }
299
+ }
300
+ }
301
+
246
302
  removeTracker(id) {
247
303
  // Try removing from web trackers first
248
304
  if (this.webLogsConfig[id]) {
@@ -50,11 +50,18 @@ class ProcessManager {
50
50
 
51
51
  writeUploadResult(result) {
52
52
  try {
53
+ logger.info('Writing upload result to file', { path: RESULT_FILE, shareLink: result.shareLink });
53
54
  fs.writeFileSync(RESULT_FILE, JSON.stringify({
54
55
  ...result,
55
56
  timestamp: Date.now()
56
57
  }, null, 2));
57
- logger.debug('Wrote upload result to file', { shareLink: result.shareLink });
58
+ logger.info('Successfully wrote upload result to file');
59
+ // Verify it was written
60
+ if (fs.existsSync(RESULT_FILE)) {
61
+ logger.info('Verified upload result file exists');
62
+ } else {
63
+ logger.error('Upload result file does not exist after write!');
64
+ }
58
65
  } catch (error) {
59
66
  logger.error('Failed to write upload result file', { error });
60
67
  }
@@ -104,7 +111,8 @@ class ProcessManager {
104
111
  const status = this.readStatus();
105
112
 
106
113
  if (!pid || !this.isProcessRunning(pid)) {
107
- this.cleanup();
114
+ // Clean up but preserve upload result in case the background process just finished uploading
115
+ this.cleanup({ preserveResult: true });
108
116
  return false;
109
117
  }
110
118
 
@@ -116,11 +124,12 @@ class ProcessManager {
116
124
  return this.readStatus();
117
125
  }
118
126
 
119
- cleanup() {
127
+ cleanup(options = {}) {
128
+ const { preserveResult = false } = options;
120
129
  try {
121
130
  if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
122
131
  if (fs.existsSync(STATUS_FILE)) fs.unlinkSync(STATUS_FILE);
123
- if (fs.existsSync(RESULT_FILE)) fs.unlinkSync(RESULT_FILE);
132
+ if (!preserveResult && fs.existsSync(RESULT_FILE)) fs.unlinkSync(RESULT_FILE);
124
133
  } catch (error) {
125
134
  logger.error('Failed to cleanup process files', { error });
126
135
  }
@@ -174,8 +183,8 @@ class ProcessManager {
174
183
  hasApps: result.apps?.length > 0
175
184
  });
176
185
 
177
- // Cleanup process files
178
- this.cleanup();
186
+ // Cleanup process files but preserve upload result for stop command
187
+ this.cleanup({ preserveResult: true });
179
188
 
180
189
  return result;
181
190
  } catch (recorderError) {
@@ -193,7 +202,7 @@ class ProcessManager {
193
202
  logs: []
194
203
  };
195
204
 
196
- this.cleanup();
205
+ this.cleanup({ preserveResult: true });
197
206
  return result;
198
207
  }
199
208
  } else {
package/lib/recorder.js CHANGED
@@ -278,8 +278,11 @@ export async function startRecording({
278
278
  // Construct FFmpeg command arguments
279
279
  const platformArgs = await getPlatformArgs({ fps, includeAudio });
280
280
  const outputArgs = [
281
- '-c:v', 'libvpx', // Use VP8 codec instead of VP9 for better compatibility
282
- '-b:v', '1M', // Set specific bitrate instead of variable
281
+ '-c:v', 'libvpx-vp9', // Use VP9 codec for better quality and compression
282
+ '-quality', 'good', // Use good quality preset for better encoding
283
+ '-cpu-used', '2', // Higher quality encoding (0-5, lower = slower but better quality)
284
+ '-deadline', 'good', // Good quality encoding mode
285
+ '-b:v', '5M', // Higher bitrate for better quality
283
286
  // Remove explicit pixel format to let ffmpeg handle conversion automatically
284
287
  '-r', fps.toString(), // Ensure output framerate matches input
285
288
  '-g', '30', // Keyframe every 30 frames
@@ -358,13 +361,11 @@ export async function startRecording({
358
361
  all: true, // Capture both stdout and stderr
359
362
  stdin: 'pipe' // Enable stdin for sending 'q' to stop recording
360
363
  });
361
-
362
- recordingStartTime = Date.now();
363
364
 
364
365
  logger.info('FFmpeg process spawned', {
365
366
  pid: currentRecording.pid,
366
367
  args: args.slice(-5), // Log last 5 args including output file
367
- tempFile
368
+ tempFile
368
369
  });
369
370
 
370
371
  // Check if temp file gets created within first few seconds
@@ -392,6 +393,14 @@ export async function startRecording({
392
393
  directory: path.dirname(outputPath)
393
394
  });
394
395
 
396
+ // Set recording start time AFTER log tracker is initialized
397
+ // This ensures the timeline starts when the tracker is ready to capture events
398
+ recordingStartTime = Date.now();
399
+ logger.info('Recording timeline started', {
400
+ recordingStartTime,
401
+ recordingStartTimeReadable: new Date(recordingStartTime).toISOString()
402
+ });
403
+
395
404
  if (currentRecording.all) {
396
405
  currentRecording.all.setEncoding('utf8');
397
406
  currentRecording.all.on('data', (data) => {
@@ -622,6 +631,12 @@ export async function stopRecording() {
622
631
  icons: appTrackingResults.icons, // Include application icons metadata
623
632
  logs: logTrackingResults // Include log tracking results
624
633
  };
634
+
635
+ logger.info('Recording stopped with clientStartDate', {
636
+ clientStartDate: recordingStartTime,
637
+ clientStartDateReadable: new Date(recordingStartTime).toISOString(),
638
+ duration: result.duration
639
+ });
625
640
 
626
641
  currentRecording = null;
627
642
  recordingStartTime = null;
@@ -1,5 +1,6 @@
1
1
  import { Tail } from 'tail';
2
2
  import { logger } from '../logger.js';
3
+ import fs from 'fs';
3
4
 
4
5
  // Simple function to get stats for events in the last minute
5
6
  function getStats(eventTimes = []) {
@@ -40,6 +41,12 @@ export class FileTracker {
40
41
  this.trackedFile = trackedFile;
41
42
 
42
43
  try {
44
+ // Ensure the file exists before creating the Tail watcher
45
+ if (!fs.existsSync(this.trackedFile)) {
46
+ logger.warn(`File does not exist, creating: ${this.trackedFile}`);
47
+ fs.writeFileSync(this.trackedFile, '', 'utf8');
48
+ }
49
+
43
50
  this.tail = new Tail(this.trackedFile, { encoding: 'ascii' });
44
51
  this.tail.on('line', (line) => {
45
52
  const time = Date.now();
@@ -55,6 +55,7 @@ export class LogsTracker {
55
55
 
56
56
  this.files[filePath] = {
57
57
  status,
58
+ filePath, // Store file path for later reference
58
59
  unsubscribe: this.fileTrackerManager.subscribe(filePath, callback),
59
60
  };
60
61
 
@@ -98,13 +99,25 @@ export class LogsTracker {
98
99
 
99
100
  getStatus() {
100
101
  let items = [];
102
+ let filePathMap = {};
103
+
101
104
  if (this.isWatchOnly) {
102
- items = Object.keys(this.files).map((filePath) => ({
103
- ...this.fileTrackerManager.getStats(filePath),
104
- item: this.fileToIndex[filePath],
105
- }));
105
+ items = Object.keys(this.files).map((filePath) => {
106
+ const index = this.fileToIndex[filePath];
107
+ filePathMap[index] = filePath;
108
+ return {
109
+ ...this.fileTrackerManager.getStats(filePath),
110
+ item: index, // Keep numeric index to match events
111
+ };
112
+ });
106
113
  } else {
107
- items = Object.values(this.files).map(({ status }) => status);
114
+ items = Object.values(this.files).map(({ status, filePath }) => {
115
+ const index = status.item;
116
+ filePathMap[index] = filePath;
117
+ return {
118
+ ...status,
119
+ };
120
+ });
108
121
  }
109
122
 
110
123
  const totalCount = items.reduce((acc, status) => acc + status.count, 0);
@@ -112,11 +125,12 @@ export class LogsTracker {
112
125
  return [
113
126
  {
114
127
  id: 'CLI',
115
- name: 'CLI',
128
+ name: 'File Logs', // More descriptive name
116
129
  type: 'cli',
117
130
  fileLocation: this.fileLocation,
118
131
  items: items,
119
132
  count: totalCount,
133
+ filePathMap: filePathMap, // Include mapping for UI to display file paths
120
134
  },
121
135
  ];
122
136
  }
@@ -126,7 +140,7 @@ export class LogsTracker {
126
140
  const status = this.getStatus();
127
141
  return status.length > 0 ? status[0] : {
128
142
  id: 'CLI',
129
- name: 'CLI',
143
+ name: 'File Logs',
130
144
  type: 'cli',
131
145
  fileLocation: this.fileLocation,
132
146
  items: [],
package/lib/uploader.js CHANGED
@@ -371,8 +371,12 @@ export async function upload(filePath, metadata = {}) {
371
371
  for (const logStatus of trimmedLogs) {
372
372
  if (logStatus.count > 0 && logStatus.trimmedFileLocation && fs.existsSync(logStatus.trimmedFileLocation)) {
373
373
  try {
374
+ // Use the name from the status, or a default descriptive name
375
+ // The name is what shows in the "App" dropdown, not the file path
376
+ let logName = logStatus.name || 'File Logs';
377
+
374
378
  logger.debug('Creating log STS credentials', {
375
- name: logStatus.name || `Log ${logStatus.id}`,
379
+ name: logName,
376
380
  type: logStatus.type,
377
381
  count: logStatus.count
378
382
  });
@@ -380,7 +384,7 @@ export async function upload(filePath, metadata = {}) {
380
384
  const logSts = await auth.createLogSts(
381
385
  newReplay.replay.id,
382
386
  logStatus.id || `log-${Date.now()}`,
383
- logStatus.name || `Log ${logStatus.id}`,
387
+ logName,
384
388
  logStatus.type || 'application'
385
389
  );
386
390
 
@@ -440,9 +444,13 @@ export async function upload(filePath, metadata = {}) {
440
444
  });
441
445
 
442
446
  logExit();
447
+
448
+ // Replace app.dashcam.io with app.testdriver.ai in the share link
449
+ const shareLink = newReplay.replay.shareLink?.replace('app.dashcam.io', 'app.testdriver.ai') || newReplay.replay.shareLink;
450
+
443
451
  return {
444
452
  replay: newReplay.replay,
445
- shareLink: newReplay.replay.shareLink
453
+ shareLink: shareLink
446
454
  };
447
455
  }
448
456
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.0.1-beta.4",
3
+ "version": "1.0.1-beta.6",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
@@ -55,6 +55,9 @@
55
55
  "@yao-pkg/pkg": "^6.10.1",
56
56
  "esbuild": "^0.19.0"
57
57
  },
58
+ "overrides": {
59
+ "@mapbox/node-pre-gyp": "^2.0.0"
60
+ },
58
61
  "type": "module",
59
62
  "engines": {
60
63
  "node": ">=20.0.0"
package/test_workflow.sh CHANGED
@@ -30,44 +30,93 @@ if [ ! -f "$TEMP_FILE" ]; then
30
30
  fi
31
31
  echo "โœ… File tracking configured"
32
32
 
33
- # 4. Start background process that logs current time to the temporary file
33
+ # 4. Start dashcam recording in background
34
34
  echo ""
35
- echo "4. Starting background logging process..."
36
- (
37
- while true; do
38
- echo "$(date): Current time logged" >> "$TEMP_FILE"
39
- sleep 2
40
- done
41
- ) &
42
- LOGGER_PID=$!
43
- echo "โœ… Background logger started (PID: $LOGGER_PID)"
35
+ echo "4. Starting dashcam recording in background..."
36
+ # Start recording and redirect output to a log file so we can still monitor it
37
+ ./bin/dashcam.js record --title "Sync Test Recording" --description "Testing video/log synchronization with timestamped events" > /tmp/dashcam-recording.log 2>&1 &
38
+ RECORD_PID=$!
44
39
 
45
- # 5. Start dashcam recording in background
40
+ # Wait for recording to initialize and log tracker to start
41
+ echo "Waiting for recording to initialize (PID: $RECORD_PID)..."
42
+ sleep 1
43
+
44
+ # Write first event after log tracker is fully ready
45
+ RECORDING_START=$(date +%s)
46
+ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
47
+ echo "๐Ÿ”ด EVENT 1: Recording START at $(date '+%H:%M:%S')"
48
+ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
49
+ echo "[EVENT 1] Recording started at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
50
+
51
+ # Verify recording is actually running
52
+ if ps -p $RECORD_PID > /dev/null; then
53
+ echo "โœ… Recording started successfully"
54
+ else
55
+ echo "โŒ Recording process died, check /tmp/dashcam-recording.log"
56
+ exit 1
57
+ fi
58
+
59
+ # 5. Create synchronized log events with visual markers
60
+ echo ""
61
+ echo "5. Creating synchronized test events..."
62
+ echo ""
63
+ echo "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—"
64
+ echo "โ•‘ SYNC TEST - Watch for these markers in the recording! โ•‘"
65
+ echo "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"
46
66
  echo ""
47
- echo "5. Starting dashcam recording in background..."
48
- ./bin/dashcam.js record --title "Test Workflow Recording" --description "Testing CLI workflow with web and file tracking" &
49
67
 
50
- # Give the recording a moment to initialize
51
- sleep 2
52
- echo "โœ… Recording started in background"
68
+ # Event 1 was already written above - now continue with the rest
69
+ sleep 3
53
70
 
54
- # 6. Let recording run for a few seconds
71
+ # Event 2 - after 3 seconds
55
72
  echo ""
56
- echo "6. Letting recording run for 20 seconds..."
57
- sleep 20
58
- echo "โœ… Recording completed"
73
+ echo "๐ŸŸก EVENT 2: 3 seconds mark at $(date '+%H:%M:%S')"
74
+ echo "[EVENT 2] 3 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
75
+ sleep 3
59
76
 
60
- # 7. Stop recording and upload (this will kill the background recording process)
77
+ # Event 3 - after 6 seconds
61
78
  echo ""
62
- echo "7. Stopping recording and uploading..."
79
+ echo "๐ŸŸข EVENT 3: 6 seconds mark at $(date '+%H:%M:%S')"
80
+ echo "[EVENT 3] 6 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
81
+ sleep 3
82
+
83
+ # Event 4 - after 9 seconds
84
+ echo ""
85
+ echo "๐Ÿ”ต EVENT 4: 9 seconds mark at $(date '+%H:%M:%S')"
86
+ echo "[EVENT 4] 9 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
87
+ sleep 3
88
+
89
+ # Event 5 - after 12 seconds
90
+ echo ""
91
+ echo "๐ŸŸฃ EVENT 5: 12 seconds mark at $(date '+%H:%M:%S')"
92
+ echo "[EVENT 5] 12 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
93
+ sleep 3
94
+
95
+ # Event 6 - before ending
96
+ echo ""
97
+ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
98
+ echo "โšซ EVENT 6: Recording END at $(date '+%H:%M:%S')"
99
+ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
100
+ RECORDING_END=$(date +%s)
101
+ echo "[EVENT 6] Recording ending at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_END" >> "$TEMP_FILE"
102
+
103
+ DURATION=$((RECORDING_END - RECORDING_START))
104
+ echo ""
105
+ echo "โœ… Test events completed (Duration: ${DURATION}s)"
106
+
107
+ # Give a moment for the last event to be fully processed
108
+ echo ""
109
+ echo "Waiting 2 seconds to ensure all events are captured..."
110
+ sleep 2
111
+
112
+ # 6. Stop recording and upload (this will kill the background recording process)
113
+ echo ""
114
+ echo "6. Stopping recording and uploading..."
63
115
  ./bin/dashcam.js stop
64
116
  echo "โœ… Recording stopped and uploaded"
65
117
 
66
- # Cleanup: Stop the background logger
67
118
  echo ""
68
119
  echo "๐Ÿงน Cleaning up..."
69
- kill $LOGGER_PID 2>/dev/null || true
70
- echo "โœ… Background logger stopped"
71
120
 
72
121
  echo ""
73
122
  echo "๐ŸŽ‰ Test workflow completed successfully!"
@@ -78,3 +127,28 @@ echo ""
78
127
  echo "๐Ÿ“Š Final Status:"
79
128
  ./bin/dashcam.js status
80
129
 
130
+ echo ""
131
+ echo "โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—"
132
+ echo "โ•‘ SYNC VERIFICATION GUIDE โ•‘"
133
+ echo "โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•"
134
+ echo ""
135
+ echo "To verify video/log synchronization in the recording:"
136
+ echo ""
137
+ echo "1. Open the uploaded recording in your browser"
138
+ echo "2. Check the log panel for '$TEMP_FILE'"
139
+ echo "3. Verify these events appear at the correct times:"
140
+ echo ""
141
+ echo " Time | Terminal Display | Log Entry"
142
+ echo " -------|---------------------------|---------------------------"
143
+ echo " 0:00 | ๐Ÿ”ด EVENT 1 | [EVENT 1] Recording started"
144
+ echo " 0:03 | ๐ŸŸก EVENT 2 | [EVENT 2] 3 seconds elapsed"
145
+ echo " 0:06 | ๐ŸŸข EVENT 3 | [EVENT 3] 6 seconds elapsed"
146
+ echo " 0:09 | ๐Ÿ”ต EVENT 4 | [EVENT 4] 9 seconds elapsed"
147
+ echo " 0:12 | ๐ŸŸฃ EVENT 5 | [EVENT 5] 12 seconds elapsed"
148
+ echo " 0:15 | โšซ EVENT 6 | [EVENT 6] Recording ending"
149
+ echo ""
150
+ echo "4. The log timestamps should match the video timeline exactly"
151
+ echo "5. Each colored event marker should appear in the video"
152
+ echo " at the same moment as the corresponding log entry"
153
+ echo ""
154
+
@@ -1,103 +0,0 @@
1
- name: Build Binaries
2
-
3
- on:
4
- workflow_dispatch:
5
- workflow_call:
6
- inputs:
7
- upload_artifacts:
8
- description: 'Whether to upload artifacts'
9
- required: false
10
- type: boolean
11
- default: true
12
-
13
- jobs:
14
- build:
15
- runs-on: ${{ matrix.os }}
16
- strategy:
17
- matrix:
18
- os: [ubuntu-latest, macos-latest, windows-latest]
19
-
20
- steps:
21
- - name: Checkout code
22
- uses: actions/checkout@v4
23
-
24
- - name: Setup Node.js
25
- uses: actions/setup-node@v4
26
- with:
27
- node-version: '20'
28
- cache: 'npm'
29
-
30
- - name: Setup Python (Windows)
31
- if: matrix.os == 'windows-latest'
32
- uses: actions/setup-python@v5
33
- with:
34
- python-version: '3.11'
35
-
36
- - name: Install dependencies
37
- run: npm ci
38
-
39
- - name: Build bundle
40
- run: npm run bundle
41
-
42
- - name: Verify bundle created
43
- run: |
44
- ls -la dist/ || dir dist\
45
- shell: bash
46
-
47
- - name: Build binaries (macOS)
48
- if: matrix.os == 'macos-latest'
49
- run: npm run build:macos
50
-
51
- - name: Build binaries (Linux)
52
- if: matrix.os == 'ubuntu-latest'
53
- run: npm run build:linux
54
-
55
- - name: Build binaries (Windows)
56
- if: matrix.os == 'windows-latest'
57
- run: npm run build:windows
58
- continue-on-error: true
59
-
60
- - name: Check Windows build
61
- if: matrix.os == 'windows-latest'
62
- run: |
63
- if (Test-Path dist/bundle-x64.exe) {
64
- Write-Host "Windows build successful"
65
- exit 0
66
- } else {
67
- Write-Host "Windows build failed, but continuing..."
68
- exit 0
69
- }
70
- shell: pwsh
71
-
72
- - name: List built files
73
- run: |
74
- ls -la dist/ || dir dist\
75
- shell: bash
76
-
77
- - name: Upload artifacts (macOS)
78
- if: matrix.os == 'macos-latest' && (inputs.upload_artifacts || github.event_name == 'pull_request')
79
- uses: actions/upload-artifact@v4
80
- with:
81
- name: dashcam-macos
82
- path: |
83
- dist/bundle-arm64
84
- dist/bundle-x64
85
-
86
- - name: Upload artifacts (Linux)
87
- if: matrix.os == 'ubuntu-latest' && (inputs.upload_artifacts || github.event_name == 'pull_request')
88
- uses: actions/upload-artifact@v4
89
- with:
90
- name: dashcam-linux
91
- path: |
92
- dist/bundle-arm64
93
- dist/bundle-x64
94
-
95
- - name: Upload artifacts (Windows)
96
- if: (matrix.os == 'windows-latest' && hashFiles('dist/bundle-x64.exe') != '') && (inputs.upload_artifacts || github.event_name == 'pull_request')
97
- uses: actions/upload-artifact@v4
98
- with:
99
- name: dashcam-windows
100
- path: |
101
- dist/bundle-x64.exe
102
- dist/bundle-arm64.exe
103
- if-no-files-found: warn