@procedures/snapshot 0.0.0-stage → 0.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 (75) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/LICENSE +21 -0
  3. package/README.md +737 -2
  4. package/dist/.tsbuildinfo +1 -0
  5. package/dist/index.d.ts +16 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +17 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/module.d.ts +21 -0
  10. package/dist/module.d.ts.map +1 -0
  11. package/dist/module.js +117 -0
  12. package/dist/module.js.map +1 -0
  13. package/dist/procedures/snapshot/archive-io.d.ts +12 -0
  14. package/dist/procedures/snapshot/archive-io.d.ts.map +1 -0
  15. package/dist/procedures/snapshot/archive-io.js +29 -0
  16. package/dist/procedures/snapshot/archive-io.js.map +1 -0
  17. package/dist/procedures/snapshot/commands.d.ts +15 -0
  18. package/dist/procedures/snapshot/commands.d.ts.map +1 -0
  19. package/dist/procedures/snapshot/commands.js +32 -0
  20. package/dist/procedures/snapshot/commands.js.map +1 -0
  21. package/dist/procedures/snapshot/create.d.ts +12 -0
  22. package/dist/procedures/snapshot/create.d.ts.map +1 -0
  23. package/dist/procedures/snapshot/create.js +279 -0
  24. package/dist/procedures/snapshot/create.js.map +1 -0
  25. package/dist/procedures/snapshot/delete.d.ts +12 -0
  26. package/dist/procedures/snapshot/delete.d.ts.map +1 -0
  27. package/dist/procedures/snapshot/delete.js +31 -0
  28. package/dist/procedures/snapshot/delete.js.map +1 -0
  29. package/dist/procedures/snapshot/diff.d.ts +12 -0
  30. package/dist/procedures/snapshot/diff.d.ts.map +1 -0
  31. package/dist/procedures/snapshot/diff.js +113 -0
  32. package/dist/procedures/snapshot/diff.js.map +1 -0
  33. package/dist/procedures/snapshot/index.d.ts +9 -0
  34. package/dist/procedures/snapshot/index.d.ts.map +1 -0
  35. package/dist/procedures/snapshot/index.js +9 -0
  36. package/dist/procedures/snapshot/index.js.map +1 -0
  37. package/dist/procedures/snapshot/list.d.ts +12 -0
  38. package/dist/procedures/snapshot/list.d.ts.map +1 -0
  39. package/dist/procedures/snapshot/list.js +57 -0
  40. package/dist/procedures/snapshot/list.js.map +1 -0
  41. package/dist/procedures/snapshot/names.d.ts +10 -0
  42. package/dist/procedures/snapshot/names.d.ts.map +1 -0
  43. package/dist/procedures/snapshot/names.js +20 -0
  44. package/dist/procedures/snapshot/names.js.map +1 -0
  45. package/dist/procedures/snapshot/restore.d.ts +12 -0
  46. package/dist/procedures/snapshot/restore.d.ts.map +1 -0
  47. package/dist/procedures/snapshot/restore.js +131 -0
  48. package/dist/procedures/snapshot/restore.js.map +1 -0
  49. package/dist/procedures/snapshot/s3-lookup.d.ts +39 -0
  50. package/dist/procedures/snapshot/s3-lookup.d.ts.map +1 -0
  51. package/dist/procedures/snapshot/s3-lookup.js +57 -0
  52. package/dist/procedures/snapshot/s3-lookup.js.map +1 -0
  53. package/dist/register.d.ts +7 -0
  54. package/dist/register.d.ts.map +1 -0
  55. package/dist/register.js +9 -0
  56. package/dist/register.js.map +1 -0
  57. package/dist/types.d.ts +193 -0
  58. package/dist/types.d.ts.map +1 -0
  59. package/dist/types.js +168 -0
  60. package/dist/types.js.map +1 -0
  61. package/package.json +87 -3
  62. package/src/index.ts +51 -0
  63. package/src/module.ts +155 -0
  64. package/src/procedures/snapshot/archive-io.ts +31 -0
  65. package/src/procedures/snapshot/commands.ts +34 -0
  66. package/src/procedures/snapshot/create.ts +354 -0
  67. package/src/procedures/snapshot/delete.ts +40 -0
  68. package/src/procedures/snapshot/diff.ts +147 -0
  69. package/src/procedures/snapshot/index.ts +9 -0
  70. package/src/procedures/snapshot/list.ts +80 -0
  71. package/src/procedures/snapshot/names.ts +24 -0
  72. package/src/procedures/snapshot/restore.ts +166 -0
  73. package/src/procedures/snapshot/s3-lookup.ts +88 -0
  74. package/src/register.ts +11 -0
  75. package/src/types.ts +381 -0
package/README.md CHANGED
@@ -1,3 +1,738 @@
1
- # Temporary Holding Version
1
+ # @procedures/snapshot
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [![npm version](https://img.shields.io/npm/v/@procedures/snapshot.svg)](https://www.npmjs.com/package/@procedures/snapshot)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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/register"; // 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/register"; // 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