@xelth/eck-snapshot 2.1.0 → 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.
- package/README.md +92 -7
- package/index.js +168 -62
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,23 +1,27 @@
|
|
|
1
1
|
# eck-snapshot
|
|
2
2
|
|
|
3
|
-
[](https://
|
|
4
|
-
[](https://
|
|
3
|
+
[](https://www.npmjs.com/package/@xelth/eck-snapshot)
|
|
4
|
+
[](https://github.com/xelth-com/eckSnapshot/blob/main/LICENSE)
|
|
5
5
|
|
|
6
6
|
A CLI tool to create and restore single-file text snapshots of a Git repository. It generates a single `.txt` file containing the directory structure and the content of all text-based files, which is ideal for providing context to Large Language Models (LLMs).
|
|
7
7
|
|
|
8
8
|
## Why eck-snapshot?
|
|
9
9
|
|
|
10
|
-
When working with
|
|
10
|
+
When working with Large Language Models (LLMs), providing the full context of your project is crucial for getting accurate results. Manually copying and pasting dozens of files is tedious and inefficient.
|
|
11
|
+
|
|
12
|
+
eck-snapshot automates this by generating a single, comprehensive text file of your entire repository. This is particularly effective with models that support large context windows (like Google's Gemini), as it often allows the entire project snapshot to be analyzed at once—a task that can be challenging with smaller context windows.
|
|
11
13
|
|
|
12
14
|
## Key Features
|
|
13
15
|
|
|
14
16
|
* **Git Integration**: Automatically includes all files tracked by Git.
|
|
15
17
|
* **Intelligent Ignoring**: Respects `.gitignore` rules and has its own configurable ignore lists for files, extensions, and directories.
|
|
16
|
-
* **Restore
|
|
18
|
+
* **Advanced Restore**: Powerful `restore` command with filtering, dry-run mode, and parallel processing.
|
|
17
19
|
* **Directory Tree**: Generates a clean, readable tree of the repository structure at the top of the snapshot.
|
|
20
|
+
* **Multiple Formats**: Supports both plain text and JSON output formats.
|
|
18
21
|
* **Configurable**: Customize behavior using an `.ecksnapshot.config.js` file.
|
|
19
22
|
* **Progress and Stats**: Provides a progress bar and a detailed summary of what was included and skipped.
|
|
20
23
|
* **Compression**: Supports gzipped (`.gz`) snapshots for smaller file sizes.
|
|
24
|
+
* **Security**: Built-in path validation to prevent directory traversal attacks during restore.
|
|
21
25
|
|
|
22
26
|
## Demo
|
|
23
27
|
|
|
@@ -95,16 +99,37 @@ eck-snapshot /path/to/your/other/project
|
|
|
95
99
|
|
|
96
100
|
# Save the snapshot to a different directory and exclude the tree view
|
|
97
101
|
eck-snapshot --output ./backups --no-tree
|
|
102
|
+
|
|
103
|
+
# Create a compressed JSON snapshot
|
|
104
|
+
eck-snapshot --format json --compress
|
|
105
|
+
|
|
106
|
+
# Include hidden files and set custom size limits
|
|
107
|
+
eck-snapshot --include-hidden --max-file-size 5MB --max-total-size 50MB
|
|
98
108
|
```
|
|
99
109
|
|
|
100
110
|
### Restoring from a Snapshot
|
|
101
111
|
|
|
102
112
|
```bash
|
|
103
|
-
#
|
|
113
|
+
# Basic restore to current directory
|
|
104
114
|
eck-snapshot restore ./snapshots/project_snapshot_...txt
|
|
105
115
|
|
|
106
|
-
# Restore
|
|
116
|
+
# Restore to a specific directory without confirmation
|
|
107
117
|
eck-snapshot restore snapshot.txt ./restored-project --force
|
|
118
|
+
|
|
119
|
+
# Preview what would be restored (dry run)
|
|
120
|
+
eck-snapshot restore snapshot.txt --dry-run
|
|
121
|
+
|
|
122
|
+
# Restore only specific files using patterns
|
|
123
|
+
eck-snapshot restore snapshot.txt --include "*.js" "*.json"
|
|
124
|
+
|
|
125
|
+
# Restore everything except certain files
|
|
126
|
+
eck-snapshot restore snapshot.txt --exclude "*.log" "node_modules/*"
|
|
127
|
+
|
|
128
|
+
# Restore with custom concurrency and verbose output
|
|
129
|
+
eck-snapshot restore snapshot.txt --concurrency 20 --verbose
|
|
130
|
+
|
|
131
|
+
# Restore compressed snapshots
|
|
132
|
+
eck-snapshot restore project_snapshot.txt.gz ./restored
|
|
108
133
|
```
|
|
109
134
|
|
|
110
135
|
## Configuration
|
|
@@ -131,11 +156,71 @@ export default {
|
|
|
131
156
|
'.git/',
|
|
132
157
|
'dist/',
|
|
133
158
|
],
|
|
134
|
-
//
|
|
159
|
+
// Size and performance settings
|
|
135
160
|
maxFileSize: '10MB',
|
|
161
|
+
maxTotalSize: '100MB',
|
|
162
|
+
maxDepth: 10,
|
|
163
|
+
concurrency: 10
|
|
136
164
|
};
|
|
137
165
|
```
|
|
138
166
|
|
|
167
|
+
## Advanced Features
|
|
168
|
+
|
|
169
|
+
### Restore Command Options
|
|
170
|
+
|
|
171
|
+
The restore command offers powerful filtering and control options:
|
|
172
|
+
|
|
173
|
+
- **`--dry-run`**: Preview what files would be restored without actually writing them
|
|
174
|
+
- **`--include <patterns>`**: Only restore files matching the specified patterns (supports wildcards)
|
|
175
|
+
- **`--exclude <patterns>`**: Skip files matching the specified patterns (supports wildcards)
|
|
176
|
+
- **`--concurrency <number>`**: Control how many files are processed simultaneously (default: 10)
|
|
177
|
+
- **`--force`**: Skip confirmation prompts and overwrite existing files
|
|
178
|
+
- **`--verbose`**: Show detailed information about each file being processed
|
|
179
|
+
|
|
180
|
+
### Supported Formats
|
|
181
|
+
|
|
182
|
+
- **Plain Text** (`.txt`): Human-readable format, ideal for LLM context
|
|
183
|
+
- **JSON** (`.json`): Structured format with metadata and statistics
|
|
184
|
+
- **Compressed** (`.gz`): Any format can be gzipped for smaller file sizes
|
|
185
|
+
|
|
186
|
+
### Security Features
|
|
187
|
+
|
|
188
|
+
- **Path Validation**: Prevents directory traversal attacks during restore operations
|
|
189
|
+
- **File Sanitization**: Validates file paths and names for security
|
|
190
|
+
- **Confirmation Prompts**: Requires user confirmation before overwriting files (unless `--force` is used)
|
|
191
|
+
|
|
192
|
+
## Command Reference
|
|
193
|
+
|
|
194
|
+
### Snapshot Command
|
|
195
|
+
```bash
|
|
196
|
+
eck-snapshot [options] [repoPath]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
**Options:**
|
|
200
|
+
- `-o, --output <dir>`: Output directory for snapshots
|
|
201
|
+
- `--no-tree`: Exclude directory tree from output
|
|
202
|
+
- `-v, --verbose`: Show detailed processing information
|
|
203
|
+
- `--max-file-size <size>`: Maximum individual file size (e.g., 10MB)
|
|
204
|
+
- `--max-total-size <size>`: Maximum total snapshot size (e.g., 100MB)
|
|
205
|
+
- `--max-depth <number>`: Maximum directory depth for tree generation
|
|
206
|
+
- `--config <path>`: Path to custom configuration file
|
|
207
|
+
- `--compress`: Create gzipped output
|
|
208
|
+
- `--include-hidden`: Include hidden files (starting with .)
|
|
209
|
+
- `--format <type>`: Output format: txt or json
|
|
210
|
+
|
|
211
|
+
### Restore Command
|
|
212
|
+
```bash
|
|
213
|
+
eck-snapshot restore [options] <snapshot_file> [target_directory]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Options:**
|
|
217
|
+
- `-f, --force`: Force overwrite without confirmation
|
|
218
|
+
- `-v, --verbose`: Show detailed processing information
|
|
219
|
+
- `--dry-run`: Preview without actually writing files
|
|
220
|
+
- `--include <patterns>`: Include only matching files
|
|
221
|
+
- `--exclude <patterns>`: Exclude matching files
|
|
222
|
+
- `--concurrency <number>`: Number of concurrent operations
|
|
223
|
+
|
|
139
224
|
## License
|
|
140
225
|
|
|
141
226
|
This project is licensed under the MIT License.
|
package/index.js
CHANGED
|
@@ -509,115 +509,175 @@ async function createRepoSnapshot(repoPath, options) {
|
|
|
509
509
|
}
|
|
510
510
|
}
|
|
511
511
|
|
|
512
|
-
// NEW: Restore Snapshot Function
|
|
513
512
|
async function restoreSnapshot(snapshotFile, targetDir, options) {
|
|
514
513
|
const absoluteSnapshotPath = path.resolve(snapshotFile);
|
|
515
514
|
const absoluteTargetDir = path.resolve(targetDir);
|
|
516
515
|
|
|
517
|
-
console.log(
|
|
518
|
-
console.log(
|
|
516
|
+
console.log(`🔄 Starting restore from snapshot: ${absoluteSnapshotPath}`);
|
|
517
|
+
console.log(`📁 Target directory: ${absoluteTargetDir}`);
|
|
519
518
|
|
|
520
519
|
try {
|
|
521
520
|
let rawContent;
|
|
522
|
-
// Check if the file is compressed
|
|
523
521
|
if (snapshotFile.endsWith('.gz')) {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
522
|
+
const compressedBuffer = await fs.readFile(absoluteSnapshotPath);
|
|
523
|
+
rawContent = (await gunzip(compressedBuffer)).toString('utf-8');
|
|
524
|
+
console.log('✅ Decompressed gzipped snapshot');
|
|
527
525
|
} else {
|
|
528
|
-
|
|
526
|
+
rawContent = await fs.readFile(absoluteSnapshotPath, 'utf-8');
|
|
529
527
|
}
|
|
530
528
|
|
|
531
|
-
// Check if the content is JSON
|
|
532
529
|
let filesToRestore;
|
|
533
530
|
try {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
531
|
+
const jsonData = JSON.parse(rawContent);
|
|
532
|
+
if (jsonData.content) {
|
|
533
|
+
console.log('📄 Detected JSON format, extracting content');
|
|
534
|
+
filesToRestore = parseSnapshotContent(jsonData.content);
|
|
535
|
+
} else {
|
|
536
|
+
throw new Error('JSON format detected, but no "content" key found');
|
|
537
|
+
}
|
|
541
538
|
} catch (e) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
filesToRestore = parseSnapshotContent(rawContent);
|
|
539
|
+
console.log('📄 Treating snapshot as plain text format');
|
|
540
|
+
filesToRestore = parseSnapshotContent(rawContent);
|
|
545
541
|
}
|
|
546
542
|
|
|
547
543
|
if (filesToRestore.length === 0) {
|
|
548
|
-
console.warn('⚠️ No files found to restore in the snapshot
|
|
544
|
+
console.warn('⚠️ No files found to restore in the snapshot');
|
|
549
545
|
return;
|
|
550
546
|
}
|
|
551
547
|
|
|
552
|
-
|
|
548
|
+
// Apply filters if specified
|
|
549
|
+
if (options.include || options.exclude) {
|
|
550
|
+
filesToRestore = filterFilesToRestore(filesToRestore, options);
|
|
551
|
+
if (filesToRestore.length === 0) {
|
|
552
|
+
console.warn('⚠️ No files remaining after applying filters');
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// Validate file paths for security
|
|
558
|
+
const invalidFiles = validateFilePaths(filesToRestore, absoluteTargetDir);
|
|
559
|
+
if (invalidFiles.length > 0) {
|
|
560
|
+
console.error('❌ Invalid file paths detected (potential directory traversal):');
|
|
561
|
+
invalidFiles.forEach(file => console.error(` ${file}`));
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
console.log(`📊 Found ${filesToRestore.length} files to restore`);
|
|
566
|
+
|
|
567
|
+
if (options.dryRun) {
|
|
568
|
+
console.log('\n🔍 Dry run mode - files that would be restored:');
|
|
569
|
+
filesToRestore.forEach(file => {
|
|
570
|
+
const fullPath = path.join(absoluteTargetDir, file.path);
|
|
571
|
+
console.log(` ${fullPath}`);
|
|
572
|
+
});
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
553
575
|
|
|
554
|
-
// Confirmation prompt
|
|
555
576
|
if (!options.force) {
|
|
556
|
-
const { confirm } = await inquirer.prompt([
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
},
|
|
563
|
-
]);
|
|
577
|
+
const { confirm } = await inquirer.prompt([{
|
|
578
|
+
type: 'confirm',
|
|
579
|
+
name: 'confirm',
|
|
580
|
+
message: `You are about to write ${filesToRestore.length} files to ${absoluteTargetDir}. Existing files will be overwritten. Continue?`,
|
|
581
|
+
default: false
|
|
582
|
+
}]);
|
|
564
583
|
|
|
565
584
|
if (!confirm) {
|
|
566
|
-
console.log('🚫 Restore operation cancelled by user
|
|
585
|
+
console.log('🚫 Restore operation cancelled by user');
|
|
567
586
|
return;
|
|
568
587
|
}
|
|
569
588
|
}
|
|
570
589
|
|
|
571
|
-
// Create target directory if it doesn't exist
|
|
572
590
|
await fs.mkdir(absoluteTargetDir, { recursive: true });
|
|
573
591
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
592
|
+
const stats = {
|
|
593
|
+
totalFiles: filesToRestore.length,
|
|
594
|
+
restoredFiles: 0,
|
|
595
|
+
failedFiles: 0,
|
|
596
|
+
errors: []
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const progressBar = options.verbose ? null : new SingleBar({
|
|
600
|
+
format: 'Restoring |{bar}| {percentage}% | {value}/{total} files',
|
|
601
|
+
barCompleteChar: '\u2588',
|
|
602
|
+
barIncompleteChar: '\u2591',
|
|
603
|
+
hideCursor: true
|
|
580
604
|
}, Presets.shades_classic);
|
|
581
605
|
|
|
582
|
-
progressBar.start(filesToRestore.length, 0);
|
|
606
|
+
if (progressBar) progressBar.start(filesToRestore.length, 0);
|
|
583
607
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
608
|
+
const limit = pLimit(options.concurrency || 10);
|
|
609
|
+
const filePromises = filesToRestore.map((file, index) =>
|
|
610
|
+
limit(async () => {
|
|
611
|
+
try {
|
|
612
|
+
const fullPath = path.join(absoluteTargetDir, file.path);
|
|
613
|
+
const dir = path.dirname(fullPath);
|
|
587
614
|
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
615
|
+
await fs.mkdir(dir, { recursive: true });
|
|
616
|
+
await fs.writeFile(fullPath, file.content, 'utf-8');
|
|
617
|
+
|
|
618
|
+
stats.restoredFiles++;
|
|
619
|
+
|
|
620
|
+
if (progressBar) {
|
|
621
|
+
progressBar.update(index + 1);
|
|
622
|
+
} else if (options.verbose) {
|
|
623
|
+
console.log(`✅ Restored: ${file.path}`);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
return { success: true, file: file.path };
|
|
627
|
+
} catch (error) {
|
|
628
|
+
stats.failedFiles++;
|
|
629
|
+
stats.errors.push({ file: file.path, error: error.message });
|
|
630
|
+
|
|
631
|
+
if (options.verbose) {
|
|
632
|
+
console.log(`❌ Failed to restore: ${file.path} - ${error.message}`);
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return { success: false, file: file.path, error: error.message };
|
|
636
|
+
}
|
|
637
|
+
})
|
|
638
|
+
);
|
|
594
639
|
|
|
595
|
-
|
|
596
|
-
|
|
640
|
+
await Promise.allSettled(filePromises);
|
|
641
|
+
if (progressBar) progressBar.stop();
|
|
642
|
+
|
|
643
|
+
console.log('\n📊 Restore Summary');
|
|
644
|
+
console.log('='.repeat(50));
|
|
645
|
+
console.log(`🎉 Restore completed!`);
|
|
646
|
+
console.log(`✅ Successfully restored: ${stats.restoredFiles} files`);
|
|
647
|
+
if (stats.failedFiles > 0) {
|
|
648
|
+
console.log(`❌ Failed to restore: ${stats.failedFiles} files`);
|
|
649
|
+
if (stats.errors.length > 0) {
|
|
650
|
+
console.log('\n⚠️ Errors encountered:');
|
|
651
|
+
stats.errors.slice(0, 5).forEach(({ file, error }) => {
|
|
652
|
+
console.log(` ${file}: ${error}`);
|
|
653
|
+
});
|
|
654
|
+
if (stats.errors.length > 5) {
|
|
655
|
+
console.log(` ... and ${stats.errors.length - 5} more errors`);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
console.log(`📁 Target directory: ${absoluteTargetDir}`);
|
|
660
|
+
console.log('='.repeat(50));
|
|
597
661
|
|
|
598
662
|
} catch (error) {
|
|
599
663
|
console.error('\n❌ An error occurred during restore:');
|
|
600
664
|
console.error(error.message);
|
|
601
665
|
if (options.verbose) {
|
|
602
|
-
|
|
666
|
+
console.error(error.stack);
|
|
603
667
|
}
|
|
604
668
|
process.exit(1);
|
|
605
669
|
}
|
|
606
670
|
}
|
|
607
671
|
|
|
608
|
-
// NEW: Snapshot Parser Function
|
|
609
672
|
function parseSnapshotContent(content) {
|
|
610
673
|
const files = [];
|
|
611
674
|
const fileRegex = /--- File: \/(.+) ---/g;
|
|
612
675
|
const sections = content.split(fileRegex);
|
|
613
676
|
|
|
614
|
-
// sections will be [ '...everything before first match...', 'path1', 'content1', 'path2', 'content2', ...]
|
|
615
|
-
// We start at index 1 because index 0 is anything before the first delimiter (like the directory tree)
|
|
616
677
|
for (let i = 1; i < sections.length; i += 2) {
|
|
617
678
|
const filePath = sections[i].trim();
|
|
618
679
|
let fileContent = sections[i + 1] || '';
|
|
619
680
|
|
|
620
|
-
// Remove the trailing newline that the split might leave
|
|
621
681
|
if (fileContent.startsWith('\n\n')) {
|
|
622
682
|
fileContent = fileContent.substring(2);
|
|
623
683
|
}
|
|
@@ -631,6 +691,49 @@ function parseSnapshotContent(content) {
|
|
|
631
691
|
return files;
|
|
632
692
|
}
|
|
633
693
|
|
|
694
|
+
function filterFilesToRestore(files, options) {
|
|
695
|
+
let filtered = files;
|
|
696
|
+
|
|
697
|
+
if (options.include) {
|
|
698
|
+
const includePatterns = Array.isArray(options.include) ? options.include : [options.include];
|
|
699
|
+
filtered = filtered.filter(file =>
|
|
700
|
+
includePatterns.some(pattern => {
|
|
701
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
702
|
+
return regex.test(file.path);
|
|
703
|
+
})
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (options.exclude) {
|
|
708
|
+
const excludePatterns = Array.isArray(options.exclude) ? options.exclude : [options.exclude];
|
|
709
|
+
filtered = filtered.filter(file =>
|
|
710
|
+
!excludePatterns.some(pattern => {
|
|
711
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
712
|
+
return regex.test(file.path);
|
|
713
|
+
})
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return filtered;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function validateFilePaths(files, targetDir) {
|
|
721
|
+
const invalidFiles = [];
|
|
722
|
+
|
|
723
|
+
for (const file of files) {
|
|
724
|
+
const normalizedPath = path.normalize(file.path);
|
|
725
|
+
|
|
726
|
+
if (normalizedPath.includes('..') ||
|
|
727
|
+
normalizedPath.startsWith('/') ||
|
|
728
|
+
normalizedPath.includes('\0') ||
|
|
729
|
+
/[<>:"|?*]/.test(normalizedPath)) {
|
|
730
|
+
invalidFiles.push(file.path);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return invalidFiles;
|
|
735
|
+
}
|
|
736
|
+
|
|
634
737
|
|
|
635
738
|
// --- CLI SETUP ---
|
|
636
739
|
const program = new Command();
|
|
@@ -657,14 +760,17 @@ program
|
|
|
657
760
|
.option('--format <type>', 'Output format: txt, json', 'txt')
|
|
658
761
|
.action((repoPath, options) => createRepoSnapshot(repoPath, options));
|
|
659
762
|
|
|
660
|
-
// NEW: Restore command (Corrected)
|
|
661
763
|
program
|
|
662
|
-
.command('restore')
|
|
663
|
-
.description('Restore files and directories from a snapshot file
|
|
664
|
-
.argument('<snapshot_file>', 'Path to the snapshot file (.txt, .json, or .gz)
|
|
665
|
-
.argument('[target_directory]', 'Directory to restore the files into
|
|
666
|
-
.option('-f, --force', 'Force overwrite of existing files without confirmation
|
|
667
|
-
.option('-v, --verbose', 'Show detailed processing information
|
|
764
|
+
.command('restore')
|
|
765
|
+
.description('Restore files and directories from a snapshot file')
|
|
766
|
+
.argument('<snapshot_file>', 'Path to the snapshot file (.txt, .json, or .gz)')
|
|
767
|
+
.argument('[target_directory]', 'Directory to restore the files into', process.cwd())
|
|
768
|
+
.option('-f, --force', 'Force overwrite of existing files without confirmation')
|
|
769
|
+
.option('-v, --verbose', 'Show detailed processing information')
|
|
770
|
+
.option('--dry-run', 'Show what would be restored without actually writing files')
|
|
771
|
+
.option('--include <patterns...>', 'Include only files matching these patterns (supports wildcards)')
|
|
772
|
+
.option('--exclude <patterns...>', 'Exclude files matching these patterns (supports wildcards)')
|
|
773
|
+
.option('--concurrency <number>', 'Number of concurrent file operations', (val) => parseInt(val), 10)
|
|
668
774
|
.action((snapshotFile, targetDir, options) => restoreSnapshot(snapshotFile, targetDir, options));
|
|
669
775
|
|
|
670
776
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xelth/eck-snapshot",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "A CLI tool to create and restore single-file text snapshots of a Git repository.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -31,4 +31,4 @@
|
|
|
31
31
|
"p-limit": "^5.0.0",
|
|
32
32
|
"inquirer": "^9.2.20"
|
|
33
33
|
}
|
|
34
|
-
}
|
|
34
|
+
}
|