autosnap 1.0.0 → 1.0.1
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/package.json +1 -1
- package/src/cli/history.js +34 -8
- package/src/core/snapshot.js +46 -2
package/package.json
CHANGED
package/src/cli/history.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const chalk = require('chalk');
|
|
4
|
-
const { readSnapHistory } = require('../core/snapshot');
|
|
4
|
+
const { readSnapHistory, calculateTotalRawSize } = require('../core/snapshot');
|
|
5
5
|
const { STORE_DIR } = require('../utils/config');
|
|
6
6
|
|
|
7
7
|
async function history(filePattern) {
|
|
@@ -53,10 +53,13 @@ async function history(filePattern) {
|
|
|
53
53
|
console.log(chalk.bold.underline(`Snapshot History for "${filePattern}":`));
|
|
54
54
|
|
|
55
55
|
for (const { file, data } of statsList) {
|
|
56
|
+
// Calculate Raw Sizes for all versions
|
|
57
|
+
const { total: totalRawSize, sizes: rawSizes } = calculateTotalRawSize(data.raw);
|
|
58
|
+
|
|
56
59
|
console.log(chalk.bold(`\nFile: ${file}`));
|
|
57
|
-
console.log(chalk.gray('
|
|
58
|
-
console.log(chalk.bold(`| ${'Snapshot ID'.padEnd(15)} | ${'Parent'.padEnd(15)} | ${'Timestamp'.padEnd(30)} | ${'Stats'.padEnd(10)} | ${'Type'}
|
|
59
|
-
console.log(chalk.gray('
|
|
60
|
+
console.log(chalk.gray('----------------------------------------------------------------------------------------------------------------------------------'));
|
|
61
|
+
console.log(chalk.bold(`| ${'Snapshot ID'.padEnd(15)} | ${'Parent'.padEnd(15)} | ${'Timestamp'.padEnd(30)} | ${'Size (Raw)'.padEnd(12)} | ${'Stats'.padEnd(10)} | ${'Type'}`));
|
|
62
|
+
console.log(chalk.gray('----------------------------------------------------------------------------------------------------------------------------------'));
|
|
60
63
|
|
|
61
64
|
// Show ALL versions (Tree View)
|
|
62
65
|
const snapData = data.raw;
|
|
@@ -78,11 +81,28 @@ async function history(filePattern) {
|
|
|
78
81
|
const type = node.p ? 'VERSION' : 'ROOT';
|
|
79
82
|
const isHead = (node.id === data.currentId) ? chalk.cyan('(HEAD)') : '';
|
|
80
83
|
const parentId = node.p || '-';
|
|
84
|
+
const sizeStr = formatBytes(rawSizes[node.id] || 0);
|
|
81
85
|
|
|
82
|
-
console.log(`| ${node.id.padEnd(15)} | ${parentId.padEnd(15)} | ${dateStr.padEnd(30)} | ${statsStr.padEnd(10)} | ${type} ${isHead}`);
|
|
86
|
+
console.log(`| ${node.id.padEnd(15)} | ${parentId.padEnd(15)} | ${dateStr.padEnd(30)} | ${sizeStr.padEnd(12)} | ${statsStr.padEnd(10)} | ${type} ${isHead}`);
|
|
83
87
|
});
|
|
84
|
-
console.log(chalk.gray('
|
|
88
|
+
console.log(chalk.gray('----------------------------------------------------------------------------------------------------------------------------------'));
|
|
85
89
|
console.log(chalk.yellow(`\nTip: Use the 'Snapshot ID' (first column) to restore. Example: npm run restore ${allNodes[0].id}`));
|
|
90
|
+
|
|
91
|
+
// --- Storage Efficiency Stats ---
|
|
92
|
+
if (data.diskSize) {
|
|
93
|
+
const diskSize = data.diskSize;
|
|
94
|
+
const currentSize = rawSizes[data.currentId] || 0;
|
|
95
|
+
const savingsBytes = totalRawSize - diskSize;
|
|
96
|
+
const savingsPercent = totalRawSize > 0 ? ((savingsBytes / totalRawSize) * 100).toFixed(1) : '0.0';
|
|
97
|
+
|
|
98
|
+
console.log(chalk.gray('\n----------------------------------------------------------------------------------------------------------------------------------'));
|
|
99
|
+
console.log(chalk.bold.magenta('Storage Efficiency Overview:'));
|
|
100
|
+
console.log(`Current File Size: ${formatBytes(currentSize)}`);
|
|
101
|
+
console.log(`Hypothetical Total Size: ${formatBytes(totalRawSize)} (Sum of all ${allNodes.length} versions)`);
|
|
102
|
+
console.log(`Actual Storage (.snap): ${formatBytes(diskSize)}`);
|
|
103
|
+
console.log(chalk.green(`Total Saved: ${formatBytes(savingsBytes)} (${savingsPercent}%)`));
|
|
104
|
+
console.log(chalk.gray('----------------------------------------------------------------------------------------------------------------------------------'));
|
|
105
|
+
}
|
|
86
106
|
}
|
|
87
107
|
return;
|
|
88
108
|
}
|
|
@@ -118,9 +138,15 @@ async function history(filePattern) {
|
|
|
118
138
|
|
|
119
139
|
} catch (err) {
|
|
120
140
|
console.error(chalk.red('Failed to retrieve history:'), err);
|
|
121
|
-
// Debug
|
|
122
|
-
// console.error(err.stack);
|
|
123
141
|
}
|
|
124
142
|
}
|
|
125
143
|
|
|
144
|
+
function formatBytes(bytes) {
|
|
145
|
+
if (bytes === 0) return '0 B';
|
|
146
|
+
const k = 1024;
|
|
147
|
+
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
148
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
149
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
150
|
+
}
|
|
151
|
+
|
|
126
152
|
module.exports = history;
|
package/src/core/snapshot.js
CHANGED
|
@@ -142,6 +142,8 @@ async function readSnapHistory(rootDir, relPath) {
|
|
|
142
142
|
const buffer = await fs.readFile(snapPath);
|
|
143
143
|
const snapData = JSON.parse((await brotliDecompress(buffer)).toString());
|
|
144
144
|
|
|
145
|
+
const stat = await fs.stat(snapPath);
|
|
146
|
+
|
|
145
147
|
// Get Current Content for Watcher
|
|
146
148
|
const currentContent = reconstructContent(snapData, snapData.c);
|
|
147
149
|
|
|
@@ -149,10 +151,51 @@ async function readSnapHistory(rootDir, relPath) {
|
|
|
149
151
|
return {
|
|
150
152
|
currentId: snapData.c,
|
|
151
153
|
currentContent: currentContent,
|
|
152
|
-
raw: snapData
|
|
154
|
+
raw: snapData,
|
|
155
|
+
diskSize: stat.size
|
|
153
156
|
};
|
|
154
157
|
}
|
|
155
158
|
|
|
159
|
+
// Calculate the total size if all versions were stored as full raw files
|
|
160
|
+
function calculateTotalRawSize(snapData) {
|
|
161
|
+
let totalBytes = 0;
|
|
162
|
+
const sizes = {}; // Map ID -> Byte Size
|
|
163
|
+
const cache = {}; // Cache content by ID to avoid re-computing
|
|
164
|
+
|
|
165
|
+
// 1. Identify Nodes
|
|
166
|
+
const nodes = Object.keys(snapData.i);
|
|
167
|
+
if (nodes.length === 0) return { total: 0, sizes: {} };
|
|
168
|
+
|
|
169
|
+
// Use a memoized reconstructor simple for this calculation
|
|
170
|
+
const getContent = (id) => {
|
|
171
|
+
if (cache[id]) return cache[id];
|
|
172
|
+
|
|
173
|
+
const node = snapData.i[id];
|
|
174
|
+
let content = '';
|
|
175
|
+
|
|
176
|
+
if (node.b !== undefined) {
|
|
177
|
+
content = node.b;
|
|
178
|
+
} else if (node.p) {
|
|
179
|
+
const parentContent = getContent(node.p);
|
|
180
|
+
const patches = dmp.patch_fromText(node.d);
|
|
181
|
+
const [newContent] = dmp.patch_apply(patches, parentContent);
|
|
182
|
+
content = newContent;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
cache[id] = content;
|
|
186
|
+
return content;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
for (const id of nodes) {
|
|
190
|
+
const content = getContent(id);
|
|
191
|
+
const size = Buffer.byteLength(content, 'utf8');
|
|
192
|
+
sizes[id] = size;
|
|
193
|
+
totalBytes += size;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { total: totalBytes, sizes };
|
|
197
|
+
}
|
|
198
|
+
|
|
156
199
|
// Helper to record deletions (optional implementation)
|
|
157
200
|
async function recordDeletion(filePath, rootDir, config) {
|
|
158
201
|
// For now, maybe just log? Or create a "deleted" node?
|
|
@@ -163,5 +206,6 @@ module.exports = {
|
|
|
163
206
|
createSnapshot,
|
|
164
207
|
restoreSnapshot,
|
|
165
208
|
readSnapHistory,
|
|
166
|
-
recordDeletion
|
|
209
|
+
recordDeletion,
|
|
210
|
+
calculateTotalRawSize
|
|
167
211
|
};
|