@mahe_pkm/buzl-capi 0.1.2 → 0.1.3

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.
@@ -1,281 +1,281 @@
1
- /**
2
- * ============================================================================
3
- * BUZL BACKUP & SNAPSHOT MANAGER
4
- * Provides 100% safe file snapshots with SHA content hashing inside `.buzl/snapshots/`
5
- *
6
- * Copyright (c) 2026 Buzl Digital Solutions
7
- * Licensed under the MIT License
8
- * ============================================================================
9
- */
10
-
11
- const fs = require('fs');
12
- const path = require('path');
13
- const crypto = require('crypto');
14
-
15
- /**
16
- * Compute a deterministic SHA-1 content hash across all candidate files
17
- */
18
- function computeFilesHash(files, rootDir) {
19
- const hashSum = crypto.createHash('sha1');
20
- for (const filePath of files) {
21
- if (fs.existsSync(filePath)) {
22
- hashSum.update(path.relative(rootDir, filePath));
23
- try {
24
- hashSum.update(fs.readFileSync(filePath));
25
- } catch (e) {}
26
- }
27
- }
28
- return hashSum.digest('hex').substring(0, 8);
29
- }
30
-
31
- /**
32
- * Create a point-in-time snapshot backup
33
- * Stored in `<rootDir>/.buzl/snapshots/<timestamp>_<hash>/`
34
- */
35
- function createBackup(arg1, arg2, arg3 = '') {
36
- let files;
37
- let rootDir;
38
- let backupName = '';
39
-
40
- if (Array.isArray(arg1)) {
41
- files = arg1;
42
- rootDir = arg2;
43
- backupName = arg3 || '';
44
- } else if (typeof arg1 === 'string') {
45
- rootDir = arg1;
46
- backupName = typeof arg2 === 'string' ? arg2 : '';
47
- const { findHtmlFiles } = require('./scanner');
48
- files = findHtmlFiles(rootDir);
49
- } else {
50
- files = [];
51
- rootDir = process.cwd();
52
- }
53
-
54
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
55
- const hash = computeFilesHash(files, rootDir) || '00000000';
56
- const dirName = `${timestamp}_${hash}`;
57
-
58
- // Dedicated unified .buzl/snapshots directory
59
- const snapshotsDir = path.join(rootDir, '.buzl', 'snapshots');
60
- const backupDir = path.join(snapshotsDir, dirName);
61
- fs.mkdirSync(backupDir, { recursive: true });
62
-
63
- const manifest = {
64
- id: dirName,
65
- hash: hash,
66
- name: (backupName || '').trim() || `Snapshot ${new Date().toLocaleTimeString()} [${hash}]`,
67
- timestamp: new Date().toISOString(),
68
- files: []
69
- };
70
-
71
- for (const filePath of files) {
72
- const relPath = path.relative(rootDir, filePath);
73
- const destPath = path.join(backupDir, relPath);
74
- const destDir = path.dirname(destPath);
75
- fs.mkdirSync(destDir, { recursive: true });
76
- fs.copyFileSync(filePath, destPath);
77
- manifest.files.push(relPath);
78
- }
79
-
80
- fs.writeFileSync(
81
- path.join(backupDir, 'backup-manifest.json'),
82
- JSON.stringify(manifest, null, 2),
83
- 'utf8'
84
- );
85
-
86
- return {
87
- success: true,
88
- backupDir,
89
- dirName,
90
- hash,
91
- timestamp: manifest.timestamp,
92
- name: manifest.name,
93
- fileCount: manifest.files.length
94
- };
95
- }
96
-
97
- /**
98
- * List all available snapshot backups
99
- * Checks `.buzl/snapshots/` first, plus legacy `.buzl-backup-*` folders
100
- */
101
- function listBackups(rootDir) {
102
- const allBackups = [];
103
-
104
- // 1. Primary: Check .buzl/snapshots/
105
- const snapshotsDir = path.join(rootDir, '.buzl', 'snapshots');
106
- if (fs.existsSync(snapshotsDir)) {
107
- try {
108
- const entries = fs.readdirSync(snapshotsDir);
109
- for (const e of entries) {
110
- const dirPath = path.join(snapshotsDir, e);
111
- if (fs.statSync(dirPath).isDirectory()) {
112
- let manifest = null;
113
- try {
114
- const mPath = path.join(dirPath, 'backup-manifest.json');
115
- if (fs.existsSync(mPath)) {
116
- manifest = JSON.parse(fs.readFileSync(mPath, 'utf8'));
117
- }
118
- } catch (err) {}
119
-
120
- allBackups.push({
121
- id: (manifest && manifest.id) || e,
122
- dirName: e,
123
- hash: (manifest && manifest.hash) || (e.split('_')[1] || ''),
124
- fullPath: dirPath,
125
- name: (manifest && manifest.name) || e,
126
- timestamp: (manifest && manifest.timestamp) || '',
127
- filesCount: (manifest && manifest.files && manifest.files.length) || 0,
128
- isLegacy: false,
129
- manifest
130
- });
131
- }
132
- }
133
- } catch (e) {}
134
- }
135
-
136
- // 2. Fallback: Check legacy root-level `.buzl-backup-*` directories
137
- try {
138
- const rootEntries = fs.readdirSync(rootDir);
139
- for (const e of rootEntries) {
140
- if (e.startsWith('.buzl-backup-')) {
141
- const dirPath = path.join(rootDir, e);
142
- if (fs.statSync(dirPath).isDirectory()) {
143
- let manifest = null;
144
- try {
145
- const mPath = path.join(dirPath, 'backup-manifest.json');
146
- if (fs.existsSync(mPath)) {
147
- manifest = JSON.parse(fs.readFileSync(mPath, 'utf8'));
148
- }
149
- } catch (err) {}
150
-
151
- allBackups.push({
152
- id: e,
153
- dirName: e,
154
- hash: (manifest && manifest.hash) || '',
155
- fullPath: dirPath,
156
- name: (manifest && manifest.name) || e.replace('.buzl-backup-', ''),
157
- timestamp: (manifest && manifest.timestamp) || '',
158
- filesCount: (manifest && manifest.files && manifest.files.length) || 0,
159
- isLegacy: true,
160
- manifest
161
- });
162
- }
163
- }
164
- }
165
- } catch (e) {}
166
-
167
- // Sort newest first
168
- allBackups.sort((a, b) => b.dirName.localeCompare(a.dirName));
169
- return allBackups;
170
- }
171
-
172
- /**
173
- * Restore a specific snapshot or 'latest'
174
- */
175
- function restoreBackup(rootDir, identifier) {
176
- const backups = listBackups(rootDir);
177
- if (backups.length === 0) {
178
- return { success: false, message: 'No backups found to restore.' };
179
- }
180
-
181
- let targetBackup = backups[0];
182
- if (identifier && identifier !== 'latest') {
183
- targetBackup = backups.find(b =>
184
- b.dirName === identifier ||
185
- b.name === identifier ||
186
- b.hash === identifier ||
187
- b.id === identifier
188
- );
189
- if (!targetBackup) {
190
- return { success: false, message: `Backup "${identifier}" not found.` };
191
- }
192
- }
193
-
194
- const manifest = targetBackup.manifest;
195
- if (!manifest || !manifest.files) {
196
- return { success: false, message: 'Backup manifest corrupted or missing.' };
197
- }
198
-
199
- let restoredCount = 0;
200
- for (const relFile of manifest.files) {
201
- const src = path.join(targetBackup.fullPath, relFile);
202
- const dest = path.join(rootDir, relFile);
203
- if (fs.existsSync(src)) {
204
- const destDir = path.dirname(dest);
205
- fs.mkdirSync(destDir, { recursive: true });
206
- fs.copyFileSync(src, dest);
207
- restoredCount++;
208
- }
209
- }
210
-
211
- return {
212
- success: true,
213
- backupUsed: targetBackup.dirName,
214
- backupName: targetBackup.name,
215
- hash: targetBackup.hash,
216
- restoredCount,
217
- message: `Successfully restored ${restoredCount} file(s) from "${targetBackup.name}" [${targetBackup.hash || targetBackup.dirName}].`
218
- };
219
- }
220
-
221
- /**
222
- * Restore the latest snapshot
223
- */
224
- function restoreLatestBackup(rootDir) {
225
- return restoreBackup(rootDir, 'latest');
226
- }
227
-
228
- /**
229
- * Delete a specific backup
230
- */
231
- function deleteBackup(rootDir, identifier) {
232
- const backups = listBackups(rootDir);
233
- const target = backups.find(b =>
234
- b.dirName === identifier ||
235
- b.name === identifier ||
236
- b.id === identifier ||
237
- b.hash === identifier
238
- );
239
-
240
- if (!target) {
241
- return { success: false, message: `Backup "${identifier}" not found.` };
242
- }
243
-
244
- try {
245
- fs.rmSync(target.fullPath, { recursive: true, force: true });
246
- return { success: true, message: `Backup "${target.name}" deleted successfully.` };
247
- } catch (e) {
248
- return { success: false, message: e.message };
249
- }
250
- }
251
-
252
- /**
253
- * Create a manual named backup
254
- */
255
- function manualBackup(rootDir, backupName = '') {
256
- const { findHtmlFiles } = require('./scanner');
257
- const files = findHtmlFiles(rootDir);
258
- if (files.length === 0) {
259
- return { success: false, message: 'No HTML files found to backup.' };
260
- }
261
- const result = createBackup(files, rootDir, backupName);
262
- return {
263
- success: true,
264
- backupDir: result.backupDir,
265
- dirName: result.dirName,
266
- hash: result.hash,
267
- name: result.name,
268
- fileCount: result.fileCount,
269
- message: `Snapshot "${result.name}" created with ${result.fileCount} file(s) [SHA: ${result.hash}].`
270
- };
271
- }
272
-
273
- module.exports = {
274
- createBackup,
275
- computeFilesHash,
276
- listBackups,
277
- restoreBackup,
278
- restoreLatestBackup,
279
- deleteBackup,
280
- manualBackup
281
- };
1
+ /**
2
+ * ============================================================================
3
+ * BUZL BACKUP & SNAPSHOT MANAGER
4
+ * Provides 100% safe file snapshots with SHA content hashing inside `.buzl/snapshots/`
5
+ *
6
+ * Copyright (c) 2026 Buzl Digital Solutions
7
+ * Licensed under the MIT License
8
+ * ============================================================================
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const crypto = require('crypto');
14
+
15
+ /**
16
+ * Compute a deterministic SHA-1 content hash across all candidate files
17
+ */
18
+ function computeFilesHash(files, rootDir) {
19
+ const hashSum = crypto.createHash('sha1');
20
+ for (const filePath of files) {
21
+ if (fs.existsSync(filePath)) {
22
+ hashSum.update(path.relative(rootDir, filePath));
23
+ try {
24
+ hashSum.update(fs.readFileSync(filePath));
25
+ } catch (e) {}
26
+ }
27
+ }
28
+ return hashSum.digest('hex').substring(0, 8);
29
+ }
30
+
31
+ /**
32
+ * Create a point-in-time snapshot backup
33
+ * Stored in `<rootDir>/.buzl/snapshots/<timestamp>_<hash>/`
34
+ */
35
+ function createBackup(arg1, arg2, arg3 = '') {
36
+ let files;
37
+ let rootDir;
38
+ let backupName = '';
39
+
40
+ if (Array.isArray(arg1)) {
41
+ files = arg1;
42
+ rootDir = arg2;
43
+ backupName = arg3 || '';
44
+ } else if (typeof arg1 === 'string') {
45
+ rootDir = arg1;
46
+ backupName = typeof arg2 === 'string' ? arg2 : '';
47
+ const { findHtmlFiles } = require('./scanner');
48
+ files = findHtmlFiles(rootDir);
49
+ } else {
50
+ files = [];
51
+ rootDir = process.cwd();
52
+ }
53
+
54
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
55
+ const hash = computeFilesHash(files, rootDir) || '00000000';
56
+ const dirName = `${timestamp}_${hash}`;
57
+
58
+ // Dedicated unified .buzl/snapshots directory
59
+ const snapshotsDir = path.join(rootDir, '.buzl', 'snapshots');
60
+ const backupDir = path.join(snapshotsDir, dirName);
61
+ fs.mkdirSync(backupDir, { recursive: true });
62
+
63
+ const manifest = {
64
+ id: dirName,
65
+ hash: hash,
66
+ name: (backupName || '').trim() || `Snapshot ${new Date().toLocaleTimeString()} [${hash}]`,
67
+ timestamp: new Date().toISOString(),
68
+ files: []
69
+ };
70
+
71
+ for (const filePath of files) {
72
+ const relPath = path.relative(rootDir, filePath);
73
+ const destPath = path.join(backupDir, relPath);
74
+ const destDir = path.dirname(destPath);
75
+ fs.mkdirSync(destDir, { recursive: true });
76
+ fs.copyFileSync(filePath, destPath);
77
+ manifest.files.push(relPath);
78
+ }
79
+
80
+ fs.writeFileSync(
81
+ path.join(backupDir, 'backup-manifest.json'),
82
+ JSON.stringify(manifest, null, 2),
83
+ 'utf8'
84
+ );
85
+
86
+ return {
87
+ success: true,
88
+ backupDir,
89
+ dirName,
90
+ hash,
91
+ timestamp: manifest.timestamp,
92
+ name: manifest.name,
93
+ fileCount: manifest.files.length
94
+ };
95
+ }
96
+
97
+ /**
98
+ * List all available snapshot backups
99
+ * Checks `.buzl/snapshots/` first, plus legacy `.buzl-backup-*` folders
100
+ */
101
+ function listBackups(rootDir) {
102
+ const allBackups = [];
103
+
104
+ // 1. Primary: Check .buzl/snapshots/
105
+ const snapshotsDir = path.join(rootDir, '.buzl', 'snapshots');
106
+ if (fs.existsSync(snapshotsDir)) {
107
+ try {
108
+ const entries = fs.readdirSync(snapshotsDir);
109
+ for (const e of entries) {
110
+ const dirPath = path.join(snapshotsDir, e);
111
+ if (fs.statSync(dirPath).isDirectory()) {
112
+ let manifest = null;
113
+ try {
114
+ const mPath = path.join(dirPath, 'backup-manifest.json');
115
+ if (fs.existsSync(mPath)) {
116
+ manifest = JSON.parse(fs.readFileSync(mPath, 'utf8'));
117
+ }
118
+ } catch (err) {}
119
+
120
+ allBackups.push({
121
+ id: (manifest && manifest.id) || e,
122
+ dirName: e,
123
+ hash: (manifest && manifest.hash) || (e.split('_')[1] || ''),
124
+ fullPath: dirPath,
125
+ name: (manifest && manifest.name) || e,
126
+ timestamp: (manifest && manifest.timestamp) || '',
127
+ filesCount: (manifest && manifest.files && manifest.files.length) || 0,
128
+ isLegacy: false,
129
+ manifest
130
+ });
131
+ }
132
+ }
133
+ } catch (e) {}
134
+ }
135
+
136
+ // 2. Fallback: Check legacy root-level `.buzl-backup-*` directories
137
+ try {
138
+ const rootEntries = fs.readdirSync(rootDir);
139
+ for (const e of rootEntries) {
140
+ if (e.startsWith('.buzl-backup-')) {
141
+ const dirPath = path.join(rootDir, e);
142
+ if (fs.statSync(dirPath).isDirectory()) {
143
+ let manifest = null;
144
+ try {
145
+ const mPath = path.join(dirPath, 'backup-manifest.json');
146
+ if (fs.existsSync(mPath)) {
147
+ manifest = JSON.parse(fs.readFileSync(mPath, 'utf8'));
148
+ }
149
+ } catch (err) {}
150
+
151
+ allBackups.push({
152
+ id: e,
153
+ dirName: e,
154
+ hash: (manifest && manifest.hash) || '',
155
+ fullPath: dirPath,
156
+ name: (manifest && manifest.name) || e.replace('.buzl-backup-', ''),
157
+ timestamp: (manifest && manifest.timestamp) || '',
158
+ filesCount: (manifest && manifest.files && manifest.files.length) || 0,
159
+ isLegacy: true,
160
+ manifest
161
+ });
162
+ }
163
+ }
164
+ }
165
+ } catch (e) {}
166
+
167
+ // Sort newest first
168
+ allBackups.sort((a, b) => b.dirName.localeCompare(a.dirName));
169
+ return allBackups;
170
+ }
171
+
172
+ /**
173
+ * Restore a specific snapshot or 'latest'
174
+ */
175
+ function restoreBackup(rootDir, identifier) {
176
+ const backups = listBackups(rootDir);
177
+ if (backups.length === 0) {
178
+ return { success: false, message: 'No backups found to restore.' };
179
+ }
180
+
181
+ let targetBackup = backups[0];
182
+ if (identifier && identifier !== 'latest') {
183
+ targetBackup = backups.find(b =>
184
+ b.dirName === identifier ||
185
+ b.name === identifier ||
186
+ b.hash === identifier ||
187
+ b.id === identifier
188
+ );
189
+ if (!targetBackup) {
190
+ return { success: false, message: `Backup "${identifier}" not found.` };
191
+ }
192
+ }
193
+
194
+ const manifest = targetBackup.manifest;
195
+ if (!manifest || !manifest.files) {
196
+ return { success: false, message: 'Backup manifest corrupted or missing.' };
197
+ }
198
+
199
+ let restoredCount = 0;
200
+ for (const relFile of manifest.files) {
201
+ const src = path.join(targetBackup.fullPath, relFile);
202
+ const dest = path.join(rootDir, relFile);
203
+ if (fs.existsSync(src)) {
204
+ const destDir = path.dirname(dest);
205
+ fs.mkdirSync(destDir, { recursive: true });
206
+ fs.copyFileSync(src, dest);
207
+ restoredCount++;
208
+ }
209
+ }
210
+
211
+ return {
212
+ success: true,
213
+ backupUsed: targetBackup.dirName,
214
+ backupName: targetBackup.name,
215
+ hash: targetBackup.hash,
216
+ restoredCount,
217
+ message: `Successfully restored ${restoredCount} file(s) from "${targetBackup.name}" [${targetBackup.hash || targetBackup.dirName}].`
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Restore the latest snapshot
223
+ */
224
+ function restoreLatestBackup(rootDir) {
225
+ return restoreBackup(rootDir, 'latest');
226
+ }
227
+
228
+ /**
229
+ * Delete a specific backup
230
+ */
231
+ function deleteBackup(rootDir, identifier) {
232
+ const backups = listBackups(rootDir);
233
+ const target = backups.find(b =>
234
+ b.dirName === identifier ||
235
+ b.name === identifier ||
236
+ b.id === identifier ||
237
+ b.hash === identifier
238
+ );
239
+
240
+ if (!target) {
241
+ return { success: false, message: `Backup "${identifier}" not found.` };
242
+ }
243
+
244
+ try {
245
+ fs.rmSync(target.fullPath, { recursive: true, force: true });
246
+ return { success: true, message: `Backup "${target.name}" deleted successfully.` };
247
+ } catch (e) {
248
+ return { success: false, message: e.message };
249
+ }
250
+ }
251
+
252
+ /**
253
+ * Create a manual named backup
254
+ */
255
+ function manualBackup(rootDir, backupName = '') {
256
+ const { findHtmlFiles } = require('./scanner');
257
+ const files = findHtmlFiles(rootDir);
258
+ if (files.length === 0) {
259
+ return { success: false, message: 'No HTML files found to backup.' };
260
+ }
261
+ const result = createBackup(files, rootDir, backupName);
262
+ return {
263
+ success: true,
264
+ backupDir: result.backupDir,
265
+ dirName: result.dirName,
266
+ hash: result.hash,
267
+ name: result.name,
268
+ fileCount: result.fileCount,
269
+ message: `Snapshot "${result.name}" created with ${result.fileCount} file(s) [SHA: ${result.hash}].`
270
+ };
271
+ }
272
+
273
+ module.exports = {
274
+ createBackup,
275
+ computeFilesHash,
276
+ listBackups,
277
+ restoreBackup,
278
+ restoreLatestBackup,
279
+ deleteBackup,
280
+ manualBackup
281
+ };