backupman 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.
Files changed (3) hide show
  1. package/README.md +46 -0
  2. package/index.js +210 -0
  3. package/package.json +22 -0
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # backupman
2
+
3
+ Ultra-simple local snapshot tool for when your code (or your AI) goes off the rails.
4
+
5
+ You run it inside your source folder (for example `src` or your project root), hit `save` when things look okay, and later you can `restore` any snapshot from an interactive list.
6
+
7
+ No branches, no remotes, no staging. Just: “this state feels safe → save”.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install -g backupman
13
+ ```
14
+
15
+ Or inside a project:
16
+
17
+ ```bash
18
+ npm install backupman
19
+ npm link
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ From the folder you want to protect (for example your project root or `src`):
25
+
26
+ ```bash
27
+ backupman save "before refactor"
28
+ backupman save "after AI edit"
29
+
30
+ backupman restore
31
+ ```
32
+
33
+ - `backupman save "message"`
34
+ - Create a full snapshot of the current directory with a short message.
35
+
36
+ - `backupman restore`
37
+ - Show an interactive list of snapshots (newest first), auto-back up the current state, then restore the chosen snapshot.
38
+
39
+ ## Notes
40
+
41
+ - Snapshots are stored under `.backupman/snapshots` in the same folder.
42
+ - On `save`, backupman copies all files and subfolders from the current directory.
43
+ - It ignores:
44
+ - `.backupman`
45
+ - `node_modules`
46
+ - `.git`
package/index.js ADDED
@@ -0,0 +1,210 @@
1
+ #!/usr/bin/env node
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const readline = require("readline");
5
+
6
+ class BackupMan {
7
+ constructor(rootDir) {
8
+ this.rootDir = rootDir;
9
+ this.backupDir = path.join(rootDir, ".backupman");
10
+ this.snapshotsDir = path.join(this.backupDir, "snapshots");
11
+ this.indexFile = path.join(this.backupDir, "index.json");
12
+ }
13
+
14
+ async ensureDirs() {
15
+ await fs.promises.mkdir(this.backupDir, { recursive: true });
16
+ await fs.promises.mkdir(this.snapshotsDir, { recursive: true });
17
+ }
18
+
19
+ async loadIndex() {
20
+ try {
21
+ const data = await fs.promises.readFile(this.indexFile, "utf8");
22
+ const parsed = JSON.parse(data);
23
+ if (!parsed.snapshots || typeof parsed.lastId !== "number") {
24
+ throw new Error("Corrupted index");
25
+ }
26
+ return parsed;
27
+ } catch (err) {
28
+ return { lastId: 0, snapshots: [] };
29
+ }
30
+ }
31
+
32
+ async saveIndex(index) {
33
+ const json = JSON.stringify(index, null, 2);
34
+ await fs.promises.writeFile(this.indexFile, json, "utf8");
35
+ }
36
+
37
+ isIgnored(name) {
38
+ return name === ".backupman" || name === "node_modules" || name === ".git";
39
+ }
40
+
41
+ async copyDir(src, dest) {
42
+ await fs.promises.mkdir(dest, { recursive: true });
43
+ const entries = await fs.promises.readdir(src, { withFileTypes: true });
44
+ for (const entry of entries) {
45
+ const name = entry.name;
46
+ if (this.isIgnored(name)) continue;
47
+ const srcPath = path.join(src, name);
48
+ const destPath = path.join(dest, name);
49
+ if (entry.isDirectory()) {
50
+ await this.copyDir(srcPath, destPath);
51
+ } else if (entry.isFile()) {
52
+ await fs.promises.copyFile(srcPath, destPath);
53
+ }
54
+ }
55
+ }
56
+
57
+ async deleteEverythingExceptBackupDir() {
58
+ const entries = await fs.promises.readdir(this.rootDir, { withFileTypes: true });
59
+ for (const entry of entries) {
60
+ const name = entry.name;
61
+ if (name === ".backupman") continue;
62
+ const target = path.join(this.rootDir, name);
63
+ await fs.promises.rm(target, { recursive: true, force: true });
64
+ }
65
+ }
66
+
67
+ async prompt(question) {
68
+ const rl = readline.createInterface({
69
+ input: process.stdin,
70
+ output: process.stdout
71
+ });
72
+ return new Promise(resolve => {
73
+ rl.question(question, answer => {
74
+ rl.close();
75
+ resolve(answer);
76
+ });
77
+ });
78
+ }
79
+
80
+ async createSnapshot(message) {
81
+ await this.ensureDirs();
82
+ const index = await this.loadIndex();
83
+ const id = index.lastId + 1;
84
+ const snapshotDir = path.join(this.snapshotsDir, String(id));
85
+ const now = new Date().toISOString();
86
+
87
+ await this.copyDir(this.rootDir, snapshotDir);
88
+
89
+ index.lastId = id;
90
+ index.snapshots.push({
91
+ id,
92
+ createdAt: now,
93
+ message
94
+ });
95
+
96
+ await this.saveIndex(index);
97
+ return id;
98
+ }
99
+
100
+ formatSnapshot(snapshot) {
101
+ const ts = snapshot.createdAt || "";
102
+ const msg = snapshot.message || "";
103
+ return `#${snapshot.id} [${ts}] ${msg}`;
104
+ }
105
+
106
+ async chooseSnapshot(index) {
107
+ if (!index.snapshots.length) {
108
+ console.log("No snapshots yet. Use `backupman save \"message\"` first.");
109
+ return null;
110
+ }
111
+
112
+ const list = [...index.snapshots].sort((a, b) => b.id - a.id);
113
+ console.log("");
114
+ console.log("Available snapshots (newest first):");
115
+ list.forEach((s, i) => {
116
+ console.log(` [${i + 1}] ${this.formatSnapshot(s)}`);
117
+ });
118
+ console.log("");
119
+
120
+ while (true) {
121
+ const answer = (await this.prompt("Select snapshot number to restore (empty = cancel): ")).trim();
122
+ if (!answer) {
123
+ return null;
124
+ }
125
+ const n = Number.parseInt(answer, 10);
126
+ if (!Number.isNaN(n) && n >= 1 && n <= list.length) {
127
+ return list[n - 1];
128
+ }
129
+ console.log("Invalid choice. Please enter a valid number or press Enter to cancel.");
130
+ }
131
+ }
132
+
133
+ async restore() {
134
+ await this.ensureDirs();
135
+ const index = await this.loadIndex();
136
+ const snapshot = await this.chooseSnapshot(index);
137
+ if (!snapshot) {
138
+ console.log("Restore cancelled.");
139
+ return;
140
+ }
141
+
142
+ console.log("");
143
+ console.log(`You chose snapshot: ${this.formatSnapshot(snapshot)}`);
144
+ const confirm = (await this.prompt("Overwrite current files with this snapshot? (y/N): ")).trim().toLowerCase();
145
+ if (confirm !== "y" && confirm !== "yes") {
146
+ console.log("Restore aborted.");
147
+ return;
148
+ }
149
+
150
+ console.log("Clearing current directory (except .backupman)...");
151
+ await this.deleteEverythingExceptBackupDir();
152
+
153
+ console.log("Restoring snapshot files...");
154
+ const srcSnapshotDir = path.join(this.snapshotsDir, String(snapshot.id));
155
+ await this.copyDir(srcSnapshotDir, this.rootDir);
156
+
157
+ console.log(`Done. Restored snapshot #${snapshot.id}.`);
158
+ }
159
+ }
160
+
161
+ function printUsage() {
162
+ console.log("backupman - ultra-simple local snapshot versioning");
163
+ console.log("");
164
+ console.log("Usage:");
165
+ console.log(" backupman save \"message\" Save current directory as a snapshot");
166
+ console.log(" backupman restore Restore from a previous snapshot");
167
+ console.log("");
168
+ console.log("Notes:");
169
+ console.log(" - Snapshots are stored in .backupman/snapshots");
170
+ console.log(" - .backupman, node_modules, .git are ignored");
171
+ }
172
+
173
+ async function main() {
174
+ const cwd = process.cwd();
175
+ const manager = new BackupMan(cwd);
176
+ const [, , command, ...args] = process.argv;
177
+
178
+ if (!command || command === "help" || command === "--help" || command === "-h") {
179
+ printUsage();
180
+ return;
181
+ }
182
+
183
+ if (command === "save") {
184
+ let message = args.join(" ").trim();
185
+ if (!message) {
186
+ message = await manager.prompt("Describe this snapshot (required): ");
187
+ if (!message || !message.trim()) {
188
+ console.error("Snapshot message is required.");
189
+ process.exit(1);
190
+ }
191
+ }
192
+ const id = await manager.createSnapshot(message.trim());
193
+ console.log(`Saved snapshot #${id}.`);
194
+ return;
195
+ }
196
+
197
+ if (command === "restore") {
198
+ await manager.restore();
199
+ return;
200
+ }
201
+
202
+ console.error(`Unknown command: ${command}`);
203
+ printUsage();
204
+ process.exit(1);
205
+ }
206
+
207
+ main().catch(err => {
208
+ console.error("Error:", err && err.message ? err.message : err);
209
+ process.exit(1);
210
+ });
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "backupman",
3
+ "version": "0.1.0",
4
+ "description": "Ultra-simple local snapshot versioning tool for chaotic devs and mischievous AIs.",
5
+ "bin": {
6
+ "backupman": "index.js"
7
+ },
8
+ "main": "index.js",
9
+ "scripts": {
10
+ "start": "node index.js"
11
+ },
12
+ "keywords": [
13
+ "backup",
14
+ "snapshot",
15
+ "versioning",
16
+ "cli",
17
+ "local"
18
+ ],
19
+ "author": "",
20
+ "license": "MIT",
21
+ "dependencies": {}
22
+ }