pagan-artifact 0.2.9 → 0.3.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/bin/art.js CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  /**
4
4
  * art - Modern version control.
5
- * CLI (v0.2.9)
5
+ * CLI (v0.3.0)
6
6
  */
7
7
 
8
8
  const art = require('../index.js');
9
+ const path = require('path');
9
10
 
10
11
  const [,, command, ...args] = process.argv;
11
12
 
@@ -69,7 +70,19 @@ async function run() {
69
70
 
70
71
  if (ignored && ignored.length > 0) {
71
72
  console.log('\nIgnored files:');
72
- ignored.forEach(f => console.log(`${GRAY}\t${f}${RESET}`));
73
+
74
+ const compressedIgnored = new Set();
75
+
76
+ ignored.forEach(f => {
77
+ const parts = f.split(path.sep);
78
+ if (parts.length > 1) {
79
+ compressedIgnored.add(`${parts[0]}${path.sep}`);
80
+ } else {
81
+ compressedIgnored.add(f);
82
+ }
83
+ });
84
+
85
+ compressedIgnored.forEach(f => console.log(`${GRAY}\t${f}${RESET}`));
73
86
  }
74
87
 
75
88
  if (untracked.length === 0 && modified.length === 0 && staged.length === 0) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Module: Branching (v0.2.9)
3
+ * Module: Branching (v0.3.0)
4
4
  */
5
5
 
6
6
  const fs = require('fs');
@@ -26,6 +26,7 @@ function branch ({ name, isDelete = false } = {}) {
26
26
  });
27
27
  }
28
28
 
29
+ const normalizedName = name.toLowerCase();
29
30
  const illegalRegExp = /[\/\\]/g;
30
31
  const controlRegExp = /[\x00-\x1f\x80-\x9f]/g;
31
32
  const reservedRegExp = /^\.+$/;
@@ -89,20 +90,25 @@ function branch ({ name, isDelete = false } = {}) {
89
90
 
90
91
  if (initialCommits.length > 0) {
91
92
  const sourceBranchPath = path.join(localHistoryPath, sourceBranchName);
93
+ const sourceRemotePath = path.join(remoteHistoryPath, sourceBranchName);
92
94
 
93
95
  for (const hash of initialCommits) {
94
- const masterFile = path.join(sourceBranchPath, `${hash}.json`);
96
+ let masterFile = path.join(sourceBranchPath, `${hash}.json`);
97
+ let currentSrcDir = sourceBranchPath;
98
+
99
+ if (!fs.existsSync(masterFile)) {
100
+ masterFile = path.join(sourceRemotePath, `${hash}.json`);
101
+ currentSrcDir = sourceRemotePath;
102
+ }
95
103
 
96
104
  if (fs.existsSync(masterFile)) {
97
- // Copy the Master File
98
105
  fs.copyFileSync(masterFile, path.join(branchLocalPath, `${hash}.json`));
99
106
 
100
- // Detect and copy all Parts
101
107
  const commitMaster = JSON.parse(fs.readFileSync(masterFile, 'utf8'));
102
108
 
103
109
  if (commitMaster.parts && Array.isArray(commitMaster.parts)) {
104
110
  for (const partName of commitMaster.parts) {
105
- const srcPart = path.join(sourceBranchPath, partName);
111
+ const srcPart = path.join(currentSrcDir, partName);
106
112
  const destPart = path.join(branchLocalPath, partName);
107
113
 
108
114
  if (fs.existsSync(srcPart)) {
@@ -287,11 +293,11 @@ function checkout (branchName, { force = false } = {}) {
287
293
  JSON.stringify({ parts: Array.from({ length: partCount }, (_, i) => `part.${i}.json`) }, null, 2)
288
294
  );
289
295
 
290
- return `Merged ${targetBranch}. ${partCount} stage parts created. Resolve conflicts and art commit.`;
296
+ return `Merged ${targetBranch}.`;
291
297
  }
292
298
 
293
299
  module.exports = {
294
- __libraryVersion: '0.2.9',
300
+ __libraryVersion: '0.3.0',
295
301
  __libraryAPIName: 'Branching',
296
302
  branch,
297
303
  checkout,
package/caches/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Module: Caches (v0.2.9)
3
+ * Module: Caches (v0.3.0)
4
4
  */
5
5
 
6
6
  const fs = require('fs');
@@ -9,15 +9,16 @@ const path = require('path');
9
9
  const { checkout } = require('../branching/index.js');
10
10
  const getStateByHash = require('../utils/getStateByHash');
11
11
 
12
+ const MAX_PART_SIZE = 32000000;
13
+
12
14
  /**
13
- * Helper to load all changes from a paginated stage directory.
15
+ * Helper to load all changes from a paginated directory (Stage or Stash).
14
16
  */
15
17
 
16
- function getStagedChanges(artPath) {
17
- const stageDir = path.join(artPath, 'stage');
18
- const manifestPath = path.join(stageDir, 'manifest.json');
18
+ function getPaginatedChanges(dirPath) {
19
+ const manifestPath = path.join(dirPath, 'manifest.json');
19
20
 
20
- if (!fs.existsSync(stageDir) || !fs.existsSync(manifestPath)) {
21
+ if (!fs.existsSync(dirPath) || !fs.existsSync(manifestPath)) {
21
22
  return {};
22
23
  }
23
24
 
@@ -26,8 +27,7 @@ function getStagedChanges(artPath) {
26
27
  let allChanges = {};
27
28
 
28
29
  for (const partName of manifest.parts) {
29
- const partPath = path.join(stageDir, partName);
30
-
30
+ const partPath = path.join(dirPath, partName);
31
31
  if (fs.existsSync(partPath)) {
32
32
  const partData = JSON.parse(fs.readFileSync(partPath, 'utf8'));
33
33
 
@@ -39,30 +39,28 @@ function getStagedChanges(artPath) {
39
39
  }
40
40
 
41
41
  /**
42
- * Helper to write changes to a paginated stage directory.
42
+ * Helper to write changes to a paginated directory.
43
43
  */
44
44
 
45
- function saveStagedChanges(artPath, changes) {
46
- const MAX_PART_SIZE = 32000000;
47
- const stageDir = path.join(artPath, 'stage');
48
-
49
- if (fs.existsSync(stageDir)) {
50
- fs.rmSync(stageDir, { recursive: true, force: true });
45
+ function savePaginatedChanges (dirPath, changes) {
46
+ if (fs.existsSync(dirPath)) {
47
+ fs.rmSync(dirPath, { recursive: true, force: true });
51
48
  }
52
49
 
53
- fs.mkdirSync(stageDir, { recursive: true });
50
+ fs.mkdirSync(dirPath, { recursive: true });
54
51
 
55
- const stageParts = [];
52
+ const parts = [];
56
53
 
57
54
  let currentPartChanges = {};
58
55
  let currentSize = 0;
59
56
 
60
57
  const savePart = () => {
61
- const partName = `part.${stageParts.length}.json`;
58
+ if (Object.keys(currentPartChanges).length === 0) return;
62
59
 
63
- fs.writeFileSync(path.join(stageDir, partName), JSON.stringify({ changes: currentPartChanges }, null, 2));
64
- stageParts.push(partName);
60
+ const partName = `part.${parts.length}.json`;
65
61
 
62
+ fs.writeFileSync(path.join(dirPath, partName), JSON.stringify({ changes: currentPartChanges }, null, 2));
63
+ parts.push(partName);
66
64
  currentPartChanges = {};
67
65
  currentSize = 0;
68
66
  };
@@ -79,14 +77,9 @@ function saveStagedChanges(artPath, changes) {
79
77
  }
80
78
 
81
79
  savePart();
82
-
83
- fs.writeFileSync(path.join(stageDir, 'manifest.json'), JSON.stringify({ parts: stageParts }, null, 2));
80
+ fs.writeFileSync(path.join(dirPath, 'manifest.json'), JSON.stringify({ parts }, null, 2));
84
81
  }
85
82
 
86
- /**
87
- * Moves changes to a cache folder, or restores the most recent stash.
88
- */
89
-
90
83
  function stash ({ pop = false, list = false } = {}) {
91
84
  const root = process.cwd();
92
85
  const artPath = path.join(root, '.art');
@@ -97,14 +90,14 @@ function stash ({ pop = false, list = false } = {}) {
97
90
  if (list) {
98
91
  if (!fs.existsSync(cachePath)) return [];
99
92
 
100
- const stashFiles = fs.readdirSync(cachePath)
101
- .filter(f => f.startsWith('stash_') && f.endsWith('.json'))
93
+ const stashDirs = fs.readdirSync(cachePath)
94
+ .filter(d => d.startsWith('stash_') && fs.statSync(path.join(cachePath, d)).isDirectory())
102
95
  .sort();
103
96
 
104
- return stashFiles.map((file, index) => ({
105
- id: `stash@{${stashFiles.length - 1 - index}}`,
106
- date: new Date(parseInt(file.replace('stash_', '').replace('.json', ''))).toLocaleString(),
107
- file
97
+ return stashDirs.map((dirName, index) => ({
98
+ id: `stash@{${stashDirs.length - 1 - index}}`,
99
+ date: new Date(parseInt(dirName.replace('stash_', ''))).toLocaleString(),
100
+ dirName
108
101
  }));
109
102
  }
110
103
 
@@ -112,18 +105,16 @@ function stash ({ pop = false, list = false } = {}) {
112
105
  if (!fs.existsSync(cachePath)) throw new Error('No stashes found.');
113
106
 
114
107
  const stashes = fs.readdirSync(cachePath)
115
- .filter(f => f.startsWith('stash_') && f.endsWith('.json'))
108
+ .filter(d => d.startsWith('stash_') && fs.statSync(path.join(cachePath, d)).isDirectory())
116
109
  .sort();
117
110
 
118
- if (stashes.length === 0) {
119
- throw new Error('No stashes found.');
120
- }
111
+ if (stashes.length === 0) throw new Error('No stashes found.');
121
112
 
122
- const latestStashName = stashes[stashes.length - 1];
123
- const latestStashPath = path.join(cachePath, latestStashName);
124
- const stashData = JSON.parse(fs.readFileSync(latestStashPath, 'utf8'));
113
+ const latestStashDirName = stashes[stashes.length - 1];
114
+ const latestStashPath = path.join(cachePath, latestStashDirName);
115
+ const stashChanges = getPaginatedChanges(latestStashPath);
125
116
 
126
- for (const [filePath, changeSet] of Object.entries(stashData.changes)) {
117
+ for (const [filePath, changeSet] of Object.entries(stashChanges)) {
127
118
  const fullPath = path.join(root, filePath);
128
119
 
129
120
  if (Array.isArray(changeSet)) {
@@ -146,9 +137,8 @@ function stash ({ pop = false, list = false } = {}) {
146
137
  }
147
138
  }
148
139
 
149
- fs.unlinkSync(latestStashPath);
150
-
151
- return `Restored changes from ${latestStashName}.`;
140
+ fs.rmSync(latestStashPath, { recursive: true, force: true });
141
+ return `Restored changes from ${latestStashDirName}.`;
152
142
  }
153
143
 
154
144
  const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
@@ -160,12 +150,16 @@ function stash ({ pop = false, list = false } = {}) {
160
150
  const stashChanges = {};
161
151
 
162
152
  for (const file of allWorkDirFiles) {
163
- const currentContent = fs.readFileSync(path.join(root, file), 'utf8');
153
+ const fullPath = path.join(root, file);
154
+ const currentBuffer = fs.readFileSync(fullPath);
155
+ const isBinary = currentBuffer.includes(0);
156
+
157
+ const currentContent = isBinary ? null : currentBuffer.toString('utf8');
164
158
  const previousContent = activeState[file];
165
159
 
166
160
  if (previousContent === undefined) {
167
- stashChanges[file] = { type: 'createFile', content: currentContent };
168
- } else if (currentContent !== previousContent) {
161
+ stashChanges[file] = { type: 'createFile', content: isBinary ? currentBuffer.toString('base64') : currentContent };
162
+ } else if (currentContent !== previousContent && !isBinary) {
169
163
  let start = 0;
170
164
 
171
165
  while (start < previousContent.length && start < currentContent.length && previousContent[start] === currentContent[start]) {
@@ -189,7 +183,9 @@ function stash ({ pop = false, list = false } = {}) {
189
183
 
190
184
  if (insCont.length > 0) ops.push({ type: 'insert', position: start, content: insCont });
191
185
 
192
- if (ops.length > 0) stashChanges[file] = ops;
186
+ if (ops.length > 0) {
187
+ stashChanges[file] = ops;
188
+ }
193
189
  }
194
190
  }
195
191
 
@@ -199,29 +195,21 @@ function stash ({ pop = false, list = false } = {}) {
199
195
  }
200
196
  }
201
197
 
202
- if (Object.keys(stashChanges).length === 0) {
203
- return 'No local changes to stash.';
204
- }
205
-
206
- if (!fs.existsSync(cachePath)) fs.mkdirSync(cachePath, { recursive: true });
198
+ if (Object.keys(stashChanges).length === 0) return 'No local changes to stash.';
207
199
 
208
200
  const timestamp = Date.now();
201
+ const newStashPath = path.join(cachePath, `stash_${timestamp}`);
209
202
 
210
- fs.writeFileSync(path.join(cachePath, `stash_${timestamp}.json`), JSON.stringify({ changes: stashChanges }, null, 2));
203
+ savePaginatedChanges(newStashPath, stashChanges);
211
204
 
212
205
  if (fs.existsSync(stageDir)) {
213
206
  fs.rmSync(stageDir, { recursive: true, force: true });
214
207
  }
215
-
216
208
  checkout(artJson.active.branch, { force: true });
217
209
 
218
- return `Saved working directory changes to stash_${timestamp}.json and reverted to clean state.`;
210
+ return `Saved working directory changes to paginated stash_${timestamp} and reverted to clean state.`;
219
211
  }
220
212
 
221
- /**
222
- * Wipes the stage and moves the active parent pointer if a hash is provided.
223
- */
224
-
225
213
  function reset (hash) {
226
214
  const artPath = path.join(process.cwd(), '.art');
227
215
  const stageDir = path.join(artPath, 'stage');
@@ -231,18 +219,14 @@ function reset (hash) {
231
219
  fs.rmSync(stageDir, { recursive: true, force: true });
232
220
  }
233
221
 
234
- if (!hash) {
235
- return 'Staging area cleared.';
236
- }
222
+ if (!hash) return 'Staging area cleared.';
237
223
 
238
224
  const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
239
225
  const branch = artJson.active.branch;
240
226
  const branchPath = path.join(artPath, 'history/local', branch);
241
227
  const commitPath = path.join(branchPath, `${hash}.json`);
242
228
 
243
- if (!fs.existsSync(commitPath)) {
244
- throw new Error(`Commit ${hash} not found in branch ${branch}.`);
245
- }
229
+ if (!fs.existsSync(commitPath)) throw new Error(`Commit ${hash} not found in branch ${branch}.`);
246
230
 
247
231
  artJson.active.parent = hash;
248
232
  fs.writeFileSync(artJsonPath, JSON.stringify(artJson, null, 2));
@@ -258,34 +242,24 @@ function reset (hash) {
258
242
 
259
243
  checkout(branch);
260
244
 
261
- return `Head is now at ${hash.slice(0, 7)}. Working directory updated.`;
245
+ return `Branch is now at ${hash.slice(0, 7)}. Working directory updated.`;
262
246
  }
263
247
 
264
- /**
265
- * Marks a file for deletion by adding a "deleteFile" entry to the stage.
266
- */
267
-
268
248
  function rm (filePath) {
269
249
  const artPath = path.join(process.cwd(), '.art');
270
250
  const fullPath = path.join(process.cwd(), filePath);
251
+ const stage = getPaginatedChanges(path.join(artPath, 'stage'));
271
252
 
272
- const stage = getStagedChanges(artPath);
253
+ stage[filePath] = { type: 'deleteFile' };
254
+ savePaginatedChanges(path.join(artPath, 'stage'), stage);
273
255
 
274
- stage[filePath] = {
275
- type: 'deleteFile'
276
- };
277
-
278
- saveStagedChanges(artPath, stage);
279
-
280
- if (fs.existsSync(fullPath)) {
281
- fs.unlinkSync(fullPath);
282
- }
256
+ if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
283
257
 
284
258
  return `File ${filePath} marked for removal.`;
285
259
  }
286
260
 
287
261
  module.exports = {
288
- __libraryVersion: '0.2.9',
262
+ __libraryVersion: '0.3.0',
289
263
  __libraryAPIName: 'Caches',
290
264
  stash,
291
265
  reset,
package/changes/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Module: Changes (v0.2.9)
3
+ * Module: Changes (v0.3.0)
4
4
  */
5
5
 
6
6
  const fs = require('fs');
@@ -34,8 +34,6 @@ function log () {
34
34
 
35
35
  let output = `Branch: ${branch}\n\n`;
36
36
 
37
- // Display commits in reverse chronological order
38
-
39
37
  for (let i = manifest.commits.length - 1; i >= 0; i--) {
40
38
  const hash = manifest.commits[i];
41
39
  const commitMasterPath = path.join(branchPath, `${hash}.json`);
@@ -57,7 +55,7 @@ function log () {
57
55
  * @returns {object} Formatted diff output and staged file list.
58
56
  */
59
57
 
60
- function diff () {
58
+ function diff () {
61
59
  const root = process.cwd();
62
60
  const artPath = path.join(root, '.art');
63
61
  const artJsonPath = path.join(artPath, 'art.json');
@@ -78,10 +76,13 @@ function log () {
78
76
 
79
77
  for (const filePath of currentFiles) {
80
78
  const fullPath = path.join(root, filePath);
81
- const currentContent = fs.readFileSync(fullPath, 'utf8');
79
+ const currentBuffer = fs.readFileSync(fullPath);
80
+ const isBinary = currentBuffer.includes(0);
81
+
82
+ const currentContent = isBinary ? null : currentBuffer.toString('utf8');
82
83
  const previousContent = lastCommitState[filePath] || '';
83
84
 
84
- if (currentContent !== previousContent) {
85
+ if (!isBinary && currentContent !== previousContent) {
85
86
  let start = 0;
86
87
 
87
88
  while (start < previousContent.length && start < currentContent.length && previousContent[start] === currentContent[start]) {
@@ -101,6 +102,12 @@ function log () {
101
102
  deleted: previousContent.slice(start, oldEnd + 1),
102
103
  added: currentContent.slice(start, newEnd + 1)
103
104
  });
105
+ } else if (isBinary) {
106
+ const prevHash = lastCommitState[filePath] ? 'exists' : 'null';
107
+
108
+ if (prevHash === 'null') {
109
+ fileDiffs.push({ file: filePath, added: '<Binary Data>', deleted: '' });
110
+ }
104
111
  }
105
112
  }
106
113
 
@@ -115,8 +122,10 @@ function log () {
115
122
 
116
123
  for (const partName of manifest.parts) {
117
124
  const partPath = path.join(stageDir, partName);
125
+
118
126
  if (fs.existsSync(partPath)) {
119
127
  const partData = JSON.parse(fs.readFileSync(partPath, 'utf8'));
128
+
120
129
  Object.keys(partData.changes).forEach(file => stagedFilesSet.add(file));
121
130
  }
122
131
  }
@@ -127,7 +136,7 @@ function log () {
127
136
  }
128
137
 
129
138
  module.exports = {
130
- __libraryVersion: '0.2.9',
139
+ __libraryVersion: '0.3.0',
131
140
  __libraryAPIName: 'Changes',
132
141
  log,
133
142
  diff
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Module: Contributions (v0.2.9)
3
+ * Module: Contributions (v0.3.0)
4
4
  */
5
5
 
6
6
  const fs = require('fs');
@@ -60,7 +60,10 @@ async function fetchRemote () {
60
60
  method: 'POST',
61
61
  headers: { 'Content-Type': 'application/json' },
62
62
  body: JSON.stringify({
63
- type: 'history', handle, repo, branch,
63
+ type: 'history',
64
+ handle,
65
+ repo,
66
+ branch,
64
67
  ...(token && { personalAccessToken: token })
65
68
  })
66
69
  });
@@ -171,20 +174,22 @@ async function push () {
171
174
  const handle = remoteParts.pop();
172
175
 
173
176
  const localBranchPath = path.join(artPath, 'history/local', branch);
177
+ const remoteBranchPath = path.join(artPath, 'history/remote', branch);
174
178
  const localManifest = JSON.parse(fs.readFileSync(path.join(localBranchPath, 'manifest.json'), 'utf8'));
175
179
 
176
180
  const response = await fetch(`${ARTIFACT_HOST}/manifest`, {
177
181
  method: 'POST',
178
182
  headers: { 'Content-Type': 'application/json' },
179
183
  body: JSON.stringify({
180
- type: 'history', handle, repo, branch,
181
-
184
+ type: 'history',
185
+ handle,
186
+ repo,
187
+ branch,
182
188
  ...(token && { personalAccessToken: token })
183
189
  })
184
190
  });
185
191
 
186
192
  const remoteManifest = await response.json();
187
-
188
193
  const missingCommits = localManifest.commits.filter(hash => !remoteManifest.commits.includes(hash));
189
194
 
190
195
  if (missingCommits.length === 0) {
@@ -208,13 +213,17 @@ async function push () {
208
213
  }
209
214
  }
210
215
 
216
+ const currentRemoteManifestPath = path.join(remoteBranchPath, 'manifest.json');
217
+ const currentRemoteManifest = fs.existsSync(currentRemoteManifestPath)
218
+ ? JSON.parse(fs.readFileSync(currentRemoteManifestPath, 'utf8'))
219
+ : { commits: [] };
220
+
211
221
  for (const commitHash of missingCommits) {
212
222
  const commitMaster = JSON.parse(fs.readFileSync(path.join(localBranchPath, `${commitHash}.json`), 'utf8'));
213
223
 
214
- await fetch(`${ARTIFACT_HOST}/push`, {
224
+ const pushRes = await fetch(`${ARTIFACT_HOST}/push`, {
215
225
  method: 'POST',
216
226
  headers: { 'Content-Type': 'application/json' },
217
-
218
227
  body: JSON.stringify({
219
228
  handle, repo, branch, commit: commitMaster,
220
229
 
@@ -224,12 +233,14 @@ async function push () {
224
233
  })
225
234
  });
226
235
 
236
+ if (!pushRes.ok) throw new Error(`Server rejected push for commit ${commitHash}`);
237
+
227
238
  rootData = null;
228
239
 
229
240
  for (const partName of commitMaster.parts) {
230
241
  const partData = JSON.parse(fs.readFileSync(path.join(localBranchPath, partName), 'utf8'));
231
242
 
232
- await fetch(`${ARTIFACT_HOST}/commit/part`, {
243
+ const partRes = await fetch(`${ARTIFACT_HOST}/commit/part`, {
233
244
  method: 'POST',
234
245
  headers: { 'Content-Type': 'application/json' },
235
246
  body: JSON.stringify({
@@ -237,10 +248,13 @@ async function push () {
237
248
  ...(token && { personalAccessToken: token })
238
249
  })
239
250
  });
251
+
252
+ if (!partRes.ok) throw new Error(`Server failed to receive part ${partName}`);
240
253
  }
241
- }
242
254
 
243
- fs.writeFileSync(path.join(artPath, 'history/remote', branch, 'manifest.json'), JSON.stringify(localManifest, null, 2));
255
+ currentRemoteManifest.commits.push(commitHash);
256
+ fs.writeFileSync(currentRemoteManifestPath, JSON.stringify(currentRemoteManifest, null, 2));
257
+ }
244
258
 
245
259
  return `Pushed ${missingCommits.length} commits to remote.`;
246
260
  }
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Core Library Entry Point (v0.2.9)
3
+ * Core Library Entry Point (v0.3.0)
4
4
  */
5
5
 
6
6
  const Setup = require('./setup');
@@ -50,7 +50,7 @@ const art = {
50
50
 
51
51
  // Metadata
52
52
 
53
- version: '0.2.9',
53
+ version: '0.3.0',
54
54
  modules: [
55
55
  Setup.__libraryAPIName,
56
56
  Workflow.__libraryAPIName,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pagan-artifact",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
4
4
  "description": "Modern version control.",
5
5
  "main": "index.js",
6
6
  "bin": {
package/setup/index.js CHANGED
@@ -1,137 +1,133 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Module: Setup (v0.2.9)
3
+ * Module: Setup (v0.3.0)
4
4
  */
5
5
 
6
6
  const fs = require('fs');
7
7
  const path = require('path');
8
8
 
9
9
  const pkg = require('../package.json');
10
- const { remote } = require('../contributions');
11
10
  const shouldIgnore = require('../utils/shouldIgnore');
12
11
 
13
12
  const ARTIFACT_HOST = pkg.artConfig.host || 'http://localhost:1337';
14
13
 
15
14
  /**
16
- * Initializes the local .art directory structure.
15
+ * Internal helper to create the .art directory tree.
17
16
  */
18
17
 
19
- function init (directoryPath = process.cwd()) {
20
- const artDirectory = path.join(directoryPath, '.art');
21
-
22
- const folders = [
23
- '',
24
- 'root',
25
- 'history',
26
- 'history/local',
27
- 'history/local/main',
28
- 'history/remote',
29
- 'history/remote/main'
30
- ];
31
-
32
- if (fs.existsSync(artDirectory)) {
33
- return `Reinitialized existing art repository in ${artDirectory}`;
34
- }
35
-
36
- for (const folder of folders) {
37
- const fullPath = path.join(artDirectory, folder);
38
-
39
- if (!fs.existsSync(fullPath)) {
40
- fs.mkdirSync(fullPath, { recursive: true });
41
- }
42
- }
18
+ function ensureDirStructure(artDirectory) {
19
+ const folders = [
20
+ '',
21
+ 'root',
22
+ 'history',
23
+ 'history/local',
24
+ 'history/local/main',
25
+ 'history/remote',
26
+ 'history/remote/main'
27
+ ];
28
+
29
+ for (const folder of folders) {
30
+ const fullPath = path.join(artDirectory, folder);
31
+
32
+ if (!fs.existsSync(fullPath)) {
33
+ fs.mkdirSync(fullPath, { recursive: true });
34
+ }
35
+ }
36
+ }
43
37
 
44
- const files = fs.readdirSync(directoryPath, { recursive: true })
45
- .filter(f => {
46
- const isInternal = f === '.art' || f.startsWith('.art' + path.sep);
38
+ /**
39
+ * Initializes the local .art directory structure and indexes current files.
40
+ */
47
41
 
48
- return !isInternal && !shouldIgnore(f);
49
- });
42
+ function init (directoryPath = process.cwd()) {
43
+ const artDirectory = path.join(directoryPath, '.art');
50
44
 
51
- const rootMasterManifest = { parts: [] };
45
+ if (fs.existsSync(artDirectory)) {
46
+ return `Reinitialized existing art repository in ${artDirectory}`;
47
+ }
52
48
 
53
- let currentPartFiles = [];
54
- let currentPartChars = 0;
49
+ ensureDirStructure(artDirectory);
55
50
 
56
- const MAX_PART_CHARS = 32000000;
51
+ const files = fs.readdirSync(directoryPath, { recursive: true })
52
+ .filter(f => {
53
+ const isInternal = f === '.art' || f.startsWith('.art' + path.sep);
54
+ return !isInternal && !shouldIgnore(f);
55
+ });
57
56
 
58
- const saveManifestPart = () => {
59
- if (currentPartFiles.length === 0) return;
57
+ const rootMasterManifest = { parts: [] };
58
+ let currentPartFiles = [];
59
+ let currentPartChars = 0;
60
+ const MAX_PART_CHARS = 32000000;
60
61
 
61
- const partIndex = rootMasterManifest.parts.length;
62
- const partName = `manifest.part.${Date.now()}.${partIndex}.json`;
63
- const partPath = path.join(artDirectory, 'root', partName);
62
+ const saveManifestPart = () => {
63
+ if (currentPartFiles.length === 0) return;
64
64
 
65
- fs.writeFileSync(
66
- partPath,
67
- JSON.stringify({ files: currentPartFiles }, null, 2)
68
- );
65
+ const partIndex = rootMasterManifest.parts.length;
66
+ const partName = `manifest.part.${Date.now()}.${partIndex}.json`;
67
+ const partPath = path.join(artDirectory, 'root', partName);
69
68
 
70
- rootMasterManifest.parts.push(partName);
69
+ fs.writeFileSync(
70
+ partPath,
71
+ JSON.stringify({ files: currentPartFiles }, null, 2)
72
+ );
71
73
 
72
- currentPartFiles = [];
73
- currentPartChars = 0;
74
- };
74
+ rootMasterManifest.parts.push(partName);
75
+ currentPartFiles = [];
76
+ currentPartChars = 0;
77
+ };
75
78
 
76
- for (const file of files) {
77
- const fullPath = path.join(directoryPath, file);
79
+ for (const file of files) {
80
+ const fullPath = path.join(directoryPath, file);
78
81
 
79
- if (fs.lstatSync(fullPath).isFile()) {
80
- const content = fs.readFileSync(fullPath, 'utf8');
82
+ if (fs.lstatSync(fullPath).isFile()) {
83
+ const content = fs.readFileSync(fullPath, 'utf8');
81
84
 
82
- if (currentPartChars + content.length > MAX_PART_CHARS && currentPartFiles.length > 0) {
83
- saveManifestPart();
84
- }
85
+ if (currentPartChars + content.length > MAX_PART_CHARS && currentPartFiles.length > 0) {
86
+ saveManifestPart();
87
+ }
85
88
 
86
- currentPartFiles.push({
87
- path: file,
88
- content: content
89
- });
89
+ currentPartFiles.push({
90
+ path: file,
91
+ content: content
92
+ });
90
93
 
91
- currentPartChars += content.length;
92
- }
93
- }
94
+ currentPartChars += content.length;
95
+ }
96
+ }
94
97
 
95
- saveManifestPart();
98
+ saveManifestPart();
96
99
 
97
- fs.writeFileSync(
98
- path.join(artDirectory, 'root/manifest.json'),
99
- JSON.stringify(rootMasterManifest, null, 2)
100
- );
100
+ fs.writeFileSync(
101
+ path.join(artDirectory, 'root/manifest.json'),
102
+ JSON.stringify(rootMasterManifest, null, 2)
103
+ );
101
104
 
102
- fs.writeFileSync(
103
- path.join(artDirectory, 'history/local/main/manifest.json'),
104
- JSON.stringify({ commits: [] }, null, 2)
105
- );
105
+ fs.writeFileSync(
106
+ path.join(artDirectory, 'history/local/main/manifest.json'),
107
+ JSON.stringify({ commits: [] }, null, 2)
108
+ );
106
109
 
107
- fs.writeFileSync(
108
- path.join(artDirectory, 'history/remote/main/manifest.json'),
109
- JSON.stringify({ commits: [] }, null, 2)
110
- );
110
+ fs.writeFileSync(
111
+ path.join(artDirectory, 'history/remote/main/manifest.json'),
112
+ JSON.stringify({ commits: [] }, null, 2)
113
+ );
111
114
 
112
- const artFile = {
113
- active: { branch: 'main', parent: null },
114
- remote: '',
115
- configuration: { handle: '', personalAccessToken: '' }
116
- };
115
+ const artFile = {
116
+ active: { branch: 'main', parent: null },
117
+ remote: '',
118
+ configuration: { handle: '', personalAccessToken: '' }
119
+ };
117
120
 
118
- fs.writeFileSync(
119
- path.join(artDirectory, 'art.json'),
120
- JSON.stringify(artFile, null, 2)
121
- );
121
+ fs.writeFileSync(
122
+ path.join(artDirectory, 'art.json'),
123
+ JSON.stringify(artFile, null, 2)
124
+ );
122
125
 
123
- return `Initialized empty art repository in ${artDirectory} with ${rootMasterManifest.parts.length} manifest part(s).`;
124
- }
126
+ return `Initialized empty art repository in ${artDirectory}.`;
127
+ }
125
128
 
126
129
  /**
127
- * Clone a repository
128
- * @param {string} repoSlug - The handle/repo identifier.
129
- * @param {string} providedToken - Optional token for authentication.
130
- */
131
-
132
- /**
133
- * art - Modern version control.
134
- * Module: Setup (v0.2.9) - Clone logic
130
+ * Clone a repository and replay history.
135
131
  */
136
132
 
137
133
  async function clone (repoSlug, providedToken = null) {
@@ -148,19 +144,20 @@ async function clone (repoSlug, providedToken = null) {
148
144
  }
149
145
 
150
146
  fs.mkdirSync(targetPath, { recursive: true });
151
- init(targetPath);
147
+
148
+ const artPath = path.join(targetPath, '.art');
149
+
150
+ ensureDirStructure(artPath);
152
151
  process.chdir(targetPath);
153
152
 
154
153
  try {
155
- const artPath = path.join(targetPath, '.art');
156
154
  const artJsonPath = path.join(artPath, 'art.json');
157
- const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
158
155
 
159
- if (providedToken) {
160
- artJson.configuration.personalAccessToken = providedToken;
161
- }
162
-
163
- artJson.remote = `${ARTIFACT_HOST}/${handle}/${repo}`;
156
+ const artJson = {
157
+ active: { branch: 'main', parent: null },
158
+ remote: `${ARTIFACT_HOST}/${handle}/${repo}`,
159
+ configuration: { handle: '', personalAccessToken: providedToken || '' }
160
+ };
164
161
 
165
162
  fs.writeFileSync(artJsonPath, JSON.stringify(artJson, null, 2));
166
163
 
@@ -174,7 +171,6 @@ async function clone (repoSlug, providedToken = null) {
174
171
  handle,
175
172
  repo,
176
173
  branch: 'main',
177
-
178
174
  ...(token && { personalAccessToken: token })
179
175
  })
180
176
  });
@@ -191,12 +187,10 @@ async function clone (repoSlug, providedToken = null) {
191
187
  });
192
188
 
193
189
  const partData = await partRes.json();
194
-
195
190
  fs.writeFileSync(path.join(artPath, 'root', partName), JSON.stringify(partData, null, 2));
196
191
 
197
192
  for (const file of partData.files) {
198
193
  const workingPath = path.join(targetPath, file.path);
199
-
200
194
  fs.mkdirSync(path.dirname(workingPath), { recursive: true });
201
195
  fs.writeFileSync(workingPath, file.content);
202
196
  }
@@ -209,7 +203,6 @@ async function clone (repoSlug, providedToken = null) {
209
203
  });
210
204
 
211
205
  const historyManifest = await historyRes.json();
212
-
213
206
  const localHistoryDir = path.join(artPath, 'history/local/main');
214
207
  const remoteHistoryDir = path.join(artPath, 'history/remote/main');
215
208
 
@@ -217,10 +210,19 @@ async function clone (repoSlug, providedToken = null) {
217
210
  const commitRes = await fetch(`${ARTIFACT_HOST}/commit`, {
218
211
  method: 'POST',
219
212
  headers: { 'Content-Type': 'application/json' },
220
- body: JSON.stringify({ handle, repo, branch: 'main', hash: commitHash, ...(token && { personalAccessToken: token }) })
213
+ body: JSON.stringify({
214
+ handle,
215
+ repo,
216
+ branch: 'main',
217
+ hash: commitHash,
218
+
219
+ ...(token && { personalAccessToken: token
220
+ })
221
+ })
221
222
  });
222
223
 
223
224
  const commitMaster = await commitRes.json();
225
+
224
226
  let fullChanges = {};
225
227
 
226
228
  if (commitMaster.parts && commitMaster.parts.length > 0) {
@@ -232,7 +234,6 @@ async function clone (repoSlug, providedToken = null) {
232
234
  });
233
235
 
234
236
  const partData = await partRes.json();
235
-
236
237
  fs.writeFileSync(path.join(localHistoryDir, partName), JSON.stringify(partData, null, 2));
237
238
  fs.writeFileSync(path.join(remoteHistoryDir, partName), JSON.stringify(partData, null, 2));
238
239
 
@@ -252,7 +253,6 @@ async function clone (repoSlug, providedToken = null) {
252
253
 
253
254
  if (Array.isArray(changeSet)) {
254
255
  let content = fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf8') : '';
255
-
256
256
  for (const op of changeSet) {
257
257
  if (op.type === 'insert') {
258
258
  content = content.slice(0, op.position) + op.content + content.slice(op.position);
@@ -260,7 +260,6 @@ async function clone (repoSlug, providedToken = null) {
260
260
  content = content.slice(0, op.position) + content.slice(op.position + op.length);
261
261
  }
262
262
  }
263
-
264
263
  fs.writeFileSync(fullPath, content);
265
264
  } else if (changeSet.type === 'createFile') {
266
265
  fs.mkdirSync(path.dirname(fullPath), { recursive: true });
@@ -292,8 +291,7 @@ async function clone (repoSlug, providedToken = null) {
292
291
  /**
293
292
  * Updates the configuration in art.json.
294
293
  */
295
-
296
- function config (key, value) {
294
+ function config(key, value) {
297
295
  const manifestPath = path.join(process.cwd(), '.art', 'art.json');
298
296
 
299
297
  if (!fs.existsSync(manifestPath)) {
@@ -304,7 +302,6 @@ function config (key, value) {
304
302
 
305
303
  if (key && value !== undefined) {
306
304
  manifest.configuration[key] = value;
307
-
308
305
  fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
309
306
  }
310
307
 
@@ -316,5 +313,6 @@ module.exports = {
316
313
  __libraryAPIName: 'Setup',
317
314
  init,
318
315
  clone,
319
- config
316
+ config,
317
+ ensureDirStructure
320
318
  };
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Module: Utils (v0.2.9)
3
+ * Module: Utils (v0.3.0)
4
4
  */
5
5
 
6
6
  const fs = require('fs');
package/workflow/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Module: Workflow (v0.2.9)
3
+ * Module: Workflow (v0.3.0)
4
4
  */
5
5
 
6
6
  const fs = require('fs');
@@ -10,6 +10,8 @@ const crypto = require('crypto');
10
10
  const getStateByHash = require('../utils/getStateByHash');
11
11
  const shouldIgnore = require('../utils/shouldIgnore');
12
12
 
13
+ const MAX_PART_SIZE = 32000000;
14
+
13
15
  /**
14
16
  * Helper to load all changes from a paginated stage directory.
15
17
  */
@@ -27,8 +29,10 @@ function getStagedChanges(artPath) {
27
29
 
28
30
  for (const partName of manifest.parts) {
29
31
  const partPath = path.join(stageDir, partName);
32
+
30
33
  if (fs.existsSync(partPath)) {
31
34
  const partData = JSON.parse(fs.readFileSync(partPath, 'utf8'));
35
+
32
36
  Object.assign(allChanges, partData.changes);
33
37
  }
34
38
  }
@@ -72,6 +76,7 @@ function status () {
72
76
 
73
77
  if (isIgnored && !isActive && !isStaged) {
74
78
  ignored.push(file);
79
+
75
80
  continue;
76
81
  }
77
82
 
@@ -79,6 +84,7 @@ function status () {
79
84
  untracked.push(file);
80
85
  } else if (!isStaged && isActive) {
81
86
  const currentContent = fs.readFileSync(path.join(root, file), 'utf8');
87
+
82
88
  if (currentContent !== activeState[file]) {
83
89
  modified.push(file);
84
90
  }
@@ -100,14 +106,15 @@ function status () {
100
106
  */
101
107
 
102
108
  function add (targetPath) {
103
- const MAX_PART_SIZE = 32000000;
104
109
  const root = process.cwd();
105
110
  const artPath = path.join(root, '.art');
106
111
  const stageDir = path.join(artPath, 'stage');
107
112
  const artJsonPath = path.join(artPath, 'art.json');
108
113
  const fullPath = path.resolve(root, targetPath);
109
114
 
110
- if (!fs.existsSync(fullPath)) throw new Error(`Path does not exist: ${targetPath}`);
115
+ if (!fs.existsSync(fullPath)) {
116
+ throw new Error(`Path does not exist: ${targetPath}`);
117
+ }
111
118
 
112
119
  const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
113
120
  const activeState = getStateByHash(artJson.active.branch, artJson.active.parent) || {};
@@ -290,7 +297,7 @@ function commit (message) {
290
297
  }
291
298
 
292
299
  module.exports = {
293
- __libraryVersion: '0.2.9',
300
+ __libraryVersion: '0.3.0',
294
301
  __libraryAPIName: 'Workflow',
295
302
  status,
296
303
  add,