@procedures/snapshot 0.0.0-stage → 0.1.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/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +737 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/module.d.ts +15 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +99 -0
- package/dist/module.js.map +1 -0
- package/dist/procedures/snapshot/archive-io.d.ts +12 -0
- package/dist/procedures/snapshot/archive-io.d.ts.map +1 -0
- package/dist/procedures/snapshot/archive-io.js +29 -0
- package/dist/procedures/snapshot/archive-io.js.map +1 -0
- package/dist/procedures/snapshot/commands.d.ts +15 -0
- package/dist/procedures/snapshot/commands.d.ts.map +1 -0
- package/dist/procedures/snapshot/commands.js +32 -0
- package/dist/procedures/snapshot/commands.js.map +1 -0
- package/dist/procedures/snapshot/create.d.ts +12 -0
- package/dist/procedures/snapshot/create.d.ts.map +1 -0
- package/dist/procedures/snapshot/create.js +279 -0
- package/dist/procedures/snapshot/create.js.map +1 -0
- package/dist/procedures/snapshot/delete.d.ts +12 -0
- package/dist/procedures/snapshot/delete.d.ts.map +1 -0
- package/dist/procedures/snapshot/delete.js +31 -0
- package/dist/procedures/snapshot/delete.js.map +1 -0
- package/dist/procedures/snapshot/diff.d.ts +12 -0
- package/dist/procedures/snapshot/diff.d.ts.map +1 -0
- package/dist/procedures/snapshot/diff.js +113 -0
- package/dist/procedures/snapshot/diff.js.map +1 -0
- package/dist/procedures/snapshot/index.d.ts +9 -0
- package/dist/procedures/snapshot/index.d.ts.map +1 -0
- package/dist/procedures/snapshot/index.js +9 -0
- package/dist/procedures/snapshot/index.js.map +1 -0
- package/dist/procedures/snapshot/list.d.ts +12 -0
- package/dist/procedures/snapshot/list.d.ts.map +1 -0
- package/dist/procedures/snapshot/list.js +57 -0
- package/dist/procedures/snapshot/list.js.map +1 -0
- package/dist/procedures/snapshot/names.d.ts +10 -0
- package/dist/procedures/snapshot/names.d.ts.map +1 -0
- package/dist/procedures/snapshot/names.js +20 -0
- package/dist/procedures/snapshot/names.js.map +1 -0
- package/dist/procedures/snapshot/restore.d.ts +12 -0
- package/dist/procedures/snapshot/restore.d.ts.map +1 -0
- package/dist/procedures/snapshot/restore.js +131 -0
- package/dist/procedures/snapshot/restore.js.map +1 -0
- package/dist/procedures/snapshot/s3-lookup.d.ts +39 -0
- package/dist/procedures/snapshot/s3-lookup.d.ts.map +1 -0
- package/dist/procedures/snapshot/s3-lookup.js +57 -0
- package/dist/procedures/snapshot/s3-lookup.js.map +1 -0
- package/dist/register.d.ts +8 -0
- package/dist/register.d.ts.map +1 -0
- package/dist/register.js +14 -0
- package/dist/register.js.map +1 -0
- package/dist/types.d.ts +193 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +168 -0
- package/dist/types.js.map +1 -0
- package/package.json +84 -3
- package/src/index.ts +52 -0
- package/src/module.ts +136 -0
- package/src/procedures/snapshot/archive-io.ts +31 -0
- package/src/procedures/snapshot/commands.ts +34 -0
- package/src/procedures/snapshot/create.ts +354 -0
- package/src/procedures/snapshot/delete.ts +40 -0
- package/src/procedures/snapshot/diff.ts +147 -0
- package/src/procedures/snapshot/index.ts +9 -0
- package/src/procedures/snapshot/list.ts +80 -0
- package/src/procedures/snapshot/names.ts +24 -0
- package/src/procedures/snapshot/restore.ts +166 -0
- package/src/procedures/snapshot/s3-lookup.ts +88 -0
- package/src/register.ts +17 -0
- package/src/types.ts +381 -0
package/README.md
CHANGED
|
@@ -1,3 +1,738 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @procedures/snapshot
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@procedures/snapshot)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
> Environment snapshot/restore procedures for testing and recovery
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
`client-snapshot` provides comprehensive procedures for capturing and restoring complete development environment snapshots. It supports multiple preset levels (light, medium, heavy) to balance snapshot size vs. completeness, integrates with S3 for cloud storage, and orchestrates multiple client packages for Git, filesystem, and package management operations.
|
|
11
|
+
|
|
12
|
+
### Key Features
|
|
13
|
+
|
|
14
|
+
- **Multi-Level Presets**: Light (Git only ~500MB), Medium (+ node_modules ~2GB), Heavy (+ pnpm store ~10GB+)
|
|
15
|
+
- **Cloud Storage**: S3 integration for remote snapshot storage and sharing
|
|
16
|
+
- **Complete State Capture**: Git repos, branches, stashes, working trees, dependencies, and more
|
|
17
|
+
- **Intelligent Diffing**: Compare current state against snapshots to track changes
|
|
18
|
+
- **Multipart Upload**: Handles large snapshots with efficient S3 multipart uploads
|
|
19
|
+
- **Rich Metadata**: Captures environment info, repository state, checksums for verification
|
|
20
|
+
- **Restore Safety**: Configurable overwrite protection and target path options
|
|
21
|
+
|
|
22
|
+
## Architecture
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
graph TB
|
|
26
|
+
subgraph "client-snapshot"
|
|
27
|
+
CREATE[snapshot.create]
|
|
28
|
+
RESTORE[snapshot.restore]
|
|
29
|
+
LIST[snapshot.list]
|
|
30
|
+
DIFF[snapshot.diff]
|
|
31
|
+
DELETE[snapshot.delete]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
subgraph "Integrated Services"
|
|
35
|
+
GIT[client-git<br/>Status, Stashes]
|
|
36
|
+
FS[client-fs<br/>File Operations]
|
|
37
|
+
S3[client-s3<br/>Upload/Download]
|
|
38
|
+
PNPM[client-pnpm<br/>Dependencies]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
subgraph "Storage"
|
|
42
|
+
LOCAL[Local Temp<br/>Tarball Creation]
|
|
43
|
+
CLOUD[S3 Bucket<br/>Archive + Metadata]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
CREATE --> GIT
|
|
47
|
+
CREATE --> FS
|
|
48
|
+
CREATE --> LOCAL
|
|
49
|
+
CREATE --> S3
|
|
50
|
+
LOCAL --> CLOUD
|
|
51
|
+
|
|
52
|
+
RESTORE --> S3
|
|
53
|
+
RESTORE --> FS
|
|
54
|
+
RESTORE --> PNPM
|
|
55
|
+
CLOUD --> RESTORE
|
|
56
|
+
|
|
57
|
+
LIST --> S3
|
|
58
|
+
DIFF --> GIT
|
|
59
|
+
DIFF --> S3
|
|
60
|
+
DELETE --> S3
|
|
61
|
+
|
|
62
|
+
style CREATE fill:#7ed321
|
|
63
|
+
style RESTORE fill:#f5a623
|
|
64
|
+
style LIST fill:#50e3c2
|
|
65
|
+
style DIFF fill:#bd10e0
|
|
66
|
+
style DELETE fill:#d0021b
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Snapshot Flow
|
|
70
|
+
|
|
71
|
+
```mermaid
|
|
72
|
+
sequenceDiagram
|
|
73
|
+
participant Client
|
|
74
|
+
participant snapshot.create
|
|
75
|
+
participant Git Repos
|
|
76
|
+
participant Local FS
|
|
77
|
+
participant S3
|
|
78
|
+
|
|
79
|
+
Client->>snapshot.create: { name, preset, bucket }
|
|
80
|
+
snapshot.create->>Git Repos: Get status, stashes, commits
|
|
81
|
+
Git Repos-->>snapshot.create: Repository info
|
|
82
|
+
snapshot.create->>Local FS: Create tarball based on preset
|
|
83
|
+
Local FS-->>snapshot.create: Compressed archive
|
|
84
|
+
snapshot.create->>snapshot.create: Calculate checksum
|
|
85
|
+
snapshot.create->>S3: Upload archive + metadata
|
|
86
|
+
S3-->>snapshot.create: S3 location
|
|
87
|
+
snapshot.create-->>Client: { id, location, metadata, duration }
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Installation
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install @procedures/snapshot
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or with pnpm:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pnpm add @procedures/snapshot
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Dependencies
|
|
103
|
+
|
|
104
|
+
This package depends on:
|
|
105
|
+
- `@procedures/client` - Core client/procedure system
|
|
106
|
+
- `@procedures/s3` - S3 storage operations
|
|
107
|
+
- `@procedures/git` - Git repository operations
|
|
108
|
+
- `@procedures/pnpm` - Package manager operations
|
|
109
|
+
- `@procedures/fs` - Filesystem operations
|
|
110
|
+
- `zod` - Schema validation
|
|
111
|
+
- `tar` - Archive creation and extraction
|
|
112
|
+
|
|
113
|
+
### External Requirements
|
|
114
|
+
|
|
115
|
+
- **AWS Credentials**: Configured for S3 access (via environment or AWS CLI)
|
|
116
|
+
- **Git**: For repository state capture
|
|
117
|
+
- **pnpm** (optional): For full dependency restoration
|
|
118
|
+
- **Node.js >= 22**: Required for all operations
|
|
119
|
+
|
|
120
|
+
## Usage
|
|
121
|
+
|
|
122
|
+
### Creating Snapshots
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { Client, LocalTransport, PROCEDURE_REGISTRY } from "@procedures/client";
|
|
126
|
+
import "@procedures/snapshot"; // registers its procedures
|
|
127
|
+
|
|
128
|
+
const client = new Client(new LocalTransport({ registry: PROCEDURE_REGISTRY }));
|
|
129
|
+
|
|
130
|
+
// Create a lightweight snapshot (Git repos only)
|
|
131
|
+
const result = await client.exec(["snapshot", "create"], {
|
|
132
|
+
name: "feature-auth-impl",
|
|
133
|
+
preset: "light",
|
|
134
|
+
bucket: "my-snapshots",
|
|
135
|
+
description: "Before refactoring authentication",
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
console.log(result);
|
|
139
|
+
// {
|
|
140
|
+
// id: "feature-auth-impl-1234567890-abc123",
|
|
141
|
+
// location: "s3://my-snapshots/snapshots/feature-auth-impl/...",
|
|
142
|
+
// metadata: {
|
|
143
|
+
// id: "feature-auth-impl-1234567890-abc123",
|
|
144
|
+
// name: "feature-auth-impl",
|
|
145
|
+
// preset: "light",
|
|
146
|
+
// createdAt: "2025-01-15T10:30:00.000Z",
|
|
147
|
+
// environment: {
|
|
148
|
+
// os: "linux",
|
|
149
|
+
// nodeVersion: "v20.0.0",
|
|
150
|
+
// pnpmVersion: "8.15.0",
|
|
151
|
+
// username: "developer",
|
|
152
|
+
// hostname: "dev-machine"
|
|
153
|
+
// },
|
|
154
|
+
// repositories: [
|
|
155
|
+
// {
|
|
156
|
+
// path: "/path/to/repo",
|
|
157
|
+
// name: "@my/package",
|
|
158
|
+
// branch: "feature/auth",
|
|
159
|
+
// commit: "abc123...",
|
|
160
|
+
// dirty: true,
|
|
161
|
+
// stashCount: 2,
|
|
162
|
+
// ahead: 3,
|
|
163
|
+
// behind: 0
|
|
164
|
+
// }
|
|
165
|
+
// ],
|
|
166
|
+
// checksum: "sha256:...",
|
|
167
|
+
// archiveSize: 524288000,
|
|
168
|
+
// description: "Before refactoring authentication"
|
|
169
|
+
// },
|
|
170
|
+
// uploadDuration: 15234
|
|
171
|
+
// }
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Snapshot Presets
|
|
175
|
+
|
|
176
|
+
Choose the right preset for your needs:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Light: ~500MB - Git repos only (fast, good for code snapshots)
|
|
180
|
+
await client.exec(["snapshot", "create"], {
|
|
181
|
+
name: "quick-checkpoint",
|
|
182
|
+
preset: "light",
|
|
183
|
+
bucket: "my-snapshots",
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Medium: ~2GB - Git + node_modules (good for reproducible builds)
|
|
187
|
+
await client.exec(["snapshot", "create"], {
|
|
188
|
+
name: "stable-state",
|
|
189
|
+
preset: "medium",
|
|
190
|
+
bucket: "my-snapshots",
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Heavy: ~10GB+ - Everything including pnpm store (full recovery)
|
|
194
|
+
await client.exec(["snapshot", "create"], {
|
|
195
|
+
name: "complete-backup",
|
|
196
|
+
preset: "heavy",
|
|
197
|
+
bucket: "my-snapshots",
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Listing Snapshots
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// List all snapshots
|
|
205
|
+
const list = await client.exec(["snapshot", "list"], {
|
|
206
|
+
bucket: "my-snapshots",
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
console.log(list);
|
|
210
|
+
// {
|
|
211
|
+
// snapshots: [
|
|
212
|
+
// {
|
|
213
|
+
// id: "feature-auth-impl-1234567890-abc123",
|
|
214
|
+
// name: "feature-auth-impl",
|
|
215
|
+
// preset: "light",
|
|
216
|
+
// createdAt: "2025-01-15T10:30:00.000Z",
|
|
217
|
+
// size: 524288000,
|
|
218
|
+
// os: "linux"
|
|
219
|
+
// },
|
|
220
|
+
// // ...
|
|
221
|
+
// ],
|
|
222
|
+
// count: 5
|
|
223
|
+
// }
|
|
224
|
+
|
|
225
|
+
// Filter by prefix
|
|
226
|
+
const filtered = await client.exec(["snapshot", "list"], {
|
|
227
|
+
bucket: "my-snapshots",
|
|
228
|
+
prefix: "feature-",
|
|
229
|
+
maxResults: 10,
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Restoring Snapshots
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
// Restore to original locations
|
|
237
|
+
const restore = await client.exec(["snapshot", "restore"], {
|
|
238
|
+
id: "feature-auth-impl-1234567890-abc123",
|
|
239
|
+
bucket: "my-snapshots",
|
|
240
|
+
overwrite: false, // Safety: don't overwrite existing files
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
console.log(restore);
|
|
244
|
+
// {
|
|
245
|
+
// success: true,
|
|
246
|
+
// metadata: { ... },
|
|
247
|
+
// restoredPaths: [
|
|
248
|
+
// "/path/to/repo",
|
|
249
|
+
// "/path/to/another-repo"
|
|
250
|
+
// ],
|
|
251
|
+
// downloadDuration: 8234,
|
|
252
|
+
// extractDuration: 3456
|
|
253
|
+
// }
|
|
254
|
+
|
|
255
|
+
// Restore to different location
|
|
256
|
+
await client.exec(["snapshot", "restore"], {
|
|
257
|
+
id: "feature-auth-impl-1234567890-abc123",
|
|
258
|
+
bucket: "my-snapshots",
|
|
259
|
+
targetPath: "/tmp/restored",
|
|
260
|
+
overwrite: true,
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Comparing Against Snapshots
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Diff current state vs snapshot
|
|
268
|
+
const diff = await client.exec(["snapshot", "diff"], {
|
|
269
|
+
id: "feature-auth-impl-1234567890-abc123",
|
|
270
|
+
bucket: "my-snapshots",
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
console.log(diff);
|
|
274
|
+
// {
|
|
275
|
+
// snapshotMetadata: { ... },
|
|
276
|
+
// repositories: [
|
|
277
|
+
// {
|
|
278
|
+
// path: "/path/to/repo",
|
|
279
|
+
// branchDiff: {
|
|
280
|
+
// current: "main",
|
|
281
|
+
// snapshot: "feature/auth"
|
|
282
|
+
// },
|
|
283
|
+
// commitDiff: {
|
|
284
|
+
// current: "xyz789...",
|
|
285
|
+
// snapshot: "abc123..."
|
|
286
|
+
// },
|
|
287
|
+
// filesChanged: 15,
|
|
288
|
+
// newStashes: 1
|
|
289
|
+
// }
|
|
290
|
+
// ],
|
|
291
|
+
// summary: {
|
|
292
|
+
// reposChanged: 1,
|
|
293
|
+
// totalFilesChanged: 15,
|
|
294
|
+
// isMatch: false
|
|
295
|
+
// }
|
|
296
|
+
// }
|
|
297
|
+
|
|
298
|
+
// Check specific paths only
|
|
299
|
+
const partialDiff = await client.exec(["snapshot", "diff"], {
|
|
300
|
+
id: "snapshot-id",
|
|
301
|
+
bucket: "my-snapshots",
|
|
302
|
+
paths: ["/path/to/specific/repo"],
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Deleting Snapshots
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
// Delete a snapshot
|
|
310
|
+
const deleted = await client.exec(["snapshot", "delete"], {
|
|
311
|
+
id: "old-snapshot-1234567890-xyz789",
|
|
312
|
+
bucket: "my-snapshots",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
console.log(deleted);
|
|
316
|
+
// {
|
|
317
|
+
// deleted: true,
|
|
318
|
+
// id: "old-snapshot-1234567890-xyz789"
|
|
319
|
+
// }
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## API Reference
|
|
323
|
+
|
|
324
|
+
### Procedures
|
|
325
|
+
|
|
326
|
+
#### `snapshot.create`
|
|
327
|
+
|
|
328
|
+
Create an environment snapshot and upload to S3.
|
|
329
|
+
|
|
330
|
+
**Input:**
|
|
331
|
+
```typescript
|
|
332
|
+
{
|
|
333
|
+
name: string; // Required: Snapshot name
|
|
334
|
+
preset?: SnapshotPreset; // Default: "medium" ("light" | "medium" | "heavy")
|
|
335
|
+
bucket: string; // Required: S3 bucket name
|
|
336
|
+
paths?: string[]; // Specific paths to include (default: all ecosystem repos)
|
|
337
|
+
description?: string; // Optional description
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**Output:**
|
|
342
|
+
```typescript
|
|
343
|
+
{
|
|
344
|
+
id: string; // Unique snapshot ID
|
|
345
|
+
location: string; // S3 location (s3://bucket/key)
|
|
346
|
+
metadata: SnapshotMetadata;
|
|
347
|
+
uploadDuration: number; // Milliseconds
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
#### `snapshot.restore`
|
|
352
|
+
|
|
353
|
+
Restore environment from S3 snapshot.
|
|
354
|
+
|
|
355
|
+
**Input:**
|
|
356
|
+
```typescript
|
|
357
|
+
{
|
|
358
|
+
id: string; // Required: Snapshot ID
|
|
359
|
+
bucket: string; // Required: S3 bucket name
|
|
360
|
+
targetPath: string; // Required: the folder to restore into
|
|
361
|
+
overwrite?: boolean; // Default: false - safety protection
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The archive is extracted into a staging folder first. Then each restored folder moves into `targetPath`. With `overwrite: true`, a restored folder replaces the existing folder of that name, so no stale file stays. Other folders of `targetPath` do not change.
|
|
366
|
+
|
|
367
|
+
**Output:**
|
|
368
|
+
```typescript
|
|
369
|
+
{
|
|
370
|
+
success: boolean;
|
|
371
|
+
metadata: SnapshotMetadata;
|
|
372
|
+
restoredPaths: string[]; // Paths that were restored
|
|
373
|
+
downloadDuration: number; // Milliseconds
|
|
374
|
+
extractDuration: number; // Milliseconds
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### `snapshot.list`
|
|
379
|
+
|
|
380
|
+
List available snapshots in S3 bucket.
|
|
381
|
+
|
|
382
|
+
**Input:**
|
|
383
|
+
```typescript
|
|
384
|
+
{
|
|
385
|
+
bucket: string; // Required: S3 bucket name
|
|
386
|
+
prefix?: string; // Filter by name prefix
|
|
387
|
+
maxResults?: number; // Default: 100
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
The procedure reads all pages of the listing and returns the newest `maxResults` snapshots.
|
|
392
|
+
|
|
393
|
+
**Output:**
|
|
394
|
+
```typescript
|
|
395
|
+
{
|
|
396
|
+
snapshots: SnapshotListEntry[];
|
|
397
|
+
count: number;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// SnapshotListEntry
|
|
401
|
+
{
|
|
402
|
+
id: string;
|
|
403
|
+
name: string;
|
|
404
|
+
preset: SnapshotPreset;
|
|
405
|
+
createdAt: string; // ISO timestamp
|
|
406
|
+
size: number; // Bytes
|
|
407
|
+
os: string; // Platform it was created on
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
#### `snapshot.diff`
|
|
412
|
+
|
|
413
|
+
Compare current state against a snapshot.
|
|
414
|
+
|
|
415
|
+
**Input:**
|
|
416
|
+
```typescript
|
|
417
|
+
{
|
|
418
|
+
id: string; // Required: Snapshot ID
|
|
419
|
+
bucket: string; // Required: S3 bucket name
|
|
420
|
+
paths?: string[]; // Compare specific paths only
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
**Output:**
|
|
425
|
+
```typescript
|
|
426
|
+
{
|
|
427
|
+
snapshotMetadata: SnapshotMetadata;
|
|
428
|
+
repositories: SnapshotDiffRepository[];
|
|
429
|
+
summary: {
|
|
430
|
+
reposChanged: number;
|
|
431
|
+
totalFilesChanged: number;
|
|
432
|
+
isMatch: boolean; // true if no differences
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// SnapshotDiffRepository
|
|
437
|
+
{
|
|
438
|
+
path: string;
|
|
439
|
+
branchDiff?: { current: string, snapshot: string };
|
|
440
|
+
commitDiff?: { current: string, snapshot: string };
|
|
441
|
+
filesChanged: number;
|
|
442
|
+
newStashes: number;
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
#### `snapshot.delete`
|
|
447
|
+
|
|
448
|
+
Delete a snapshot from S3.
|
|
449
|
+
|
|
450
|
+
**Input:**
|
|
451
|
+
```typescript
|
|
452
|
+
{
|
|
453
|
+
id: string; // Required: Snapshot ID
|
|
454
|
+
bucket: string; // Required: S3 bucket name
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**Output:**
|
|
459
|
+
```typescript
|
|
460
|
+
{
|
|
461
|
+
deleted: boolean;
|
|
462
|
+
id: string;
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
## Types
|
|
467
|
+
|
|
468
|
+
### SnapshotMetadata
|
|
469
|
+
|
|
470
|
+
Complete metadata stored with each snapshot:
|
|
471
|
+
|
|
472
|
+
```typescript
|
|
473
|
+
interface SnapshotMetadata {
|
|
474
|
+
id: string;
|
|
475
|
+
name: string;
|
|
476
|
+
preset: SnapshotPreset;
|
|
477
|
+
createdAt: string; // ISO timestamp
|
|
478
|
+
environment: {
|
|
479
|
+
os: string; // win32, darwin, linux
|
|
480
|
+
nodeVersion: string;
|
|
481
|
+
pnpmVersion: string;
|
|
482
|
+
username: string;
|
|
483
|
+
hostname: string;
|
|
484
|
+
};
|
|
485
|
+
repositories: RepositoryInfo[];
|
|
486
|
+
checksum: string; // SHA-256
|
|
487
|
+
archiveSize: number; // Bytes
|
|
488
|
+
description?: string;
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### RepositoryInfo
|
|
493
|
+
|
|
494
|
+
Per-repository state information:
|
|
495
|
+
|
|
496
|
+
```typescript
|
|
497
|
+
interface RepositoryInfo {
|
|
498
|
+
path: string; // Relative to snapshot root
|
|
499
|
+
name: string; // Package name
|
|
500
|
+
branch: string; // Current branch
|
|
501
|
+
commit: string; // HEAD commit hash
|
|
502
|
+
dirty: boolean; // Working tree has changes
|
|
503
|
+
stashCount: number; // Number of stashes
|
|
504
|
+
remoteUrl?: string; // Remote origin URL
|
|
505
|
+
ahead: number; // Commits ahead of remote
|
|
506
|
+
behind: number; // Commits behind remote
|
|
507
|
+
}
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
### SnapshotPreset
|
|
511
|
+
|
|
512
|
+
```typescript
|
|
513
|
+
type SnapshotPreset = "light" | "medium" | "heavy";
|
|
514
|
+
|
|
515
|
+
// Preset Details:
|
|
516
|
+
// - light: Git repos only (.git, working tree, branches, stashes) ~500MB
|
|
517
|
+
// - medium: + node_modules + pnpm lockfiles ~2GB
|
|
518
|
+
// - heavy: + pnpm store + global installs + configs ~10GB+
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
## Use Cases
|
|
522
|
+
|
|
523
|
+
### 1. Feature Branch Snapshots
|
|
524
|
+
|
|
525
|
+
```typescript
|
|
526
|
+
// Before starting risky refactor
|
|
527
|
+
await client.exec(["snapshot", "create"], {
|
|
528
|
+
name: `before-refactor-${Date.now()}`,
|
|
529
|
+
preset: "medium",
|
|
530
|
+
bucket: "team-snapshots",
|
|
531
|
+
description: "Safe state before authentication refactor",
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
// After refactor, compare
|
|
535
|
+
const diff = await client.exec(["snapshot", "diff"], {
|
|
536
|
+
id: "snapshot-id",
|
|
537
|
+
bucket: "team-snapshots",
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
if (diff.summary.totalFilesChanged > 100) {
|
|
541
|
+
console.warn("Large changes detected!");
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
### 2. CI/CD Reproducible Builds
|
|
546
|
+
|
|
547
|
+
```typescript
|
|
548
|
+
// In CI pipeline - capture exact build state
|
|
549
|
+
await client.exec(["snapshot", "create"], {
|
|
550
|
+
name: `build-${process.env.CI_COMMIT_SHA}`,
|
|
551
|
+
preset: "medium",
|
|
552
|
+
bucket: "ci-snapshots",
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// Later - restore exact build environment
|
|
556
|
+
await client.exec(["snapshot", "restore"], {
|
|
557
|
+
id: `build-${commitSha}`,
|
|
558
|
+
bucket: "ci-snapshots",
|
|
559
|
+
targetPath: "/tmp/build",
|
|
560
|
+
});
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### 3. Team Environment Sharing
|
|
564
|
+
|
|
565
|
+
```typescript
|
|
566
|
+
// Developer creates snapshot
|
|
567
|
+
await client.exec(["snapshot", "create"], {
|
|
568
|
+
name: "onboarding-2025-01",
|
|
569
|
+
preset: "heavy",
|
|
570
|
+
bucket: "team-snapshots",
|
|
571
|
+
description: "Complete setup for new team members",
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
// New team member restores
|
|
575
|
+
await client.exec(["snapshot", "restore"], {
|
|
576
|
+
id: "onboarding-2025-01-...",
|
|
577
|
+
bucket: "team-snapshots",
|
|
578
|
+
targetPath: "/home/newdev/workspace",
|
|
579
|
+
});
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
### 4. Disaster Recovery
|
|
583
|
+
|
|
584
|
+
```typescript
|
|
585
|
+
// Daily backup
|
|
586
|
+
await client.exec(["snapshot", "create"], {
|
|
587
|
+
name: `daily-${new Date().toISOString().split('T')[0]}`,
|
|
588
|
+
preset: "heavy",
|
|
589
|
+
bucket: "backups",
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// After disaster - full restore
|
|
593
|
+
await client.exec(["snapshot", "restore"], {
|
|
594
|
+
id: "daily-2025-01-14-...",
|
|
595
|
+
bucket: "backups",
|
|
596
|
+
overwrite: true,
|
|
597
|
+
});
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
## Advanced Usage
|
|
601
|
+
|
|
602
|
+
### Custom Snapshot Paths
|
|
603
|
+
|
|
604
|
+
```typescript
|
|
605
|
+
// Snapshot specific repositories only
|
|
606
|
+
await client.exec(["snapshot", "create"], {
|
|
607
|
+
name: "critical-services",
|
|
608
|
+
preset: "medium",
|
|
609
|
+
bucket: "my-snapshots",
|
|
610
|
+
paths: [
|
|
611
|
+
"/workspace/api-gateway",
|
|
612
|
+
"/workspace/auth-service",
|
|
613
|
+
"/workspace/payment-service",
|
|
614
|
+
],
|
|
615
|
+
});
|
|
616
|
+
```
|
|
617
|
+
|
|
618
|
+
### Snapshot Rotation
|
|
619
|
+
|
|
620
|
+
```typescript
|
|
621
|
+
// List and delete old snapshots
|
|
622
|
+
const list = await client.exec(["snapshot", "list"], {
|
|
623
|
+
bucket: "my-snapshots",
|
|
624
|
+
prefix: "daily-",
|
|
625
|
+
maxResults: 100,
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Keep last 7, delete older
|
|
629
|
+
const toDelete = list.snapshots
|
|
630
|
+
.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())
|
|
631
|
+
.slice(7);
|
|
632
|
+
|
|
633
|
+
for (const snapshot of toDelete) {
|
|
634
|
+
await client.exec(["snapshot", "delete"], {
|
|
635
|
+
id: snapshot.id,
|
|
636
|
+
bucket: "my-snapshots",
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
```
|
|
640
|
+
|
|
641
|
+
### Verification Before Restore
|
|
642
|
+
|
|
643
|
+
```typescript
|
|
644
|
+
// Check what would change before restore
|
|
645
|
+
const diff = await client.exec(["snapshot", "diff"], {
|
|
646
|
+
id: "snapshot-id",
|
|
647
|
+
bucket: "my-snapshots",
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
if (!diff.summary.isMatch) {
|
|
651
|
+
console.log(`Will modify ${diff.summary.reposChanged} repositories`);
|
|
652
|
+
console.log(`${diff.summary.totalFilesChanged} files will change`);
|
|
653
|
+
|
|
654
|
+
// Prompt user for confirmation
|
|
655
|
+
const confirmed = await askUserConfirmation();
|
|
656
|
+
|
|
657
|
+
if (confirmed) {
|
|
658
|
+
await client.exec(["snapshot", "restore"], {
|
|
659
|
+
id: "snapshot-id",
|
|
660
|
+
bucket: "my-snapshots",
|
|
661
|
+
overwrite: true,
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
## Performance Considerations
|
|
668
|
+
|
|
669
|
+
- **Preset Selection**: Use lightest preset that meets your needs
|
|
670
|
+
- `light` for code-only snapshots (fast, small)
|
|
671
|
+
- `medium` for reproducible builds
|
|
672
|
+
- `heavy` for complete disaster recovery
|
|
673
|
+
- **Multipart Upload**: Automatically used for files > 5MB
|
|
674
|
+
- **Compression**: Gzip level varies by preset (9 for light/medium, 6 for heavy)
|
|
675
|
+
- **Parallel Operations**: Repository info gathering runs in parallel
|
|
676
|
+
- **S3 Regions**: Use bucket in same region as compute for faster transfers
|
|
677
|
+
|
|
678
|
+
## Error Handling
|
|
679
|
+
|
|
680
|
+
```typescript
|
|
681
|
+
try {
|
|
682
|
+
await client.exec(["snapshot", "create"], {
|
|
683
|
+
name: "my-snapshot",
|
|
684
|
+
preset: "medium",
|
|
685
|
+
bucket: "my-snapshots",
|
|
686
|
+
});
|
|
687
|
+
} catch (error) {
|
|
688
|
+
if (error.message.includes("NoSuchBucket")) {
|
|
689
|
+
console.error("S3 bucket does not exist");
|
|
690
|
+
} else if (error.message.includes("AccessDenied")) {
|
|
691
|
+
console.error("Insufficient S3 permissions");
|
|
692
|
+
} else if (error.message.includes("Not a git repository")) {
|
|
693
|
+
console.error("Path is not a git repository");
|
|
694
|
+
} else {
|
|
695
|
+
console.error("Snapshot failed:", error);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
## Integration with Ecosystem
|
|
701
|
+
|
|
702
|
+
### With bundle-dev
|
|
703
|
+
|
|
704
|
+
```typescript
|
|
705
|
+
import "@procedures/bundle-dev"; // registers its procedures
|
|
706
|
+
|
|
707
|
+
// snapshot.* procedures available
|
|
708
|
+
await client.exec(["snapshot", "create"], { ... });
|
|
709
|
+
```
|
|
710
|
+
|
|
711
|
+
### With client-s3
|
|
712
|
+
|
|
713
|
+
`client-snapshot` uses `client-s3` for all S3 operations:
|
|
714
|
+
- Multipart uploads for large archives
|
|
715
|
+
- Metadata storage as separate JSON objects
|
|
716
|
+
- Bucket operations and listing
|
|
717
|
+
|
|
718
|
+
### With client-git
|
|
719
|
+
|
|
720
|
+
Captures comprehensive Git state:
|
|
721
|
+
- Current branch and commit
|
|
722
|
+
- Working tree status (dirty/clean)
|
|
723
|
+
- Stash list and counts
|
|
724
|
+
- Remote tracking information
|
|
725
|
+
- Ahead/behind commit counts
|
|
726
|
+
|
|
727
|
+
## License
|
|
728
|
+
|
|
729
|
+
MIT
|
|
730
|
+
|
|
731
|
+
## Related Packages
|
|
732
|
+
|
|
733
|
+
- `@procedures/client` - Core client/procedure system
|
|
734
|
+
- `@procedures/s3` - S3 storage operations
|
|
735
|
+
- `@procedures/git` - Git repository operations
|
|
736
|
+
- `@procedures/pnpm` - Package manager operations
|
|
737
|
+
- `@procedures/fs` - Filesystem operations
|
|
738
|
+
- `@procedures/bundle-dev` - Development bundle
|