dashcam 1.3.0-beta → 1.3.2-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
@@ -99,16 +99,6 @@ async function recordingAction(options, command) {
99
99
  process.exit(1);
100
100
  }
101
101
 
102
- // Check for piped input (description from stdin) if description option not set
103
- let description = options.description;
104
- if (!description && !process.stdin.isTTY) {
105
- const chunks = [];
106
- for await (const chunk of process.stdin) {
107
- chunks.push(chunk);
108
- }
109
- description = Buffer.concat(chunks).toString('utf-8');
110
- }
111
-
112
102
  // Check screen recording permissions (macOS only)
113
103
  const { ensurePermissions } = await import('../lib/permissions.js');
114
104
  const hasPermissions = await ensurePermissions();
@@ -126,7 +116,7 @@ async function recordingAction(options, command) {
126
116
  audio: options.audio,
127
117
  output: options.output,
128
118
  title: options.title,
129
- description: description,
119
+ description: options.description,
130
120
  project: options.project || options.k // Support both -p and -k for project
131
121
  });
132
122
 
@@ -153,21 +143,11 @@ program
153
143
  .command('create')
154
144
  .description('Create a clip and output the resulting url or markdown. Will launch desktop app for local editing before publishing.')
155
145
  .option('-t, --title <string>', 'Title of the replay. Automatically generated if not supplied.')
156
- .option('-d, --description [text]', 'Replay markdown body. This may also be piped in: `cat README.md | dashcam create`')
146
+ .option('-d, --description [text]', 'Replay markdown body')
157
147
  .option('--md', 'Returns code for a rich markdown image link.')
158
148
  .option('-k, --project <project>', 'Project ID to publish to')
159
149
  .action(async (options) => {
160
150
  try {
161
- // Check for piped input (description from stdin)
162
- let description = options.description;
163
- if (!description && !process.stdin.isTTY) {
164
- const chunks = [];
165
- for await (const chunk of process.stdin) {
166
- chunks.push(chunk);
167
- }
168
- description = Buffer.concat(chunks).toString('utf-8');
169
- }
170
-
171
151
  if (!processManager.isRecordingActive()) {
172
152
  console.log('No active recording to create clip from');
173
153
  console.log('Start a recording first with "dashcam record" or "dashcam start"');
@@ -192,7 +172,7 @@ program
192
172
  try {
193
173
  const uploadResult = await upload(result.outputPath, {
194
174
  title: options.title || activeStatus?.options?.title || 'Dashcam Recording',
195
- description: description || activeStatus?.options?.description,
175
+ description: options.description || activeStatus?.options?.description,
196
176
  project: options.project || options.k || activeStatus?.options?.project,
197
177
  duration: result.duration,
198
178
  clientStartDate: result.clientStartDate,
@@ -1,4 +1,3 @@
1
- import { spawn } from 'child_process';
2
1
  import fs from 'fs';
3
2
  import path from 'path';
4
3
  import os from 'os';
@@ -148,57 +147,36 @@ class ProcessManager {
148
147
  this.isStopping = true;
149
148
 
150
149
  try {
151
- const pid = this.readPid();
152
150
  const status = this.readStatus();
153
151
 
154
- if (!pid || !this.isProcessRunning(pid)) {
155
- logger.warn('No active recording process found');
152
+ if (!status || !status.isRecording) {
153
+ logger.warn('No active recording found');
156
154
  return false;
157
155
  }
158
156
 
159
- // Recording is active, send SIGINT to trigger graceful shutdown
160
- logger.info('Stopping active recording process', { pid });
161
- process.kill(pid, 'SIGINT');
157
+ // Import recorder module
158
+ const { stopRecording } = await import('./recorder.js');
162
159
 
163
- // Wait for the process to actually finish and upload
164
- // Increase timeout to allow for upload to complete
165
- const maxWaitTime = 120000; // 2 minutes max to allow for upload
166
- const startWait = Date.now();
160
+ logger.info('Stopping recording directly');
167
161
 
168
- while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
169
- await new Promise(resolve => setTimeout(resolve, 500));
170
- }
162
+ // Stop the recording
163
+ const result = await stopRecording();
171
164
 
172
- if (this.isProcessRunning(pid)) {
173
- logger.warn('Process did not stop within timeout, forcing termination');
174
- process.kill(pid, 'SIGKILL');
175
- await new Promise(resolve => setTimeout(resolve, 1000));
176
- }
165
+ logger.info('Recording stopped successfully', {
166
+ outputPath: result.outputPath,
167
+ duration: result.duration
168
+ });
177
169
 
178
- // The background process handles stopRecording() internally via SIGINT
179
- // We just need to return the basic result from the status file
180
- if (status) {
181
- logger.info('Background recording stopped, returning status', {
182
- outputPath: status.outputPath,
183
- duration: Date.now() - status.startTime
184
- });
185
-
186
- const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
187
- const result = {
188
- outputPath: status.outputPath,
189
- gifPath: `${basePath}.gif`,
190
- snapshotPath: `${basePath}.png`,
191
- duration: Date.now() - status.startTime,
192
- clientStartDate: status.startTime,
193
- apps: [],
194
- logs: []
195
- };
196
-
197
- this.cleanup({ preserveResult: true });
198
- return result;
199
- } else {
200
- throw new Error('No status information available for active recording');
201
- }
170
+ // Update status to indicate recording stopped
171
+ this.writeStatus({
172
+ isRecording: false,
173
+ completedTime: Date.now(),
174
+ pid: process.pid
175
+ });
176
+
177
+ this.cleanup({ preserveResult: true });
178
+
179
+ return result;
202
180
  } catch (error) {
203
181
  logger.error('Failed to stop recording', { error });
204
182
  throw error;
@@ -213,69 +191,42 @@ class ProcessManager {
213
191
  throw new Error('Recording already in progress');
214
192
  }
215
193
 
216
- // Always run in background mode by spawning a detached process
217
- logger.info('Starting recording in background mode');
218
-
219
- // Get the path to the CLI binary
220
- const binPath = path.join(__dirname, '..', 'bin', 'dashcam-background.js');
221
-
222
- logger.debug('Background process path', { binPath, exists: fs.existsSync(binPath) });
223
-
224
- // Create log files for background process
225
- const logDir = PROCESS_DIR;
226
- const stdoutLog = path.join(logDir, 'background-stdout.log');
227
- const stderrLog = path.join(logDir, 'background-stderr.log');
228
-
229
- // Always use 'ignore' for stdio to ensure complete detachment
230
- // This is critical when parent process is invoked via exec() or other methods
231
- // that might have open stdio pipes
232
- const isWindows = process.platform === 'win32';
233
-
234
- // Spawn a detached process that will handle the recording
235
- const backgroundProcess = spawn(process.execPath, [
236
- binPath,
237
- JSON.stringify(options)
238
- ], {
239
- detached: true,
240
- stdio: 'ignore', // Always ignore to prevent hanging when parent has open pipes
241
- windowsHide: true, // Hide the console window on Windows
242
- env: {
243
- ...process.env,
244
- DASHCAM_BACKGROUND: 'true'
245
- }
246
- });
247
-
248
- // Get the background process PID before unreffing
249
- const backgroundPid = backgroundProcess.pid;
194
+ // Import recorder module
195
+ const { startRecording } = await import('./recorder.js');
250
196
 
251
- // Write PID file immediately so other commands can find the background process
252
- // Use the spawned process PID rather than waiting for status file
253
- this.writePid(backgroundPid);
197
+ logger.info('Starting recording directly');
254
198
 
255
- // Allow the parent process to exit independently
256
- backgroundProcess.unref();
199
+ // Start recording with the provided options
200
+ const recordingOptions = {
201
+ fps: parseInt(options.fps) || 10,
202
+ includeAudio: options.audio || false,
203
+ customOutputPath: options.output || null
204
+ };
257
205
 
258
- // Wait a moment for the background process to initialize
259
- await new Promise(resolve => setTimeout(resolve, 2000));
206
+ logger.info('Starting recording with options', { recordingOptions });
260
207
 
261
- // Read the status file to get recording details
262
- const status = this.readStatus();
208
+ const recordingResult = await startRecording(recordingOptions);
263
209
 
264
- if (!status || !status.isRecording) {
265
- // Clean up PID file if recording failed to start
266
- this.cleanup();
267
- throw new Error('Background process failed to start recording');
268
- }
210
+ // Write PID and status files for status tracking
211
+ this.writePid(process.pid);
212
+
213
+ this.writeStatus({
214
+ isRecording: true,
215
+ startTime: recordingResult.startTime,
216
+ options,
217
+ pid: process.pid,
218
+ outputPath: recordingResult.outputPath
219
+ });
269
220
 
270
- logger.info('Background recording process started', {
271
- pid: status.pid,
272
- outputPath: status.outputPath
221
+ logger.info('Recording started successfully', {
222
+ outputPath: recordingResult.outputPath,
223
+ startTime: recordingResult.startTime
273
224
  });
274
225
 
275
226
  return {
276
- pid: status.pid,
277
- outputPath: status.outputPath,
278
- startTime: status.startTime
227
+ pid: process.pid,
228
+ outputPath: recordingResult.outputPath,
229
+ startTime: recordingResult.startTime
279
230
  };
280
231
  }
281
232
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.3.0-beta",
3
+ "version": "1.3.2-beta",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/dashcam.js",
6
6
  "bin": {