dashcam 1.0.1-beta.2 → 1.0.1-beta.22

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.
@@ -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: [],
@@ -20,8 +20,9 @@ async function ensureIconModule() {
20
20
  const windowsModule = await import("./windows.js");
21
21
  getIconAsBuffer = windowsModule.getIconAsBuffer;
22
22
  } else {
23
- // Linux fallback
24
- getIconAsBuffer = () => null;
23
+ // Linux support
24
+ const linuxModule = await import("./linux.js");
25
+ getIconAsBuffer = linuxModule.getIconAsBuffer;
25
26
  }
26
27
 
27
28
  iconModuleLoaded = true;
@@ -0,0 +1,277 @@
1
+ import { logger } from "../../logger.js";
2
+ import { execa } from "execa";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import os from "os";
6
+
7
+ /**
8
+ * Find icon for a Linux application using various strategies
9
+ */
10
+ const findLinuxIcon = async (appName) => {
11
+ // Strategy 1: Look for .desktop file
12
+ const desktopFile = await findDesktopFile(appName);
13
+ if (desktopFile) {
14
+ const iconName = await extractIconFromDesktop(desktopFile);
15
+ if (iconName) {
16
+ const iconPath = await findIconInTheme(iconName);
17
+ if (iconPath) {
18
+ logger.debug("Found icon via .desktop file", { appName, iconPath });
19
+ return iconPath;
20
+ }
21
+ }
22
+ }
23
+
24
+ // Strategy 2: Try to find icon directly in icon themes
25
+ const iconPath = await findIconInTheme(appName);
26
+ if (iconPath) {
27
+ logger.debug("Found icon in theme", { appName, iconPath });
28
+ return iconPath;
29
+ }
30
+
31
+ // Strategy 3: Common application paths
32
+ const commonPaths = [
33
+ `/usr/share/pixmaps/${appName}.png`,
34
+ `/usr/share/pixmaps/${appName}.svg`,
35
+ `/usr/share/icons/hicolor/48x48/apps/${appName}.png`,
36
+ `/usr/share/icons/hicolor/scalable/apps/${appName}.svg`,
37
+ ];
38
+
39
+ for (const iconPath of commonPaths) {
40
+ if (fs.existsSync(iconPath)) {
41
+ logger.debug("Found icon in common path", { appName, iconPath });
42
+ return iconPath;
43
+ }
44
+ }
45
+
46
+ logger.debug("No icon found for Linux app", { appName });
47
+ return null;
48
+ };
49
+
50
+ /**
51
+ * Find .desktop file for an application
52
+ */
53
+ const findDesktopFile = async (appName) => {
54
+ const desktopDirs = [
55
+ "/usr/share/applications",
56
+ "/usr/local/share/applications",
57
+ path.join(os.homedir(), ".local/share/applications"),
58
+ ];
59
+
60
+ // Try exact match first
61
+ for (const dir of desktopDirs) {
62
+ const desktopFile = path.join(dir, `${appName}.desktop`);
63
+ if (fs.existsSync(desktopFile)) {
64
+ return desktopFile;
65
+ }
66
+ }
67
+
68
+ // Try case-insensitive search
69
+ for (const dir of desktopDirs) {
70
+ try {
71
+ if (!fs.existsSync(dir)) continue;
72
+
73
+ const files = fs.readdirSync(dir);
74
+ const match = files.find(
75
+ (f) => f.toLowerCase() === `${appName.toLowerCase()}.desktop`
76
+ );
77
+ if (match) {
78
+ return path.join(dir, match);
79
+ }
80
+ } catch (error) {
81
+ logger.debug("Error reading desktop directory", { dir, error: error.message });
82
+ }
83
+ }
84
+
85
+ return null;
86
+ };
87
+
88
+ /**
89
+ * Extract icon name from .desktop file
90
+ */
91
+ const extractIconFromDesktop = async (desktopFilePath) => {
92
+ try {
93
+ const content = fs.readFileSync(desktopFilePath, "utf8");
94
+ const iconMatch = content.match(/^Icon=(.+)$/m);
95
+ if (iconMatch) {
96
+ return iconMatch[1].trim();
97
+ }
98
+ } catch (error) {
99
+ logger.debug("Error reading desktop file", {
100
+ desktopFilePath,
101
+ error: error.message
102
+ });
103
+ }
104
+ return null;
105
+ };
106
+
107
+ /**
108
+ * Find icon in XDG icon themes
109
+ */
110
+ const findIconInTheme = async (iconName) => {
111
+ // Common icon theme locations and sizes
112
+ const iconThemes = ["hicolor", "gnome", "Adwaita", "breeze", "oxygen"];
113
+ const iconSizes = ["48x48", "64x64", "scalable", "128x128", "256x256"];
114
+ const iconFormats = ["png", "svg", "xpm"];
115
+
116
+ const searchPaths = [
117
+ "/usr/share/icons",
118
+ "/usr/local/share/icons",
119
+ path.join(os.homedir(), ".local/share/icons"),
120
+ path.join(os.homedir(), ".icons"),
121
+ ];
122
+
123
+ for (const basePath of searchPaths) {
124
+ if (!fs.existsSync(basePath)) continue;
125
+
126
+ for (const theme of iconThemes) {
127
+ const themePath = path.join(basePath, theme);
128
+ if (!fs.existsSync(themePath)) continue;
129
+
130
+ for (const size of iconSizes) {
131
+ const sizePath = path.join(themePath, size, "apps");
132
+ if (!fs.existsSync(sizePath)) continue;
133
+
134
+ for (const format of iconFormats) {
135
+ const iconPath = path.join(sizePath, `${iconName}.${format}`);
136
+ if (fs.existsSync(iconPath)) {
137
+ return iconPath;
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ return null;
145
+ };
146
+
147
+ /**
148
+ * Convert image to PNG if needed
149
+ */
150
+ const convertToPng = async (iconPath) => {
151
+ const ext = path.extname(iconPath).toLowerCase();
152
+
153
+ // If already PNG, read and return
154
+ if (ext === ".png") {
155
+ return fs.readFileSync(iconPath);
156
+ }
157
+
158
+ // For SVG, try to convert using ImageMagick or rsvg-convert
159
+ if (ext === ".svg") {
160
+ const tmpPngPath = path.join(os.tmpdir(), `icon-${Date.now()}.png`);
161
+
162
+ try {
163
+ // Try rsvg-convert first (commonly available on Linux)
164
+ await execa("rsvg-convert", [
165
+ "-w", "48",
166
+ "-h", "48",
167
+ "-o", tmpPngPath,
168
+ iconPath
169
+ ]);
170
+
171
+ const buffer = fs.readFileSync(tmpPngPath);
172
+ fs.unlinkSync(tmpPngPath);
173
+ return buffer;
174
+ } catch (error) {
175
+ logger.debug("rsvg-convert failed, trying ImageMagick", { error: error.message });
176
+
177
+ try {
178
+ // Fallback to ImageMagick convert
179
+ await execa("convert", [
180
+ "-background", "none",
181
+ "-resize", "48x48",
182
+ iconPath,
183
+ tmpPngPath
184
+ ]);
185
+
186
+ const buffer = fs.readFileSync(tmpPngPath);
187
+ fs.unlinkSync(tmpPngPath);
188
+ return buffer;
189
+ } catch (convertError) {
190
+ logger.debug("ImageMagick convert failed", { error: convertError.message });
191
+
192
+ // Clean up temp file if it exists
193
+ if (fs.existsSync(tmpPngPath)) {
194
+ fs.unlinkSync(tmpPngPath);
195
+ }
196
+
197
+ return null;
198
+ }
199
+ }
200
+ }
201
+
202
+ // For XPM, try ImageMagick
203
+ if (ext === ".xpm") {
204
+ const tmpPngPath = path.join(os.tmpdir(), `icon-${Date.now()}.png`);
205
+
206
+ try {
207
+ await execa("convert", [
208
+ "-background", "none",
209
+ "-resize", "48x48",
210
+ iconPath,
211
+ tmpPngPath
212
+ ]);
213
+
214
+ const buffer = fs.readFileSync(tmpPngPath);
215
+ fs.unlinkSync(tmpPngPath);
216
+ return buffer;
217
+ } catch (error) {
218
+ logger.debug("Failed to convert XPM to PNG", { error: error.message });
219
+
220
+ // Clean up temp file if it exists
221
+ if (fs.existsSync(tmpPngPath)) {
222
+ fs.unlinkSync(tmpPngPath);
223
+ }
224
+
225
+ return null;
226
+ }
227
+ }
228
+
229
+ logger.debug("Unsupported icon format", { ext, iconPath });
230
+ return null;
231
+ };
232
+
233
+ /**
234
+ * Get icon as buffer for Linux application
235
+ * @param {string} appPath - Path to the application or process name
236
+ */
237
+ const getIconAsBuffer = async (appPath) => {
238
+ try {
239
+ // Extract app name from path
240
+ let appName = path.basename(appPath);
241
+
242
+ // Remove common extensions
243
+ appName = appName.replace(/\.(exe|bin|sh|py|js)$/i, "");
244
+
245
+ logger.debug("Extracting icon for Linux app", { appName, appPath });
246
+
247
+ // Find the icon file
248
+ const iconPath = await findLinuxIcon(appName);
249
+ if (!iconPath) {
250
+ logger.debug("No icon found for Linux app", { appName });
251
+ return null;
252
+ }
253
+
254
+ // Convert to PNG if needed
255
+ const buffer = await convertToPng(iconPath);
256
+ if (!buffer) {
257
+ logger.debug("Failed to convert icon to PNG", { iconPath });
258
+ return null;
259
+ }
260
+
261
+ logger.debug("Successfully extracted Linux icon", {
262
+ appName,
263
+ iconPath,
264
+ bufferSize: buffer.length,
265
+ });
266
+
267
+ return { extension: "png", buffer };
268
+ } catch (error) {
269
+ logger.warn("Failed to extract Linux icon", {
270
+ appPath,
271
+ error: error.message,
272
+ });
273
+ return null;
274
+ }
275
+ };
276
+
277
+ export { getIconAsBuffer };
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,12 @@ export async function upload(filePath, metadata = {}) {
440
444
  });
441
445
 
442
446
  logExit();
447
+
448
+ const shareLink = newReplay.replay.shareLink;
449
+
443
450
  return {
444
451
  replay: newReplay.replay,
445
- shareLink: newReplay.replay.shareLink
452
+ shareLink: shareLink
446
453
  };
447
454
  }
448
455
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.0.1-beta.2",
3
+ "version": "1.0.1-beta.22",
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"
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+
3
+ # Sync local version with npm registry
4
+ # This fixes version drift when publishes fail
5
+
6
+ echo "🔍 Checking version sync..."
7
+
8
+ # Get current version in package.json
9
+ LOCAL_VERSION=$(node -p "require('./package.json').version")
10
+ echo "📦 Local version: $LOCAL_VERSION"
11
+
12
+ # Get current beta version from npm
13
+ NPM_BETA_VERSION=$(npm view dashcam dist-tags.beta 2>/dev/null)
14
+ if [ -z "$NPM_BETA_VERSION" ]; then
15
+ echo "⚠️ No beta tag found on npm, checking latest version..."
16
+ NPM_BETA_VERSION=$(npm view dashcam versions --json | jq -r '.[] | select(contains("beta"))' | tail -1)
17
+ fi
18
+ echo "📡 NPM beta version: $NPM_BETA_VERSION"
19
+
20
+ # Get all local git tags
21
+ echo ""
22
+ echo "🏷️ Local git tags:"
23
+ git tag | grep beta | tail -5
24
+
25
+ echo ""
26
+ echo "📡 NPM published versions:"
27
+ npm view dashcam versions --json | jq -r '.[] | select(contains("beta"))' | tail -5
28
+
29
+ echo ""
30
+ if [ "$LOCAL_VERSION" != "$NPM_BETA_VERSION" ]; then
31
+ echo "⚠️ Version mismatch detected!"
32
+ echo " Local: $LOCAL_VERSION"
33
+ echo " NPM: $NPM_BETA_VERSION"
34
+ echo ""
35
+ read -p "Do you want to sync package.json to $NPM_BETA_VERSION? (y/n) " -n 1 -r
36
+ echo
37
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
38
+ npm version $NPM_BETA_VERSION --no-git-tag-version --allow-same-version
39
+ echo "✅ Synced package.json to $NPM_BETA_VERSION"
40
+ echo ""
41
+ echo "⚠️ Note: You may have unpublished git tags. To clean them up:"
42
+ echo " git tag | grep beta | tail -10 # Review tags"
43
+ echo " git tag -d v1.0.1-beta.XX # Delete unpublished tags"
44
+ echo " git push origin :refs/tags/v1.0.1-beta.XX # Delete remote tags"
45
+ fi
46
+ else
47
+ echo "✅ Versions are in sync!"
48
+ fi