@dynamicu/chromedebug-mcp 2.2.0

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.
Files changed (95) hide show
  1. package/CLAUDE.md +344 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/chrome-extension/README.md +41 -0
  5. package/chrome-extension/background.js +3917 -0
  6. package/chrome-extension/chrome-session-manager.js +706 -0
  7. package/chrome-extension/content.css +181 -0
  8. package/chrome-extension/content.js +3022 -0
  9. package/chrome-extension/data-buffer.js +435 -0
  10. package/chrome-extension/dom-tracker.js +411 -0
  11. package/chrome-extension/extension-config.js +78 -0
  12. package/chrome-extension/firebase-client.js +278 -0
  13. package/chrome-extension/firebase-config.js +32 -0
  14. package/chrome-extension/firebase-config.module.js +22 -0
  15. package/chrome-extension/firebase-config.module.template.js +27 -0
  16. package/chrome-extension/firebase-config.template.js +36 -0
  17. package/chrome-extension/frame-capture.js +407 -0
  18. package/chrome-extension/icon128.png +1 -0
  19. package/chrome-extension/icon16.png +1 -0
  20. package/chrome-extension/icon48.png +1 -0
  21. package/chrome-extension/license-helper.js +181 -0
  22. package/chrome-extension/logger.js +23 -0
  23. package/chrome-extension/manifest.json +73 -0
  24. package/chrome-extension/network-tracker.js +510 -0
  25. package/chrome-extension/offscreen.html +10 -0
  26. package/chrome-extension/options.html +203 -0
  27. package/chrome-extension/options.js +282 -0
  28. package/chrome-extension/pako.min.js +2 -0
  29. package/chrome-extension/performance-monitor.js +533 -0
  30. package/chrome-extension/pii-redactor.js +405 -0
  31. package/chrome-extension/popup.html +532 -0
  32. package/chrome-extension/popup.js +2446 -0
  33. package/chrome-extension/upload-manager.js +323 -0
  34. package/chrome-extension/web-vitals.iife.js +1 -0
  35. package/config/api-keys.json +11 -0
  36. package/config/chrome-pilot-config.json +45 -0
  37. package/package.json +126 -0
  38. package/scripts/cleanup-processes.js +109 -0
  39. package/scripts/config-manager.js +280 -0
  40. package/scripts/generate-extension-config.js +53 -0
  41. package/scripts/setup-security.js +64 -0
  42. package/src/capture/architecture.js +426 -0
  43. package/src/capture/error-handling-tests.md +38 -0
  44. package/src/capture/error-handling-types.ts +360 -0
  45. package/src/capture/index.js +508 -0
  46. package/src/capture/interfaces.js +625 -0
  47. package/src/capture/memory-manager.js +713 -0
  48. package/src/capture/types.js +342 -0
  49. package/src/chrome-controller.js +2658 -0
  50. package/src/cli.js +19 -0
  51. package/src/config-loader.js +303 -0
  52. package/src/database.js +2178 -0
  53. package/src/firebase-license-manager.js +462 -0
  54. package/src/firebase-privacy-guard.js +397 -0
  55. package/src/http-server.js +1516 -0
  56. package/src/index-direct.js +157 -0
  57. package/src/index-modular.js +219 -0
  58. package/src/index-monolithic-backup.js +2230 -0
  59. package/src/index.js +305 -0
  60. package/src/legacy/chrome-controller-old.js +1406 -0
  61. package/src/legacy/index-express.js +625 -0
  62. package/src/legacy/index-old.js +977 -0
  63. package/src/legacy/routes.js +260 -0
  64. package/src/legacy/shared-storage.js +101 -0
  65. package/src/logger.js +10 -0
  66. package/src/mcp/handlers/chrome-tool-handler.js +306 -0
  67. package/src/mcp/handlers/element-tool-handler.js +51 -0
  68. package/src/mcp/handlers/frame-tool-handler.js +957 -0
  69. package/src/mcp/handlers/request-handler.js +104 -0
  70. package/src/mcp/handlers/workflow-tool-handler.js +636 -0
  71. package/src/mcp/server.js +68 -0
  72. package/src/mcp/tools/index.js +701 -0
  73. package/src/middleware/auth.js +371 -0
  74. package/src/middleware/security.js +267 -0
  75. package/src/port-discovery.js +258 -0
  76. package/src/routes/admin.js +182 -0
  77. package/src/services/browser-daemon.js +494 -0
  78. package/src/services/chrome-service.js +375 -0
  79. package/src/services/failover-manager.js +412 -0
  80. package/src/services/git-safety-service.js +675 -0
  81. package/src/services/heartbeat-manager.js +200 -0
  82. package/src/services/http-client.js +195 -0
  83. package/src/services/process-manager.js +318 -0
  84. package/src/services/process-tracker.js +574 -0
  85. package/src/services/profile-manager.js +449 -0
  86. package/src/services/project-manager.js +415 -0
  87. package/src/services/session-manager.js +497 -0
  88. package/src/services/session-registry.js +491 -0
  89. package/src/services/unified-session-manager.js +678 -0
  90. package/src/shared-storage-old.js +267 -0
  91. package/src/standalone-server.js +53 -0
  92. package/src/utils/extension-path.js +145 -0
  93. package/src/utils.js +187 -0
  94. package/src/validation/log-transformer.js +125 -0
  95. package/src/validation/schemas.js +391 -0
@@ -0,0 +1,675 @@
1
+ /**
2
+ * Git Safety Service - Provides comprehensive Git safety operations for instrumentation
3
+ * Follows ChromeDebug MCP service patterns with robust error handling and rollback capabilities
4
+ *
5
+ * Features:
6
+ * - Git status validation and working directory analysis
7
+ * - Atomic backup creation with Git stash or file copying
8
+ * - Branch protection to prevent modifications on protected branches
9
+ * - Rollback capabilities for failed operations
10
+ * - Commit state analysis and validation
11
+ */
12
+
13
+ import { execSync } from 'child_process';
14
+ import { existsSync, statSync, mkdirSync, copyFileSync, rmSync } from 'fs';
15
+ import { join, resolve, dirname } from 'path';
16
+ import { glob } from 'glob';
17
+
18
+ export class GitSafetyService {
19
+ constructor(options = {}) {
20
+ this.options = {
21
+ timeout: 30000, // 30 seconds for git operations
22
+ protectedBranches: ['main', 'master', 'production', 'develop'],
23
+ maxStashEntries: 10, // Limit stash entries to prevent bloat
24
+ backupRetentionDays: 7,
25
+ ...options
26
+ };
27
+
28
+ // Service state tracking
29
+ this.activeBackups = new Map(); // operationId -> backup info
30
+ this.operationHistory = [];
31
+ }
32
+
33
+ /**
34
+ * Validates Git repository status and safety for operations
35
+ * @param {string} projectPath - Absolute path to project root
36
+ * @param {Object} options - Validation options
37
+ * @returns {Promise<Object>} Git validation result
38
+ */
39
+ async validateGitSafety(projectPath, options = {}) {
40
+ try {
41
+ const validationOptions = {
42
+ allowUncommittedChanges: false,
43
+ requireGitRepo: false,
44
+ protectedBranches: this.options.protectedBranches,
45
+ ...options
46
+ };
47
+
48
+ // Basic path validation
49
+ if (!existsSync(projectPath)) {
50
+ return {
51
+ valid: false,
52
+ error: 'Project path does not exist',
53
+ path: projectPath
54
+ };
55
+ }
56
+
57
+ // Check if it's a Git repository
58
+ const gitPath = join(projectPath, '.git');
59
+ const isGitRepo = existsSync(gitPath);
60
+
61
+ if (!isGitRepo) {
62
+ if (validationOptions.requireGitRepo) {
63
+ return {
64
+ valid: false,
65
+ error: 'Git repository required but not found',
66
+ recommendation: 'Initialize git repository with: git init'
67
+ };
68
+ }
69
+
70
+ return {
71
+ valid: true,
72
+ isGitRepo: false,
73
+ warning: 'Not a Git repository - manual backup recommended',
74
+ recommendation: 'Initialize git: git init && git add . && git commit -m "Initial commit"'
75
+ };
76
+ }
77
+
78
+ // Validate Git status
79
+ const gitStatus = await this._getGitStatus(projectPath);
80
+ if (!gitStatus.valid) {
81
+ return gitStatus;
82
+ }
83
+
84
+ // Check for uncommitted changes
85
+ if (gitStatus.hasUncommittedChanges && !validationOptions.allowUncommittedChanges) {
86
+ return {
87
+ valid: false,
88
+ error: 'Uncommitted changes detected',
89
+ details: 'Commit or stash changes before proceeding',
90
+ uncommittedFiles: gitStatus.uncommittedFiles,
91
+ recommendation: 'git add . && git commit -m "Pre-instrumentation commit"'
92
+ };
93
+ }
94
+
95
+ // Check protected branches
96
+ const branchCheck = await this._checkProtectedBranch(projectPath, validationOptions.protectedBranches);
97
+ if (!branchCheck.valid) {
98
+ return branchCheck;
99
+ }
100
+
101
+ // Check for merge conflicts
102
+ const conflictCheck = await this._checkMergeConflicts(projectPath);
103
+ if (!conflictCheck.valid) {
104
+ return conflictCheck;
105
+ }
106
+
107
+ return {
108
+ valid: true,
109
+ isGitRepo: true,
110
+ gitStatus,
111
+ branchInfo: branchCheck,
112
+ recommendation: gitStatus.hasUncommittedChanges ?
113
+ 'Consider committing changes before instrumentation' :
114
+ 'Git repository is ready for safe operations'
115
+ };
116
+
117
+ } catch (error) {
118
+ return {
119
+ valid: false,
120
+ error: 'Git safety validation failed',
121
+ details: error.message,
122
+ stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
123
+ };
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Creates a backup using Git stash or file copying
129
+ * @param {string} projectPath - Project root path
130
+ * @param {string} operationId - Unique operation identifier
131
+ * @param {string} strategy - Backup strategy ('git-stash', 'file-copy', 'none')
132
+ * @returns {Promise<Object>} Backup result
133
+ */
134
+ async createBackup(projectPath, operationId, strategy = 'git-stash') {
135
+ try {
136
+ if (strategy === 'none') {
137
+ return {
138
+ success: true,
139
+ strategy: 'none',
140
+ message: 'No backup created as requested'
141
+ };
142
+ }
143
+
144
+ const backupInfo = {
145
+ operationId,
146
+ projectPath: resolve(projectPath),
147
+ strategy,
148
+ timestamp: new Date().toISOString(),
149
+ created: false
150
+ };
151
+
152
+ if (strategy === 'git-stash') {
153
+ const gitBackup = await this._createGitStashBackup(projectPath, operationId);
154
+ if (gitBackup.success) {
155
+ backupInfo.stashRef = gitBackup.stashRef;
156
+ backupInfo.stashMessage = gitBackup.message;
157
+ backupInfo.created = true;
158
+ } else {
159
+ // Fallback to file copy if git stash fails
160
+ console.warn('Git stash failed, falling back to file copy:', gitBackup.error);
161
+ const fileBackup = await this._createFileCopyBackup(projectPath, operationId);
162
+ backupInfo.strategy = 'file-copy';
163
+ backupInfo.backupPath = fileBackup.backupPath;
164
+ backupInfo.created = fileBackup.success;
165
+ }
166
+ } else if (strategy === 'file-copy') {
167
+ const fileBackup = await this._createFileCopyBackup(projectPath, operationId);
168
+ backupInfo.backupPath = fileBackup.backupPath;
169
+ backupInfo.created = fileBackup.success;
170
+ } else {
171
+ throw new Error(`Unknown backup strategy: ${strategy}`);
172
+ }
173
+
174
+ if (backupInfo.created) {
175
+ this.activeBackups.set(operationId, backupInfo);
176
+ this.operationHistory.push({
177
+ operationId,
178
+ action: 'backup_created',
179
+ timestamp: new Date().toISOString(),
180
+ strategy
181
+ });
182
+ }
183
+
184
+ return {
185
+ success: backupInfo.created,
186
+ operationId,
187
+ strategy: backupInfo.strategy,
188
+ backupInfo: backupInfo.created ? backupInfo : null,
189
+ message: backupInfo.created ?
190
+ `Backup created successfully using ${backupInfo.strategy}` :
191
+ 'Backup creation failed'
192
+ };
193
+
194
+ } catch (error) {
195
+ return {
196
+ success: false,
197
+ error: 'Backup creation failed',
198
+ details: error.message,
199
+ operationId
200
+ };
201
+ }
202
+ }
203
+
204
+ /**
205
+ * Restores from backup (rollback operation)
206
+ * @param {string} operationId - Operation to rollback
207
+ * @returns {Promise<Object>} Rollback result
208
+ */
209
+ async rollbackOperation(operationId) {
210
+ try {
211
+ const backupInfo = this.activeBackups.get(operationId);
212
+ if (!backupInfo) {
213
+ return {
214
+ success: false,
215
+ error: 'No backup found for operation',
216
+ operationId
217
+ };
218
+ }
219
+
220
+ let rollbackResult;
221
+
222
+ if (backupInfo.strategy === 'git-stash' && backupInfo.stashRef) {
223
+ rollbackResult = await this._rollbackFromGitStash(backupInfo);
224
+ } else if (backupInfo.strategy === 'file-copy' && backupInfo.backupPath) {
225
+ rollbackResult = await this._rollbackFromFileCopy(backupInfo);
226
+ } else {
227
+ throw new Error(`Cannot rollback: invalid backup strategy or missing backup data`);
228
+ }
229
+
230
+ if (rollbackResult.success) {
231
+ this.operationHistory.push({
232
+ operationId,
233
+ action: 'rollback_completed',
234
+ timestamp: new Date().toISOString(),
235
+ strategy: backupInfo.strategy
236
+ });
237
+ }
238
+
239
+ return rollbackResult;
240
+
241
+ } catch (error) {
242
+ return {
243
+ success: false,
244
+ error: 'Rollback operation failed',
245
+ details: error.message,
246
+ operationId
247
+ };
248
+ }
249
+ }
250
+
251
+ /**
252
+ * Cleans up backup resources for completed operations
253
+ * @param {string} operationId - Operation to clean up
254
+ * @returns {Promise<Object>} Cleanup result
255
+ */
256
+ async cleanupBackup(operationId) {
257
+ try {
258
+ const backupInfo = this.activeBackups.get(operationId);
259
+ if (!backupInfo) {
260
+ return {
261
+ success: true,
262
+ message: 'No backup to clean up'
263
+ };
264
+ }
265
+
266
+ let cleanupResult = { success: false };
267
+
268
+ if (backupInfo.strategy === 'git-stash' && backupInfo.stashRef) {
269
+ cleanupResult = await this._cleanupGitStash(backupInfo);
270
+ } else if (backupInfo.strategy === 'file-copy' && backupInfo.backupPath) {
271
+ cleanupResult = await this._cleanupFileCopy(backupInfo);
272
+ }
273
+
274
+ if (cleanupResult.success) {
275
+ this.activeBackups.delete(operationId);
276
+ this.operationHistory.push({
277
+ operationId,
278
+ action: 'backup_cleaned',
279
+ timestamp: new Date().toISOString(),
280
+ strategy: backupInfo.strategy
281
+ });
282
+ }
283
+
284
+ return cleanupResult;
285
+
286
+ } catch (error) {
287
+ return {
288
+ success: false,
289
+ error: 'Cleanup operation failed',
290
+ details: error.message,
291
+ operationId
292
+ };
293
+ }
294
+ }
295
+
296
+ // Private implementation methods
297
+
298
+ /**
299
+ * Gets comprehensive Git status information
300
+ * @private
301
+ */
302
+ async _getGitStatus(projectPath) {
303
+ try {
304
+ // Check git status with porcelain format for parsing
305
+ const statusOutput = execSync('git status --porcelain', {
306
+ cwd: projectPath,
307
+ encoding: 'utf8',
308
+ timeout: this.options.timeout
309
+ });
310
+
311
+ const uncommittedFiles = statusOutput.trim() ?
312
+ statusOutput.trim().split('\n').map(line => line.substring(3)) : [];
313
+
314
+ // Get current branch
315
+ const branchOutput = execSync('git rev-parse --abbrev-ref HEAD', {
316
+ cwd: projectPath,
317
+ encoding: 'utf8',
318
+ timeout: this.options.timeout
319
+ });
320
+
321
+ const currentBranch = branchOutput.trim();
322
+
323
+ // Check if there are staged changes
324
+ const stagedOutput = execSync('git diff --cached --name-only', {
325
+ cwd: projectPath,
326
+ encoding: 'utf8',
327
+ timeout: this.options.timeout
328
+ });
329
+
330
+ const stagedFiles = stagedOutput.trim() ? stagedOutput.trim().split('\n') : [];
331
+
332
+ return {
333
+ valid: true,
334
+ hasUncommittedChanges: uncommittedFiles.length > 0,
335
+ uncommittedFiles,
336
+ hasStagedChanges: stagedFiles.length > 0,
337
+ stagedFiles,
338
+ currentBranch
339
+ };
340
+
341
+ } catch (error) {
342
+ return {
343
+ valid: false,
344
+ error: 'Failed to get git status',
345
+ details: error.message
346
+ };
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Checks if current branch is protected
352
+ * @private
353
+ */
354
+ async _checkProtectedBranch(projectPath, protectedBranches) {
355
+ try {
356
+ const branchOutput = execSync('git rev-parse --abbrev-ref HEAD', {
357
+ cwd: projectPath,
358
+ encoding: 'utf8',
359
+ timeout: this.options.timeout
360
+ });
361
+
362
+ const currentBranch = branchOutput.trim();
363
+ const isProtected = protectedBranches.includes(currentBranch);
364
+
365
+ if (isProtected) {
366
+ return {
367
+ valid: false,
368
+ error: 'Protected branch detected',
369
+ currentBranch,
370
+ protectedBranches,
371
+ recommendation: `Switch to a feature branch: git checkout -b instrumentation-${Date.now()}`
372
+ };
373
+ }
374
+
375
+ return {
376
+ valid: true,
377
+ currentBranch,
378
+ isProtected: false
379
+ };
380
+
381
+ } catch (error) {
382
+ return {
383
+ valid: false,
384
+ error: 'Failed to check branch protection',
385
+ details: error.message
386
+ };
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Checks for merge conflicts
392
+ * @private
393
+ */
394
+ async _checkMergeConflicts(projectPath) {
395
+ try {
396
+ // Check for merge conflict markers in staged files
397
+ const conflictCheck = execSync('git ls-files -u', {
398
+ cwd: projectPath,
399
+ encoding: 'utf8',
400
+ timeout: this.options.timeout
401
+ });
402
+
403
+ const hasConflicts = conflictCheck.trim().length > 0;
404
+
405
+ if (hasConflicts) {
406
+ return {
407
+ valid: false,
408
+ error: 'Merge conflicts detected',
409
+ recommendation: 'Resolve conflicts before proceeding: git status'
410
+ };
411
+ }
412
+
413
+ return { valid: true, hasConflicts: false };
414
+
415
+ } catch (error) {
416
+ return {
417
+ valid: false,
418
+ error: 'Failed to check merge conflicts',
419
+ details: error.message
420
+ };
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Creates a Git stash backup
426
+ * @private
427
+ */
428
+ async _createGitStashBackup(projectPath, operationId) {
429
+ try {
430
+ const stashMessage = `ChromeDebug MCP instrumentation backup - ${operationId} - ${new Date().toISOString()}`;
431
+
432
+ // Create stash including untracked files
433
+ execSync(`git stash push -u -m "${stashMessage}"`, {
434
+ cwd: projectPath,
435
+ encoding: 'utf8',
436
+ timeout: this.options.timeout
437
+ });
438
+
439
+ // Get the stash reference
440
+ const stashList = execSync('git stash list --oneline', {
441
+ cwd: projectPath,
442
+ encoding: 'utf8',
443
+ timeout: this.options.timeout
444
+ });
445
+
446
+ const stashLines = stashList.trim().split('\n');
447
+ const latestStash = stashLines[0];
448
+ const stashRef = latestStash ? latestStash.split(':')[0] : 'stash@{0}';
449
+
450
+ return {
451
+ success: true,
452
+ stashRef,
453
+ message: stashMessage,
454
+ operationId
455
+ };
456
+
457
+ } catch (error) {
458
+ return {
459
+ success: false,
460
+ error: 'Git stash creation failed',
461
+ details: error.message
462
+ };
463
+ }
464
+ }
465
+
466
+ /**
467
+ * Creates a file copy backup
468
+ * @private
469
+ */
470
+ async _createFileCopyBackup(projectPath, operationId) {
471
+ try {
472
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
473
+ const backupPath = `${projectPath}-backup-${operationId}-${timestamp}`;
474
+
475
+ // Create backup directory
476
+ mkdirSync(backupPath, { recursive: true });
477
+
478
+ // Copy files (excluding node_modules, .git, etc.)
479
+ const patterns = ['**/*'];
480
+ const exclusions = [
481
+ 'node_modules/**',
482
+ '.git/**',
483
+ 'dist/**',
484
+ 'build/**',
485
+ '**/*.log',
486
+ '**/.*cache*/**'
487
+ ];
488
+
489
+ const files = await glob(patterns, {
490
+ cwd: projectPath,
491
+ absolute: true,
492
+ ignore: exclusions,
493
+ nodir: true
494
+ });
495
+
496
+ for (const file of files) {
497
+ try {
498
+ const relativePath = file.replace(projectPath, '');
499
+ const destPath = join(backupPath, relativePath);
500
+ const destDir = dirname(destPath);
501
+
502
+ mkdirSync(destDir, { recursive: true });
503
+ copyFileSync(file, destPath);
504
+ } catch (copyError) {
505
+ console.warn(`Failed to copy file ${file}:`, copyError.message);
506
+ }
507
+ }
508
+
509
+ return {
510
+ success: true,
511
+ backupPath,
512
+ filesCopied: files.length,
513
+ operationId
514
+ };
515
+
516
+ } catch (error) {
517
+ return {
518
+ success: false,
519
+ error: 'File copy backup failed',
520
+ details: error.message
521
+ };
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Rollback from Git stash
527
+ * @private
528
+ */
529
+ async _rollbackFromGitStash(backupInfo) {
530
+ try {
531
+ // Reset working directory to clean state
532
+ execSync('git reset --hard HEAD', {
533
+ cwd: backupInfo.projectPath,
534
+ timeout: this.options.timeout
535
+ });
536
+
537
+ // Apply the stash
538
+ execSync(`git stash apply ${backupInfo.stashRef}`, {
539
+ cwd: backupInfo.projectPath,
540
+ timeout: this.options.timeout
541
+ });
542
+
543
+ return {
544
+ success: true,
545
+ message: 'Successfully rolled back from Git stash',
546
+ stashRef: backupInfo.stashRef
547
+ };
548
+
549
+ } catch (error) {
550
+ return {
551
+ success: false,
552
+ error: 'Git stash rollback failed',
553
+ details: error.message
554
+ };
555
+ }
556
+ }
557
+
558
+ /**
559
+ * Rollback from file copy
560
+ * @private
561
+ */
562
+ async _rollbackFromFileCopy(backupInfo) {
563
+ try {
564
+ // Remove current files and restore from backup
565
+ const files = await glob('**/*', {
566
+ cwd: backupInfo.backupPath,
567
+ absolute: true,
568
+ nodir: true
569
+ });
570
+
571
+ for (const file of files) {
572
+ const relativePath = file.replace(backupInfo.backupPath, '');
573
+ const destPath = join(backupInfo.projectPath, relativePath);
574
+ const destDir = dirname(destPath);
575
+
576
+ mkdirSync(destDir, { recursive: true });
577
+ copyFileSync(file, destPath);
578
+ }
579
+
580
+ return {
581
+ success: true,
582
+ message: 'Successfully rolled back from file copy',
583
+ filesRestored: files.length
584
+ };
585
+
586
+ } catch (error) {
587
+ return {
588
+ success: false,
589
+ error: 'File copy rollback failed',
590
+ details: error.message
591
+ };
592
+ }
593
+ }
594
+
595
+ /**
596
+ * Cleanup Git stash
597
+ * @private
598
+ */
599
+ async _cleanupGitStash(backupInfo) {
600
+ try {
601
+ execSync(`git stash drop ${backupInfo.stashRef}`, {
602
+ cwd: backupInfo.projectPath,
603
+ timeout: this.options.timeout
604
+ });
605
+
606
+ return {
607
+ success: true,
608
+ message: 'Git stash cleaned up successfully'
609
+ };
610
+
611
+ } catch (error) {
612
+ return {
613
+ success: false,
614
+ error: 'Git stash cleanup failed',
615
+ details: error.message
616
+ };
617
+ }
618
+ }
619
+
620
+ /**
621
+ * Cleanup file copy backup
622
+ * @private
623
+ */
624
+ async _cleanupFileCopy(backupInfo) {
625
+ try {
626
+ if (existsSync(backupInfo.backupPath)) {
627
+ rmSync(backupInfo.backupPath, { recursive: true, force: true });
628
+ }
629
+
630
+ return {
631
+ success: true,
632
+ message: 'File copy backup cleaned up successfully'
633
+ };
634
+
635
+ } catch (error) {
636
+ return {
637
+ success: false,
638
+ error: 'File copy cleanup failed',
639
+ details: error.message
640
+ };
641
+ }
642
+ }
643
+
644
+ /**
645
+ * Gets service status and active operations
646
+ * @returns {Object} Service status
647
+ */
648
+ getServiceStatus() {
649
+ return {
650
+ activeBackups: this.activeBackups.size,
651
+ operationHistory: this.operationHistory.slice(-10), // Last 10 operations
652
+ options: this.options
653
+ };
654
+ }
655
+
656
+ /**
657
+ * Gets backup information for an operation
658
+ * @param {string} operationId - Operation ID
659
+ * @returns {Object|null} Backup information
660
+ */
661
+ getBackupInfo(operationId) {
662
+ return this.activeBackups.get(operationId) || null;
663
+ }
664
+
665
+ /**
666
+ * Cleanup service resources
667
+ * @returns {Promise<void>}
668
+ */
669
+ async cleanup() {
670
+ // Clean up any temporary resources
671
+ this.activeBackups.clear();
672
+ this.operationHistory = [];
673
+ console.log('GitSafetyService cleanup completed');
674
+ }
675
+ }