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.
@@ -1,6 +1,8 @@
1
1
  /**
2
- * art - Modern version control.
3
- * Module: Contributions (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: Contributions (v0.3.3)
4
6
  */
5
7
 
6
8
  const fs = require('fs');
@@ -13,21 +15,39 @@ const ARTIFACT_HOST = pkg.artConfig.host || 'http://localhost:1337';
13
15
 
14
16
  /**
15
17
  * Configures the single URL endpoint in art.json for synchronization.
18
+ * @param {string} input - The remote URL or slug.
19
+ * @returns {string} The updated remote URL.
16
20
  */
17
21
 
18
22
  function remote (input) {
19
- const artPath = path.join(process.cwd(), '.art', 'art.json');
20
- if (!fs.existsSync(artPath)) throw new Error('No art repository found.');
23
+ /**
24
+ * Validate the existence of the repository configuration before proceeding.
25
+ */
21
26
 
22
- const artJson = JSON.parse(fs.readFileSync(artPath, 'utf8'));
27
+ const artifactPath = path.join(process.cwd(), '.art', 'art.json');
28
+
29
+ if (!fs.existsSync(artifactPath)) {
30
+ throw new Error('No art repository found.');
31
+ }
32
+
33
+ const artJson = JSON.parse(
34
+ fs.readFileSync(artifactPath, 'utf8')
35
+ );
36
+
37
+ /**
38
+ * Normalize the input URL.
39
+ * If a slug (handle/repo) is provided, prepend the default host.
40
+ */
23
41
 
24
42
  if (input) {
25
43
  let finalUrl = input;
44
+
26
45
  if (input.includes('/') && !input.startsWith('http')) {
27
46
  finalUrl = `${ARTIFACT_HOST}/${input}`;
28
47
  }
48
+
29
49
  artJson.remote = finalUrl;
30
- fs.writeFileSync(artPath, JSON.stringify(artJson, null, 2));
50
+ fs.writeFileSync(artifactPath, JSON.stringify(artJson, null, 2));
31
51
  }
32
52
 
33
53
  return artJson.remote;
@@ -35,53 +55,80 @@ function remote (input) {
35
55
 
36
56
  /**
37
57
  * Downloads paginated JSON diff files from the remote server.
58
+ * @returns {Promise<string>} Success message.
38
59
  */
39
60
 
40
61
  async function fetchRemote () {
41
- const artPath = path.join(process.cwd(), '.art');
42
- const artJson = JSON.parse(fs.readFileSync(path.join(artPath, 'art.json'), 'utf8'));
62
+ /**
63
+ * Load local configuration and extract remote repository metadata.
64
+ */
43
65
 
44
- if (!artJson.remote) throw new Error('Remote URL not configured.');
66
+ const artifactPath = path.join(process.cwd(), '.art');
67
+ const artifactJson = JSON.parse(
68
+ fs.readFileSync(path.join(artifactPath, 'art.json'), 'utf8')
69
+ );
45
70
 
46
- const branch = artJson.active.branch;
47
- const token = artJson.configuration.personalAccessToken;
71
+ if (!artifactJson.remote) {
72
+ throw new Error('Remote URL not configured.');
73
+ }
48
74
 
49
- const remoteParts = artJson.remote.split('/');
75
+ const branch = artifactJson.active.branch;
76
+ const token = artifactJson.configuration.personalAccessToken;
77
+ const remoteParts = artifactJson.remote.split('/');
50
78
  const repo = remoteParts.pop();
51
79
  const handle = remoteParts.pop();
80
+ const remoteBranchPath = path.join(artifactPath, 'history/remote', branch);
52
81
 
53
- const remoteBranchPath = path.join(artPath, 'history/remote', branch);
82
+ /**
83
+ * Ensure the remote history directory exists for the current branch.
84
+ */
54
85
 
55
86
  if (!fs.existsSync(remoteBranchPath)) {
56
87
  fs.mkdirSync(remoteBranchPath, { recursive: true });
57
88
  }
58
89
 
59
- const response = await fetch(`${ARTIFACT_HOST}/manifest`, {
60
- method: 'POST',
61
- headers: { 'Content-Type': 'application/json' },
62
- body: JSON.stringify({
63
- type: 'history',
64
- handle,
65
- repo,
66
- branch,
67
- ...(token && { personalAccessToken: token })
68
- })
69
- });
90
+ /**
91
+ * Request the commit manifest from the remote server.
92
+ */
93
+
94
+ const response = await fetch(
95
+ `${ARTIFACT_HOST}/manifest`,
96
+ {
97
+ method: 'POST',
98
+ headers: { 'Content-Type': 'application/json' },
99
+ body: JSON.stringify({
100
+ type: 'history',
101
+ handle,
102
+ repo,
103
+ branch,
104
+
105
+ ...(token && { personalAccessToken: token })
106
+ })
107
+ }
108
+ );
70
109
 
71
110
  const remoteManifest = await response.json();
72
111
 
112
+ /**
113
+ * Synchronize missing commits and their respective file parts from the server.
114
+ */
115
+
73
116
  for (const commitHash of remoteManifest.commits) {
74
117
  const commitMasterPath = path.join(remoteBranchPath, `${commitHash}.json`);
75
118
 
76
119
  if (!fs.existsSync(commitMasterPath)) {
77
- const commitRes = await fetch(`${ARTIFACT_HOST}/commit`, {
78
- method: 'POST',
79
- headers: { 'Content-Type': 'application/json' },
80
- body: JSON.stringify({
81
- handle, repo, branch, hash: commitHash,
82
- ...(token && { personalAccessToken: token })
83
- })
84
- });
120
+ const commitRes = await fetch(
121
+ `${ARTIFACT_HOST}/commit`,
122
+ {
123
+ method: 'POST',
124
+ headers: { 'Content-Type': 'application/json' },
125
+ body: JSON.stringify({
126
+ handle, repo, branch, hash: commitHash,
127
+
128
+ ...(token && { personalAccessToken: token })
129
+ })
130
+ }
131
+ );
85
132
 
86
133
  const commitMaster = await commitRes.json();
87
134
 
@@ -91,14 +138,22 @@ async function fetchRemote () {
91
138
  const partPath = path.join(remoteBranchPath, partName);
92
139
 
93
140
  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
- });
141
+ const partRes = await fetch(
142
+ `${ARTIFACT_HOST}/part`,
143
+ {
144
+ method: 'POST',
145
+ headers: { 'Content-Type': 'application/json' },
146
+ body: JSON.stringify({
147
+ type: 'history',
148
+ handle,
149
+ repo,
150
+ branch,
151
+ partName,
152
+
153
+ ...(token && { personalAccessToken: token })
154
+ })
155
+ }
156
+ );
102
157
 
103
158
  const partData = await partRes.json();
104
159
 
@@ -108,34 +163,67 @@ async function fetchRemote () {
108
163
  }
109
164
  }
110
165
 
111
- fs.writeFileSync(path.join(remoteBranchPath, 'manifest.json'), JSON.stringify(remoteManifest, null, 2));
166
+ /**
167
+ * Update the local remote-tracking manifest.
168
+ */
169
+
170
+ fs.writeFileSync(
171
+ path.join(remoteBranchPath, 'manifest.json'),
172
+ JSON.stringify(remoteManifest, null, 2)
173
+ );
112
174
 
113
175
  return `Fetched remote history for branch: ${branch}`;
114
176
  }
115
177
 
116
178
  /**
117
- * Applies remote paginated commits to local branch.
179
+ * Applies remote paginated commits to local branch and updates the working tree.
180
+ * @returns {Promise<string>} Success message.
118
181
  */
119
182
 
120
183
  async function pull () {
121
- const artPath = path.join(process.cwd(), '.art');
122
- const artJson = JSON.parse(fs.readFileSync(path.join(artPath, 'art.json'), 'utf8'));
123
- const branch = artJson.active.branch;
184
+ /**
185
+ * Initialize pull by fetching the latest remote data.
186
+ */
187
+
188
+ const artifactPath = path.join(process.cwd(), '.art');
189
+ const artifactJson = JSON.parse(
190
+ fs.readFileSync(path.join(artifactPath, 'art.json'), 'utf8')
191
+ );
192
+ const branch = artifactJson.active.branch;
124
193
 
125
194
  await fetchRemote();
126
195
 
127
- const remoteBranchPath = path.join(artPath, 'history/remote', branch);
128
- const localBranchPath = path.join(artPath, 'history/local', branch);
196
+ /**
197
+ * Compare local and remote manifests to identify new commits.
198
+ */
199
+
200
+ const remoteBranchPath = path.join(artifactPath, 'history/remote', branch);
201
+ const localBranchPath = path.join(artifactPath, 'history/local', branch);
129
202
 
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'));
203
+ const remoteManifest = JSON.parse(
204
+ fs.readFileSync(path.join(remoteBranchPath, 'manifest.json'), 'utf8')
205
+ );
132
206
 
133
- const newCommits = remoteManifest.commits.filter(hash => !localManifest.commits.includes(hash));
207
+ const localManifest = JSON.parse(
208
+ fs.readFileSync(path.join(localBranchPath, 'manifest.json'), 'utf8')
209
+ );
210
+
211
+ const newCommits = [];
212
+
213
+ for (const hash of remoteManifest.commits) {
214
+ if (!localManifest.commits.includes(hash)) {
215
+ newCommits.push(hash);
216
+ }
217
+ }
134
218
 
135
219
  if (newCommits.length === 0) {
136
220
  return 'Already up to date.';
137
221
  }
138
222
 
223
+ /**
224
+ * Transfer commit files and data parts from remote history to local history.
225
+ */
226
+
139
227
  for (const commitHash of newCommits) {
140
228
  const masterData = fs.readFileSync(path.join(remoteBranchPath, `${commitHash}.json`), 'utf8');
141
229
  const masterJson = JSON.parse(masterData);
@@ -151,7 +239,15 @@ async function pull () {
151
239
  localManifest.commits.push(commitHash);
152
240
  }
153
241
 
154
- fs.writeFileSync(path.join(localBranchPath, 'manifest.json'), JSON.stringify(localManifest, null, 2));
242
+ /**
243
+ * Finalize the pull by updating the local manifest and reconstructing the working directory.
244
+ */
245
+
246
+ fs.writeFileSync(
247
+ path.join(localBranchPath, 'manifest.json'),
248
+ JSON.stringify(localManifest, null, 2)
249
+ );
250
+
155
251
  checkout(branch);
156
252
 
157
253
  return `Applied ${newCommits.length} commits.`;
@@ -159,54 +255,87 @@ async function pull () {
159
255
 
160
256
  /**
161
257
  * Uploads local commits via the two-step /push and /commit/part flow.
258
+ * @returns {Promise<string>} Success message.
162
259
  */
163
260
 
164
261
  async function push () {
165
- const artPath = path.join(process.cwd(), '.art');
166
- const artJson = JSON.parse(fs.readFileSync(path.join(artPath, 'art.json'), 'utf8'));
262
+ /**
263
+ * Load local state and prepare for server-side manifest comparison.
264
+ */
265
+
266
+ const artifactPath = path.join(process.cwd(), '.art');
267
+ const artifactJson = JSON.parse(
268
+ fs.readFileSync(path.join(artifactPath, 'art.json'), 'utf8')
269
+ );
167
270
 
168
- if (!artJson.remote) throw new Error('Remote URL not configured.');
271
+ if (!artifactJson.remote) {
272
+ throw new Error('Remote URL not configured.');
273
+ }
169
274
 
170
- const branch = artJson.active.branch;
171
- const token = artJson.configuration.personalAccessToken;
172
- const remoteParts = artJson.remote.split('/');
275
+ const branch = artifactJson.active.branch;
276
+ const token = artifactJson.configuration.personalAccessToken;
277
+ const remoteParts = artifactJson.remote.split('/');
173
278
  const repo = remoteParts.pop();
174
279
  const handle = remoteParts.pop();
280
+ const localBranchPath = path.join(artifactPath, 'history/local', branch);
281
+ const remoteBranchPath = path.join(artifactPath, 'history/remote', branch);
282
+ const localManifest = JSON.parse(
283
+ fs.readFileSync(path.join(localBranchPath, 'manifest.json'), 'utf8')
284
+ );
285
+
286
+ /**
287
+ * Identify local commits that do not yet exist on the remote server.
288
+ */
289
+
290
+ const response = await fetch(
291
+ `${ARTIFACT_HOST}/manifest`,
292
+ {
293
+ method: 'POST',
294
+ headers: { 'Content-Type': 'application/json' },
295
+ body: JSON.stringify({
296
+ type: 'history',
297
+ handle,
298
+ repo,
299
+ branch,
175
300
 
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
- });
301
+ ...(token && { personalAccessToken: token })
302
+ })
303
+ }
304
+ );
191
305
 
192
306
  const remoteManifest = await response.json();
193
- const missingCommits = localManifest.commits.filter(hash => !remoteManifest.commits.includes(hash));
307
+ const missingCommits = [];
308
+
309
+ for (const hash of localManifest.commits) {
310
+ if (!remoteManifest.commits.includes(hash)) {
311
+ missingCommits.push(hash);
312
+ }
313
+ }
194
314
 
195
315
  if (missingCommits.length === 0) {
196
316
  return 'Everything up to date.';
197
317
  }
198
318
 
319
+ /**
320
+ * If the remote repository is empty, prepare the initial root data for upload.
321
+ */
322
+
199
323
  let rootData = null;
200
324
 
201
325
  if (remoteManifest.commits.length === 0) {
202
- const rootMasterPath = path.join(artPath, 'root/manifest.json');
326
+ const rootMasterPath = path.join(artifactPath, 'root/manifest.json');
203
327
 
204
328
  if (fs.existsSync(rootMasterPath)) {
205
- const master = JSON.parse(fs.readFileSync(rootMasterPath, 'utf8'));
329
+ const master = JSON.parse(
330
+ fs.readFileSync(rootMasterPath, 'utf8')
331
+ );
332
+
206
333
  const parts = {};
207
334
 
208
335
  for (const partName of master.parts) {
209
- parts[partName] = JSON.parse(fs.readFileSync(path.join(artPath, 'root', partName), 'utf8'));
336
+ parts[partName] = JSON.parse(
337
+ fs.readFileSync(path.join(artifactPath, 'root', partName), 'utf8')
338
+ );
210
339
  }
211
340
 
212
341
  rootData = { master, parts };
@@ -214,47 +343,66 @@ async function push () {
214
343
  }
215
344
 
216
345
  const currentRemoteManifestPath = path.join(remoteBranchPath, 'manifest.json');
346
+
217
347
  const currentRemoteManifest = fs.existsSync(currentRemoteManifestPath)
218
348
  ? JSON.parse(fs.readFileSync(currentRemoteManifestPath, 'utf8'))
219
349
  : { commits: [] };
220
350
 
221
- for (const commitHash of missingCommits) {
222
- const commitMaster = JSON.parse(fs.readFileSync(path.join(localBranchPath, `${commitHash}.json`), 'utf8'));
351
+ /**
352
+ * Perform sequential uploads for each missing commit
353
+ * and its associated file parts.
354
+ */
223
355
 
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,
229
-
230
- ...(rootData && { root: rootData }),
231
-
232
- ...(token && { personalAccessToken: token })
233
- })
234
- });
235
-
236
- if (!pushRes.ok) throw new Error(`Server rejected push for commit ${commitHash}`);
237
-
238
- rootData = null;
239
-
240
- for (const partName of commitMaster.parts) {
241
- const partData = JSON.parse(fs.readFileSync(path.join(localBranchPath, partName), 'utf8'));
356
+ for (const commitHash of missingCommits) {
357
+ const commitMaster = JSON.parse(
358
+ fs.readFileSync(path.join(localBranchPath, `${commitHash}.json`), 'utf8')
359
+ );
242
360
 
243
- const partRes = await fetch(`${ARTIFACT_HOST}/commit/part`, {
361
+ const pushRes = await fetch(
362
+ `${ARTIFACT_HOST}/push`,
363
+ {
244
364
  method: 'POST',
245
365
  headers: { 'Content-Type': 'application/json' },
246
366
  body: JSON.stringify({
247
- handle, repo, branch, partName, partData,
248
-
367
+ handle, repo, branch, commit: commitMaster,
368
+ ...(rootData && { root: rootData }),
249
369
  ...(token && { personalAccessToken: token })
250
370
  })
251
- });
371
+ }
372
+ );
373
+
374
+ if (!pushRes.ok) {
375
+ throw new Error(`Push rejected (commit: ${commitHash}).`);
376
+ }
377
+
378
+ rootData = null;
379
+
380
+ for (const partName of commitMaster.parts) {
381
+ const partData = JSON.parse(
382
+ fs.readFileSync(path.join(localBranchPath, partName), 'utf8')
383
+ );
384
+
385
+ const partRes = await fetch(
386
+ `${ARTIFACT_HOST}/commit/part`,
387
+ {
388
+ method: 'POST',
389
+ headers: { 'Content-Type': 'application/json' },
390
+ body: JSON.stringify({
391
+ handle, repo, branch, partName, partData,
392
+ ...(token && { personalAccessToken: token })
393
+ })
394
+ }
395
+ );
252
396
 
253
397
  if (!partRes.ok) {
254
- throw new Error(`Server failed to receive part ${partName}`);
398
+ throw new Error(`Push rejected (file part: ${partName}).`);
255
399
  }
256
400
  }
257
401
 
402
+ /**
403
+ * Update the remote-tracking manifest local cache.
404
+ */
405
+
258
406
  currentRemoteManifest.commits.push(commitHash);
259
407
  fs.writeFileSync(currentRemoteManifestPath, JSON.stringify(currentRemoteManifest, null, 2));
260
408
  }
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * art - Modern version control.
3
- * Core Library Entry Point (v0.3.2)
3
+ * Core Library Entry Point (v0.3.3)
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.3.2',
53
+ version: '0.3.3',
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.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Modern version control.",
5
5
  "main": "index.js",
6
6
  "bin": {