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/BACKWARD_COMPATIBILITY.md +177 -0
- package/bin/dashcam.js +342 -144
- package/lib/logs/index.js +67 -11
- package/lib/processManager.js +16 -7
- package/lib/recorder.js +20 -5
- package/lib/tracking/FileTracker.js +7 -0
- package/lib/tracking/LogsTracker.js +21 -7
- package/lib/uploader.js +11 -3
- package/package.json +4 -1
- package/test_workflow.sh +99 -25
- package/.github/workflows/build.yml +0 -103
- package/.github/workflows/release.yml +0 -107
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', {
|
|
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
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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]) {
|
package/lib/processManager.js
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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',
|
|
282
|
-
'-
|
|
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
|
-
|
|
104
|
-
|
|
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 }) =>
|
|
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: '
|
|
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: '
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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
|
|
33
|
+
# 4. Start dashcam recording in background
|
|
34
34
|
echo ""
|
|
35
|
-
echo "4. Starting
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
51
|
-
sleep
|
|
52
|
-
echo "โ
Recording started in background"
|
|
68
|
+
# Event 1 was already written above - now continue with the rest
|
|
69
|
+
sleep 3
|
|
53
70
|
|
|
54
|
-
#
|
|
71
|
+
# Event 2 - after 3 seconds
|
|
55
72
|
echo ""
|
|
56
|
-
echo "
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
#
|
|
77
|
+
# Event 3 - after 6 seconds
|
|
61
78
|
echo ""
|
|
62
|
-
echo "
|
|
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
|