dashcam 1.0.1-beta.30 → 1.0.1-beta.32

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
@@ -136,55 +136,11 @@ async function recordingAction(options, command) {
136
136
  log('Use "dashcam status" to check progress');
137
137
  log('Use "dashcam stop" to stop recording and upload');
138
138
 
139
- // Keep the process alive so recording continues
140
- // Set up graceful shutdown handlers
141
- const handleShutdown = async (signal) => {
142
- log(`\nReceived ${signal}, stopping recording...`);
143
- try {
144
- const result = await processManager.stopActiveRecording();
145
-
146
- if (result) {
147
- log('Recording stopped successfully');
148
- log('Uploading recording...');
149
-
150
- try {
151
- const uploadResult = await upload(result.outputPath, {
152
- title: options.title || 'Dashcam Recording',
153
- description: description,
154
- project: options.project || options.k,
155
- duration: result.duration,
156
- clientStartDate: result.clientStartDate,
157
- apps: result.apps,
158
- icons: result.icons,
159
- gifPath: result.gifPath,
160
- snapshotPath: result.snapshotPath
161
- });
162
-
163
- // Write upload result for stop command to read
164
- processManager.writeUploadResult({
165
- shareLink: uploadResult.shareLink,
166
- replayId: uploadResult.replay?.id
167
- });
168
-
169
- log('📹 Watch your recording:', uploadResult.shareLink);
170
- } catch (uploadError) {
171
- logError('Upload failed:', uploadError.message);
172
- log('Recording saved locally:', result.outputPath);
173
- }
174
- }
175
-
176
- process.exit(0);
177
- } catch (error) {
178
- logError('Failed to stop recording:', error.message);
179
- process.exit(1);
180
- }
181
- };
182
-
183
- process.on('SIGINT', handleShutdown);
184
- process.on('SIGTERM', handleShutdown);
185
-
186
- // Keep process alive indefinitely until stopped
187
- await new Promise(() => {}); // Wait forever
139
+ // Close stdout/stderr and exit immediately to prevent blocking
140
+ // when called from automation scripts with piped output
141
+ process.stdout.end();
142
+ process.stderr.end();
143
+ process.exit(0);
188
144
 
189
145
  } catch (error) {
190
146
  logError('Failed to start recording:', error.message);
@@ -1,6 +1,8 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
+ import { spawn } from 'child_process';
5
+ import { fileURLToPath } from 'url';
4
6
  import { logger } from './logger.js';
5
7
 
6
8
  // Use a fixed directory in the user's home directory for cross-process communication
@@ -151,91 +153,69 @@ class ProcessManager {
151
153
  return false;
152
154
  }
153
155
 
154
- // Check if this is the same process (direct recording)
155
- if (pid === process.pid) {
156
- logger.info('Stopping recording in current process');
157
-
158
- // Import recorder module
159
- const { stopRecording: stopRecorderRecording } = await import('./recorder.js');
160
-
161
- // Stop recording directly
162
- const result = await stopRecorderRecording();
163
-
164
- // Update status to indicate recording stopped
165
- this.writeStatus({
166
- isRecording: false,
167
- completedTime: Date.now(),
168
- pid: process.pid
169
- });
170
-
171
- this.cleanup({ preserveResult: true });
172
- return result;
156
+ logger.info('Stopping detached background process', { pid });
157
+
158
+ // Send signal to background process
159
+ const isWindows = process.platform === 'win32';
160
+
161
+ if (isWindows) {
162
+ logger.info('Windows detected, using taskkill to stop process');
163
+ try {
164
+ // Use taskkill to forcefully stop the process tree on Windows
165
+ const { execSync } = await import('child_process');
166
+ execSync(`taskkill /PID ${pid} /F /T`, { stdio: 'ignore' });
167
+ } catch (error) {
168
+ logger.warn('Failed to kill process with taskkill', { error: error.message });
169
+ }
173
170
  } else {
174
- // Different process - need to stop it
175
- logger.info('Stopping active recording process', { pid });
176
-
177
- // On Windows, signals don't work the same way, so we need to forcefully terminate
178
- const isWindows = process.platform === 'win32';
179
-
180
- if (isWindows) {
181
- logger.info('Windows detected, using taskkill to stop process');
182
- try {
183
- // Use taskkill to forcefully stop the process tree on Windows
184
- const { execSync } = await import('child_process');
185
- execSync(`taskkill /PID ${pid} /F /T`, { stdio: 'ignore' });
186
- } catch (error) {
187
- logger.warn('Failed to kill process with taskkill', { error: error.message });
188
- }
189
- } else {
171
+ try {
172
+ process.kill(pid, 'SIGINT');
173
+ } catch (error) {
174
+ logger.warn('Failed to send SIGINT', { error: error.message });
175
+ }
176
+ }
177
+
178
+ // Wait for the process to actually finish
179
+ const maxWaitTime = 30000; // 30 seconds
180
+ const startWait = Date.now();
181
+
182
+ while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
183
+ await new Promise(resolve => setTimeout(resolve, 500));
184
+ }
185
+
186
+ if (this.isProcessRunning(pid)) {
187
+ logger.warn('Process did not stop within timeout');
188
+ if (!isWindows) {
190
189
  try {
191
- process.kill(pid, 'SIGINT');
190
+ process.kill(pid, 'SIGKILL');
192
191
  } catch (error) {
193
- logger.warn('Failed to send SIGINT', { error: error.message });
192
+ logger.warn('Failed to send SIGKILL', { error: error.message });
194
193
  }
194
+ await new Promise(resolve => setTimeout(resolve, 1000));
195
195
  }
196
+ }
197
+
198
+ if (status) {
199
+ logger.info('Recording stopped, returning status', {
200
+ outputPath: status.outputPath,
201
+ duration: Date.now() - status.startTime
202
+ });
196
203
 
197
- // Wait for the process to actually finish
198
- const maxWaitTime = 30000; // 30 seconds
199
- const startWait = Date.now();
200
-
201
- while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
202
- await new Promise(resolve => setTimeout(resolve, 500));
203
- }
204
-
205
- if (this.isProcessRunning(pid)) {
206
- logger.warn('Process did not stop within timeout');
207
- if (!isWindows) {
208
- try {
209
- process.kill(pid, 'SIGKILL');
210
- } catch (error) {
211
- logger.warn('Failed to send SIGKILL', { error: error.message });
212
- }
213
- await new Promise(resolve => setTimeout(resolve, 1000));
214
- }
215
- }
204
+ const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
205
+ const result = {
206
+ outputPath: status.outputPath,
207
+ gifPath: `${basePath}.gif`,
208
+ snapshotPath: `${basePath}.png`,
209
+ duration: Date.now() - status.startTime,
210
+ clientStartDate: status.startTime,
211
+ apps: [],
212
+ logs: []
213
+ };
216
214
 
217
- if (status) {
218
- logger.info('Recording stopped, returning status', {
219
- outputPath: status.outputPath,
220
- duration: Date.now() - status.startTime
221
- });
222
-
223
- const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
224
- const result = {
225
- outputPath: status.outputPath,
226
- gifPath: `${basePath}.gif`,
227
- snapshotPath: `${basePath}.png`,
228
- duration: Date.now() - status.startTime,
229
- clientStartDate: status.startTime,
230
- apps: [],
231
- logs: []
232
- };
233
-
234
- this.cleanup({ preserveResult: true });
235
- return result;
236
- } else {
237
- throw new Error('No status information available for active recording');
238
- }
215
+ this.cleanup({ preserveResult: true });
216
+ return result;
217
+ } else {
218
+ throw new Error('No status information available for active recording');
239
219
  }
240
220
  } catch (error) {
241
221
  logger.error('Failed to stop recording', { error });
@@ -251,41 +231,56 @@ class ProcessManager {
251
231
  throw new Error('Recording already in progress');
252
232
  }
253
233
 
254
- // Import recorder module
255
- const { startRecording: startRecorderRecording } = await import('./recorder.js');
234
+ logger.info('Starting recording in detached background process');
256
235
 
257
- logger.info('Starting recording directly');
236
+ // Get the path to the background script
237
+ const __filename = fileURLToPath(import.meta.url);
238
+ const __dirname = path.dirname(__filename);
239
+ const backgroundScript = path.join(__dirname, '..', 'bin', 'dashcam-background.js');
258
240
 
259
- // Start recording using the recorder module
260
- const recordingOptions = {
261
- fps: parseInt(options.fps) || 10,
262
- includeAudio: options.audio || false,
263
- customOutputPath: options.output || null
241
+ // On Windows, we need to ensure stdio is completely detached
242
+ const isWindows = process.platform === 'win32';
243
+ const spawnOptions = {
244
+ detached: true,
245
+ stdio: 'ignore'
264
246
  };
247
+
248
+ // On Windows, also create a new process group to fully detach
249
+ if (isWindows) {
250
+ spawnOptions.windowsHide = true;
251
+ }
252
+
253
+ // Spawn a detached background process
254
+ const child = spawn(process.execPath, [backgroundScript, JSON.stringify(options)], spawnOptions);
255
+
256
+ // Unref so the parent process can exit
257
+ child.unref();
258
+
259
+ const pid = child.pid;
260
+
261
+ // Write PID file immediately so other commands can find the recording process
262
+ this.writePid(pid);
265
263
 
266
- const result = await startRecorderRecording(recordingOptions);
267
-
268
- // Write status to track the recording
269
- this.writeStatus({
270
- isRecording: true,
271
- startTime: result.startTime,
272
- options,
273
- pid: process.pid,
274
- outputPath: result.outputPath
275
- });
276
-
277
- // Write PID file so other commands can find the recording process
278
- this.writePid(process.pid);
279
-
280
- logger.info('Recording started successfully', {
281
- pid: process.pid,
282
- outputPath: result.outputPath
264
+ logger.info('Background process spawned successfully', {
265
+ pid,
266
+ backgroundScript,
267
+ platform: process.platform
283
268
  });
269
+
270
+ // Wait a moment for the background process to write its status
271
+ await new Promise(resolve => setTimeout(resolve, 1000));
272
+
273
+ // Read the status to get output path and start time
274
+ const status = this.readStatus();
275
+
276
+ if (!status) {
277
+ throw new Error('Background process failed to write status');
278
+ }
284
279
 
285
280
  return {
286
- pid: process.pid,
287
- outputPath: result.outputPath,
288
- startTime: result.startTime
281
+ pid,
282
+ outputPath: status.outputPath,
283
+ startTime: status.startTime
289
284
  };
290
285
  }
291
286
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.0.1-beta.30",
3
+ "version": "1.0.1-beta.32",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/index.js",
6
6
  "bin": {