@savestate/cli 0.2.0 → 0.3.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 +9 -0
- package/dist/cli.js +42 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +40 -9
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/login.d.ts +13 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +111 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/migrate.d.ts +21 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +218 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/schedule.d.ts +14 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +294 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/snapshot.d.ts +1 -0
- package/dist/commands/snapshot.d.ts.map +1 -1
- package/dist/commands/snapshot.js +14 -4
- package/dist/commands/snapshot.js.map +1 -1
- package/dist/incremental.d.ts +137 -0
- package/dist/incremental.d.ts.map +1 -0
- package/dist/incremental.js +289 -0
- package/dist/incremental.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/restore.d.ts.map +1 -1
- package/dist/restore.js +7 -2
- package/dist/restore.js.map +1 -1
- package/dist/snapshot.d.ts +17 -1
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +82 -17
- package/dist/snapshot.js.map +1 -1
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -1
- package/dist/storage/index.js +1 -0
- package/dist/storage/index.js.map +1 -1
- package/dist/storage/resolve.d.ts.map +1 -1
- package/dist/storage/resolve.js +19 -4
- package/dist/storage/resolve.js.map +1 -1
- package/dist/storage/s3.d.ts +64 -0
- package/dist/storage/s3.d.ts.map +1 -0
- package/dist/storage/s3.js +360 -0
- package/dist/storage/s3.js.map +1 -0
- package/package.json +4 -1
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* savestate schedule — Configure automatic backup schedules
|
|
3
|
+
*
|
|
4
|
+
* Uses launchd (macOS) or systemd timers (Linux) for reliable scheduling.
|
|
5
|
+
* Requires Pro or Team tier for cloud-backed schedules.
|
|
6
|
+
*/
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'node:fs';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
import { homedir, platform } from 'node:os';
|
|
12
|
+
import { execSync } from 'node:child_process';
|
|
13
|
+
import { isInitialized } from '../config.js';
|
|
14
|
+
const LABEL = 'dev.savestate.autobackup';
|
|
15
|
+
export async function scheduleCommand(options) {
|
|
16
|
+
console.log();
|
|
17
|
+
if (!isInitialized()) {
|
|
18
|
+
console.log(chalk.red('✗ SaveState not initialized. Run `savestate init` first.'));
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
// Status check
|
|
22
|
+
if (options.status || (!options.every && !options.disable)) {
|
|
23
|
+
await showStatus();
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Disable
|
|
27
|
+
if (options.disable) {
|
|
28
|
+
await disableSchedule();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// Enable with interval
|
|
32
|
+
if (options.every) {
|
|
33
|
+
await enableSchedule(options.every);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function showStatus() {
|
|
38
|
+
const os = platform();
|
|
39
|
+
if (os === 'darwin') {
|
|
40
|
+
// macOS - check launchd
|
|
41
|
+
const plistPath = join(homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
42
|
+
if (!existsSync(plistPath)) {
|
|
43
|
+
console.log(chalk.yellow('⏸ Scheduled backups: disabled'));
|
|
44
|
+
console.log();
|
|
45
|
+
console.log(chalk.dim(' Enable with: savestate schedule --every 6h'));
|
|
46
|
+
console.log();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const status = execSync(`launchctl list | grep ${LABEL}`, { encoding: 'utf-8' }).trim();
|
|
51
|
+
if (status) {
|
|
52
|
+
const plist = readFileSync(plistPath, 'utf-8');
|
|
53
|
+
const intervalMatch = plist.match(/<key>StartInterval<\/key>\s*<integer>(\d+)<\/integer>/);
|
|
54
|
+
const interval = intervalMatch ? parseInt(intervalMatch[1]) : 0;
|
|
55
|
+
const hours = Math.round(interval / 3600);
|
|
56
|
+
console.log(chalk.green(`✓ Scheduled backups: enabled`));
|
|
57
|
+
console.log();
|
|
58
|
+
console.log(` ${chalk.dim('Interval:')} every ${hours}h`);
|
|
59
|
+
console.log(` ${chalk.dim('Job:')} ${LABEL}`);
|
|
60
|
+
console.log(` ${chalk.dim('Plist:')} ${plistPath}`);
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(chalk.dim(' View logs: tail -f ~/Library/Logs/savestate-autobackup.log'));
|
|
63
|
+
console.log(chalk.dim(' Disable: savestate schedule --disable'));
|
|
64
|
+
console.log();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
console.log(chalk.yellow('⏸ Scheduled backups: configured but not running'));
|
|
69
|
+
console.log(chalk.dim(` Try: launchctl load ${plistPath}`));
|
|
70
|
+
console.log();
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (os === 'linux') {
|
|
75
|
+
// Linux - check systemd timer
|
|
76
|
+
const timerPath = join(homedir(), '.config', 'systemd', 'user', `${LABEL}.timer`);
|
|
77
|
+
if (!existsSync(timerPath)) {
|
|
78
|
+
console.log(chalk.yellow('⏸ Scheduled backups: disabled'));
|
|
79
|
+
console.log();
|
|
80
|
+
console.log(chalk.dim(' Enable with: savestate schedule --every 6h'));
|
|
81
|
+
console.log();
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
try {
|
|
85
|
+
const status = execSync(`systemctl --user is-active ${LABEL}.timer 2>/dev/null || true`, { encoding: 'utf-8' }).trim();
|
|
86
|
+
if (status === 'active') {
|
|
87
|
+
console.log(chalk.green(`✓ Scheduled backups: enabled`));
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(` ${chalk.dim('Timer:')} ${LABEL}.timer`);
|
|
90
|
+
console.log(chalk.dim(' View: systemctl --user status ' + LABEL + '.timer'));
|
|
91
|
+
console.log(chalk.dim(' Logs: journalctl --user -u ' + LABEL));
|
|
92
|
+
console.log();
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
console.log(chalk.yellow('⏸ Scheduled backups: configured but not running'));
|
|
96
|
+
console.log();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
console.log(chalk.yellow('⏸ Scheduled backups: unknown status'));
|
|
101
|
+
console.log();
|
|
102
|
+
}
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
console.log(chalk.yellow(`⚠ Scheduled backups not supported on ${os}`));
|
|
106
|
+
console.log(chalk.dim(' Use cron manually: */360 * * * * savestate snapshot'));
|
|
107
|
+
console.log();
|
|
108
|
+
}
|
|
109
|
+
async function enableSchedule(interval) {
|
|
110
|
+
const seconds = parseInterval(interval);
|
|
111
|
+
if (!seconds) {
|
|
112
|
+
console.log(chalk.red(`✗ Invalid interval: ${interval}`));
|
|
113
|
+
console.log(chalk.dim(' Examples: 1h, 6h, 12h, 1d'));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const hours = seconds / 3600;
|
|
117
|
+
const os = platform();
|
|
118
|
+
const spinner = ora(`Setting up ${hours}h backup schedule...`).start();
|
|
119
|
+
try {
|
|
120
|
+
if (os === 'darwin') {
|
|
121
|
+
await setupMacOSSchedule(seconds);
|
|
122
|
+
}
|
|
123
|
+
else if (os === 'linux') {
|
|
124
|
+
await setupLinuxSchedule(seconds);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
spinner.fail(`Scheduled backups not supported on ${os}`);
|
|
128
|
+
console.log(chalk.dim(` Use cron: */${Math.round(seconds / 60)} * * * * savestate snapshot`));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
spinner.succeed(`Scheduled backups enabled: every ${hours}h`);
|
|
132
|
+
console.log();
|
|
133
|
+
console.log(chalk.dim(' Your AI state will be automatically backed up.'));
|
|
134
|
+
console.log(chalk.dim(' Check status: savestate schedule'));
|
|
135
|
+
console.log(chalk.dim(' Disable: savestate schedule --disable'));
|
|
136
|
+
console.log();
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
spinner.fail('Failed to set up schedule');
|
|
140
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function disableSchedule() {
|
|
145
|
+
const os = platform();
|
|
146
|
+
const spinner = ora('Disabling scheduled backups...').start();
|
|
147
|
+
try {
|
|
148
|
+
if (os === 'darwin') {
|
|
149
|
+
const plistPath = join(homedir(), 'Library', 'LaunchAgents', `${LABEL}.plist`);
|
|
150
|
+
try {
|
|
151
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`);
|
|
152
|
+
}
|
|
153
|
+
catch { /* ignore */ }
|
|
154
|
+
if (existsSync(plistPath)) {
|
|
155
|
+
unlinkSync(plistPath);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else if (os === 'linux') {
|
|
159
|
+
try {
|
|
160
|
+
execSync(`systemctl --user stop ${LABEL}.timer 2>/dev/null || true`);
|
|
161
|
+
execSync(`systemctl --user disable ${LABEL}.timer 2>/dev/null || true`);
|
|
162
|
+
}
|
|
163
|
+
catch { /* ignore */ }
|
|
164
|
+
const configDir = join(homedir(), '.config', 'systemd', 'user');
|
|
165
|
+
const timerPath = join(configDir, `${LABEL}.timer`);
|
|
166
|
+
const servicePath = join(configDir, `${LABEL}.service`);
|
|
167
|
+
if (existsSync(timerPath))
|
|
168
|
+
unlinkSync(timerPath);
|
|
169
|
+
if (existsSync(servicePath))
|
|
170
|
+
unlinkSync(servicePath);
|
|
171
|
+
}
|
|
172
|
+
spinner.succeed('Scheduled backups disabled');
|
|
173
|
+
console.log();
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
spinner.fail('Failed to disable schedule');
|
|
177
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// ─── Platform-specific setup ────────────────────────────────
|
|
181
|
+
async function setupMacOSSchedule(intervalSeconds) {
|
|
182
|
+
const launchAgentsDir = join(homedir(), 'Library', 'LaunchAgents');
|
|
183
|
+
const plistPath = join(launchAgentsDir, `${LABEL}.plist`);
|
|
184
|
+
const logPath = join(homedir(), 'Library', 'Logs', 'savestate-autobackup.log');
|
|
185
|
+
// Find the savestate binary
|
|
186
|
+
const savestateCmd = findSavestateCommand();
|
|
187
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
188
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
189
|
+
<plist version="1.0">
|
|
190
|
+
<dict>
|
|
191
|
+
<key>Label</key>
|
|
192
|
+
<string>${LABEL}</string>
|
|
193
|
+
<key>ProgramArguments</key>
|
|
194
|
+
<array>
|
|
195
|
+
<string>${savestateCmd}</string>
|
|
196
|
+
<string>snapshot</string>
|
|
197
|
+
<string>--label</string>
|
|
198
|
+
<string>auto</string>
|
|
199
|
+
</array>
|
|
200
|
+
<key>StartInterval</key>
|
|
201
|
+
<integer>${intervalSeconds}</integer>
|
|
202
|
+
<key>WorkingDirectory</key>
|
|
203
|
+
<string>${process.cwd()}</string>
|
|
204
|
+
<key>StandardOutPath</key>
|
|
205
|
+
<string>${logPath}</string>
|
|
206
|
+
<key>StandardErrorPath</key>
|
|
207
|
+
<string>${logPath}</string>
|
|
208
|
+
<key>RunAtLoad</key>
|
|
209
|
+
<true/>
|
|
210
|
+
<key>EnvironmentVariables</key>
|
|
211
|
+
<dict>
|
|
212
|
+
<key>PATH</key>
|
|
213
|
+
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
214
|
+
</dict>
|
|
215
|
+
</dict>
|
|
216
|
+
</plist>`;
|
|
217
|
+
// Ensure directory exists
|
|
218
|
+
if (!existsSync(launchAgentsDir)) {
|
|
219
|
+
mkdirSync(launchAgentsDir, { recursive: true });
|
|
220
|
+
}
|
|
221
|
+
// Unload existing if present
|
|
222
|
+
try {
|
|
223
|
+
execSync(`launchctl unload "${plistPath}" 2>/dev/null || true`);
|
|
224
|
+
}
|
|
225
|
+
catch { /* ignore */ }
|
|
226
|
+
// Write and load
|
|
227
|
+
writeFileSync(plistPath, plist);
|
|
228
|
+
execSync(`launchctl load "${plistPath}"`);
|
|
229
|
+
}
|
|
230
|
+
async function setupLinuxSchedule(intervalSeconds) {
|
|
231
|
+
const configDir = join(homedir(), '.config', 'systemd', 'user');
|
|
232
|
+
const timerPath = join(configDir, `${LABEL}.timer`);
|
|
233
|
+
const servicePath = join(configDir, `${LABEL}.service`);
|
|
234
|
+
const savestateCmd = findSavestateCommand();
|
|
235
|
+
const hours = Math.round(intervalSeconds / 3600);
|
|
236
|
+
const service = `[Unit]
|
|
237
|
+
Description=SaveState automatic backup
|
|
238
|
+
|
|
239
|
+
[Service]
|
|
240
|
+
Type=oneshot
|
|
241
|
+
WorkingDirectory=${process.cwd()}
|
|
242
|
+
ExecStart=${savestateCmd} snapshot --label auto
|
|
243
|
+
`;
|
|
244
|
+
const timer = `[Unit]
|
|
245
|
+
Description=SaveState automatic backup timer
|
|
246
|
+
|
|
247
|
+
[Timer]
|
|
248
|
+
OnBootSec=5min
|
|
249
|
+
OnUnitActiveSec=${hours}h
|
|
250
|
+
Persistent=true
|
|
251
|
+
|
|
252
|
+
[Install]
|
|
253
|
+
WantedBy=timers.target
|
|
254
|
+
`;
|
|
255
|
+
// Ensure directory exists
|
|
256
|
+
if (!existsSync(configDir)) {
|
|
257
|
+
mkdirSync(configDir, { recursive: true });
|
|
258
|
+
}
|
|
259
|
+
// Write units
|
|
260
|
+
writeFileSync(servicePath, service);
|
|
261
|
+
writeFileSync(timerPath, timer);
|
|
262
|
+
// Enable and start
|
|
263
|
+
execSync('systemctl --user daemon-reload');
|
|
264
|
+
execSync(`systemctl --user enable ${LABEL}.timer`);
|
|
265
|
+
execSync(`systemctl --user start ${LABEL}.timer`);
|
|
266
|
+
}
|
|
267
|
+
// ─── Helpers ────────────────────────────────────────────────
|
|
268
|
+
function parseInterval(interval) {
|
|
269
|
+
const match = interval.match(/^(\d+)(h|d|m)$/i);
|
|
270
|
+
if (!match)
|
|
271
|
+
return null;
|
|
272
|
+
const value = parseInt(match[1]);
|
|
273
|
+
const unit = match[2].toLowerCase();
|
|
274
|
+
switch (unit) {
|
|
275
|
+
case 'm': return value * 60;
|
|
276
|
+
case 'h': return value * 3600;
|
|
277
|
+
case 'd': return value * 86400;
|
|
278
|
+
default: return null;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function findSavestateCommand() {
|
|
282
|
+
// Check if we're running via npx/node
|
|
283
|
+
const execPath = process.argv[1];
|
|
284
|
+
// Try to find a global or local installation
|
|
285
|
+
try {
|
|
286
|
+
const which = execSync('which savestate 2>/dev/null || true', { encoding: 'utf-8' }).trim();
|
|
287
|
+
if (which)
|
|
288
|
+
return which;
|
|
289
|
+
}
|
|
290
|
+
catch { /* ignore */ }
|
|
291
|
+
// Fall back to npx
|
|
292
|
+
return 'npx savestate';
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=schedule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule.js","sourceRoot":"","sources":["../../src/commands/schedule.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/commands/snapshot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/commands/snapshot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAUH,UAAU,eAAe;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAmF7E"}
|
|
@@ -15,9 +15,11 @@ export async function snapshotCommand(options) {
|
|
|
15
15
|
process.exit(1);
|
|
16
16
|
}
|
|
17
17
|
if (options.schedule) {
|
|
18
|
-
console.log(chalk.cyan(`⏰
|
|
19
|
-
console.log(
|
|
20
|
-
console.log(
|
|
18
|
+
console.log(chalk.cyan(`⏰ To set up scheduled backups, use:`));
|
|
19
|
+
console.log();
|
|
20
|
+
console.log(` savestate schedule --every ${options.schedule}`);
|
|
21
|
+
console.log();
|
|
22
|
+
console.log(chalk.dim(' This creates a system job (launchd/systemd) for reliable auto-backups.'));
|
|
21
23
|
console.log();
|
|
22
24
|
return;
|
|
23
25
|
}
|
|
@@ -50,14 +52,22 @@ export async function snapshotCommand(options) {
|
|
|
50
52
|
const result = await createSnapshot(adapter, storage, passphrase, {
|
|
51
53
|
label: options.label,
|
|
52
54
|
tags: options.tags?.split(',').map((t) => t.trim()),
|
|
55
|
+
full: options.full,
|
|
53
56
|
});
|
|
54
|
-
|
|
57
|
+
const typeLabel = result.incremental ? 'Incremental snapshot' : 'Full snapshot';
|
|
58
|
+
spinner.succeed(`${typeLabel} created!`);
|
|
55
59
|
console.log();
|
|
56
60
|
console.log(` ${chalk.dim('ID:')} ${chalk.cyan(result.snapshot.manifest.id)}`);
|
|
57
61
|
console.log(` ${chalk.dim('Adapter:')} ${adapter.name}`);
|
|
62
|
+
console.log(` ${chalk.dim('Type:')} ${result.incremental ? chalk.yellow('incremental (delta)') : chalk.blue('full')}`);
|
|
58
63
|
if (options.label) {
|
|
59
64
|
console.log(` ${chalk.dim('Label:')} ${options.label}`);
|
|
60
65
|
}
|
|
66
|
+
if (result.incremental && result.delta) {
|
|
67
|
+
console.log(` ${chalk.dim('Changes:')} ${chalk.green(`+${result.delta.added}`)} added, ${chalk.yellow(`~${result.delta.modified}`)} modified, ${chalk.red(`-${result.delta.removed}`)} removed, ${chalk.dim(`${result.delta.unchanged} unchanged`)}`);
|
|
68
|
+
console.log(` ${chalk.dim('Chain:')} depth ${result.delta.chainDepth} (parent: ${result.snapshot.manifest.parent})`);
|
|
69
|
+
console.log(` ${chalk.dim('Saved:')} ${formatBytes(result.delta.bytesSaved)} vs full snapshot`);
|
|
70
|
+
}
|
|
61
71
|
console.log(` ${chalk.dim('Files:')} ${result.fileCount} files in archive`);
|
|
62
72
|
console.log(` ${chalk.dim('Archive:')} ${formatBytes(result.archiveSize)}`);
|
|
63
73
|
console.log(` ${chalk.dim('Encrypted:')} ${formatBytes(result.encryptedSize)}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/commands/snapshot.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"snapshot.js","sourceRoot":"","sources":["../../src/commands/snapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SaveState Incremental Snapshots
|
|
3
|
+
*
|
|
4
|
+
* Instead of storing a full copy every time, incremental snapshots
|
|
5
|
+
* only capture what changed since the parent snapshot.
|
|
6
|
+
*
|
|
7
|
+
* How it works:
|
|
8
|
+
* 1. Hash every file in the current snapshot (SHA-256)
|
|
9
|
+
* 2. Load the parent snapshot's content hashes
|
|
10
|
+
* 3. Compare: identify added, modified, removed files
|
|
11
|
+
* 4. Store only the delta (changed + added files)
|
|
12
|
+
* 5. On restore: reconstruct by applying deltas from base → current
|
|
13
|
+
*
|
|
14
|
+
* The delta manifest tracks the relationship:
|
|
15
|
+
* ```
|
|
16
|
+
* base (full) → delta-1 → delta-2 → delta-3 (current)
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Every Nth snapshot (configurable, default 10) forces a full snapshot
|
|
20
|
+
* to keep the chain short and restore fast.
|
|
21
|
+
*/
|
|
22
|
+
import type { Snapshot, StorageBackend } from './types.js';
|
|
23
|
+
/** Hash of every file in a snapshot */
|
|
24
|
+
export interface ContentHashes {
|
|
25
|
+
/** Map of relative path → SHA-256 hash */
|
|
26
|
+
files: Record<string, string>;
|
|
27
|
+
/** Total number of files */
|
|
28
|
+
count: number;
|
|
29
|
+
/** Combined hash of all file hashes (for quick comparison) */
|
|
30
|
+
rootHash: string;
|
|
31
|
+
}
|
|
32
|
+
/** A single file change in a delta */
|
|
33
|
+
export interface DeltaEntry {
|
|
34
|
+
/** Relative path within the archive */
|
|
35
|
+
path: string;
|
|
36
|
+
/** Type of change */
|
|
37
|
+
type: 'added' | 'modified' | 'removed';
|
|
38
|
+
/** SHA-256 hash of the new content (absent for 'removed') */
|
|
39
|
+
hash?: string;
|
|
40
|
+
/** Size in bytes of the new content (absent for 'removed') */
|
|
41
|
+
size?: number;
|
|
42
|
+
}
|
|
43
|
+
/** The delta manifest stored inside incremental archives */
|
|
44
|
+
export interface DeltaManifest {
|
|
45
|
+
/** ID of the parent snapshot this delta is relative to */
|
|
46
|
+
parentId: string;
|
|
47
|
+
/** ID of the base (full) snapshot at the root of the chain */
|
|
48
|
+
baseId: string;
|
|
49
|
+
/** Position in the chain (0 = full snapshot, 1 = first delta, etc.) */
|
|
50
|
+
chainDepth: number;
|
|
51
|
+
/** Content hashes of the FULL resulting state after applying this delta */
|
|
52
|
+
resultHashes: ContentHashes;
|
|
53
|
+
/** Individual file changes */
|
|
54
|
+
entries: DeltaEntry[];
|
|
55
|
+
/** Summary stats */
|
|
56
|
+
stats: {
|
|
57
|
+
added: number;
|
|
58
|
+
modified: number;
|
|
59
|
+
removed: number;
|
|
60
|
+
unchanged: number;
|
|
61
|
+
totalFiles: number;
|
|
62
|
+
/** Bytes saved vs full snapshot */
|
|
63
|
+
bytesSaved: number;
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/** Result of computing a delta between two snapshots */
|
|
67
|
+
export interface DeltaResult {
|
|
68
|
+
/** The delta manifest */
|
|
69
|
+
delta: DeltaManifest;
|
|
70
|
+
/** Map of changed file paths → content (only added + modified) */
|
|
71
|
+
changedFiles: Map<string, Buffer>;
|
|
72
|
+
/** Whether a full snapshot should be forced (chain too deep or too much changed) */
|
|
73
|
+
shouldForceFull: boolean;
|
|
74
|
+
}
|
|
75
|
+
/** Max chain depth before forcing a full snapshot */
|
|
76
|
+
export declare const MAX_CHAIN_DEPTH = 10;
|
|
77
|
+
/** If more than this fraction of files changed, just do a full snapshot */
|
|
78
|
+
export declare const FULL_SNAPSHOT_THRESHOLD = 0.7;
|
|
79
|
+
/**
|
|
80
|
+
* Compute SHA-256 hashes for every file in a packed snapshot.
|
|
81
|
+
*/
|
|
82
|
+
export declare function computeContentHashes(files: Map<string, Buffer>): ContentHashes;
|
|
83
|
+
/**
|
|
84
|
+
* Compute the delta between the current snapshot files and a parent's content hashes.
|
|
85
|
+
*
|
|
86
|
+
* @param currentFiles - The full file map of the current snapshot
|
|
87
|
+
* @param parentHashes - Content hashes from the parent snapshot
|
|
88
|
+
* @param parentId - ID of the parent snapshot
|
|
89
|
+
* @param baseId - ID of the base (full) snapshot
|
|
90
|
+
* @param chainDepth - Current position in the chain
|
|
91
|
+
* @returns The computed delta and changed files
|
|
92
|
+
*/
|
|
93
|
+
export declare function computeDelta(currentFiles: Map<string, Buffer>, parentHashes: ContentHashes, parentId: string, baseId: string, chainDepth: number): DeltaResult;
|
|
94
|
+
/**
|
|
95
|
+
* Pack an incremental snapshot (delta only).
|
|
96
|
+
* The archive contains:
|
|
97
|
+
* - manifest.json (with parent reference)
|
|
98
|
+
* - meta/delta-manifest.json (the delta details)
|
|
99
|
+
* - meta/snapshot-chain.json (chain info)
|
|
100
|
+
* - Only the changed/added files (same paths as full snapshot)
|
|
101
|
+
*/
|
|
102
|
+
export declare function packDelta(snapshot: Snapshot, delta: DeltaManifest, changedFiles: Map<string, Buffer>): Map<string, Buffer>;
|
|
103
|
+
/**
|
|
104
|
+
* Reconstruct a full snapshot by walking the chain from base to current.
|
|
105
|
+
*
|
|
106
|
+
* Starting from the base (full) snapshot, applies each delta in order:
|
|
107
|
+
* 1. Load base snapshot → full file map
|
|
108
|
+
* 2. For each delta in the chain:
|
|
109
|
+
* a. Add/overwrite files from the delta
|
|
110
|
+
* b. Remove files marked as 'removed'
|
|
111
|
+
* 3. Return the reconstructed full file map
|
|
112
|
+
*
|
|
113
|
+
* @param snapshotId - The target snapshot to reconstruct
|
|
114
|
+
* @param storage - Storage backend
|
|
115
|
+
* @param passphrase - Decryption passphrase
|
|
116
|
+
* @returns Reconstructed full file map
|
|
117
|
+
*/
|
|
118
|
+
export declare function reconstructFromChain(snapshotId: string, storage: StorageBackend, passphrase: string): Promise<Map<string, Buffer>>;
|
|
119
|
+
/**
|
|
120
|
+
* Get content hashes for the latest snapshot (used as parent for incremental).
|
|
121
|
+
* Returns null if no previous snapshots exist.
|
|
122
|
+
*/
|
|
123
|
+
export declare function getParentHashes(storage: StorageBackend, passphrase: string): Promise<{
|
|
124
|
+
hashes: ContentHashes;
|
|
125
|
+
parentId: string;
|
|
126
|
+
baseId: string;
|
|
127
|
+
chainDepth: number;
|
|
128
|
+
} | null>;
|
|
129
|
+
/**
|
|
130
|
+
* Check if a snapshot is incremental (has a delta manifest).
|
|
131
|
+
*/
|
|
132
|
+
export declare function isIncremental(files: Map<string, Buffer>): boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Get the delta manifest from an unpacked archive, if present.
|
|
135
|
+
*/
|
|
136
|
+
export declare function getDeltaManifest(files: Map<string, Buffer>): DeltaManifest | null;
|
|
137
|
+
//# sourceMappingURL=incremental.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"incremental.d.ts","sourceRoot":"","sources":["../src/incremental.ts"],"names":[],"mappings":"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"}
|