dep-vcs 0.0.1

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,12 @@
1
+ # dep-vcs
2
+
3
+ **dep** is an efficient version control system designed for simplicity and transparency. It tracks changes as discrete state snapshots and synchronizes seamlessly the network.
4
+
5
+
6
+
7
+ ## Installation
8
+
9
+ Install globally via npm to use the `dep` command anywhere in your terminal:
10
+
11
+ ```bash
12
+ npm install -g dep-vcs
package/bin/dep.js ADDED
@@ -0,0 +1,148 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * dep - Simple version control
5
+ * CLI Binary (v0.0.1)
6
+ */
7
+
8
+ const dep = require('../index.js');
9
+
10
+ const [,, command, ...args] = process.argv;
11
+
12
+ async function run() {
13
+ try {
14
+ switch (command) {
15
+ case 'init':
16
+ console.log(dep.init(args[0]));
17
+
18
+ break;
19
+
20
+ case 'clone':
21
+ console.log(await dep.clone(args[0]));
22
+
23
+ break;
24
+
25
+ case 'config':
26
+ console.log(dep.config(args[0], args[1]));
27
+
28
+ break;
29
+
30
+ case 'status':
31
+ const s = dep.status();
32
+
33
+ console.log(`On branch ${s.activeBranch}`);
34
+ console.log(`Last commit: ${s.lastCommit || 'None'}`);
35
+
36
+ if (s.staged.length > 0) {
37
+ console.log('\nChanges to be committed:');
38
+
39
+ for (const file of s.staged) console.log(` (staged): ${file}`);
40
+ } else {
41
+ console.log('\nNothing staged.');
42
+ }
43
+
44
+ break;
45
+
46
+ case 'add':
47
+ if (!args[0]) throw new Error('Specify a file path to add.');
48
+
49
+ console.log(dep.add(args[0]));
50
+
51
+ break;
52
+
53
+ case 'commit':
54
+ if (!args[0]) throw new Error('Specify a commit message.');
55
+ console.log(dep.commit(args[0]));
56
+
57
+ break;
58
+
59
+ case 'branch':
60
+ const branches = dep.branch(args[0]);
61
+
62
+ if (Array.isArray(branches)) {
63
+ for (const b of branches) console.log(b);
64
+ } else {
65
+ console.log(branches);
66
+ }
67
+
68
+ break;
69
+
70
+ case 'checkout':
71
+ if (!args[0]) throw new Error('Specify a branch name.');
72
+
73
+ console.log(dep.checkout(args[0]));
74
+
75
+ break;
76
+
77
+ case 'merge':
78
+ if (!args[0]) throw new Error('Specify a target branch to merge.');
79
+
80
+ console.log(dep.merge(args[0]));
81
+
82
+ break;
83
+
84
+ case 'remote':
85
+ console.log(dep.remote(args[0]));
86
+
87
+ break;
88
+
89
+ case 'fetch':
90
+ console.log(await dep.fetch());
91
+
92
+ break;
93
+
94
+ case 'pull':
95
+ console.log(await dep.pull());
96
+
97
+ break;
98
+
99
+ case 'push':
100
+ console.log(await dep.push());
101
+
102
+ break;
103
+
104
+ case 'log':
105
+ console.log(dep.log());
106
+
107
+ break;
108
+
109
+ case 'diff':
110
+ console.log(dep.diff());
111
+ break;
112
+
113
+ case 'stash':
114
+ const isPop = args[0] === 'pop';
115
+
116
+ console.log(dep.stash({ pop: isPop }));
117
+
118
+ break;
119
+
120
+ case 'reset':
121
+ console.log(dep.reset(args[0]));
122
+
123
+ break;
124
+
125
+ case 'rm':
126
+ if (!args[0]) throw new Error('Specify a file path to remove.');
127
+
128
+ console.log(dep.rm(args[0]));
129
+
130
+ break;
131
+
132
+ case '--version':
133
+ case '-v':
134
+ console.log(`dep version ${dep.version}`);
135
+
136
+ break;
137
+
138
+ default:
139
+ console.log('Usage: dep <command> [arguments]');
140
+ console.log('Available commands: init, clone, status, add, commit, branch, checkout, merge, remote, fetch, pull, push, log, diff, stash, reset, rm');
141
+ }
142
+ } catch (error) {
143
+ console.error(`Error: ${error.message}`);
144
+ process.exit(1);
145
+ }
146
+ }
147
+
148
+ run();
@@ -0,0 +1,141 @@
1
+ /**
2
+ * dep - Simple version control
3
+ * Module: Branching (v0.0.1)
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) {
16
+ const depPath = path.join(process.cwd(), '.dep');
17
+ const localHistoryPath = path.join(depPath, 'history/local');
18
+
19
+ if (!name) {
20
+ return fs.readdirSync(localHistoryPath);
21
+ }
22
+
23
+ const branchPath = path.join(localHistoryPath, name);
24
+
25
+ if (fs.existsSync(branchPath)) {
26
+ throw new Error(`Branch "${name}" already exists.`);
27
+ }
28
+
29
+ fs.mkdirSync(branchPath, { recursive: true });
30
+
31
+ const historyManifest = {
32
+ commits: []
33
+ };
34
+
35
+ fs.writeFileSync(
36
+ path.join(branchPath, 'manifest.json'),
37
+ JSON.stringify(historyManifest, null, 2)
38
+ );
39
+
40
+ return `Created branch "${name}".`;
41
+ }
42
+
43
+ /**
44
+ * Updates the active pointer and reconstructs the working directory.
45
+ */
46
+
47
+ function checkout (branchName) {
48
+ const depPath = path.join(process.cwd(), '.dep');
49
+ const depJsonPath = path.join(depPath, 'dep.json');
50
+ const branchPath = path.join(depPath, 'history/local', branchName);
51
+
52
+ if (!fs.existsSync(branchPath)) {
53
+ branch(branchName);
54
+ }
55
+
56
+ const depJson = JSON.parse(fs.readFileSync(depJsonPath, 'utf8'));
57
+ const targetState = getStateByHash(branchName, null);
58
+
59
+ const currentFiles = fs.readdirSync(process.cwd()).filter(f => f !== '.dep');
60
+
61
+ for (const f of currentFiles) {
62
+ fs.rmSync(path.join(process.cwd(), f), { recursive: true, force: true });
63
+ }
64
+
65
+ for (const [filePath, content] of Object.entries(targetState)) {
66
+ const fullPath = path.join(process.cwd(), filePath);
67
+
68
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
69
+ fs.writeFileSync(fullPath, content);
70
+ }
71
+
72
+ const manifest = JSON.parse(fs.readFileSync(path.join(branchPath, 'manifest.json'), 'utf8'));
73
+
74
+ depJson.active.branch = branchName;
75
+ depJson.active.parent = manifest.commits.length > 0 ? manifest.commits[manifest.commits.length - 1] : null;
76
+
77
+ fs.writeFileSync(depJsonPath, JSON.stringify(depJson, null, 2));
78
+
79
+ return `Switched to branch "${branchName}".`;
80
+ }
81
+
82
+ /**
83
+ * Performs a three-way merge. Overwrites working directory with conflicts.
84
+ */
85
+
86
+ function merge (targetBranch) {
87
+ const root = process.cwd();
88
+ const depPath = path.join(root, '.dep');
89
+ const depJson = JSON.parse(fs.readFileSync(path.join(depPath, 'dep.json'), 'utf8'));
90
+ const activeBranch = depJson.active.branch;
91
+
92
+ const activeManifest = JSON.parse(fs.readFileSync(path.join(depPath, `history/local/${activeBranch}/manifest.json`), 'utf8'));
93
+ const targetManifest = JSON.parse(fs.readFileSync(path.join(depPath, `history/local/${targetBranch}/manifest.json`), 'utf8'));
94
+
95
+ const commonAncestorHash = [...activeManifest.commits].reverse().find(h => targetManifest.commits.includes(h)) || null;
96
+
97
+ const baseState = commonAncestorHash ? getStateByHash(activeBranch, commonAncestorHash) : {};
98
+ const activeState = getStateByHash(activeBranch, depJson.active.parent);
99
+ const lastTargetHash = targetManifest.commits[targetManifest.commits.length - 1];
100
+ const targetState = getStateByHash(targetBranch, lastTargetHash);
101
+
102
+ const mergedChanges = {};
103
+ const allFiles = new Set([...Object.keys(activeState), ...Object.keys(targetState)]);
104
+
105
+ for (const filePath of allFiles) {
106
+ const base = baseState[filePath];
107
+ const active = activeState[filePath];
108
+ const target = targetState[filePath];
109
+ const fullPath = path.join(root, filePath);
110
+
111
+ if (active === target) continue;
112
+
113
+ if (base === active && base !== target) {
114
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
115
+ fs.writeFileSync(fullPath, target || '');
116
+
117
+ mergedChanges[filePath] = { type: 'createFile', content: target };
118
+ } else if (base !== active && base !== target && active !== target) {
119
+ const conflictContent = `<<<<<<< active\n${active || ''}\n=======\n${target || ''}\n>>>>>>> ${targetBranch}`;
120
+
121
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
122
+ fs.writeFileSync(fullPath, conflictContent);
123
+
124
+ mergedChanges[filePath] = { type: 'createFile', content: conflictContent };
125
+ }
126
+ }
127
+
128
+ const stage = { changes: mergedChanges };
129
+
130
+ fs.writeFileSync(path.join(depPath, 'stage.json'), JSON.stringify(stage, null, 2));
131
+
132
+ return `Merged ${targetBranch}. Conflicts written to the stage and working directory to be resolved.`;
133
+ }
134
+
135
+ module.exports = {
136
+ __libraryVersion: '0.0.1',
137
+ __libraryAPIName: 'Branching',
138
+ branch,
139
+ checkout,
140
+ merge
141
+ };
@@ -0,0 +1,162 @@
1
+ /**
2
+ * dep - Simple version control
3
+ * Module: Caches (v0.0.1)
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const { checkout } = require('../Branching/index.js');
10
+
11
+ /**
12
+ * Moves stage.json to a cache folder, or restores the most recent stash.
13
+ * @param {object} options - Configuration for the stash operation.
14
+ * @param {boolean} options.pop - Whether to restore and remove the latest stash.
15
+ * @returns {string} Result message.
16
+ */
17
+
18
+ function stash ({ pop = false } = {}) {
19
+ const depPath = path.join(process.cwd(), '.dep');
20
+ const stagePath = path.join(depPath, 'stage.json');
21
+ const cachePath = path.join(depPath, 'cache');
22
+
23
+ if (pop) {
24
+ if (!fs.existsSync(cachePath)) {
25
+ return 'No stashes found.';
26
+ }
27
+
28
+ const stashes = fs.readdirSync(cachePath)
29
+ .filter(f => f.startsWith('stash_') && f.endsWith('.json'))
30
+ .sort();
31
+
32
+ if (stashes.length === 0) {
33
+ return 'No stashes found.';
34
+ }
35
+
36
+ const latestStashName = stashes[stashes.length - 1];
37
+ const latestStashPath = path.join(cachePath, latestStashName);
38
+ const stashData = JSON.parse(fs.readFileSync(latestStashPath, 'utf8'));
39
+
40
+ let currentStage = { changes: {} };
41
+
42
+ if (fs.existsSync(stagePath)) {
43
+ currentStage = JSON.parse(fs.readFileSync(stagePath, 'utf8'));
44
+ }
45
+
46
+ // Merge stashed changes into current stage and apply to working directory
47
+
48
+ for (const [filePath, change] of Object.entries(stashData.changes)) {
49
+ currentStage.changes[filePath] = change;
50
+
51
+ const fullPath = path.join(process.cwd(), filePath);
52
+
53
+ if (change.type === 'createFile' || change.type === 'update') {
54
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
55
+ fs.writeFileSync(fullPath, change.content);
56
+ } else if (change.type === 'deleteFile') {
57
+ if (fs.existsSync(fullPath)) fs.unlinkSync(fullPath);
58
+ }
59
+ }
60
+
61
+ fs.writeFileSync(stagePath, JSON.stringify(currentStage, null, 2));
62
+ fs.unlinkSync(latestStashPath);
63
+
64
+ return `Dropped ${latestStashName} and updated working directory.`;
65
+ }
66
+
67
+ // Standard Stash (Push) logic
68
+
69
+ if (!fs.existsSync(stagePath)) {
70
+ return 'No changes to stash.';
71
+ }
72
+
73
+ if (!fs.existsSync(cachePath)) {
74
+ fs.mkdirSync(cachePath, { recursive: true });
75
+ }
76
+
77
+ const timestamp = Date.now();
78
+ const stashFilePath = path.join(cachePath, `stash_${timestamp}.json`);
79
+
80
+ fs.renameSync(stagePath, stashFilePath);
81
+
82
+ return `Saved working directory and index state in stash_${timestamp}`;
83
+ }
84
+
85
+ /**
86
+ * Wipes the stage and moves the active parent pointer if a hash is provided.
87
+ */
88
+
89
+ function reset (hash) {
90
+ const depPath = path.join(process.cwd(), '.dep');
91
+ const stagePath = path.join(depPath, 'stage.json');
92
+ const depJsonPath = path.join(depPath, 'dep.json');
93
+
94
+ if (fs.existsSync(stagePath)) {
95
+ fs.unlinkSync(stagePath);
96
+ }
97
+
98
+ if (!hash) {
99
+ return 'Staging area cleared.';
100
+ }
101
+
102
+ const depJson = JSON.parse(fs.readFileSync(depJsonPath, 'utf8'));
103
+ const branch = depJson.active.branch;
104
+ const branchPath = path.join(depPath, 'history/local', branch);
105
+ const commitPath = path.join(branchPath, `${hash}.json`);
106
+
107
+ if (!fs.existsSync(commitPath)) {
108
+ throw new Error(`Commit ${hash} not found in branch ${branch}.`);
109
+ }
110
+
111
+ depJson.active.parent = hash;
112
+ fs.writeFileSync(depJsonPath, JSON.stringify(depJson, null, 2));
113
+
114
+ const manifestPath = path.join(branchPath, 'manifest.json');
115
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
116
+ const hashIndex = manifest.commits.indexOf(hash);
117
+
118
+ if (hashIndex !== -1) {
119
+ manifest.commits = manifest.commits.slice(0, hashIndex + 1);
120
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
121
+ }
122
+
123
+ checkout(branch);
124
+
125
+ return `Head is now at ${hash.slice(0, 7)}. Working directory updated.`;
126
+ }
127
+
128
+ /**
129
+ * Marks a file for deletion by adding a "deleteFile" entry to the stage.
130
+ */
131
+
132
+ function rm (filePath) {
133
+ const depPath = path.join(process.cwd(), '.dep');
134
+ const stagePath = path.join(depPath, 'stage.json');
135
+ const fullPath = path.join(process.cwd(), filePath);
136
+
137
+ let stage = { changes: {} };
138
+
139
+ if (fs.existsSync(stagePath)) {
140
+ stage = JSON.parse(fs.readFileSync(stagePath, 'utf8'));
141
+ }
142
+
143
+ stage.changes[filePath] = {
144
+ type: 'deleteFile'
145
+ };
146
+
147
+ fs.writeFileSync(stagePath, JSON.stringify(stage, null, 2));
148
+
149
+ if (fs.existsSync(fullPath)) {
150
+ fs.unlinkSync(fullPath);
151
+ }
152
+
153
+ return `File ${filePath} marked for removal.`;
154
+ }
155
+
156
+ module.exports = {
157
+ __libraryVersion: '0.0.1',
158
+ __libraryAPIName: 'Caches',
159
+ stash,
160
+ reset,
161
+ rm
162
+ };
@@ -0,0 +1,120 @@
1
+ /**
2
+ * dep - Simple version control
3
+ * Module: Changes (v0.0.1)
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const getStateByHash = require('../utils/getStateByHash');
10
+
11
+ /**
12
+ * Iterates through the JSON commit files in the active branch folder.
13
+ * @returns {string} A formatted string of commit history.
14
+ */
15
+
16
+ function log () {
17
+ const depPath = path.join(process.cwd(), '.dep');
18
+ const depJsonPath = path.join(depPath, 'dep.json');
19
+
20
+ if (!fs.existsSync(depJsonPath)) {
21
+ throw new Error('No dep repository found.');
22
+ }
23
+
24
+ const depJson = JSON.parse(fs.readFileSync(depJsonPath, 'utf8'));
25
+ const branch = depJson.active.branch;
26
+ const branchPath = path.join(depPath, 'history/local', branch);
27
+ const manifestPath = path.join(branchPath, 'manifest.json');
28
+
29
+ if (!fs.existsSync(manifestPath)) {
30
+ return 'No commits found.';
31
+ }
32
+
33
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
34
+
35
+ let output = `Branch: ${branch}\n\n`;
36
+
37
+ // Display commits in reverse chronological order
38
+
39
+ for (let i = manifest.commits.length - 1; i >= 0; i--) {
40
+ const hash = manifest.commits[i];
41
+ const commitData = JSON.parse(fs.readFileSync(path.join(branchPath, `${hash}.json`), 'utf8'));
42
+
43
+ output += `commit ${commitData.hash}\n`;
44
+ output += `Date: ${new Date(commitData.timestamp).toLocaleString()}\n`;
45
+ output += `\n ${commitData.message}\n\n`;
46
+ }
47
+
48
+ return output;
49
+ }
50
+
51
+ /**
52
+ * Displays line-by-line differences between working directory and the last commit/stage.
53
+ * @returns {string} Formatted diff output.
54
+ */
55
+
56
+ function diff () {
57
+ const root = process.cwd();
58
+ const depPath = path.join(root, '.dep');
59
+ const depJson = JSON.parse(fs.readFileSync(path.join(depPath, 'dep.json'), 'utf8'));
60
+
61
+ const activeBranch = depJson.active.branch;
62
+ const lastCommitHash = depJson.active.parent;
63
+
64
+ // Get the state as of the last commit
65
+
66
+ const lastCommitState = lastCommitHash ? getStateByHash(activeBranch, lastCommitHash) : {};
67
+
68
+ // Get current working directory files (excluding .dep)
69
+
70
+ const currentFiles = fs.readdirSync(root).filter(f => f !== '.dep' && fs.lstatSync(path.join(root, f)).isFile());
71
+
72
+ let output = '';
73
+
74
+ for (const filePath of currentFiles) {
75
+ const fullPath = path.join(root, filePath);
76
+ const currentContent = fs.readFileSync(fullPath, 'utf8');
77
+ const previousContent = lastCommitState[filePath] || '';
78
+
79
+ if (currentContent !== previousContent) {
80
+ output += `diff --dep a/${filePath} b/${filePath}\n`;
81
+
82
+ const prevLines = previousContent.split('\n');
83
+ const currLines = currentContent.split('\n');
84
+
85
+ // Simple line-by-line comparison
86
+
87
+ for (const line of prevLines) {
88
+ if (!currLines.includes(line)) {
89
+ output += `- ${line}\n`;
90
+ }
91
+ }
92
+
93
+ for (const line of currLines) {
94
+ if (!prevLines.includes(line)) {
95
+ output += `+ ${line}\n`;
96
+ }
97
+ }
98
+ output += '\n';
99
+ }
100
+ }
101
+
102
+ const stagePath = path.join(depPath, 'stage.json');
103
+
104
+ if (fs.existsSync(stagePath)) {
105
+ output += `--- Staged Changes ---\n`;
106
+ const stage = JSON.parse(fs.readFileSync(stagePath, 'utf8'));
107
+ for (const stagedFile of Object.keys(stage.changes)) {
108
+ output += `staged: ${stagedFile}\n`;
109
+ }
110
+ }
111
+
112
+ return output || 'No changes detected.';
113
+ }
114
+
115
+ module.exports = {
116
+ __libraryVersion: '0.0.1',
117
+ __libraryAPIName: 'Changes',
118
+ log,
119
+ diff
120
+ };
@@ -0,0 +1,174 @@
1
+ /**
2
+ * dep - Simple version control
3
+ * Module: Contributions (v0.0.1)
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const pkg = require('../../package.json');
9
+
10
+ const { checkout } = require('../Branching/index.js');
11
+
12
+ const DEP_HOST = pkg.depConfig.host || 'http://localhost:1337';
13
+
14
+ /**
15
+ * Configures the single URL endpoint in dep.json for synchronization.
16
+ * Supports full URLs or "userName/repoName" slugs.
17
+ * @param {string} input - The remote endpoint or slug.
18
+ * @returns {string} The current remote URL.
19
+ */
20
+
21
+ function remote (input) {
22
+ const depPath = path.join(process.cwd(), '.dep', 'dep.json');
23
+
24
+ if (!fs.existsSync(depPath)) {
25
+ throw new Error('No dep repository found.');
26
+ }
27
+
28
+ const manifest = JSON.parse(fs.readFileSync(depPath, 'utf8'));
29
+
30
+ if (input) {
31
+ let finalUrl = input;
32
+
33
+ if (input.includes('/') && !input.startsWith('http')) {
34
+ finalUrl = `${DEP_HOST}/code/${input}`;
35
+ }
36
+
37
+ manifest.remote = finalUrl;
38
+ fs.writeFileSync(depPath, JSON.stringify(manifest, null, 2));
39
+ }
40
+
41
+ return manifest.remote;
42
+ }
43
+
44
+ /**
45
+ * Downloads JSON diff files from the remote server into history/remote.
46
+ * @returns {Promise<string>} Result message.
47
+ */
48
+
49
+ async function fetchRemote () {
50
+ const depPath = path.join(process.cwd(), '.dep');
51
+ const depJson = JSON.parse(fs.readFileSync(path.join(depPath, 'dep.json'), 'utf8'));
52
+
53
+ if (!depJson.remote) {
54
+ throw new Error('Remote URL not configured. Use "dep remote <url|slug>".');
55
+ }
56
+
57
+ const branch = depJson.active.branch;
58
+ const remoteBranchPath = path.join(depPath, 'history/remote', branch);
59
+
60
+ if (!fs.existsSync(remoteBranchPath)) {
61
+ fs.mkdirSync(remoteBranchPath, { recursive: true });
62
+ }
63
+
64
+ const response = await fetch(`${depJson.remote}/history/remote/${branch}/manifest.json`);
65
+ const remoteManifest = await response.json();
66
+
67
+ for (const commitHash of remoteManifest.commits) {
68
+ const commitFilePath = path.join(remoteBranchPath, `${commitHash}.json`);
69
+
70
+ if (!fs.existsSync(commitFilePath)) {
71
+ const commitResponse = await fetch(`${depJson.remote}/history/remote/${branch}/${commitHash}.json`);
72
+ const commitDiff = await commitResponse.json();
73
+
74
+ fs.writeFileSync(commitFilePath, JSON.stringify(commitDiff, null, 2));
75
+ }
76
+ }
77
+
78
+ fs.writeFileSync(
79
+ path.join(remoteBranchPath, 'manifest.json'),
80
+ JSON.stringify(remoteManifest, null, 2)
81
+ );
82
+
83
+ return `Fetched remote history for branch: ${branch}`;
84
+ }
85
+
86
+ /**
87
+ * Performs a fetch and applies remote JSON diffs to local branch and files.
88
+ */
89
+
90
+ async function pull () {
91
+ const depPath = path.join(process.cwd(), '.dep');
92
+ const depJson = JSON.parse(fs.readFileSync(path.join(depPath, 'dep.json'), 'utf8'));
93
+ const branch = depJson.active.branch;
94
+
95
+ await fetchRemote();
96
+
97
+ const remoteManifestPath = path.join(depPath, 'history/remote', branch, 'manifest.json');
98
+ const remoteManifest = JSON.parse(fs.readFileSync(remoteManifestPath, 'utf8'));
99
+
100
+ const localManifestPath = path.join(depPath, 'history/local', branch, 'manifest.json');
101
+ const localManifest = JSON.parse(fs.readFileSync(localManifestPath, 'utf8'));
102
+
103
+ const newCommits = remoteManifest.commits.filter(hash => !localManifest.commits.includes(hash));
104
+
105
+ if (newCommits.length === 0) {
106
+ return 'Already up to date.';
107
+ }
108
+
109
+ for (const commitHash of newCommits) {
110
+ const remoteCommitFile = path.join(depPath, 'history/remote', branch, `${commitHash}.json`);
111
+ const remoteData = fs.readFileSync(remoteCommitFile, 'utf8');
112
+
113
+ fs.writeFileSync(
114
+ path.join(depPath, 'history/local', branch, `${commitHash}.json`),
115
+ remoteData
116
+ );
117
+
118
+ localManifest.commits.push(commitHash);
119
+ }
120
+
121
+ fs.writeFileSync(localManifestPath, JSON.stringify(localManifest, null, 2));
122
+ checkout(branch);
123
+
124
+ return `Applied ${newCommits.length} commits.`;
125
+ }
126
+
127
+ /**
128
+ * Uploads local JSON diffs that do not exist in the remote history.
129
+ */
130
+
131
+ async function push () {
132
+ const depPath = path.join(process.cwd(), '.dep');
133
+ const depJson = JSON.parse(fs.readFileSync(path.join(depPath, 'dep.json'), 'utf8'));
134
+
135
+ if (!depJson.remote) {
136
+ throw new Error('Remote URL not configured.');
137
+ }
138
+
139
+ const branch = depJson.active.branch;
140
+ const localManifest = JSON.parse(fs.readFileSync(path.join(depPath, 'history/local', branch, 'manifest.json'), 'utf8'));
141
+
142
+ const response = await fetch(`${depJson.remote}/history/remote/${branch}/manifest.json`);
143
+ const remoteManifest = await response.json();
144
+
145
+ const missingCommits = localManifest.commits.filter(hash => !remoteManifest.commits.includes(hash));
146
+
147
+ if (missingCommits.length === 0) {
148
+ return 'Everything up to date.';
149
+ }
150
+
151
+ for (const commitHash of missingCommits) {
152
+ const commitData = JSON.parse(fs.readFileSync(path.join(depPath, 'history/local', branch, `${commitHash}.json`), 'utf8'));
153
+
154
+ await fetch(`${depJson.remote}/source/push`, {
155
+ method: 'POST',
156
+ headers: { 'Content-Type': 'application/json' },
157
+ body: JSON.stringify({
158
+ branch: branch,
159
+ commit: commitData
160
+ })
161
+ });
162
+ }
163
+
164
+ return `Pushed ${missingCommits.length} commits to remote.`;
165
+ }
166
+
167
+ module.exports = {
168
+ __libraryVersion: pkg.version,
169
+ __libraryAPIName: 'Contributions',
170
+ remote,
171
+ fetch: fetchRemote,
172
+ pull,
173
+ push
174
+ };
package/index.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * dep - Simple version control
3
+ * Core Library Entry Point (v0.0.1)
4
+ */
5
+
6
+ const Setup = require('./setup');
7
+ const Workflow = require('./workflow');
8
+ const Branching = require('./branching');
9
+ const Contributions = require('./contributions');
10
+ const Changes = require('./changes');
11
+ const Caches = require('./caches');
12
+
13
+ const dep = {
14
+
15
+ // Setup
16
+
17
+ init: Setup.init,
18
+ clone: Setup.clone,
19
+ config: Setup.config,
20
+
21
+ // Workflow
22
+
23
+ status: Workflow.status,
24
+ add: Workflow.add,
25
+ commit: Workflow.commit,
26
+
27
+ // Branching
28
+
29
+ branch: Branching.branch,
30
+ checkout: Branching.checkout,
31
+ merge: Branching.merge,
32
+
33
+ // Contributions
34
+
35
+ remote: Contributions.remote,
36
+ fetch: Contributions.fetch,
37
+ pull: Contributions.pull,
38
+ push: Contributions.push,
39
+
40
+ // Changes
41
+
42
+ log: Changes.log,
43
+ diff: Changes.diff,
44
+
45
+ // Caches
46
+
47
+ stash: Caches.stash,
48
+ reset: Caches.reset,
49
+ rm: Caches.rm,
50
+
51
+ // Metadata
52
+
53
+ version: '0.0.1',
54
+ modules: [
55
+ Setup.__libraryAPIName,
56
+ Workflow.__libraryAPIName,
57
+ Branching.__libraryAPIName,
58
+ Contributions.__libraryAPIName,
59
+ Changes.__libraryAPIName,
60
+ Caches.__libraryAPIName
61
+ ]
62
+ };
63
+
64
+ module.exports = dep;
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "dep-vcs",
3
+ "version": "0.0.1",
4
+ "description": "Efficient version control.",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "dep": "./bin/dep.js"
8
+ },
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "keywords": [
13
+ "vcs",
14
+ "version-control",
15
+ "git-alternative",
16
+ "json-diff"
17
+ ],
18
+ "author": "bennyschmidt",
19
+ "license": "ISC",
20
+ "engines": {
21
+ "node": ">=24.11.0"
22
+ },
23
+ "depConfig": {
24
+ "host": "https://www.gametorq.com/source"
25
+ }
26
+ }
package/setup/index.js ADDED
@@ -0,0 +1,201 @@
1
+ /**
2
+ * dep - Simple version control
3
+ * Module: Setup (v0.0.1)
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ const pkg = require('../../package.json');
10
+ const DEP_HOST = pkg.depConfig.host || 'http://localhost:1337';
11
+
12
+ /**
13
+ * Initializes the local .dep directory structure.
14
+ * @param {string} directoryPath - The path where the repository should be initialized.
15
+ * @returns {string} A message indicating the result of the operation.
16
+ */
17
+
18
+ function init (directoryPath = process.cwd()) {
19
+ const depDirectory = path.join(directoryPath, '.dep');
20
+
21
+ const folders = [
22
+ '',
23
+ 'data',
24
+ 'history',
25
+ 'history/local',
26
+ 'history/local/main',
27
+ 'history/remote',
28
+ 'history/remote/main'
29
+ ];
30
+
31
+ if (fs.existsSync(depDirectory)) {
32
+ return `Reinitialized existing dep repository in ${depDirectory}`;
33
+ }
34
+
35
+ for (const folder of folders) {
36
+ const fullPath = path.join(depDirectory, folder);
37
+
38
+ if (!fs.existsSync(fullPath)) {
39
+ fs.mkdirSync(fullPath, { recursive: true });
40
+ }
41
+ }
42
+
43
+ const files = fs.readdirSync(directoryPath).filter(f => f !== '.dep');
44
+
45
+ const dataManifest = {
46
+ files: []
47
+ };
48
+
49
+ for (const file of files) {
50
+ const fullPath = path.join(directoryPath, file);
51
+ if (fs.lstatSync(fullPath).isFile()) {
52
+ dataManifest.files.push({
53
+ path: file,
54
+ content: fs.readFileSync(fullPath, 'utf8')
55
+ });
56
+ }
57
+ }
58
+
59
+ fs.writeFileSync(
60
+ path.join(depDirectory, 'data/manifest.json'),
61
+ JSON.stringify(dataManifest, null, 2)
62
+ );
63
+
64
+ const historyManifest = {
65
+ commits: []
66
+ };
67
+
68
+ fs.writeFileSync(
69
+ path.join(depDirectory, 'history/local/main/manifest.json'),
70
+ JSON.stringify(historyManifest, null, 2)
71
+ );
72
+
73
+ const depFile = {
74
+ active: {
75
+ branch: 'main',
76
+ parent: null
77
+ },
78
+ remote: '',
79
+ configuration: {
80
+ userName: ''
81
+ }
82
+ };
83
+
84
+ fs.writeFileSync(
85
+ path.join(depDirectory, 'dep.json'),
86
+ JSON.stringify(depFile, null, 2)
87
+ );
88
+
89
+ return `Initialized empty dep repository in ${depDirectory}`;
90
+ }
91
+
92
+ /**
93
+ * Clones a repository, populates "data", and replays "history".
94
+ * @param {string} repoSlug - The "userName/repoName" slug.
95
+ * @returns {Promise<string>}
96
+ */
97
+
98
+ async function clone (repoSlug) {
99
+ if (!repoSlug || !repoSlug.includes('/')) {
100
+ throw new Error('A valid slug is required.');
101
+ }
102
+
103
+ const [userName, repoName] = repoSlug.split('/');
104
+ const targetPath = path.join(process.cwd(), repoName);
105
+ const depPath = path.join(targetPath, '.dep');
106
+
107
+ if (fs.existsSync(targetPath)) {
108
+ throw new Error(`Destination path "${targetPath}" already exists.`);
109
+ }
110
+
111
+ fs.mkdirSync(targetPath);
112
+ init(targetPath);
113
+
114
+ const dataRes = await fetch(`${DEP_HOST}/${userName}/${repoName}/data/manifest.json`);
115
+ const dataManifest = await dataRes.json();
116
+
117
+ if (dataManifest.files) {
118
+ for (const file of dataManifest.files) {
119
+ const internalDataPath = path.join(depPath, 'data', file.path);
120
+ const workingPath = path.join(targetPath, file.path);
121
+
122
+ fs.mkdirSync(path.dirname(internalDataPath), { recursive: true });
123
+ fs.writeFileSync(internalDataPath, file.content);
124
+ fs.writeFileSync(workingPath, file.content);
125
+ }
126
+ }
127
+
128
+ const historyUrl = `${DEP_HOST}/${userName}/${repoName}/history/remote/main/manifest.json`;
129
+ const historyRes = await fetch(historyUrl);
130
+ const historyManifest = await historyRes.json();
131
+
132
+ if (historyManifest.commits) {
133
+ for (const commitHash of historyManifest.commits) {
134
+ const commitRes = await fetch(`${DEP_HOST}/${userName}/${repoName}/history/remote/main/${commitHash}.json`);
135
+ const commitDiff = await commitRes.json();
136
+
137
+ for (const filePath of Object.keys(commitDiff.changes)) {
138
+ const fullPath = path.join(targetPath, filePath);
139
+ const changeSet = commitDiff.changes[filePath];
140
+
141
+ if (Array.isArray(changeSet)) {
142
+ let currentContent = fs.existsSync(fullPath)
143
+ ? fs.readFileSync(fullPath, 'utf8')
144
+ : '';
145
+
146
+ for (const operation of changeSet) {
147
+ if (operation.type === 'insert') {
148
+ currentContent = `${currentContent.slice(0, operation.position)}${operation.content}${currentContent.slice(operation.position)}`;
149
+ } else if (operation.type === 'delete') {
150
+ currentContent = `${currentContent.slice(0, operation.position)}${currentContent.slice(operation.position + operation.length)}`;
151
+ }
152
+ }
153
+
154
+ fs.writeFileSync(fullPath, currentContent);
155
+ } else if (changeSet.type === 'deleteFile') {
156
+ if (fs.existsSync(fullPath)) {
157
+ fs.unlinkSync(fullPath);
158
+ }
159
+ } else if (changeSet.type === 'createFile') {
160
+ fs.writeFileSync(fullPath, changeSet.content || '');
161
+ }
162
+ }
163
+
164
+ fs.writeFileSync(
165
+ path.join(depPath, 'history/remote/main', `${commitHash}.json`),
166
+ JSON.stringify(commitDiff, null, 2)
167
+ );
168
+ }
169
+ }
170
+
171
+ return `Successfully cloned and replayed ${repoSlug}.`;
172
+ }
173
+
174
+ /**
175
+ * Updates the configuration in dep.json.
176
+ */
177
+
178
+ function config (key, value) {
179
+ const manifestPath = path.join(process.cwd(), '.dep', 'dep.json');
180
+
181
+ if (!fs.existsSync(manifestPath)) {
182
+ throw new Error('No dep repository found.');
183
+ }
184
+
185
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
186
+
187
+ if (key && value !== undefined) {
188
+ manifest.configuration[key] = value;
189
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
190
+ }
191
+
192
+ return manifest.configuration;
193
+ }
194
+
195
+ module.exports = {
196
+ __libraryVersion: pkg.version,
197
+ __libraryAPIName: 'Setup',
198
+ init,
199
+ clone,
200
+ config
201
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * dep - Simple version control
3
+ * Module: Utils (v0.0.1)
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ /**
10
+ * Helper to reconstruct file states at a specific commit hash.
11
+ */
12
+
13
+ module.exports = (branchName, targetHash) => {
14
+ const depPath = path.join(process.cwd(), '.dep');
15
+ const dataPath = path.join(depPath, 'data/manifest.json');
16
+
17
+ if (!fs.existsSync(dataPath)) return {};
18
+
19
+ const dataManifest = JSON.parse(fs.readFileSync(dataPath, 'utf8'));
20
+ const branchPath = path.join(depPath, 'history/local', branchName);
21
+ const manifestPath = path.join(branchPath, 'manifest.json');
22
+
23
+ if (!fs.existsSync(manifestPath)) return {};
24
+
25
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
26
+ let state = {};
27
+
28
+ for (const file of dataManifest.files) {
29
+ state[file.path] = file.content;
30
+ }
31
+
32
+ for (const hash of manifest.commits) {
33
+ const commitPath = path.join(branchPath, `${hash}.json`);
34
+ const commit = JSON.parse(fs.readFileSync(commitPath, 'utf8'));
35
+
36
+ for (const [filePath, change] of Object.entries(commit.changes)) {
37
+ if (change.type === 'createFile' || change.type === 'update') {
38
+ state[filePath] = change.content;
39
+ } else if (change.type === 'deleteFile') {
40
+ delete state[filePath];
41
+ }
42
+ }
43
+
44
+ if (hash === targetHash) break;
45
+ }
46
+
47
+ return state;
48
+ };
@@ -0,0 +1,148 @@
1
+ /**
2
+ * dep - Simple version control
3
+ * Module: Workflow (v0.0.1)
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const crypto = require('crypto');
9
+
10
+ /**
11
+ * Compares the working directory against the last commit and pending stage.
12
+ * @returns {object} Status object containing branch info and change summaries.
13
+ */
14
+
15
+ function status () {
16
+ const depPath = path.join(process.cwd(), '.dep');
17
+ const depJsonPath = path.join(depPath, 'dep.json');
18
+
19
+ if (!fs.existsSync(depJsonPath)) {
20
+ throw new Error('No dep repository found.');
21
+ }
22
+
23
+ const depJson = JSON.parse(fs.readFileSync(depJsonPath, 'utf8'));
24
+ const stagePath = path.join(depPath, 'stage.json');
25
+
26
+ let stagedFiles = [];
27
+
28
+ if (fs.existsSync(stagePath)) {
29
+ const stage = JSON.parse(fs.readFileSync(stagePath, 'utf8'));
30
+
31
+ stagedFiles = Object.keys(stage.changes);
32
+ }
33
+
34
+ return {
35
+ activeBranch: depJson.active.branch,
36
+ lastCommit: depJson.active.parent,
37
+ staged: stagedFiles
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Updates or creates a JSON diff in the stage.json file.
43
+ * @param {string} filePath - Path of the file to add.
44
+ * @returns {string} Confirmation message.
45
+ */
46
+
47
+ function add (filePath) {
48
+ const depPath = path.join(process.cwd(), '.dep');
49
+ const stagePath = path.join(depPath, 'stage.json');
50
+ const fullPath = path.join(process.cwd(), filePath);
51
+
52
+ if (!fs.existsSync(fullPath)) {
53
+ throw new Error(`Path does not exist: ${filePath}`);
54
+ }
55
+
56
+ let stage = { changes: {} };
57
+
58
+ if (fs.existsSync(stagePath)) {
59
+ stage = JSON.parse(fs.readFileSync(stagePath, 'utf8'));
60
+ }
61
+
62
+ const content = fs.readFileSync(fullPath, 'utf8');
63
+
64
+ // Currently records full content for the commit diff logic.
65
+ // In our replay design, this is treated as a 'createFile' or 'update' operation.
66
+
67
+ stage.changes[filePath] = {
68
+ type: 'createFile',
69
+ content: content
70
+ };
71
+
72
+ fs.writeFileSync(stagePath, JSON.stringify(stage, null, 2));
73
+
74
+ return `Added ${filePath} to stage.`;
75
+ }
76
+
77
+ /**
78
+ * Finalizes the stage into a commit file within the history directory.
79
+ * @param {string} message - The commit message.
80
+ * @returns {string} Confirmation message with the new hash.
81
+ */
82
+
83
+ function commit (message) {
84
+ if (!message) {
85
+ throw new Error('A commit message is required.');
86
+ }
87
+
88
+ const depPath = path.join(process.cwd(), '.dep');
89
+ const stagePath = path.join(depPath, 'stage.json');
90
+ const depJsonPath = path.join(depPath, 'dep.json');
91
+
92
+ if (!fs.existsSync(stagePath)) {
93
+ throw new Error('Nothing to commit (stage is empty).');
94
+ }
95
+
96
+ const stage = JSON.parse(fs.readFileSync(stagePath, 'utf8'));
97
+ const depJson = JSON.parse(fs.readFileSync(depJsonPath, 'utf8'));
98
+ const branch = depJson.active.branch;
99
+
100
+ const timestamp = Date.now();
101
+
102
+ const hash = crypto
103
+ .createHash('sha1')
104
+ .update(JSON.stringify(stage.changes) + timestamp + message)
105
+ .digest('hex');
106
+
107
+ const commitObject = {
108
+ hash: hash,
109
+ message: message,
110
+ timestamp: timestamp,
111
+ parent: depJson.active.parent,
112
+ changes: stage.changes
113
+ };
114
+
115
+ const branchHistoryDir = path.join(depPath, 'history', 'local', branch);
116
+ const commitFilePath = path.join(branchHistoryDir, `${hash}.json`);
117
+ const manifestPath = path.join(branchHistoryDir, 'manifest.json');
118
+
119
+ // Write the commit file
120
+
121
+ fs.writeFileSync(commitFilePath, JSON.stringify(commitObject, null, 2));
122
+
123
+ // Update the branch manifest
124
+
125
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
126
+
127
+ manifest.commits.push(hash);
128
+ fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
129
+
130
+ // Update dep.json active pointer
131
+
132
+ depJson.active.parent = hash;
133
+ fs.writeFileSync(depJsonPath, JSON.stringify(depJson, null, 2));
134
+
135
+ // Clear stage by deleting the file
136
+
137
+ fs.unlinkSync(stagePath);
138
+
139
+ return `[${branch} ${hash.slice(0, 7)}] ${message}`;
140
+ }
141
+
142
+ module.exports = {
143
+ __libraryVersion: '0.0.1',
144
+ __libraryAPIName: 'Workflow',
145
+ status,
146
+ add,
147
+ commit
148
+ };