pagan-artifact 0.3.2 → 0.3.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.
package/caches/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  /**
2
- * art - Modern version control.
3
- * Module: Caches (v0.3.2)
2
+ * Artifact - Modern version control.
3
+ * @author Benny Schmidt (https://github.com/bennyschmidt)
4
+ * @project https://github.com/bennyschmidt/artifact
5
+ * Module: Caches (v0.3.3)
4
6
  */
5
7
 
6
8
  const fs = require('fs');
@@ -8,29 +10,40 @@ const path = require('path');
8
10
 
9
11
  const { checkout } = require('../branching/index.js');
10
12
  const getStateByHash = require('../utils/getStateByHash');
11
-
12
- const MAX_PART_SIZE = 32000000;
13
+ const { MAX_PART_SIZE } = require('../utils/constants');
13
14
 
14
15
  /**
15
16
  * Helper to load all changes from a paginated directory (Stage or Stash).
16
17
  */
17
18
 
18
- function getPaginatedChanges (dirPath) {
19
- const manifestPath = path.join(dirPath, 'manifest.json');
19
+ function getPaginatedChanges (directoryPath) {
20
+ /**
21
+ * Locate the manifest file to determine how many parts exist in the cache.
22
+ */
23
+
24
+ const manifestPath = path.join(directoryPath, 'manifest.json');
20
25
 
21
- if (!fs.existsSync(dirPath) || !fs.existsSync(manifestPath)) {
26
+ if (!fs.existsSync(directoryPath) || !fs.existsSync(manifestPath)) {
22
27
  return {};
23
28
  }
24
29
 
25
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
30
+ const manifest = JSON.parse(
31
+ fs.readFileSync(manifestPath, 'utf8')
32
+ );
26
33
 
27
34
  let allChanges = {};
28
35
 
36
+ /**
37
+ * Iterate through the parts defined in the manifest and merge them into a single object.
38
+ */
39
+
29
40
  for (const partName of manifest.parts) {
30
- const partPath = path.join(dirPath, partName);
41
+ const partPath = path.join(directoryPath, partName);
31
42
 
32
43
  if (fs.existsSync(partPath)) {
33
- const partData = JSON.parse(fs.readFileSync(partPath, 'utf8'));
44
+ const partData = JSON.parse(
45
+ fs.readFileSync(partPath, 'utf8')
46
+ );
34
47
 
35
48
  Object.assign(allChanges, partData.changes);
36
49
  }
@@ -43,29 +56,47 @@ function getPaginatedChanges (dirPath) {
43
56
  * Helper to write changes to a paginated directory.
44
57
  */
45
58
 
46
- function savePaginatedChanges (dirPath, changes) {
47
- if (fs.existsSync(dirPath)) {
48
- fs.rmSync(dirPath, { recursive: true, force: true });
59
+ function savePaginatedChanges (directoryPath, changes) {
60
+ /**
61
+ * Clear any existing cache data at the target path before writing new parts.
62
+ */
63
+
64
+ if (fs.existsSync(directoryPath)) {
65
+ fs.rmSync(directoryPath, { recursive: true, force: true });
49
66
  }
50
67
 
51
- fs.mkdirSync(dirPath, { recursive: true });
68
+ fs.mkdirSync(directoryPath, { recursive: true });
52
69
 
53
70
  const parts = [];
54
71
 
55
72
  let currentPartChanges = {};
56
73
  let currentSize = 0;
57
74
 
75
+ /**
76
+ * Internal helper to serialize the current change set to a JSON file.
77
+ */
78
+
58
79
  const savePart = () => {
59
- if (Object.keys(currentPartChanges).length === 0) return;
80
+ if (Object.keys(currentPartChanges).length === 0) {
81
+ return;
82
+ }
60
83
 
61
84
  const partName = `part.${parts.length}.json`;
62
85
 
63
- fs.writeFileSync(path.join(dirPath, partName), JSON.stringify({ changes: currentPartChanges }, null, 2));
86
+ fs.writeFileSync(
87
+ path.join(directoryPath, partName),
88
+ JSON.stringify({ changes: currentPartChanges }, null, 2)
89
+ );
90
+
64
91
  parts.push(partName);
65
92
  currentPartChanges = {};
66
93
  currentSize = 0;
67
94
  };
68
95
 
96
+ /**
97
+ * Distribute changes across multiple files if they exceed the MAX_PART_SIZE.
98
+ */
99
+
69
100
  for (const [file, changeSet] of Object.entries(changes)) {
70
101
  const size = JSON.stringify(changeSet).length;
71
102
 
@@ -78,48 +109,112 @@ function savePaginatedChanges (dirPath, changes) {
78
109
  }
79
110
 
80
111
  savePart();
81
- fs.writeFileSync(path.join(dirPath, 'manifest.json'), JSON.stringify({ parts }, null, 2));
112
+
113
+ /**
114
+ * Write the final manifest so the reader knows which parts to load.
115
+ */
116
+
117
+ fs.writeFileSync(
118
+ path.join(directoryPath, 'manifest.json'),
119
+ JSON.stringify({ parts }, null, 2)
120
+ );
82
121
  }
83
122
 
123
+ /**
124
+ * Saves current local changes to a temporary storage or restores the latest stash.
125
+ * @param {Object} options - Stash options.
126
+ * @param {boolean} options.pop - Whether to restore and remove the latest stash.
127
+ * @param {boolean} options.list - Whether to list all existing stashes.
128
+ * @returns {string|Object[]} - Status message or list of stash objects.
129
+ */
130
+
84
131
  function stash ({ pop = false, list = false } = {}) {
132
+ /**
133
+ * Define root and artifact paths required for stash operations.
134
+ */
135
+
85
136
  const root = process.cwd();
86
- const artPath = path.join(root, '.art');
87
- const stageDir = path.join(artPath, 'stage');
88
- const cachePath = path.join(artPath, 'cache');
89
- const artJsonPath = path.join(artPath, 'art.json');
137
+ const artifactPath = path.join(root, '.art');
138
+ const stageDirectory = path.join(artifactPath, 'stage');
139
+ const cachePath = path.join(artifactPath, 'cache');
140
+ const artifactJsonPath = path.join(artifactPath, 'art.json');
141
+
142
+ /**
143
+ * Retrieve and format a list of all saved stashes if the list flag is active.
144
+ */
90
145
 
91
146
  if (list) {
92
- if (!fs.existsSync(cachePath)) return [];
147
+ if (!fs.existsSync(cachePath)) {
148
+ return [];
149
+ }
150
+
151
+ const stashDirectories = [];
152
+ const entries = fs.readdirSync(cachePath);
153
+
154
+ for (const entry of entries) {
155
+ const entryPath = path.join(cachePath, entry);
156
+ if (entry.startsWith('stash_') && fs.statSync(entryPath).isDirectory()) {
157
+ stashDirectories.push(entry);
158
+ }
159
+ }
160
+
161
+ stashDirectories.sort();
162
+
163
+ const formattedStashes = [];
93
164
 
94
- const stashDirs = fs.readdirSync(cachePath)
95
- .filter(d => d.startsWith('stash_') && fs.statSync(path.join(cachePath, d)).isDirectory())
96
- .sort();
165
+ for (const [index, directoryName] of stashDirectories.entries()) {
166
+ formattedStashes.push({
167
+ id: `stash@{${stashDirectories.length - 1 - index}}`,
168
+ date: new Date(
169
+ parseInt(directoryName.replace('stash_', ''))
170
+ ).toLocaleString(),
171
+ directoryName
172
+ });
173
+ }
97
174
 
98
- return stashDirs.map((dirName, index) => ({
99
- id: `stash@{${stashDirs.length - 1 - index}}`,
100
- date: new Date(parseInt(dirName.replace('stash_', ''))).toLocaleString(),
101
- dirName
102
- }));
175
+ return formattedStashes;
103
176
  }
104
177
 
178
+ /**
179
+ * If `pop: true`, restore the most recent stash and delete its cache.
180
+ */
181
+
105
182
  if (pop) {
106
- if (!fs.existsSync(cachePath)) throw new Error('No stashes found.');
183
+ if (!fs.existsSync(cachePath)) {
184
+ throw new Error('No stashes found.');
185
+ }
107
186
 
108
- const stashes = fs.readdirSync(cachePath)
109
- .filter(d => d.startsWith('stash_') && fs.statSync(path.join(cachePath, d)).isDirectory())
110
- .sort();
187
+ const stashes = [];
188
+ const entries = fs.readdirSync(cachePath);
111
189
 
112
- if (stashes.length === 0) throw new Error('No stashes found.');
190
+ for (const entry of entries) {
191
+ const entryPath = path.join(cachePath, entry);
192
+ if (entry.startsWith('stash_') && fs.statSync(entryPath).isDirectory()) {
193
+ stashes.push(entry);
194
+ }
195
+ }
196
+
197
+ stashes.sort();
113
198
 
114
- const latestStashDirName = stashes[stashes.length - 1];
115
- const latestStashPath = path.join(cachePath, latestStashDirName);
199
+ if (stashes.length === 0) {
200
+ throw new Error('No stashes found.');
201
+ }
202
+
203
+ const latestStashDirectoryName = stashes[stashes.length - 1];
204
+ const latestStashPath = path.join(cachePath, latestStashDirectoryName);
116
205
  const stashChanges = getPaginatedChanges(latestStashPath);
117
206
 
207
+ /**
208
+ * Apply the cached changes back to the working directory.
209
+ */
210
+
118
211
  for (const [filePath, changeSet] of Object.entries(stashChanges)) {
119
212
  const fullPath = path.join(root, filePath);
120
213
 
121
214
  if (Array.isArray(changeSet)) {
122
- let content = fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf8') : '';
215
+ let content = fs.existsSync(fullPath)
216
+ ? fs.readFileSync(fullPath, 'utf8')
217
+ : '';
123
218
 
124
219
  for (const operation of changeSet) {
125
220
  if (operation.type === 'insert') {
@@ -134,23 +229,38 @@ function stash ({ pop = false, list = false } = {}) {
134
229
  fs.mkdirSync(path.dirname(fullPath), { recursive: true });
135
230
  fs.writeFileSync(fullPath, changeSet.content);
136
231
  } else if (changeSet.type === 'deleteFile') {
137
- if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
232
+ if (fs.existsSync(fullPath)) {
233
+ fs.unlinkSync(fullPath);
234
+ }
138
235
  }
139
236
  }
140
237
 
141
238
  fs.rmSync(latestStashPath, { recursive: true, force: true });
142
- return `Restored changes from ${latestStashDirName}.`;
239
+
240
+ return `Restored changes from ${latestStashDirectoryName}.`;
143
241
  }
144
242
 
145
- const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
146
- const activeState = getStateByHash(artJson.active.branch, artJson.active.parent) || {};
243
+ /**
244
+ * Create a new stash by comparing the workdir to the last commit.
245
+ */
147
246
 
148
- const allWorkDirFiles = fs.readdirSync(root, { recursive: true })
149
- .filter(f => !f.startsWith('.art') && !fs.statSync(path.join(root, f)).isDirectory());
247
+ const artifactJson = JSON.parse(
248
+ fs.readFileSync(artifactJsonPath, 'utf8')
249
+ );
250
+ const activeState = getStateByHash(artifactJson.active.branch, artifactJson.active.parent) || {};
251
+
252
+ const allWorkingDirectoryFiles = fs.readdirSync(root, { recursive: true })
253
+ .filter(file => {
254
+ return !file.startsWith('.art') && !fs.statSync(path.join(root, file)).isDirectory();
255
+ });
150
256
 
151
257
  const stashChanges = {};
152
258
 
153
- for (const file of allWorkDirFiles) {
259
+ /**
260
+ * Diff existing files, handling both text and binary content.
261
+ */
262
+
263
+ for (const file of allWorkingDirectoryFiles) {
154
264
  const fullPath = path.join(root, file);
155
265
  const currentBuffer = fs.readFileSync(fullPath);
156
266
  const isBinary = currentBuffer.includes(0);
@@ -159,7 +269,10 @@ function stash ({ pop = false, list = false } = {}) {
159
269
  const previousContent = activeState[file];
160
270
 
161
271
  if (previousContent === undefined) {
162
- stashChanges[file] = { type: 'createFile', content: isBinary ? currentBuffer.toString('base64') : currentContent };
272
+ stashChanges[file] = {
273
+ type: 'createFile',
274
+ content: isBinary ? currentBuffer.toString('base64') : currentContent
275
+ };
163
276
  } else if (currentContent !== previousContent && !isBinary) {
164
277
  let start = 0;
165
278
 
@@ -175,66 +288,106 @@ function stash ({ pop = false, list = false } = {}) {
175
288
  newEnd--;
176
289
  }
177
290
 
178
- const ops = [];
179
- const delLen = oldEnd - start + 1;
291
+ const operations = [];
292
+ const deletionLength = oldEnd - start + 1;
180
293
 
181
- if (delLen > 0) ops.push({ type: 'delete', position: start, length: delLen });
294
+ if (deletionLength > 0) {
295
+ operations.push({ type: 'delete', position: start, length: deletionLength });
296
+ }
182
297
 
183
- const insCont = currentContent.slice(start, newEnd + 1);
298
+ const insertionContent = currentContent.slice(start, newEnd + 1);
184
299
 
185
- if (insCont.length > 0) ops.push({ type: 'insert', position: start, content: insCont });
300
+ if (insertionContent.length > 0) {
301
+ operations.push({ type: 'insert', position: start, content: insertionContent });
302
+ }
186
303
 
187
- if (ops.length > 0) {
188
- stashChanges[file] = ops;
304
+ if (operations.length > 0) {
305
+ stashChanges[file] = operations;
189
306
  }
190
307
  }
191
308
  }
192
309
 
310
+ /**
311
+ * Detect files that were deleted from the working directory.
312
+ */
313
+
193
314
  for (const file in activeState) {
194
315
  if (!fs.existsSync(path.join(root, file))) {
195
316
  stashChanges[file] = { type: 'deleteFile' };
196
317
  }
197
318
  }
198
319
 
199
- if (Object.keys(stashChanges).length === 0) return 'No local changes to stash.';
320
+ if (Object.keys(stashChanges).length === 0) {
321
+ return 'No local changes to stash.';
322
+ }
323
+
324
+ /**
325
+ * Save the detected changes to the cache and revert the working directory.
326
+ */
200
327
 
201
328
  const timestamp = Date.now();
202
329
  const newStashPath = path.join(cachePath, `stash_${timestamp}`);
203
330
 
204
331
  savePaginatedChanges(newStashPath, stashChanges);
205
332
 
206
- if (fs.existsSync(stageDir)) {
207
- fs.rmSync(stageDir, { recursive: true, force: true });
333
+ if (fs.existsSync(stageDirectory)) {
334
+ fs.rmSync(stageDirectory, { recursive: true, force: true });
208
335
  }
209
336
 
210
- checkout(artJson.active.branch, { force: true });
337
+ checkout(artifactJson.active.branch, { force: true });
211
338
 
212
- return `Saved working directory changes to stash_${timestamp} and reverted to clean state.`;
339
+ return `Stashed working directory changes and reverted to a clean state.`;
213
340
  }
214
341
 
342
+ /**
343
+ * Resets the active state to a specific commit.
344
+ * @param {string} hash - The commit hash to reset to.
345
+ * @returns {string} - Status message.
346
+ */
347
+
215
348
  function reset (hash) {
216
- const artPath = path.join(process.cwd(), '.art');
217
- const stageDir = path.join(artPath, 'stage');
218
- const artJsonPath = path.join(artPath, 'art.json');
349
+ /**
350
+ * Clear the staging area before performing a reset.
351
+ */
219
352
 
220
- if (fs.existsSync(stageDir)) {
221
- fs.rmSync(stageDir, { recursive: true, force: true });
353
+ const artifactPath = path.join(process.cwd(), '.art');
354
+ const stageDirectory = path.join(artifactPath, 'stage');
355
+ const artifactJsonPath = path.join(artifactPath, 'art.json');
356
+
357
+ if (fs.existsSync(stageDirectory)) {
358
+ fs.rmSync(stageDirectory, { recursive: true, force: true });
359
+ }
360
+
361
+ if (!hash) {
362
+ return 'Staging area cleared.';
222
363
  }
223
364
 
224
- if (!hash) return 'Staging area cleared.';
365
+ /**
366
+ * Verify the existence of the commit hash within the active branch history.
367
+ */
225
368
 
226
- const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
227
- const branch = artJson.active.branch;
228
- const branchPath = path.join(artPath, 'history/local', branch);
369
+ const artifactJson = JSON.parse(
370
+ fs.readFileSync(artifactJsonPath, 'utf8')
371
+ );
372
+ const branchName = artifactJson.active.branch;
373
+ const branchPath = path.join(artifactPath, 'history/local', branchName);
229
374
  const commitPath = path.join(branchPath, `${hash}.json`);
230
375
 
231
- if (!fs.existsSync(commitPath)) throw new Error(`Commit ${hash} not found in branch ${branch}.`);
376
+ if (!fs.existsSync(commitPath)) {
377
+ throw new Error(`Commit ${hash} not found in branch ${branchName}.`);
378
+ }
232
379
 
233
- artJson.active.parent = hash;
234
- fs.writeFileSync(artJsonPath, JSON.stringify(artJson, null, 2));
380
+ /**
381
+ * Update the active parent pointer and truncate the manifest commits.
382
+ */
383
+
384
+ artifactJson.active.parent = hash;
385
+ fs.writeFileSync(artifactJsonPath, JSON.stringify(artifactJson, null, 2));
235
386
 
236
387
  const manifestPath = path.join(branchPath, 'manifest.json');
237
- const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
388
+ const manifest = JSON.parse(
389
+ fs.readFileSync(manifestPath, 'utf8')
390
+ );
238
391
  const hashIndex = manifest.commits.indexOf(hash);
239
392
 
240
393
  if (hashIndex !== -1) {
@@ -242,26 +395,53 @@ function reset (hash) {
242
395
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
243
396
  }
244
397
 
245
- checkout(branch);
398
+ /**
399
+ * Reconstruct the working directory to match the reset commit state.
400
+ */
401
+
402
+ checkout(branchName);
246
403
 
247
404
  return `Branch is now at ${hash.slice(0, 7)}. Working directory updated.`;
248
405
  }
249
406
 
407
+ /**
408
+ * Removes a file from the working tree and stages the deletion.
409
+ * @param {string} filePath - Path to the file to be removed.
410
+ * @returns {string} - Status message.
411
+ */
412
+
250
413
  function rm (filePath) {
251
- const artPath = path.join(process.cwd(), '.art');
414
+ /**
415
+ * Stage the file deletion by updating the paginated stage cache.
416
+ */
417
+
418
+ const artifactPath = path.join(process.cwd(), '.art');
252
419
  const fullPath = path.join(process.cwd(), filePath);
253
- const stage = getPaginatedChanges(path.join(artPath, 'stage'));
420
+
421
+ const stage = getPaginatedChanges(
422
+ path.join(artifactPath, 'stage')
423
+ );
254
424
 
255
425
  stage[filePath] = { type: 'deleteFile' };
256
- savePaginatedChanges(path.join(artPath, 'stage'), stage);
257
426
 
258
- if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
427
+ savePaginatedChanges(
428
+ path.join(artifactPath, 'stage'),
429
+ stage
430
+ );
431
+
432
+ /**
433
+ * Physically remove the file from the working directory if it exists.
434
+ */
435
+
436
+ if (fs.existsSync(fullPath)) {
437
+ fs.unlinkSync(fullPath);
438
+ }
259
439
 
260
440
  return `File ${filePath} marked for removal.`;
261
441
  }
262
442
 
263
443
  module.exports = {
264
- __libraryVersion: '0.3.2',
444
+ __libraryVersion: '0.3.3',
265
445
  __libraryAPIName: 'Caches',
266
446
  stash,
267
447
  reset,