myvcs 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.myvcsignore ADDED
@@ -0,0 +1 @@
1
+ node_modules
@@ -0,0 +1,94 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+ async function readIgnoreFile() {
4
+ const ignorePath = path.join(process.cwd(), ".myvcsignore");
5
+
6
+ try {
7
+ // Check if file exists
8
+ await fs.access(ignorePath);
9
+
10
+ // Read file content
11
+ const content = await fs.readFile(ignorePath, "utf-8");
12
+
13
+ // Split into lines and clean up
14
+ const patterns = content
15
+ .split("\n") // Split by newline
16
+ .map(line => line.trim()) // Remove whitespace
17
+ .filter(line => line.length > 0) // Remove empty lines
18
+ .filter(line => !line.startsWith("#")); // Remove comments
19
+
20
+ return patterns;
21
+
22
+ } catch {
23
+ // File doesn't exist - return default ignores
24
+ return null;
25
+ }
26
+ }
27
+ const ignoreList = await readIgnoreFile();
28
+ async function checkExists(path) {
29
+ try {
30
+ await fs.access(path);
31
+ return true; // Exists
32
+ } catch {
33
+ return false; // Doesn't exist
34
+ }
35
+ }
36
+ async function addRepo(filePath){
37
+ const repoPath=path.resolve(process.cwd(),".myvcs");
38
+ const stagingPath=path.resolve(repoPath,"staging");
39
+ const repoExists=await checkExists(repoPath);
40
+ if (!repoExists) {
41
+ console.log("Not a repository. Run 'myvcs init' first.");
42
+ return;
43
+ }
44
+ try{
45
+ await fs.mkdir(stagingPath,{recursive:true});
46
+ const fileName=path.basename(filePath);
47
+ if(fileName==".") addAllFiles(process.cwd(),stagingPath);
48
+ else if(ignoreList && ignoreList.includes(fileName)){
49
+ console.log("File not added to staging area Ignored!!")
50
+ }
51
+ else{
52
+ await fs.copyFile(filePath,path.join(stagingPath,fileName));
53
+ console.log(`File ${fileName} added to staging area`);
54
+ }
55
+ }catch(err){
56
+ console.error("Failed to add file to staging area!! ",err)
57
+ }
58
+ }
59
+ async function shouldIgnore(filename) {
60
+
61
+ // Ignore specific files/folders
62
+ if (ignoreList && ignoreList.includes(filename)) return true;
63
+
64
+ // Ignore hidden files (starting with .)
65
+ if (filename.startsWith(".")) return true;
66
+
67
+ return false;
68
+ }
69
+ async function addAllFiles(directory,stagingPath,baseDir=process.cwd()){
70
+ const items=await fs.readdir(directory);
71
+ let addedCount=0;
72
+ for(const item of items){
73
+ if(await shouldIgnore(item)) continue;
74
+ const fullPath=path.join(directory,item);
75
+ const stat=await fs.stat(fullPath);
76
+ if(stat.isDirectory()){
77
+ const count=await addAllFiles(fullPath,stagingPath);
78
+ addedCount+=count;
79
+ }
80
+ else{
81
+ const relativePath=path.relative(baseDir,fullPath);
82
+ const destPath=path.join(stagingPath,relativePath);
83
+ await fs.mkdir(path.dirname(destPath),{recursive:true});
84
+ await fs.copyFile(fullPath,destPath);
85
+ addedCount++;
86
+ console.log(`added file ${relativePath}`);
87
+ }
88
+ }
89
+ if(directory==baseDir){
90
+ console.log(`Added ${addedCount} files to staging area`);
91
+ }
92
+ return addedCount;
93
+ }
94
+ export {addRepo}
@@ -0,0 +1,49 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+ import { v5 as uuidv5 } from 'uuid';
4
+ import dotenv from "dotenv"
5
+ dotenv.config();
6
+ function generateCommitID(message) {
7
+ return uuidv5(message, process.env.MY_NAMESPACE);
8
+ }
9
+ async function commitRepo(message){
10
+ const repoPath=path.resolve(process.cwd(),".myvcs");
11
+ const commitPath=path.resolve(repoPath,"commits");
12
+ const stagedPath=path.resolve(repoPath,"staging");
13
+ const commitId=generateCommitID(message);
14
+ const thisCommitPath=path.resolve(path.join(commitPath,commitId));
15
+ const commitsJsonPath = path.join(commitPath, "commits.json");
16
+ try{
17
+ await fs.mkdir(commitPath,{recursive:true});
18
+ await fs.mkdir(thisCommitPath,{recursive:true});
19
+ const files=await fs.readdir(stagedPath);
20
+ for(const file of files){
21
+ await fs.copyFile(path.join(stagedPath,file),path.join(thisCommitPath,file));
22
+ }
23
+ let commits = [];
24
+ try {
25
+ const data = await fs.readFile(commitsJsonPath, 'utf-8');
26
+ commits = JSON.parse(data);
27
+ } catch (err) {
28
+ // File doesn't exist yet, start with empty array
29
+ if (err.code !== 'ENOENT') throw err;
30
+ }
31
+
32
+ // Append new commit
33
+ commits.push({
34
+ commitId,
35
+ message,
36
+ date: new Date().toISOString()
37
+ });
38
+
39
+ // Write back to file
40
+ await fs.writeFile(commitsJsonPath, JSON.stringify(commits, null, 2));
41
+ console.log(`commit done ${commitId} with message ${message}`);
42
+ }catch(err){
43
+ console.error("Commit failed!! ",err);
44
+ }
45
+
46
+
47
+ }
48
+
49
+ export {commitRepo}
@@ -0,0 +1,36 @@
1
+ import {readConfig,setConfigValue} from "../config/vcsConfig.js"
2
+
3
+ export function showConfig(){
4
+ try{
5
+ const config=readConfig();
6
+ console.log("CURRENT CONGIGURATION");
7
+ console.log("======================");
8
+
9
+ for(const [key,value] of Object.entries(config)){
10
+ console.log(`${key}:${value||"Not set"}`)
11
+ }
12
+ console.log("");
13
+ }catch(err){
14
+ console.log(`Error: ${err}`);
15
+ process.exit(1);
16
+ }
17
+ }
18
+
19
+ export function setConfig(key,value){
20
+ try{
21
+ const validKeys=["username","email"];
22
+ if(!validKeys.includes(key)){
23
+ console.log(`Invalid Config Key ${key}`);
24
+ process.exit(1);
25
+ }
26
+ if(key=="username"){
27
+ if(!/^[a-zA-Z0-9_-]+$/.test(value)){
28
+ console.log("username must only contain letters,underscores and hyphen");
29
+ process.exit(1);
30
+ }
31
+ }
32
+ setConfigValue(key,value);
33
+ }catch(e){
34
+ console.error(e);
35
+ }
36
+ }
@@ -0,0 +1,29 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+ import dotenv from "dotenv"
4
+ import {initConfig,setConfigValue} from "../config/vcsConfig.js"
5
+ dotenv.config();
6
+ async function initRepo(){
7
+ const repoPath=path.resolve(process.cwd(),".myvcs");
8
+ const commitPath=path.resolve(repoPath,"commits");
9
+ const currentFolder = path.basename(process.cwd());
10
+ try{
11
+ await fs.mkdir(repoPath,{recursive:true});
12
+ await fs.mkdir(commitPath,{recursive:true});
13
+ await fs.writeFile(
14
+ path.join(repoPath,"config.json"),
15
+ JSON.stringify({bucket:process.env.S3_BUCKET})
16
+ )
17
+ initConfig();
18
+ setConfigValue("repoName",currentFolder);
19
+ console.log("Repository Initialised!",repoPath)
20
+ console.log("\nNext steps:");
21
+ console.log(" 1. Set your username: myvcs config set username <your-username>");
22
+ console.log(" 2. Set your email: myvcs config set email <your-email>");
23
+ console.log(" 3. Start tracking files: myvcs add <file>");
24
+ }catch(err){
25
+ console.error("Repository initialisation failed! ",err);
26
+ }
27
+ }
28
+
29
+ export {initRepo}
@@ -0,0 +1,64 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+ import {S3,S3_BUCKET} from "../config/aws-config.js"
4
+ import {getUsername,getConfigValue} from "../config/vcsConfig.js"
5
+ async function pullRepo(){
6
+ const repoPath=path.resolve(process.cwd(),".myvcs");
7
+ const commitsPath=path.resolve(repoPath,"commits");
8
+ const username=getUsername();
9
+ const repoName=getConfigValue("repoName");
10
+ try{
11
+ const data = await S3.listObjectsV2({
12
+ Bucket:S3_BUCKET,
13
+ Prefix:`commits/${username}/${repoName}`
14
+ }).promise();
15
+ const objects=data.Contents;
16
+ console.log(`Found ${objects.length} files to pull`);
17
+ for(const object of objects){
18
+ const key=object.Key;
19
+ console.log(key);
20
+ // Remove 'commits/' prefix to get relative path
21
+ const relativePath = key.replace('commits/', ''); // e.g., 'a0f0a67b.../hello.txt'
22
+ const localFilePath = path.join(commitsPath, relativePath);
23
+ // Create parent directory if needed
24
+ const localDir = path.dirname(localFilePath);
25
+ await fs.mkdir(localDir, {recursive: true});
26
+ const params={
27
+ Bucket: S3_BUCKET,
28
+ Key:key
29
+ }
30
+ const fileContent=await S3.getObject(params).promise();
31
+ await fs.writeFile(localFilePath,fileContent.Body);
32
+ console.log(`Downloaded: ${relativePath}`);
33
+ }
34
+ console.log("Pulled all commits");
35
+ const commitsJsonPath=path.resolve(commitsPath,"commits.json");
36
+ const commitsData=await fs.readFile(commitsJsonPath,'utf-8');
37
+ const commits=JSON.parse(commitsData);
38
+ if(commits.length==0){
39
+ console.log("No commits to Restore");
40
+ return;
41
+ }
42
+ const latestCommit=commits[commits.length-1];
43
+ const latestCommitId=latestCommit.commitId;
44
+ const latestPathCommit=path.join(commitsPath,latestCommitId);
45
+
46
+ console.log(`Restoring files from commit: ${latestCommitId}`);
47
+ console.log(`Message: ${latestCommit.message}`);
48
+
49
+ const files=await fs.readdir(latestPathCommit);
50
+
51
+ for(const file of files){
52
+ const sourcePath=path.join(latestPathCommit,file);
53
+ const destPath=path.join(process.cwd(),file);
54
+
55
+ await fs.copyFile(sourcePath,destPath);
56
+ console.log(`Restored ${file} \n`);
57
+ }
58
+
59
+ }catch(err){
60
+ console.log("Error pulling from S3!! ",err);
61
+ }
62
+ }
63
+
64
+ export {pullRepo}
@@ -0,0 +1,49 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+ import {S3,S3_BUCKET} from "../config/aws-config.js"
4
+ import {getUsername,getConfigValue} from "../config/vcsConfig.js"
5
+ async function pushRepo(){
6
+ const repoPath=path.resolve(process.cwd(),".myvcs");
7
+ const commitsPath=path.resolve(repoPath,"commits");
8
+ const username=getUsername();
9
+ const repoName=getConfigValue("repoName");
10
+ try{
11
+ const commitDirs=await fs.readdir(commitsPath);
12
+ console.log(commitDirs);
13
+ for(const dir of commitDirs){
14
+ const dirPath=path.join(commitsPath,dir);
15
+ // CHECK IF IT'S A DIRECTORY FIRST
16
+ const stats = await fs.stat(dirPath);
17
+ if(!stats.isDirectory()){
18
+ // If it's commits.json, upload it separately
19
+ if(dir === 'commits.json'){
20
+ const fileContent = await fs.readFile(dirPath);
21
+ const params = {
22
+ Bucket: S3_BUCKET,
23
+ Key: `commits/${username}/${repoName}/commits.json`,
24
+ Body: fileContent
25
+ };
26
+ await S3.upload(params).promise();
27
+ console.log('commits.json uploaded to S3');
28
+ }
29
+ continue; // Skip to next item
30
+ }
31
+ const files=await fs.readdir(dirPath);
32
+ for(const file of files){
33
+ const filePath=path.join(dirPath,file);
34
+ const fileContent=await fs.readFile(filePath);
35
+ const params={
36
+ Bucket:S3_BUCKET,
37
+ Key:`commits/${username}/${repoName}/${dir}/${file}`,
38
+ Body:fileContent
39
+ }
40
+ await S3.upload(params).promise();
41
+ }
42
+ }
43
+ console.log("All commits pushed to myVCS")
44
+ }catch(err){
45
+ console.error("Error in pushing code to GitHub!!",err);
46
+ }
47
+ }
48
+
49
+ export {pushRepo}
@@ -0,0 +1,23 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+
4
+ async function revertRepo(commitID){
5
+ const repoPath=path.resolve(process.cwd(),".myvcs");
6
+ const commitPath=path.resolve(repoPath,"commits");
7
+ const thisCommit=path.join(commitPath,commitID);
8
+ const parentDir=path.resolve(repoPath,"..");
9
+ try{
10
+ const files=await fs.readdir(thisCommit);
11
+
12
+ for(const file of files){
13
+ await fs.copyFile(path.join(thisCommit,file),path.join(parentDir,file));
14
+ }
15
+
16
+ console.log(`Reverted to commitId:${commitID} successfully!!`);
17
+ }
18
+ catch(err){
19
+ console.log("Unable to Revert!! ",err);
20
+ }
21
+ }
22
+
23
+ export {revertRepo}
@@ -0,0 +1,18 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+
4
+ async function showCommits(){
5
+ const repoPath=path.resolve(process.cwd(),".myvcs");
6
+ const commitPath=path.join(repoPath,"commits");
7
+ const commitJSONPath=path.join(commitPath,"commits.json");
8
+ try{
9
+ const fileContent=await fs.readFile(commitJSONPath,'utf-8');
10
+ const commits=JSON.parse(fileContent);
11
+ console.log(commits);
12
+ }
13
+ catch(err){
14
+ console.log("No commits ",err)
15
+ }
16
+ }
17
+
18
+ export {showCommits}
package/README.md ADDED
@@ -0,0 +1,43 @@
1
+ # MyVCS
2
+
3
+ A simple version control system with S3 cloud storage.
4
+
5
+ ## Installation
6
+ ```bash
7
+ npm install -g myvcs
8
+ ```
9
+
10
+ ## Setup
11
+ ```
12
+ Initialize a repository:
13
+ ```bash
14
+ myvcs init
15
+ myvcs config set username your-username
16
+ myvcs config set email your-email
17
+ ```
18
+
19
+ ## Usage
20
+ ```bash
21
+ # Add files
22
+ myvcs add file.txt
23
+ myvcs add . # Add all files
24
+
25
+ # Commit
26
+ myvcs commit "Your commit message"
27
+
28
+ # Push to cloud
29
+ myvcs push
30
+
31
+ # Pull from cloud
32
+ myvcs pull
33
+
34
+ # View commits
35
+ myvcs list commits
36
+
37
+ # Revert to a commit
38
+ myvcs revert <commitID>
39
+ ```
40
+
41
+ ## License
42
+
43
+ MIT
@@ -0,0 +1,13 @@
1
+ import aws from "aws-sdk"
2
+
3
+ aws.config.update({
4
+ region: process.env.AWS_REGION || "ap-south-1",
5
+ accessKeyId: process.env.AWS_ACCESS_KEY_ID,
6
+ secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
7
+ });
8
+
9
+ const S3=new aws.S3();
10
+ const S3_BUCKET="amzn-777";
11
+
12
+ export {S3,S3_BUCKET}
13
+
@@ -0,0 +1,73 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+
4
+ function getConfigPath(){
5
+ return path.join(process.cwd(),".myvcs","config.json");
6
+ }
7
+ function configDirExists(){
8
+ const configDir=path.join(process.cwd(),".myvcs");
9
+ return fs.existsSync(configDir);
10
+ }
11
+
12
+ function initConfig(){
13
+ const defaultConfig={
14
+ "username":null,
15
+ "email":null
16
+ }
17
+ writeConfig(defaultConfig);
18
+ }
19
+ function readConfig(){
20
+ if(!configDirExists()){
21
+ throw new Error("myvcs not initialised.. Run 'myvcs init' first!!");
22
+ }
23
+ const configPath=getConfigPath();
24
+
25
+ if(!fs.existsSync(configPath)){
26
+ return {
27
+ "username":null,
28
+ "email":null
29
+ };
30
+ }
31
+
32
+ try{
33
+ const data=fs.readFileSync(configPath,'utf-8');
34
+ return JSON.parse(data);
35
+ }catch(err){
36
+ throw new Error(`Failed to read config: ${error.message}`);
37
+ }
38
+ }
39
+ function writeConfig(config){
40
+ if(!configDirExists){
41
+ throw new Error("Not a myvcs repository. Run 'myvcs init' first.");
42
+ }
43
+
44
+ const configPath=getConfigPath();
45
+
46
+ try{
47
+ fs.writeFileSync(configPath,JSON.stringify(config,null,2));
48
+ return true;
49
+ }catch(err){
50
+ throw new Error("Not a myvcs repository. Run 'myvcs init' first.")
51
+ }
52
+ }
53
+ function setConfigValue(key,value){
54
+ const config=readConfig();
55
+ config[key]=value;
56
+ writeConfig(config);
57
+ console.log(`Config Updated ${key}=${value}`);
58
+ }
59
+ function getConfigValue(key) {
60
+ const config = readConfig();
61
+ return config[key];
62
+ }
63
+ function getUsername(){
64
+ const username=getConfigValue("username");
65
+ if(!username){
66
+ throw new Error(
67
+ "Username not configured. Please set your username with:\n" +
68
+ " myvcs config set username <your-username>"
69
+ );
70
+ }
71
+ return username;
72
+ }
73
+ export {initConfig,readConfig,setConfigValue,getUsername,getConfigValue}
package/index.js ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import yargs from "yargs/yargs"
3
+ import {hideBin} from "yargs/helpers"
4
+ import {initRepo} from "./Controllers/initRepo.js"
5
+ import { addRepo } from "./Controllers/addRepo.js";
6
+ import { commitRepo } from "./Controllers/commitRepo.js";
7
+ import { pushRepo } from "./Controllers/pushRepo.js";
8
+ import { pullRepo } from "./Controllers/pullRepo.js";
9
+ import {revertRepo} from "./Controllers/revertRepo.js"
10
+ import {showCommits} from "./Controllers/showCommits.js"
11
+ import {showConfig,setConfig} from "./Controllers/config.js"
12
+ yargs(hideBin(process.argv))
13
+ .command("init","initialise a new repository",{},initRepo)
14
+ .command("add <file>","Add a file to repo/staging area use 'add .' to add all files",
15
+ (yargs)=>{
16
+ yargs.positional("file",{
17
+ describe:"add file to staging area",
18
+ type:"string"
19
+ })
20
+ },async (argv)=>{
21
+ await addRepo(argv.file);
22
+ }
23
+ )
24
+ .command("commit <message>","commit files added to staging area",
25
+ (yagrs)=>{
26
+ yagrs.positional("message",{
27
+ describe:"commit file from staging area",
28
+ type:"string"
29
+ })
30
+ },
31
+ (argv)=>{
32
+ commitRepo(argv.message);
33
+ }
34
+ )
35
+ .command("push","push commits to cloud storage",{},pushRepo)
36
+ .command("pull","pull commits from cloud to local system",{},pullRepo)
37
+ .command("revert <commitID>","revert back to specified commitID",
38
+ (yargs)=>{
39
+ yargs.positional("commitID",{
40
+ describe:"commitID to revert",
41
+ type:"string"
42
+ })
43
+ },
44
+ (argv)=>{
45
+ revertRepo(argv.commitID);
46
+ }
47
+ )
48
+ .command("list commits","show all commmits",{},showCommits)
49
+ .command("config","configure myVCS on your system",
50
+ (yargs)=>{
51
+ return yargs
52
+ .command("set <key> <value>","set a config value",
53
+ (yargs)=>{
54
+ yargs
55
+ .positional("key",{
56
+ describe:"config key (username)",
57
+ type:"string"
58
+ })
59
+ .positional("value",{
60
+ describe:"config value",
61
+ type:"string"
62
+ })
63
+ },
64
+ (argv)=>{
65
+ setConfig(argv.key,argv.value);
66
+ }
67
+ )
68
+ .command("list","show config details",{},showConfig)
69
+ .demandCommand(1,"Please specify a config suncommand")
70
+ }
71
+ )
72
+ .demandCommand(1,"you need atleast one command")
73
+ .help().argv;
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "myvcs",
3
+ "version": "0.1.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "keywords": ["vcs", "version-control", "git", "s3", "cloud-storage"],
9
+ "type": "module",
10
+ "author": "Amarnath Vasanth amarnathvasanth39@gmail.com",
11
+ "license": "ISC",
12
+ "description": "A simple version control system with S3 cloud storage",
13
+ "bin": {
14
+ "myvcs": "./index.js"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/yourusername/myvcs.git"
19
+ },
20
+ "dependencies": {
21
+ "aws-sdk": "^2.1693.0",
22
+ "dotenv": "^17.2.3",
23
+ "uuid": "^13.0.0",
24
+ "yargs": "^18.0.0"
25
+ }
26
+ }