pagan-artifact 0.3.2 → 0.3.4
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 +205 -53
- package/branching/index.js +266 -110
- package/caches/index.js +257 -77
- package/changes/index.js +93 -29
- package/contributions/index.js +249 -101
- package/index.js +2 -2
- package/package.json +1 -1
- package/setup/index.js +190 -55
- package/utils/constants.js +15 -0
- package/utils/getStateByHash/index.js +135 -78
- package/utils/shouldIgnore/index.js +44 -3
- package/workflow/index.js +256 -132
package/contributions/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
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.4)
|
|
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
|
-
|
|
20
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Validate the existence of the repository configuration before proceeding.
|
|
25
|
+
*/
|
|
21
26
|
|
|
22
|
-
const
|
|
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(
|
|
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
|
-
|
|
42
|
-
|
|
62
|
+
/**
|
|
63
|
+
* Load local configuration and extract remote repository metadata.
|
|
64
|
+
*/
|
|
43
65
|
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
71
|
+
if (!artifactJson.remote) {
|
|
72
|
+
throw new Error('Remote URL not configured.');
|
|
73
|
+
}
|
|
48
74
|
|
|
49
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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(
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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(
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
128
|
-
|
|
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(
|
|
131
|
-
|
|
203
|
+
const remoteManifest = JSON.parse(
|
|
204
|
+
fs.readFileSync(path.join(remoteBranchPath, 'manifest.json'), 'utf8')
|
|
205
|
+
);
|
|
132
206
|
|
|
133
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
166
|
-
|
|
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 (!
|
|
271
|
+
if (!artifactJson.remote) {
|
|
272
|
+
throw new Error('Remote URL not configured.');
|
|
273
|
+
}
|
|
169
274
|
|
|
170
|
-
const branch =
|
|
171
|
-
const token =
|
|
172
|
-
const remoteParts =
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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 =
|
|
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(
|
|
326
|
+
const rootMasterPath = path.join(artifactPath, 'root/manifest.json');
|
|
203
327
|
|
|
204
328
|
if (fs.existsSync(rootMasterPath)) {
|
|
205
|
-
const master = JSON.parse(
|
|
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(
|
|
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
|
-
|
|
222
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Perform sequential uploads for each missing commit
|
|
353
|
+
* and its associated file parts.
|
|
354
|
+
*/
|
|
223
355
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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,
|
|
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(`
|
|
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.
|
|
3
|
+
* Core Library Entry Point (v0.3.4)
|
|
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.
|
|
53
|
+
version: '0.3.4',
|
|
54
54
|
modules: [
|
|
55
55
|
Setup.__libraryAPIName,
|
|
56
56
|
Workflow.__libraryAPIName,
|