pagan-artifact 0.2.5

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/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 24.11.0
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # Artifact
2
+
3
+ Artifact is a modern and efficient version control system designed for simplicity and transparency. It tracks changes as discrete state snapshots and synchronizes seamlessly the network.
4
+
5
+ ## Installation
6
+
7
+ Install globally via npm to use the `art` command anywhere in your terminal:
8
+
9
+ ```bash
10
+ npm i -g pagan-artifact
11
+ ```
12
+
13
+ Available commands: `init`, `clone`, `status`, `add`, `commit`, `branch`, `checkout`, `merge`, `remote`, `fetch`, `pull`, `push`, `log`, `diff`, `stash`, `reset`, `rm`
package/bin/art.js ADDED
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * art - Modern version control.
5
+ * CLI (v0.2.5)
6
+ */
7
+
8
+ const art = require('../index.js');
9
+
10
+ const [,, command, ...args] = process.argv;
11
+
12
+ const RED = '\x1b[31m';
13
+ const GREEN = '\x1b[32m';
14
+ const RESET = '\x1b[0m';
15
+
16
+ async function run() {
17
+ try {
18
+ switch (command) {
19
+ case 'init':
20
+ console.log(art.init(args[0]));
21
+
22
+ break;
23
+
24
+ case 'clone':
25
+ if (!args[0]) {
26
+ throw new Error('Specify a repository slug (handle/repo).');
27
+ }
28
+
29
+ const tokenIndex = args.indexOf('--token');
30
+ const cliToken = (tokenIndex !== -1) && args[tokenIndex + 1];
31
+
32
+ console.log(await art.clone(args[0], cliToken));
33
+
34
+ break;
35
+
36
+ case 'config':
37
+ console.log(art.config(args[0], args[1]));
38
+
39
+ break;
40
+
41
+ case 'status':
42
+ const {
43
+ activeBranch,
44
+ lastCommit,
45
+ staged,
46
+ modified,
47
+ untracked
48
+ } = art.status();
49
+
50
+ console.log(`On branch ${activeBranch}`);
51
+ console.log(`Last commit: ${lastCommit || 'None'}`);
52
+
53
+ if (staged.length > 0) {
54
+ console.log('\nChanges to be committed:');
55
+ staged.forEach(f => console.log(`${GREEN}\t${f}${RESET}`));
56
+ }
57
+
58
+ if (modified.length > 0) {
59
+ console.log('\nChanges not staged for commit:');
60
+ modified.forEach(f => console.log(`${RED}\tmodified: ${f}${RESET}`));
61
+ }
62
+
63
+ if (untracked.length > 0) {
64
+ console.log('\nUntracked files:');
65
+ untracked.forEach(f => console.log(`${RED}\t${f}${RESET}`));
66
+ }
67
+
68
+ if (untracked.length === 0 && modified.length === 0 && staged.length === 0) {
69
+ console.log('Nothing to commit.');
70
+ }
71
+
72
+ break;
73
+
74
+ case 'add':
75
+ if (!args[0]) throw new Error('Specify a file path to add.');
76
+
77
+ console.log(art.add(args[0]));
78
+
79
+ break;
80
+
81
+ case 'commit':
82
+ if (!args[0]) throw new Error('Specify a commit message.');
83
+ console.log(art.commit(args[0]));
84
+
85
+ break;
86
+
87
+ case 'branch':
88
+ const deleteFlags = ['--delete', '-d', '-D'];
89
+ const isDelete = deleteFlags.includes(args[0]);
90
+ const branchName = isDelete ? args[1] : args[0];
91
+
92
+ const branches = art.branch({ name: branchName, isDelete });
93
+
94
+ if (Array.isArray(branches)) {
95
+ for (const b of branches) console.log(b);
96
+ } else {
97
+ console.log(branches);
98
+ }
99
+
100
+ break;
101
+
102
+ case 'checkout':
103
+ if (!args[0]) throw new Error('Specify a branch name.');
104
+
105
+ console.log(art.checkout(args[0]));
106
+
107
+ break;
108
+
109
+ case 'merge':
110
+ if (!args[0]) throw new Error('Specify a target branch to merge.');
111
+
112
+ console.log(art.merge(args[0]));
113
+
114
+ break;
115
+
116
+ case 'remote':
117
+ console.log(art.remote(args[0]));
118
+
119
+ break;
120
+
121
+ case 'fetch':
122
+ console.log(await art.fetch());
123
+
124
+ break;
125
+
126
+ case 'pull':
127
+ console.log(await art.pull());
128
+
129
+ break;
130
+
131
+ case 'push':
132
+ console.log(await art.push());
133
+
134
+ break;
135
+
136
+ case 'log':
137
+ console.log(art.log());
138
+
139
+ break;
140
+
141
+ case 'diff':
142
+ const { fileDiffs, staged: diffStaged } = art.diff();
143
+
144
+ if (fileDiffs.length === 0 && diffStaged.length === 0) {
145
+ console.log('No changes detected.');
146
+
147
+ break;
148
+ }
149
+
150
+ for (const df of fileDiffs) {
151
+ console.log(`diff --art a/${df.file} b/${df.file}`);
152
+
153
+ if (df.deleted) {
154
+ df.deleted.split('\n').forEach(line => {
155
+ console.log(`${RED}- ${line}${RESET}`);
156
+ });
157
+ }
158
+
159
+ if (df.added) {
160
+ df.added.split('\n').forEach(line => {
161
+ console.log(`${GREEN}+ ${line}${RESET}`);
162
+ });
163
+ }
164
+
165
+ console.log('');
166
+ }
167
+
168
+ if (diffStaged.length > 0) {
169
+ console.log('--- Staged Changes ---');
170
+ diffStaged.forEach(f => console.log(`staged: ${GREEN}${f}${RESET}`));
171
+ }
172
+
173
+ break;
174
+
175
+ case 'stash':
176
+ const isPop = args[0] === 'pop';
177
+ const isList = args[0] === 'list';
178
+ const result = art.stash({ pop: isPop, list: isList });
179
+
180
+ if (isList && Array.isArray(result)) {
181
+ if (result.length === 0) {
182
+ console.log('No stashes found.');
183
+ } else {
184
+ console.log('Saved stashes:');
185
+
186
+ for (const s of result) {
187
+ console.log(`${s.id}: WIP on branch: (${s.date})`);
188
+ }
189
+ }
190
+ } else {
191
+ console.log(result);
192
+ }
193
+
194
+ break;
195
+
196
+ case 'reset':
197
+ console.log(art.reset(args[0]));
198
+
199
+ break;
200
+
201
+ case 'rm':
202
+ if (!args[0]) throw new Error('Specify a file path to remove.');
203
+
204
+ console.log(art.rm(args[0]));
205
+
206
+ break;
207
+
208
+ case '--version':
209
+ case '-v':
210
+ console.log(`art version ${art.version}`);
211
+
212
+ break;
213
+
214
+ default:
215
+ console.log('Usage: art <command> [arguments]');
216
+ console.log('Available commands: init, clone, status, add, commit, branch, checkout, merge, remote, fetch, pull, push, log, diff, stash, reset, rm');
217
+ }
218
+ } catch (error) {
219
+ console.error(`Error: ${error.message}`);
220
+ process.exit(1);
221
+ }
222
+ }
223
+
224
+ run();
@@ -0,0 +1,234 @@
1
+ /**
2
+ * art - Modern version control.
3
+ * Module: Branching (v0.2.5)
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const getStateByHash = require('../utils/getStateByHash');
10
+
11
+ /**
12
+ * Lists, creates, or deletes branches.
13
+ */
14
+
15
+ function branch ({ name, isDelete = false } = {}) {
16
+ const artPath = path.join(process.cwd(), '.art');
17
+ const localHistoryPath = path.join(artPath, 'history/local');
18
+ const remoteHistoryPath = path.join(artPath, 'history/remote');
19
+ const artJsonPath = path.join(artPath, 'art.json');
20
+
21
+ if (!name) {
22
+ return fs.readdirSync(localHistoryPath).filter(f => {
23
+ return f !== '.DS_Store' && f !== 'desktop.ini' && f !== 'thumbs.db';
24
+ });
25
+ }
26
+
27
+ const illegalRegExp = /[\/\\]/g;
28
+ const controlRegExp = /[\x00-\x1f\x80-\x9f]/g;
29
+ const reservedRegExp = /^\.+$/;
30
+
31
+ if (illegalRegExp.test(name) || controlRegExp.test(name) || reservedRegExp.test(name)) {
32
+ throw new Error(`Invalid branch name: "${name}". Branch names cannot contain slashes or illegal characters.`);
33
+ }
34
+
35
+ const branchLocalPath = path.join(localHistoryPath, name);
36
+ const branchRemotePath = path.join(remoteHistoryPath, name);
37
+
38
+ if (isDelete) {
39
+ if (!fs.existsSync(branchLocalPath)) {
40
+ throw new Error(`Local branch "${name}" does not exist.`);
41
+ }
42
+
43
+ const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
44
+
45
+ if (artJson.active.branch === name) {
46
+ throw new Error(`Local branch "${name}" is in use and can't be deleted right now.`);
47
+ }
48
+
49
+ fs.rmSync(branchLocalPath, { recursive: true, force: true });
50
+
51
+ if (fs.existsSync(branchRemotePath)) {
52
+ fs.rmSync(branchRemotePath, { recursive: true, force: true });
53
+ }
54
+
55
+ return `Deleted local branch "${name}".`;
56
+ }
57
+
58
+ if (fs.existsSync(branchLocalPath)) {
59
+ throw new Error(`Local branch "${name}" already exists.`);
60
+ }
61
+
62
+ const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
63
+ const currentBranchManifest = path.join(localHistoryPath, artJson.active.branch, 'manifest.json');
64
+
65
+ let initialCommits = [];
66
+
67
+ if (fs.existsSync(currentBranchManifest)) {
68
+ initialCommits = JSON.parse(fs.readFileSync(currentBranchManifest, 'utf8')).commits;
69
+ }
70
+
71
+ fs.mkdirSync(branchLocalPath, { recursive: true });
72
+
73
+ fs.writeFileSync(
74
+ path.join(branchLocalPath, 'manifest.json'),
75
+ JSON.stringify({ commits: initialCommits }, null, 2)
76
+ );
77
+
78
+ if (!fs.existsSync(branchRemotePath)) {
79
+ fs.mkdirSync(branchRemotePath, { recursive: true });
80
+
81
+ fs.writeFileSync(
82
+ path.join(branchRemotePath, 'manifest.json'),
83
+ JSON.stringify({ commits: initialCommits }, null, 2)
84
+ );
85
+ }
86
+
87
+ if (initialCommits.length > 0) {
88
+ const sourceBranchPath = path.join(localHistoryPath, artJson.active.branch);
89
+
90
+ for (const hash of initialCommits) {
91
+ const srcFile = path.join(sourceBranchPath, `${hash}.json`);
92
+ const destFile = path.join(branchLocalPath, `${hash}.json`);
93
+
94
+ if (fs.existsSync(srcFile)) {
95
+ fs.copyFileSync(srcFile, destFile);
96
+ }
97
+ }
98
+ }
99
+
100
+ return `Created branch "${name}".`;
101
+ }
102
+
103
+ /**
104
+ * Updates the active pointer and reconstructs the working directory.
105
+ */
106
+
107
+ function checkout (branchName, { force = false } = {}) {
108
+ const root = process.cwd();
109
+ const artPath = path.join(root, '.art');
110
+ const artJsonPath = path.join(artPath, 'art.json');
111
+ const branchPath = path.join(artPath, 'history/local', branchName);
112
+
113
+ if (!fs.existsSync(branchPath)) {
114
+ branch({ name: branchName });
115
+ }
116
+
117
+ const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
118
+ const currentState = getStateByHash(artJson.active.branch, artJson.active.parent) || {};
119
+
120
+ if (!force) {
121
+ const allWorkDirFiles = fs.readdirSync(root, { recursive: true })
122
+ .filter(f => !f.startsWith('.art') && !fs.statSync(path.join(root, f)).isDirectory());
123
+
124
+ let isDirty = false;
125
+ for (const file of allWorkDirFiles) {
126
+ const currentContent = fs.readFileSync(path.join(root, file), 'utf8');
127
+ if (currentContent !== currentState[file]) {
128
+ isDirty = true;
129
+ break;
130
+ }
131
+ }
132
+
133
+ if (!isDirty) {
134
+ for (const file in currentState) {
135
+ if (!fs.existsSync(path.join(root, file))) {
136
+ isDirty = true;
137
+ break;
138
+ }
139
+ }
140
+ }
141
+
142
+ if (isDirty) {
143
+ throw new Error('Your local changes would be overwritten by checkout. Please commit or stash them.');
144
+ }
145
+ }
146
+
147
+ const manifest = JSON.parse(fs.readFileSync(path.join(branchPath, 'manifest.json'), 'utf8'));
148
+
149
+ const targetHash = manifest.commits.length > 0
150
+ ? manifest.commits[manifest.commits.length - 1]
151
+ : null;
152
+
153
+ const targetState = getStateByHash(branchName, targetHash);
154
+
155
+ for (const filePath of Object.keys(currentState)) {
156
+ if (!targetState[filePath]) {
157
+ const fullPath = path.join(root, filePath);
158
+ if (fs.existsSync(fullPath)) fs.rmSync(fullPath, { recursive: true, force: true });
159
+ }
160
+ }
161
+
162
+ for (const [filePath, content] of Object.entries(targetState)) {
163
+ const fullPath = path.join(root, filePath);
164
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
165
+ fs.writeFileSync(fullPath, content);
166
+ }
167
+
168
+ artJson.active.branch = branchName;
169
+ artJson.active.parent = targetHash;
170
+ fs.writeFileSync(artJsonPath, JSON.stringify(artJson, null, 2));
171
+
172
+ return `Switched to branch "${branchName}".`;
173
+ }
174
+
175
+ /**
176
+ * Performs a three-way merge. Overwrites working directory with conflicts.
177
+ */
178
+
179
+ function merge (targetBranch) {
180
+ const root = process.cwd();
181
+ const artPath = path.join(root, '.art');
182
+ const artJson = JSON.parse(fs.readFileSync(path.join(artPath, 'art.json'), 'utf8'));
183
+ const activeBranch = artJson.active.branch;
184
+
185
+ const activeManifest = JSON.parse(fs.readFileSync(path.join(artPath, `history/local/${activeBranch}/manifest.json`), 'utf8'));
186
+ const targetManifest = JSON.parse(fs.readFileSync(path.join(artPath, `history/local/${targetBranch}/manifest.json`), 'utf8'));
187
+
188
+ const commonAncestorHash = [...activeManifest.commits].reverse().find(h => targetManifest.commits.includes(h)) || null;
189
+
190
+ const baseState = commonAncestorHash ? getStateByHash(activeBranch, commonAncestorHash) : {};
191
+ const activeState = getStateByHash(activeBranch, artJson.active.parent);
192
+ const lastTargetHash = targetManifest.commits[targetManifest.commits.length - 1];
193
+ const targetState = getStateByHash(targetBranch, lastTargetHash);
194
+
195
+ const mergedChanges = {};
196
+ const allFiles = new Set([...Object.keys(activeState), ...Object.keys(targetState)]);
197
+
198
+ for (const filePath of allFiles) {
199
+ const base = baseState[filePath];
200
+ const active = activeState[filePath];
201
+ const target = targetState[filePath];
202
+ const fullPath = path.join(root, filePath);
203
+
204
+ if (active === target) continue;
205
+
206
+ if (base === active && base !== target) {
207
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
208
+ fs.writeFileSync(fullPath, target || '');
209
+
210
+ mergedChanges[filePath] = { type: 'createFile', content: target };
211
+ } else if (base !== active && base !== target && active !== target) {
212
+ const conflictContent = `<<<<<<< active\n${active || ''}\n=======\n${target || ''}\n>>>>>>> ${targetBranch}`;
213
+
214
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
215
+ fs.writeFileSync(fullPath, conflictContent);
216
+
217
+ mergedChanges[filePath] = { type: 'createFile', content: conflictContent };
218
+ }
219
+ }
220
+
221
+ const stage = { changes: mergedChanges };
222
+
223
+ fs.writeFileSync(path.join(artPath, 'stage.json'), JSON.stringify(stage, null, 2));
224
+
225
+ return `Merged ${targetBranch}. Conflicts written to the stage and working directory to be resolved.`;
226
+ }
227
+
228
+ module.exports = {
229
+ __libraryVersion: '0.2.5',
230
+ __libraryAPIName: 'Branching',
231
+ branch,
232
+ checkout,
233
+ merge
234
+ };
@@ -0,0 +1,223 @@
1
+ /**
2
+ * art - Modern version control.
3
+ * Module: Caches (v0.2.5)
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const { checkout } = require('../branching/index.js');
10
+ const getStateByHash = require('../utils/getStateByHash');
11
+
12
+ /**
13
+ * Moves changes to a cache folder, or restores the most recent stash.
14
+ */
15
+
16
+ function stash ({ pop = false, list = false } = {}) {
17
+ const root = process.cwd();
18
+ const artPath = path.join(root, '.art');
19
+ const stagePath = path.join(artPath, 'stage.json');
20
+ const cachePath = path.join(artPath, 'cache');
21
+ const artJsonPath = path.join(artPath, 'art.json');
22
+
23
+ if (list) {
24
+ if (!fs.existsSync(cachePath)) return [];
25
+ const stashFiles = fs.readdirSync(cachePath)
26
+ .filter(f => f.startsWith('stash_') && f.endsWith('.json'))
27
+ .sort();
28
+
29
+ return stashFiles.map((file, index) => ({
30
+ id: `stash@{${stashFiles.length - 1 - index}}`,
31
+ date: new Date(parseInt(file.replace('stash_', '').replace('.json', ''))).toLocaleString(),
32
+ file
33
+ }));
34
+ }
35
+
36
+ if (pop) {
37
+ if (!fs.existsSync(cachePath)) throw new Error('No stashes found.');
38
+
39
+ const stashes = fs.readdirSync(cachePath)
40
+ .filter(f => f.startsWith('stash_') && f.endsWith('.json'))
41
+ .sort();
42
+
43
+ if (stashes.length === 0) {
44
+ throw new Error('No stashes found.');
45
+ }
46
+
47
+ const latestStashName = stashes[stashes.length - 1];
48
+ const latestStashPath = path.join(cachePath, latestStashName);
49
+ const stashData = JSON.parse(fs.readFileSync(latestStashPath, 'utf8'));
50
+
51
+ for (const [filePath, changeSet] of Object.entries(stashData.changes)) {
52
+ const fullPath = path.join(root, filePath);
53
+
54
+ if (Array.isArray(changeSet)) {
55
+ let content = fs.existsSync(fullPath) ? fs.readFileSync(fullPath, 'utf8') : '';
56
+
57
+ for (const operation of changeSet) {
58
+ if (operation.type === 'insert') {
59
+ content = `${content.slice(0, operation.position)}${operation.content}${content.slice(operation.position)}`;
60
+ } else if (operation.type === 'delete') {
61
+ content = `${content.slice(0, operation.position)}${content.slice(operation.position + operation.length)}`;
62
+ }
63
+ }
64
+
65
+ fs.writeFileSync(fullPath, content);
66
+ } else if (changeSet.type === 'createFile') {
67
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
68
+ fs.writeFileSync(fullPath, changeSet.content);
69
+ } else if (changeSet.type === 'deleteFile') {
70
+ if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
71
+ }
72
+ }
73
+
74
+ fs.unlinkSync(latestStashPath);
75
+
76
+ return `Restored changes from ${latestStashName}.`;
77
+ }
78
+
79
+ const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
80
+ const activeState = getStateByHash(artJson.active.branch, artJson.active.parent) || {};
81
+
82
+ const allWorkDirFiles = fs.readdirSync(root, { recursive: true })
83
+ .filter(f => !f.startsWith('.art') && !fs.statSync(path.join(root, f)).isDirectory());
84
+
85
+ const stashChanges = {};
86
+
87
+ for (const file of allWorkDirFiles) {
88
+ const currentContent = fs.readFileSync(path.join(root, file), 'utf8');
89
+ const previousContent = activeState[file];
90
+
91
+ if (previousContent === undefined) {
92
+ stashChanges[file] = { type: 'createFile', content: currentContent };
93
+ } else if (currentContent !== previousContent) {
94
+ let start = 0;
95
+
96
+ while (start < previousContent.length && start < currentContent.length && previousContent[start] === currentContent[start]) {
97
+ start++;
98
+ }
99
+
100
+ let oldEnd = previousContent.length - 1;
101
+ let newEnd = currentContent.length - 1;
102
+
103
+ while (oldEnd >= start && newEnd >= start && previousContent[oldEnd] === currentContent[newEnd]) {
104
+ oldEnd--;
105
+ newEnd--;
106
+ }
107
+
108
+ const ops = [];
109
+ const delLen = oldEnd - start + 1;
110
+
111
+ if (delLen > 0) ops.push({ type: 'delete', position: start, length: delLen });
112
+
113
+ const insCont = currentContent.slice(start, newEnd + 1);
114
+
115
+ if (insCont.length > 0) ops.push({ type: 'insert', position: start, content: insCont });
116
+
117
+ if (ops.length > 0) stashChanges[file] = ops;
118
+ }
119
+ }
120
+
121
+ for (const file in activeState) {
122
+ if (!fs.existsSync(path.join(root, file))) {
123
+ stashChanges[file] = { type: 'deleteFile' };
124
+ }
125
+ }
126
+
127
+ if (Object.keys(stashChanges).length === 0) {
128
+ return 'No local changes to stash.';
129
+ }
130
+
131
+ if (!fs.existsSync(cachePath)) fs.mkdirSync(cachePath, { recursive: true });
132
+
133
+ const timestamp = Date.now();
134
+
135
+ fs.writeFileSync(path.join(cachePath, `stash_${timestamp}.json`), JSON.stringify({ changes: stashChanges }, null, 2));
136
+
137
+ if (fs.existsSync(stagePath)) {
138
+ fs.unlinkSync(stagePath);
139
+ }
140
+
141
+ checkout(artJson.active.branch, { force: true });
142
+
143
+ return `Saved working directory changes to stash_${timestamp}.json and reverted to clean state.`;
144
+ }
145
+
146
+ /**
147
+ * Wipes the stage and moves the active parent pointer if a hash is provided.
148
+ */
149
+
150
+ function reset (hash) {
151
+ const artPath = path.join(process.cwd(), '.art');
152
+ const stagePath = path.join(artPath, 'stage.json');
153
+ const artJsonPath = path.join(artPath, 'art.json');
154
+
155
+ if (fs.existsSync(stagePath)) {
156
+ fs.unlinkSync(stagePath);
157
+ }
158
+
159
+ if (!hash) {
160
+ return 'Staging area cleared.';
161
+ }
162
+
163
+ const artJson = JSON.parse(fs.readFileSync(artJsonPath, 'utf8'));
164
+ const branch = artJson.active.branch;
165
+ const branchPath = path.join(artPath, 'history/local', branch);
166
+ const commitPath = path.join(branchPath, `${hash}.json`);
167
+
168
+ if (!fs.existsSync(commitPath)) {
169
+ throw new Error(`Commit ${hash} not found in branch ${branch}.`);
170
+ }
171
+
172
+ artJson.active.parent = hash;
173
+ fs.writeFileSync(artJsonPath, JSON.stringify(artJson, null, 2));
174
+
175
+ const manifestPath = path.join(branchPath, 'manifest.json');
176
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
177
+ const hashIndex = manifest.commits.indexOf(hash);
178
+
179
+ if (hashIndex !== -1) {
180
+ manifest.commits = manifest.commits.slice(0, hashIndex + 1);
181
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
182
+ }
183
+
184
+ checkout(branch);
185
+
186
+ return `Head is now at ${hash.slice(0, 7)}. Working directory updated.`;
187
+ }
188
+
189
+ /**
190
+ * Marks a file for deletion by adding a "deleteFile" entry to the stage.
191
+ */
192
+
193
+ function rm (filePath) {
194
+ const artPath = path.join(process.cwd(), '.art');
195
+ const stagePath = path.join(artPath, 'stage.json');
196
+ const fullPath = path.join(process.cwd(), filePath);
197
+
198
+ let stage = { changes: {} };
199
+
200
+ if (fs.existsSync(stagePath)) {
201
+ stage = JSON.parse(fs.readFileSync(stagePath, 'utf8'));
202
+ }
203
+
204
+ stage.changes[filePath] = {
205
+ type: 'deleteFile'
206
+ };
207
+
208
+ fs.writeFileSync(stagePath, JSON.stringify(stage, null, 2));
209
+
210
+ if (fs.existsSync(fullPath)) {
211
+ fs.unlinkSync(fullPath);
212
+ }
213
+
214
+ return `File ${filePath} marked for removal.`;
215
+ }
216
+
217
+ module.exports = {
218
+ __libraryVersion: '0.2.5',
219
+ __libraryAPIName: 'Caches',
220
+ stash,
221
+ reset,
222
+ rm
223
+ };