pagan-artifact 0.2.8 → 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/changes/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Module: Changes (v0.2.8)
3
+ * Module: Changes (v0.3.0)
4
4
  */
5
5
 
6
6
  const fs = require('fs');
@@ -34,15 +34,17 @@ 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
- const commitData = JSON.parse(fs.readFileSync(path.join(branchPath, `${hash}.json`), 'utf8'));
39
+ const commitMasterPath = path.join(branchPath, `${hash}.json`);
40
+
41
+ if (fs.existsSync(commitMasterPath)) {
42
+ const commitData = JSON.parse(fs.readFileSync(commitMasterPath, 'utf8'));
42
43
 
43
- output += `commit ${commitData.hash}\n`;
44
- output += `Date: ${new Date(commitData.timestamp).toLocaleString()}\n`;
45
- output += `\n ${commitData.message}\n\n`;
44
+ output += `commit ${commitData.hash}\n`;
45
+ output += `Date: ${new Date(commitData.timestamp).toLocaleString()}\n`;
46
+ output += `\n ${commitData.message}\n\n`;
47
+ }
46
48
  }
47
49
 
48
50
  return output;
@@ -50,14 +52,19 @@ function log () {
50
52
 
51
53
  /**
52
54
  * Displays line-by-line differences between working directory and the last commit/stage.
53
- * @returns {string} Formatted diff output.
55
+ * @returns {object} Formatted diff output and staged file list.
54
56
  */
55
57
 
56
- function diff () {
58
+ function diff () {
57
59
  const root = process.cwd();
58
60
  const artPath = path.join(root, '.art');
59
- const artJson = JSON.parse(fs.readFileSync(path.join(artPath, 'art.json'), 'utf8'));
61
+ const artJsonPath = path.join(artPath, 'art.json');
62
+
63
+ if (!fs.existsSync(artJsonPath)) {
64
+ throw new Error('No art repository found.');
65
+ }
60
66
 
67
+ const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
61
68
  const activeBranch = artJson.active.branch;
62
69
  const lastCommitHash = artJson.active.parent;
63
70
  const lastCommitState = lastCommitHash ? getStateByHash(activeBranch, lastCommitHash) : {};
@@ -69,10 +76,13 @@ function log () {
69
76
 
70
77
  for (const filePath of currentFiles) {
71
78
  const fullPath = path.join(root, filePath);
72
- 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');
73
83
  const previousContent = lastCommitState[filePath] || '';
74
84
 
75
- if (currentContent !== previousContent) {
85
+ if (!isBinary && currentContent !== previousContent) {
76
86
  let start = 0;
77
87
 
78
88
  while (start < previousContent.length && start < currentContent.length && previousContent[start] === currentContent[start]) {
@@ -92,17 +102,41 @@ function log () {
92
102
  deleted: previousContent.slice(start, oldEnd + 1),
93
103
  added: currentContent.slice(start, newEnd + 1)
94
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
+ }
95
111
  }
96
112
  }
97
113
 
98
- const stagePath = path.join(artPath, 'stage.json');
99
- const staged = fs.existsSync(stagePath) ? Object.keys(JSON.parse(fs.readFileSync(stagePath, 'utf8')).changes) : [];
114
+ const stageDir = path.join(artPath, 'stage');
115
+ const stageManifestPath = path.join(stageDir, 'manifest.json');
116
+
117
+ let staged = [];
118
+
119
+ if (fs.existsSync(stageManifestPath)) {
120
+ const manifest = JSON.parse(fs.readFileSync(stageManifestPath, 'utf8'));
121
+ const stagedFilesSet = new Set();
122
+
123
+ for (const partName of manifest.parts) {
124
+ const partPath = path.join(stageDir, partName);
125
+
126
+ if (fs.existsSync(partPath)) {
127
+ const partData = JSON.parse(fs.readFileSync(partPath, 'utf8'));
128
+
129
+ Object.keys(partData.changes).forEach(file => stagedFilesSet.add(file));
130
+ }
131
+ }
132
+ staged = Array.from(stagedFilesSet);
133
+ }
100
134
 
101
135
  return { fileDiffs, staged };
102
136
  }
103
137
 
104
138
  module.exports = {
105
- __libraryVersion: '0.2.8',
139
+ __libraryVersion: '0.3.0',
106
140
  __libraryAPIName: 'Changes',
107
141
  log,
108
142
  diff
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Module: Contributions (v0.2.8)
3
+ * Module: Contributions (v0.3.0)
4
4
  */
5
5
 
6
6
  const fs = require('fs');
@@ -13,25 +13,19 @@ const ARTIFACT_HOST = pkg.artConfig.host || 'http://localhost:1337';
13
13
 
14
14
  /**
15
15
  * Configures the single URL endpoint in art.json for synchronization.
16
- * Supports full URLs or "handle/repo" slugs.
17
16
  */
18
17
 
19
18
  function remote (input) {
20
19
  const artPath = path.join(process.cwd(), '.art', 'art.json');
21
-
22
- if (!fs.existsSync(artPath)) {
23
- throw new Error('No art repository found.');
24
- }
20
+ if (!fs.existsSync(artPath)) throw new Error('No art repository found.');
25
21
 
26
22
  const artJson = JSON.parse(fs.readFileSync(artPath, 'utf8'));
27
23
 
28
24
  if (input) {
29
25
  let finalUrl = input;
30
-
31
26
  if (input.includes('/') && !input.startsWith('http')) {
32
27
  finalUrl = `${ARTIFACT_HOST}/${input}`;
33
28
  }
34
-
35
29
  artJson.remote = finalUrl;
36
30
  fs.writeFileSync(artPath, JSON.stringify(artJson, null, 2));
37
31
  }
@@ -40,16 +34,14 @@ function remote (input) {
40
34
  }
41
35
 
42
36
  /**
43
- * Downloads JSON diff files from the remote server via POST.
37
+ * Downloads paginated JSON diff files from the remote server.
44
38
  */
45
39
 
46
40
  async function fetchRemote () {
47
41
  const artPath = path.join(process.cwd(), '.art');
48
42
  const artJson = JSON.parse(fs.readFileSync(path.join(artPath, 'art.json'), 'utf8'));
49
43
 
50
- if (!artJson.remote) {
51
- throw new Error('Remote URL not configured. Use "art remote <handle>/<repo>".');
52
- }
44
+ if (!artJson.remote) throw new Error('Remote URL not configured.');
53
45
 
54
46
  const branch = artJson.active.branch;
55
47
  const token = artJson.configuration.personalAccessToken;
@@ -72,7 +64,6 @@ async function fetchRemote () {
72
64
  handle,
73
65
  repo,
74
66
  branch,
75
-
76
67
  ...(token && { personalAccessToken: token })
77
68
  })
78
69
  });
@@ -80,38 +71,50 @@ async function fetchRemote () {
80
71
  const remoteManifest = await response.json();
81
72
 
82
73
  for (const commitHash of remoteManifest.commits) {
83
- const commitFilePath = path.join(remoteBranchPath, `${commitHash}.json`);
74
+ const commitMasterPath = path.join(remoteBranchPath, `${commitHash}.json`);
84
75
 
85
- if (!fs.existsSync(commitFilePath)) {
86
- const commitResponse = await fetch(`${ARTIFACT_HOST}/commit`, {
76
+ if (!fs.existsSync(commitMasterPath)) {
77
+ const commitRes = await fetch(`${ARTIFACT_HOST}/commit`, {
87
78
  method: 'POST',
88
79
  headers: { 'Content-Type': 'application/json' },
89
80
  body: JSON.stringify({
90
- handle,
91
- repo,
92
- branch,
93
- hash: commitHash,
94
-
81
+ handle, repo, branch, hash: commitHash,
95
82
  ...(token && { personalAccessToken: token })
96
83
  })
97
84
  });
98
85
 
99
- const commitDiff = await commitResponse.json();
86
+ const commitMaster = await commitRes.json();
100
87
 
101
- fs.writeFileSync(commitFilePath, JSON.stringify(commitDiff, null, 2));
88
+ fs.writeFileSync(commitMasterPath, JSON.stringify(commitMaster, null, 2));
89
+
90
+ for (const partName of commitMaster.parts) {
91
+ const partPath = path.join(remoteBranchPath, partName);
92
+
93
+ if (!fs.existsSync(partPath)) {
94
+ const partRes = await fetch(`${ARTIFACT_HOST}/commit/part`, {
95
+ method: 'POST',
96
+ headers: { 'Content-Type': 'application/json' },
97
+ body: JSON.stringify({
98
+ handle, repo, branch, partName,
99
+ ...(token && { personalAccessToken: token })
100
+ })
101
+ });
102
+
103
+ const partData = await partRes.json();
104
+
105
+ fs.writeFileSync(partPath, JSON.stringify(partData, null, 2));
106
+ }
107
+ }
102
108
  }
103
109
  }
104
110
 
105
- fs.writeFileSync(
106
- path.join(remoteBranchPath, 'manifest.json'),
107
- JSON.stringify(remoteManifest, null, 2)
108
- );
111
+ fs.writeFileSync(path.join(remoteBranchPath, 'manifest.json'), JSON.stringify(remoteManifest, null, 2));
109
112
 
110
113
  return `Fetched remote history for branch: ${branch}`;
111
114
  }
112
115
 
113
116
  /**
114
- * Performs a fetch and applies remote JSON diffs to local branch and files.
117
+ * Applies remote paginated commits to local branch.
115
118
  */
116
119
 
117
120
  async function pull () {
@@ -121,11 +124,11 @@ async function pull () {
121
124
 
122
125
  await fetchRemote();
123
126
 
124
- const remoteManifestPath = path.join(artPath, 'history/remote', branch, 'manifest.json');
125
- const remoteManifest = JSON.parse(fs.readFileSync(remoteManifestPath, 'utf8'));
127
+ const remoteBranchPath = path.join(artPath, 'history/remote', branch);
128
+ const localBranchPath = path.join(artPath, 'history/local', branch);
126
129
 
127
- const localManifestPath = path.join(artPath, 'history/local', branch, 'manifest.json');
128
- const localManifest = JSON.parse(fs.readFileSync(localManifestPath, 'utf8'));
130
+ const remoteManifest = JSON.parse(fs.readFileSync(path.join(remoteBranchPath, 'manifest.json'), 'utf8'));
131
+ const localManifest = JSON.parse(fs.readFileSync(path.join(localBranchPath, 'manifest.json'), 'utf8'));
129
132
 
130
133
  const newCommits = remoteManifest.commits.filter(hash => !localManifest.commits.includes(hash));
131
134
 
@@ -134,100 +137,127 @@ async function pull () {
134
137
  }
135
138
 
136
139
  for (const commitHash of newCommits) {
137
- const remoteCommitFile = path.join(artPath, 'history/remote', branch, `${commitHash}.json`);
138
- const remoteData = fs.readFileSync(remoteCommitFile, 'utf8');
140
+ const masterData = fs.readFileSync(path.join(remoteBranchPath, `${commitHash}.json`), 'utf8');
141
+ const masterJson = JSON.parse(masterData);
142
+
143
+ fs.writeFileSync(path.join(localBranchPath, `${commitHash}.json`), masterData);
139
144
 
140
- fs.writeFileSync(
141
- path.join(artPath, 'history/local', branch, `${commitHash}.json`),
142
- remoteData
143
- );
145
+ for (const partName of masterJson.parts) {
146
+ const partContent = fs.readFileSync(path.join(remoteBranchPath, partName), 'utf8');
147
+
148
+ fs.writeFileSync(path.join(localBranchPath, partName), partContent);
149
+ }
144
150
 
145
151
  localManifest.commits.push(commitHash);
146
152
  }
147
153
 
148
- fs.writeFileSync(localManifestPath, JSON.stringify(localManifest, null, 2));
154
+ fs.writeFileSync(path.join(localBranchPath, 'manifest.json'), JSON.stringify(localManifest, null, 2));
149
155
  checkout(branch);
150
156
 
151
157
  return `Applied ${newCommits.length} commits.`;
152
158
  }
153
159
 
154
160
  /**
155
- * Uploads local JSON diffs that do not exist in the remote history.
161
+ * Uploads local commits via the two-step /push and /commit/part flow.
156
162
  */
157
163
 
158
- async function push () {
159
- const artPath = path.join(process.cwd(), '.art');
160
- const artJson = JSON.parse(fs.readFileSync(path.join(artPath, 'art.json'), 'utf8'));
164
+ async function push () {
165
+ const artPath = path.join(process.cwd(), '.art');
166
+ const artJson = JSON.parse(fs.readFileSync(path.join(artPath, 'art.json'), 'utf8'));
167
+
168
+ if (!artJson.remote) throw new Error('Remote URL not configured.');
169
+
170
+ const branch = artJson.active.branch;
171
+ const token = artJson.configuration.personalAccessToken;
172
+ const remoteParts = artJson.remote.split('/');
173
+ const repo = remoteParts.pop();
174
+ const handle = remoteParts.pop();
175
+
176
+ const localBranchPath = path.join(artPath, 'history/local', branch);
177
+ const remoteBranchPath = path.join(artPath, 'history/remote', branch);
178
+ const localManifest = JSON.parse(fs.readFileSync(path.join(localBranchPath, 'manifest.json'), 'utf8'));
179
+
180
+ const response = await fetch(`${ARTIFACT_HOST}/manifest`, {
181
+ method: 'POST',
182
+ headers: { 'Content-Type': 'application/json' },
183
+ body: JSON.stringify({
184
+ type: 'history',
185
+ handle,
186
+ repo,
187
+ branch,
188
+ ...(token && { personalAccessToken: token })
189
+ })
190
+ });
191
+
192
+ const remoteManifest = await response.json();
193
+ const missingCommits = localManifest.commits.filter(hash => !remoteManifest.commits.includes(hash));
161
194
 
162
- if (!artJson.remote) {
163
- throw new Error('Remote URL not configured.');
164
- }
195
+ if (missingCommits.length === 0) {
196
+ return 'Everything up to date.';
197
+ }
165
198
 
166
- const branch = artJson.active.branch;
167
- const token = artJson.configuration.personalAccessToken;
168
- const remoteParts = artJson.remote.split('/');
169
- const repo = remoteParts.pop();
170
- const handle = remoteParts.pop();
199
+ let rootData = null;
171
200
 
172
- const localManifest = JSON.parse(fs.readFileSync(path.join(artPath, 'history/local', branch, 'manifest.json'), 'utf8'));
201
+ if (remoteManifest.commits.length === 0) {
202
+ const rootMasterPath = path.join(artPath, 'root/manifest.json');
173
203
 
174
- const response = await fetch(`${ARTIFACT_HOST}/manifest`, {
175
- method: 'POST',
176
- headers: { 'Content-Type': 'application/json' },
177
- body: JSON.stringify({
178
- type: 'history',
179
- handle,
180
- repo,
181
- branch,
204
+ if (fs.existsSync(rootMasterPath)) {
205
+ const master = JSON.parse(fs.readFileSync(rootMasterPath, 'utf8'));
206
+ const parts = {};
182
207
 
183
- ...(token && { personalAccessToken: token })
184
- })
185
- });
208
+ for (const partName of master.parts) {
209
+ parts[partName] = JSON.parse(fs.readFileSync(path.join(artPath, 'root', partName), 'utf8'));
210
+ }
186
211
 
187
- const remoteManifest = await response.json();
188
- const missingCommits = localManifest.commits.filter(hash => !remoteManifest.commits.includes(hash));
212
+ rootData = { master, parts };
213
+ }
214
+ }
189
215
 
190
- if (missingCommits.length === 0) {
191
- return 'Everything up to date.';
192
- }
216
+ const currentRemoteManifestPath = path.join(remoteBranchPath, 'manifest.json');
217
+ const currentRemoteManifest = fs.existsSync(currentRemoteManifestPath)
218
+ ? JSON.parse(fs.readFileSync(currentRemoteManifestPath, 'utf8'))
219
+ : { commits: [] };
193
220
 
194
- let rootData = null;
221
+ for (const commitHash of missingCommits) {
222
+ const commitMaster = JSON.parse(fs.readFileSync(path.join(localBranchPath, `${commitHash}.json`), 'utf8'));
195
223
 
196
- if (remoteManifest.commits.length === 0) {
197
- const rootManifestPath = path.join(artPath, 'root/manifest.json');
224
+ const pushRes = await fetch(`${ARTIFACT_HOST}/push`, {
225
+ method: 'POST',
226
+ headers: { 'Content-Type': 'application/json' },
227
+ body: JSON.stringify({
228
+ handle, repo, branch, commit: commitMaster,
198
229
 
199
- if (fs.existsSync(rootManifestPath)) {
200
- rootData = JSON.parse(fs.readFileSync(rootManifestPath, 'utf8'));
201
- }
202
- }
230
+ ...(rootData && { root: rootData }),
203
231
 
204
- for (const commitHash of missingCommits) {
205
- const commitData = JSON.parse(fs.readFileSync(path.join(artPath, 'history/local', branch, `${commitHash}.json`), 'utf8'));
232
+ ...(token && { personalAccessToken: token })
233
+ })
234
+ });
206
235
 
207
- await fetch(`${ARTIFACT_HOST}/push`, {
208
- method: 'POST',
209
- headers: { 'Content-Type': 'application/json' },
210
- body: JSON.stringify({
211
- handle,
212
- repo,
213
- branch,
214
- commit: commitData,
236
+ if (!pushRes.ok) throw new Error(`Server rejected push for commit ${commitHash}`);
215
237
 
216
- ...(rootData && { root: rootData }),
238
+ rootData = null;
217
239
 
218
- ...(token && { personalAccessToken: token })
219
- })
220
- });
240
+ for (const partName of commitMaster.parts) {
241
+ const partData = JSON.parse(fs.readFileSync(path.join(localBranchPath, partName), 'utf8'));
221
242
 
222
- rootData = null;
223
- }
243
+ const partRes = await fetch(`${ARTIFACT_HOST}/commit/part`, {
244
+ method: 'POST',
245
+ headers: { 'Content-Type': 'application/json' },
246
+ body: JSON.stringify({
247
+ handle, repo, branch, partName, partData,
248
+ ...(token && { personalAccessToken: token })
249
+ })
250
+ });
224
251
 
225
- const remoteManifestPath = path.join(artPath, 'history/remote', branch, 'manifest.json');
252
+ if (!partRes.ok) throw new Error(`Server failed to receive part ${partName}`);
253
+ }
226
254
 
227
- fs.writeFileSync(remoteManifestPath, JSON.stringify(localManifest, null, 2));
255
+ currentRemoteManifest.commits.push(commitHash);
256
+ fs.writeFileSync(currentRemoteManifestPath, JSON.stringify(currentRemoteManifest, null, 2));
257
+ }
228
258
 
229
- return `Pushed ${missingCommits.length} commits to remote.`;
230
- }
259
+ return `Pushed ${missingCommits.length} commits to remote.`;
260
+ }
231
261
 
232
262
  module.exports = {
233
263
  __libraryVersion: pkg.version,
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Core Library Entry Point (v0.2.8)
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.8',
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.8",
3
+ "version": "0.3.0",
4
4
  "description": "Modern version control.",
5
5
  "main": "index.js",
6
6
  "bin": {