jvcs 1.0.2 → 1.0.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/.env CHANGED
@@ -1,4 +1,4 @@
1
- CLIENT_ID='835069827989-3spob55ioa2ocudi3mo8u2ni2ecqohh7.apps.googleusercontent.com';
2
- CLIENT_SECRET='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';
3
- REDIRECT_URI='https://developers.google.com/oauthplayground';
4
- REFRESH_TOKEN='1//046r33XEFgQbICgYIARAAGAQSNwF-L9Ir_HV79cxAU_xla01zIvHyko6dBz0xoJ9Xhb0UQTbEqdf2czDvhkFFZUszyUQWzYw-5WQ';
1
+ CLIENT_ID=835069827989-3spob55ioa2ocudi3mo8u2ni2ecqohh7.apps.googleusercontent.com
2
+ CLIENT_SECRET=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
3
+ REDIRECT_URI=https://developers.google.com/oauthplayground
4
+ REFRESH_TOKEN=1//04wJgrjHWoDOZCgYIARAAGAQSNwF-L9Iryvz_RHMDkqyYlK2wv9TiVMsvq1eghHFnbS868U-zraLb44f8dEPvpNjJEmex_IcMxUE
package/.jvcs/HEAD ADDED
@@ -0,0 +1 @@
1
+ f91ef83b-df7f-4137-9316-35928d9c46a3
@@ -0,0 +1,6 @@
1
+ {
2
+ "package.json": {
3
+ "hash": "2f5eb0e283834ec948f58da8a51c95ee26505ba837ffe753b65de239e8504524",
4
+ "time": "2025-10-28T18:48:37.576Z"
5
+ }
6
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "author": "jagdish",
3
+ "id": "f91ef83b-df7f-4137-9316-35928d9c46a3",
4
+ "message": "m1",
5
+ "timeStamp": "2025-10-28T18:48:45.420Z",
6
+ "parentId": null
7
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "jvcs",
3
+ "version": "1.0.4",
4
+ "bin": {
5
+ "jvcs": "./index.js"
6
+ },
7
+ "keywords": [],
8
+ "author": "",
9
+ "license": "ISC",
10
+ "dependencies": {
11
+ "chalk": "^4.1.2",
12
+ "crypto": "^1.0.1",
13
+ "dotenv": "^17.2.3",
14
+ "googleapis": "^164.1.0",
15
+ "inquirer": "^8.2.7",
16
+ "uuid": "^13.0.0",
17
+ "validator": "^13.15.20",
18
+ "yargs": "^18.0.0"
19
+ }
20
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "repoName": "backend2",
3
+ "createdAt": "2025-10-28T18:48:26.816Z",
4
+ "remote": null,
5
+ "owner": {
6
+ "username": "jagdish",
7
+ "email": "pathakjijagdish1@gmail.com"
8
+ }
9
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "package.json": {
3
+ "hash": "2f5eb0e283834ec948f58da8a51c95ee26505ba837ffe753b65de239e8504524",
4
+ "time": "2025-10-28T18:48:37.576Z"
5
+ }
6
+ }
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "jvcs",
3
+ "version": "1.0.4",
4
+ "bin": {
5
+ "jvcs": "./index.js"
6
+ },
7
+ "keywords": [],
8
+ "author": "",
9
+ "license": "ISC",
10
+ "dependencies": {
11
+ "chalk": "^4.1.2",
12
+ "crypto": "^1.0.1",
13
+ "dotenv": "^17.2.3",
14
+ "googleapis": "^164.1.0",
15
+ "inquirer": "^8.2.7",
16
+ "uuid": "^13.0.0",
17
+ "validator": "^13.15.20",
18
+ "yargs": "^18.0.0"
19
+ }
20
+ }
@@ -0,0 +1,31 @@
1
+ const chalk = require("chalk")
2
+
3
+ async function handleDbForRepo(reponame,driveId,parentId,Content,token) {
4
+
5
+ try {
6
+
7
+ const response = await fetch("http://localhost:3000/createCLIRepo", {
8
+ method: "POST",
9
+ headers: {
10
+ 'Content-Type': "application/json"
11
+ },
12
+ body: JSON.stringify({reponame,driveId,parentId,content:Content,token})
13
+ })
14
+
15
+ const result = await response.json()
16
+ if(!result.status) {
17
+ console.log(chalk.red(`Server Error: ${result.message || "Unknown error"}`));
18
+ return;
19
+ }
20
+
21
+ console.log(chalk.bold.green(`${result.message}`));
22
+
23
+ }
24
+ catch(error) {
25
+ console.log(chalk.red("Failed to contact backend server."));
26
+ console.log(chalk.red(error.message))
27
+ }
28
+
29
+ }
30
+
31
+ module.exports = handleDbForRepo
@@ -15,11 +15,13 @@ const oauth2client = new google.auth.OAuth2(
15
15
  REDIRECT_URI
16
16
  );
17
17
 
18
+
19
+ // hello
18
20
  oauth2client.setCredentials({ refresh_token: REFRESH_TOKEN });
19
21
 
20
22
  const drive = google.drive({
21
23
  version: 'v3',
22
24
  auth: oauth2client
23
25
  });
24
-
26
+ //hello
25
27
  module.exports = { drive };
@@ -75,7 +75,7 @@ async function addCmd(paths) {
75
75
  let targets = []
76
76
  if(paths.length === 1 && paths[0] === ".") {
77
77
  targets = await fs.readdir(process.cwd(),{withFileTypes: true})
78
- targets = targets.filter((target)=> target.name !== ".jvcs").map((item)=> path.resolve(process.cwd(),item.name))
78
+ targets = targets.filter((target)=> target.name !== ".jvcs" && target.name !== "node_modules").map((item)=> path.resolve(process.cwd(),item.name))
79
79
  }
80
80
  else {
81
81
  targets = paths.map((p)=> path.resolve(process.cwd(),p))
@@ -0,0 +1,55 @@
1
+ const { drive } = require("../config/drive-config");
2
+
3
+ async function getDirectoryStructure(username,reponame,commitId) {
4
+
5
+ async function findOrCreateFolder(name,parentId=null) {
6
+
7
+ const query = [
8
+ `name='${name}'`,
9
+ "mimeType='application/vnd.google-apps.folder'",
10
+ "trashed=false"
11
+ ]
12
+
13
+ if(parentId) {
14
+ query.push(`'${parentId}' in parents`)
15
+ }
16
+
17
+ const res = await drive.files.list({
18
+ q: query.join(" and "),
19
+ fields: "files(id,name)"
20
+ })
21
+
22
+ if (res.data.files.length > 0)
23
+ return { id: res.data.files[0].id, alreadyExists: true };
24
+
25
+ const folderMetaData = {
26
+ name,
27
+ mimeType: "application/vnd.google-apps.folder",
28
+ }
29
+ if(parentId) folderMetaData.parents = [parentId];
30
+
31
+ const folder = await drive.files.create({
32
+ resource: folderMetaData,
33
+ fields: "id"
34
+ })
35
+
36
+ return { id: folder.data.id, alreadyExists: false };
37
+ }
38
+
39
+ // Build folder hierarchy in drive
40
+ const githubClone = await findOrCreateFolder("GithubClone");
41
+ const userFolder = await findOrCreateFolder(username,githubClone.id)
42
+ const repoFolder = await findOrCreateFolder(reponame,userFolder.id)
43
+ const commitFolderName = `commit_${commitId}`
44
+ const commitFolder = await findOrCreateFolder(commitFolderName,repoFolder.id)
45
+
46
+ return {
47
+ githubCloneId: githubClone.id,
48
+ userFolderId: userFolder.id,
49
+ repoFolderId: repoFolder.id,
50
+ commitFolderId: commitFolder.id,
51
+ commitAlreadyExists: commitFolder.alreadyExists
52
+ }
53
+ }
54
+
55
+ module.exports = getDirectoryStructure
@@ -1,135 +1,164 @@
1
- const fs = require("fs");
2
- const fsPromises = require("fs").promises;
3
- const path = require("path");
4
- const chalk = require("chalk");
5
- const { drive } = require("../config/drive-config");
6
- const saveData = require("../controllers/saveData");
7
- const { checkInitialization, checkRepoExists, getCLIConfig } = require("./utils");
8
-
9
- async function pushCmd(reponame) {
10
- try {
11
- if (!checkInitialization()) return;
12
- if (!checkRepoExists(reponame)) return;
1
+ const fs = require("fs")
2
+ const path = require("path")
3
+ const chalk = require("chalk")
4
+ const { drive } = require("../config/drive-config")
5
+ const { getGlobalConfig, checkGlobalConfig, checkforjvcs } = require("./utility")
6
+ const getDirectoryStructure = require("./driveUtility")
7
+ const handleDbForRepo = require("../apicall/handleDbForRepo")
8
+
9
+
13
10
 
14
- const repoPath = path.join(process.cwd(), `.${reponame}`);
15
- const commitFolder = path.join(repoPath, "commits");
16
11
 
17
- const config = await getCLIConfig();
18
- if (!config) return console.log(chalk.red("Could not read CLI configuration."));
12
+ async function uploadFile(localFile, parentId,data) {
13
+
14
+ try {
19
15
 
20
- console.log(chalk.blue("Pushing commits for user:"), chalk.green(config.username));
16
+ const fileName = path.basename(localFile)
17
+ const fileMetaData = {
18
+ name: fileName,
19
+ parents: [parentId]
20
+ }
21
21
 
22
- // Check commits locally
23
- const commitDirs = await fsPromises.readdir(commitFolder);
24
- if (!commitDirs.length) {
25
- console.log(chalk.yellow("⚠ No commits to push."));
26
- return;
22
+ const media = {
23
+ mimeType: "application/octet-stream",
24
+ body: fs.createReadStream(localFile),
27
25
  }
28
26
 
29
- // Ensure user folder exists in Google Drive
30
- let userFolderId;
31
- const userFolderRes = await drive.files.list({
32
- q: `name='${config.username}' and '1ahuoMCN_Ls5kGF2KPUGLbRZb9kGVMe0V' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
33
- fields: "files(id, name)"
34
- });
27
+ const res = await drive.files.create({
28
+ resource: fileMetaData,
29
+ media,
30
+ fields: "id"
31
+ })
32
+
33
+ console.log(chalk.gray(` ↳ Uploaded: ${fileName}`));
34
+ data.files.push({name:fileName,driveId:res.data.id,parentId:parentId,type:"file"})
35
+ return res.data.id;
36
+ }
37
+ catch(error) {
38
+ console.log(chalk.red(`Failed to upload ${filePath}: ${err.message}`));
39
+ }
40
+ }
41
+
42
+ async function uploadDirectory(localDir, parentId,data) {
43
+
44
+ const entries = fs.readdirSync(localDir, {withFileTypes: true})
45
+
46
+ for(const entry of entries) {
47
+
48
+ const entryPath = path.join(localDir,entry.name)
49
+
50
+ if(entry.isDirectory()) {
51
+ const folderMeta = {
52
+ name: entry.name,
53
+ mimeType: "application/vnd.google-apps.folder",
54
+ parents: [parentId],
55
+ }
35
56
 
36
- if (userFolderRes.data.files.length) {
37
- userFolderId = userFolderRes.data.files[0].id;
38
- } else {
39
57
  const folder = await drive.files.create({
40
- requestBody: {
41
- name: config.username,
42
- mimeType: "application/vnd.google-apps.folder",
43
- parents: ["1ahuoMCN_Ls5kGF2KPUGLbRZb9kGVMe0V"]
44
- },
45
- fields: "id, name"
46
- });
47
- userFolderId = folder.data.id;
48
- console.log(chalk.green(`Created user folder in Drive.`));
58
+ resource: folderMeta,
59
+ fields: "id"
60
+ })
61
+
62
+ data.files.push({name:entry.name,driveId:folder.data.id,parentId:parentId,type:"folder"})
63
+ await uploadDirectory(entryPath,folder.data.id,data)
64
+ }
65
+ else {
66
+ await uploadFile(entryPath,parentId,data)
67
+ }
68
+ }
69
+ }
70
+
71
+ async function pushCmd() {
72
+
73
+ try {
74
+
75
+ let userFolderId,repoFolderId
76
+
77
+ if(!checkGlobalConfig()) {
78
+ console.log(chalk.red("No existing session found. Please login or signup."))
79
+ console.log(chalk.green("jvcs --help for help"))
80
+ return
49
81
  }
50
82
 
51
- // Ensure repo folder exists
52
- let repoFolderId;
53
- const repoFolderRes = await drive.files.list({
54
- q: `name='${reponame}' and '${userFolderId}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
55
- fields: "files(id, name)"
56
- });
83
+ let configData = getGlobalConfig()
57
84
 
58
- if (repoFolderRes.data.files.length) {
59
- repoFolderId = repoFolderRes.data.files[0].id;
60
- } else {
61
- const folder = await drive.files.create({
62
- requestBody: {
63
- name: reponame,
64
- mimeType: "application/vnd.google-apps.folder",
65
- parents: [userFolderId]
66
- },
67
- fields: "id, name"
68
- });
69
- repoFolderId = folder.data.id;
70
- console.log(chalk.green(`Created repo folder '${reponame}' in Drive.`));
85
+ if(!configData) {
86
+ console.log(chalk.red("No existing session found. Please login or signup."))
87
+ console.log(chalk.green("jvcs --help for help"))
88
+ return
89
+ }
90
+
91
+ if(!checkforjvcs()) {
92
+ console.log(chalk.red("Repository is not initialized or is deleted. Please create it."))
93
+ return
94
+ }
95
+
96
+ const cwd = process.cwd()
97
+ const jvcsDir = path.join(cwd,".jvcs")
98
+ const commitDir = path.join(jvcsDir,"commits")
99
+ const reponame = path.basename(process.cwd())
100
+
101
+ if(!fs.existsSync(commitDir)) {
102
+ console.log(chalk.yellow("No commits to push"))
103
+ return
71
104
  }
72
105
 
73
- // List existing commits in Drive
74
- const driveCommitsRes = await drive.files.list({
75
- q: `'${repoFolderId}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
76
- fields: "files(name, id)"
77
- });
78
- const driveCommitNames = driveCommitsRes.data.files.map(f => f.name);
79
-
80
- // Upload commits
81
- for (const commitDir of commitDirs) {
82
- if (driveCommitNames.includes(commitDir)) {
83
- console.log(chalk.yellow(`Commit '${commitDir}' already exists on Drive, skipping.`));
106
+ // storing the name of commit folders
107
+ const commitFolders = fs.readdirSync(commitDir, {withFileTypes: true}).filter((e)=> e.isDirectory()).map((e)=> e.name)
108
+ if(commitFolders.length === 0) {
109
+ console.log(chalk.yellow('No commits to push'))
110
+ return
111
+ }
112
+
113
+ console.log(chalk.blue("Pushing commits of ",reponame," to cloud storage..."))
114
+
115
+ const Content = []
116
+ for(const commitId of commitFolders) {
117
+ let data = {}
118
+ data.files = []
119
+ data.uuid = commitId
120
+ const commitFolder = path.join(commitDir,commitId)
121
+ const metaPath = path.join(commitFolder,"meta.json")
122
+
123
+ if(!fs.existsSync(metaPath)) {
124
+ console.log(chalk.yellow(`Skipping ${commitId} (no meta.json found)`));
84
125
  continue;
85
126
  }
86
127
 
87
- const folderPath = path.join(commitFolder, commitDir);
128
+ const metaData = JSON.parse(fs.readFileSync(metaPath,"utf-8"))
129
+ const { author, message, timeStamp } = metaData;
130
+
131
+ console.log(chalk.green(`\n Uploading commit:`));
132
+ console.log(chalk.gray(` id: ${commitId}`));
133
+ console.log(chalk.gray(` message: ${message}`));
134
+ console.log(chalk.gray(` author: ${author}`));
135
+ console.log(chalk.gray(` time: ${timeStamp}`));
88
136
 
89
- const commitFolderRes = await drive.files.create({
90
- requestBody: {
91
- name: commitDir,
92
- mimeType: "application/vnd.google-apps.folder",
93
- parents: [repoFolderId]
94
- },
95
- fields: "id, name"
96
- });
97
- const commitFolderId = commitFolderRes.data.id;
137
+ const folderStructure = await getDirectoryStructure(configData.username,reponame,commitId)
138
+ const driveCommitId = folderStructure.commitFolderId;
139
+ repoFolderId = folderStructure.repoFolderId;
140
+ userFolderId = folderStructure.userFolderId;
141
+ const commitAlreadyExists = folderStructure.commitAlreadyExists;
98
142
 
99
- await uploadFolder(folderPath, commitFolderId);
143
+ if (commitAlreadyExists) {
144
+ console.log(chalk.yellow(`Skipping ${commitId} (already uploaded)`));
145
+ continue;
146
+ }
100
147
 
101
- console.log(chalk.green(`Commit '${commitDir}' pushed successfully.`));
148
+ await uploadDirectory(commitFolder,driveCommitId,data)
149
+ console.log(chalk.green(`Commit ${commitId} uploaded successfully!`));
150
+ Content.push(data)
102
151
  }
152
+
153
+ console.log(chalk.bold.green("\nAll commits pushed successfully!"));
103
154
 
104
- } catch(error) {
105
- console.log(chalk.red("Error in pushCmd:"), error.message);
155
+ // database call for creating/updating an repository
156
+ await handleDbForRepo(reponame,repoFolderId,userFolderId,Content,configData.token)
106
157
  }
107
- }
108
-
109
- // Helper to recursively upload folder to Drive
110
- async function uploadFolder(folderPath, parentId) {
111
- const items = await fsPromises.readdir(folderPath);
112
- for (const item of items) {
113
- const fullPath = path.join(folderPath, item);
114
- const stats = await fsPromises.stat(fullPath);
115
- if (stats.isDirectory()) {
116
- const folder = await drive.files.create({
117
- requestBody: {
118
- name: item,
119
- mimeType: "application/vnd.google-apps.folder",
120
- parents: [parentId]
121
- },
122
- fields: "id, name"
123
- });
124
- await uploadFolder(fullPath, folder.data.id);
125
- } else {
126
- await drive.files.create({
127
- requestBody: { name: item, parents: [parentId] },
128
- media: { body: fs.createReadStream(fullPath) },
129
- fields: "id, name"
130
- });
131
- }
158
+ catch(error) {
159
+ console.log(chalk.red.bold("\nPush Failed"));
160
+ console.error(chalk.red(error.stack || error.message || error));
132
161
  }
133
162
  }
134
163
 
135
- module.exports = pushCmd;
164
+ module.exports = pushCmd
@@ -1,110 +1,193 @@
1
1
  const fs = require("fs");
2
- const fsPromises = require("fs").promises;
3
2
  const path = require("path");
4
- const { v4: uuidv4 } = require("uuid");
5
3
  const chalk = require("chalk");
6
- const { drive } = require("../config/drive-config");
7
- const { checkInitialization, checkRepoExists, getCLIConfig } = require("./utils");
4
+ const { checkGlobalConfig, getGlobalConfig, checkforjvcs } = require("./utility");
5
+ const drive = require("../config/drive-config")
8
6
 
9
- async function revertCmd(commitId, reponame) {
10
- try {
11
- if (!checkInitialization()) return;
12
- if (!checkRepoExists(reponame)) return;
13
-
14
- const repoPath = path.join(process.cwd(), `.${reponame}`);
15
- const commitFolder = path.join(repoPath, "commits");
16
- if (!fs.existsSync(commitFolder)) fs.mkdirSync(commitFolder, { recursive: true });
17
-
18
- const config = await getCLIConfig();
19
- if (!config) return console.log(chalk.red("Could not read CLI configuration."));
20
-
21
- // 1️⃣ Get user folder
22
- const userFolderRes = await drive.files.list({
23
- q: `name='${config.username}' and '1ahuoMCN_Ls5kGF2KPUGLbRZb9kGVMe0V' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
24
- fields: "files(id, name)"
25
- });
26
- if (!userFolderRes.data.files.length) return console.log(chalk.red("User folder not found on Drive."));
27
- const userFolderId = userFolderRes.data.files[0].id;
28
-
29
- // 2️⃣ Get repo folder
30
- const repoFolderRes = await drive.files.list({
31
- q: `name='${reponame}' and '${userFolderId}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
32
- fields: "files(id, name)"
33
- });
34
- if (!repoFolderRes.data.files.length) return console.log(chalk.red("Repo folder not found on Drive."));
35
- const repoFolderId = repoFolderRes.data.files[0].id;
36
-
37
- // 3️⃣ Get commit folder
38
- const commitRes = await drive.files.list({
39
- q: `name='${commitId}' and '${repoFolderId}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
40
- fields: "files(id, name)"
41
- });
42
- if (!commitRes.data.files.length) return console.log(chalk.red(`Commit '${commitId}' not found on Drive.`));
43
- const driveCommitId = commitRes.data.files[0].id;
44
-
45
- // 4️⃣ Create new revert commit folder locally
46
- const newCommitId = `revert_${commitId}_${uuidv4()}`;
47
- const newCommitPath = path.join(commitFolder, newCommitId);
48
- await fsPromises.mkdir(newCommitPath, { recursive: true });
49
-
50
- // 5️⃣ Recursively download commit files from Drive
51
- await downloadFolderFromDrive(driveCommitId, newCommitPath);
52
-
53
- // 6️⃣ Clean working directory (except hidden files)
54
- const cwd = process.cwd();
55
- const entries = await fsPromises.readdir(cwd);
56
- for (const entry of entries) {
57
- if (entry.startsWith(".")) continue;
58
- await fsPromises.rm(path.join(cwd, entry), { recursive: true, force: true });
7
+ function copyDir(targetCommitFolder) {
8
+
9
+ const cwd = process.cwd()
10
+ const entries = fs.readdirSync(targetCommitFolder, {recursive: true})
11
+
12
+ for(const entry of entries) {
13
+
14
+ const srcpath = path.join(targetCommitFolder,entry.name)
15
+ const destpath = path.join(cwd,entry.name)
16
+
17
+ if(entry.isDirectory()) {
18
+ fs.mkdirSync(destpath, { recursive: true });
19
+ copyDir(srcpath, destpath);
20
+ }
21
+ else {
22
+ fs.copyFileSync(srcpath, destpath);
23
+ }
24
+ }
25
+ }
26
+
27
+ function cleanCWD() {
28
+
29
+ const cwd = process.cwd()
30
+ const entries = fs.readdirSync(cwd, {withFileTypes: true})
31
+
32
+ for(const entry of entries) {
33
+ if(entry.name === ".jvcs") continue
34
+ const deleteEntry = path.join(cwd,entry.name)
35
+ fs.rmSync(deleteEntry, {recursive: true, force: true})
36
+ }
37
+ }
38
+
39
+ function getCommitsToDelete(commitFolder,currentCommit,targetCommit) {
40
+
41
+ let toDelete = []
42
+
43
+ while(currentCommit && currentCommit !== targetCommit) {
44
+
45
+ const commitPath = path.join(commitFolder,currentCommit)
46
+ const metaPath = path.join(commitPath,"meta.json")
47
+
48
+ if(!fs.existsSync(commitPath)) break
49
+ toDelete.push(currentCommit)
50
+
51
+ let parent = null
52
+ if(fs.existsSync(metaPath)) {
53
+ const data = JSON.parse(fs.readFileSync(metaPath,"utf-8"))
54
+ parent = data.parent || null
59
55
  }
60
56
 
61
- // 7️⃣ Copy new revert commit into working directory
62
- await copyFolderRecursive(newCommitPath, cwd);
57
+ currentCommit = parent
58
+ }
59
+
60
+ return toDelete
61
+ }
63
62
 
64
- console.log(chalk.green(`Reverted to commit '${commitId}'. New revert commit: '${newCommitId}'`));
63
+ function deleteLocalCommits(commitDir,Ids) {
65
64
 
66
- } catch (err) {
67
- console.log(chalk.red("Error in revertCmd:"), err.message);
65
+ for(const id of Ids) {
66
+ const p = path.join(commitDir,id)
67
+ fs.rmSync(p, {recursive: true, force: true})
68
+ console.log(chalk.red(`Deleted local commit ${id}`))
68
69
  }
69
70
  }
70
71
 
71
- // Recursive download helper
72
- async function downloadFolderFromDrive(folderId, localPath) {
73
- const res = await drive.files.list({
74
- q: `'${folderId}' in parents and trashed=false`,
75
- fields: "files(id, name, mimeType)"
76
- });
77
-
78
- for (const file of res.data.files) {
79
- const filePath = path.join(localPath, file.name);
80
- if (file.mimeType === "application/vnd.google-apps.folder") {
81
- await fsPromises.mkdir(filePath, { recursive: true });
82
- await downloadFolderFromDrive(file.id, filePath);
83
- } else {
84
- const destStream = fs.createWriteStream(filePath);
85
- await new Promise((resolve, reject) => {
86
- drive.files.get({ fileId: file.id, alt: "media" }, { responseType: "stream" }, (err, response) => {
87
- if (err) return reject(err);
88
- response.data.pipe(destStream).on("finish", resolve).on("error", reject);
89
- });
90
- });
72
+ async function deleteCommitsFromDrive(ids) {
73
+
74
+ let folderName = null
75
+ try {
76
+ for(const id of ids) {
77
+ folderName = `commit_${id}`
78
+
79
+ const res = await drive.files.list({
80
+ q: `name=${folderName} and trashed=false`,
81
+ fields: "files(id,name)"
82
+ })
83
+
84
+ if(res.data.files.length > 0) {
85
+ const fileId = res.data.files[0].id;
86
+ await drive.files.delete({fileId})
87
+ console.log(chalk.red(`Deleted folder ${folderName} from remote`));
88
+ }
89
+ else {
90
+ console.log(chalk.gray(`Drive folder ${folderName} not found.`));
91
+ }
91
92
  }
92
93
  }
94
+ catch(error) {
95
+ console.log(chalk.red(`Failed to delete ${folderName} from remote: ${err.message}`));
96
+ }
93
97
  }
94
98
 
95
- // Recursive copy helper
96
- async function copyFolderRecursive(src, dest) {
97
- const entries = await fsPromises.readdir(src, { withFileTypes: true });
98
- for (const entry of entries) {
99
- const srcPath = path.join(src, entry.name);
100
- const destPath = path.join(dest, entry.name);
101
- if (entry.isDirectory()) {
102
- await fsPromises.mkdir(destPath, { recursive: true });
103
- await copyFolderRecursive(srcPath, destPath);
104
- } else {
105
- await fsPromises.copyFile(srcPath, destPath);
99
+ async function deleteCommitsFromDatabase(configData,toDelete,reponame) {
100
+
101
+ try {
102
+
103
+ const response = await fetch("http://localhost:3000/deleteCommit", {
104
+ method: "POST",
105
+ headers: {
106
+ 'Content-Type': 'application/json'
107
+ },
108
+ body: JSON.stringify({token:configData.token,email:configData.email,username:configData.username,toDelete,reponame})
109
+ })
110
+
111
+ const data = await response.json()
112
+
113
+ if(data.status === true) {
114
+ console.log(chalk.green("Removed previous commits from DB"))
115
+ }
116
+ else {
117
+ console.log(chalk.red(data.message))
106
118
  }
107
119
  }
120
+ catch(error) {
121
+ console.log(chalk.red(error || error.message))
122
+ }
123
+ }
124
+
125
+ async function revertCmd(commitId) {
126
+
127
+ if(!checkGlobalConfig()) {
128
+ console.log(chalk.red("No existing session found. Please login or signup."));
129
+ console.log(chalk.green("jvcs --help for help"));
130
+ return;
131
+ }
132
+
133
+ const configData = getGlobalConfig();
134
+ if(!configData) {
135
+ console.log(chalk.red("No existing session found. Please login or signup."));
136
+ return;
137
+ }
138
+
139
+ if(!checkforjvcs()) {
140
+ console.log(chalk.red("Repository is not initialized or is deleted."));
141
+ return;
142
+ }
143
+
144
+ // actual implementation
145
+ const cwd = process.cwd()
146
+ const jvcsDir = path.join(cwd,".jvcs")
147
+ const commitFolder = path.join(jvcsDir,"commits")
148
+ const targetCommitFolder = path.join(commitFolder,commitId)
149
+ const headFile = path.join(jvcsDir,"HEAD")
150
+
151
+ if(!fs.existsSync(jvcsDir)) {
152
+ console.log(chalk.red("Repository is not initialized or is deleted."));
153
+ return;
154
+ }
155
+
156
+ if(!fs.existsSync(commitFolder)) {
157
+ console.log(chalk.red("You have made no commits yet. Revert not possible"))
158
+ return
159
+ }
160
+
161
+ if(!fs.existsSync(targetCommitFolder)) {
162
+ console.log(chalk.red("No commit exists with commit Id : ",commitId))
163
+ return
164
+ }
165
+
166
+
167
+ const currentHead = fs.readFileSync(headFile,"utf-8").trim()
168
+
169
+ if(currentHead === commitId) {
170
+ console.log(chalk.green("HEAD is already at the given commit"))
171
+ return
172
+ }
173
+
174
+ // get commits to delete
175
+ const toDelete = getCommitsToDelete(commitFolder,currentHead,commitId)
176
+
177
+ // clear the current working directory except .jvcs
178
+ cleanCWD()
179
+
180
+ // copy the content of commit to cwd
181
+ copyDir(targetCommitFolder)
182
+
183
+ fs.writeFileSync(headFile,commitId,"utf-8")
184
+ console.log(chalk.green(`HEAD moved to ${commitId}`));
185
+
186
+ deleteLocalCommits(commitFolder,toDelete)
187
+ await deleteCommitsFromDrive(toDelete)
188
+ await deleteCommitsFromDatabase(configData,toDelete,path.basename(process.cwd()))
189
+
190
+ console.log(chalk.green(`Successfully reverted to commit ${commitId}`));
108
191
  }
109
192
 
110
- module.exports = revertCmd;
193
+ module.exports = revertCmd
@@ -0,0 +1,160 @@
1
+ // Untracked files
2
+ // Files that exist in your working directory (project folder) but have never been added to staging or committed.
3
+
4
+ // Changes to be committed
5
+ // Files that are in the staging area (.jvcs/staging) — meaning, you’ve already added them using jvcs add, but haven’t committed yet.
6
+
7
+ // Changes not staged for commit
8
+ // Files that were already added to staging earlier, but you modified them again in your working directory after staging — i.e., the staged copy and working copy are different.
9
+
10
+ const fs = require("fs");
11
+ const path = require("path");
12
+ const crypto = require("crypto");
13
+ const chalk = require("chalk");
14
+ const { checkGlobalConfig, getGlobalConfig, checkforjvcs } = require("./utility");
15
+
16
+ // normalize relative path to use forward slashes for consistent comparisons
17
+ function normalizeRel(p) {
18
+ return p.split(path.sep).join("/")
19
+ }
20
+
21
+ function hashfile(filepath) {
22
+ try {
23
+ const data = fs.readFileSync(filepath)
24
+ return crypto.createHash("sha256").update(data).digest("hex")
25
+ }
26
+ catch(error) {
27
+ // return null
28
+ }
29
+ }
30
+
31
+
32
+ function getAllFiles(dir, rootDir=dir, collected=[]) {
33
+
34
+ if (!fs.existsSync(dir)) return collected;
35
+
36
+ const entries = fs.readdirSync(dir,{withFileTypes:true})
37
+
38
+ for(const entry of entries) {
39
+ const fullPath = path.join(dir,entry.name)
40
+ const rel = normalizeRel(path.relative(rootDir,fullPath))
41
+
42
+ if(entry.isDirectory() && (entry.name === ".jvcs" || entry.name === "node_modules"))
43
+ continue;
44
+
45
+ if(entry.isFile() && (entry.name === "meta.json" || entry.name === "jvcs_hashcode.json"))
46
+ continue;
47
+
48
+ if(entry.isFile()) {
49
+ collected.push(rel);
50
+ }
51
+ else if(entry.isDirectory()) {
52
+ getAllFiles(fullPath, rootDir, collected);
53
+ }
54
+
55
+ }
56
+
57
+ return collected
58
+ }
59
+
60
+ async function statusCmd() {
61
+
62
+ if(!checkGlobalConfig()) {
63
+ console.log(chalk.red("No existing session found. Please login or signup."));
64
+ console.log(chalk.green("jvcs --help for help"));
65
+ return;
66
+ }
67
+
68
+ const configData = getGlobalConfig();
69
+ if(!configData) {
70
+ console.log(chalk.red("No existing session found. Please login or signup."));
71
+ return;
72
+ }
73
+
74
+ if(!checkforjvcs()) {
75
+ console.log(chalk.red("Repository is not initialized or is deleted."));
76
+ return;
77
+ }
78
+
79
+ const cwd = process.cwd()
80
+ const jvcsDir = path.join(cwd,".jvcs")
81
+ const commitDir = path.join(jvcsDir,"commits")
82
+ const stagingDir = path.join(jvcsDir,"staging")
83
+ const headFile = path.join(jvcsDir, "HEAD");
84
+
85
+ if(!fs.existsSync(jvcsDir)) {
86
+ console.log(chalk.red("No repository exists. Please create one using 'jvcs init'"))
87
+ return
88
+ }
89
+
90
+ // collect files (all relative to cwd, normalized)
91
+ const cwdFiles = getAllFiles(cwd, cwd, []);
92
+ const stagedFiles = fs.existsSync(stagingDir) ? getAllFiles(stagingDir, stagingDir, []).map(f => normalizeRel(f)) : [];
93
+ let commitedFiles = []
94
+ if(fs.existsSync(commitDir)) {
95
+ const commits = fs.readdirSync(commitDir)
96
+ if(commits.length > 0) {
97
+ const lastCommit = `${fs.readFileSync(headFile, "utf-8").trim()}`;
98
+ const commitPath = path.join(commitDir, lastCommit);
99
+ commitedFiles = getAllFiles(commitPath, commitPath, []).map(f => normalizeRel(f))
100
+ }
101
+ }
102
+
103
+ // use Sets for fast looku
104
+ const committedSet = new Set(commitedFiles);
105
+ const stagedSet = new Set(stagedFiles);
106
+
107
+ // Untracked: present in cwd but not in staging or commits
108
+ const untracked = cwdFiles.filter(f => !stagedSet.has(f) && !committedSet.has(f));
109
+
110
+ // Changes to be committed: present in staging but not in (any) commits
111
+ const toBeCommitted = stagedFiles.filter(f => !committedSet.has(f));
112
+
113
+ // Changes not staged for commit: present in staging and in cwd, but changed in cwd compared to staged copy
114
+ const modified = stagedFiles.filter((file)=> {
115
+ const cwdFilePath = path.join(process.cwd(),file)
116
+ const stagedFilePath = path.join(stagingDir,file)
117
+
118
+ if(!fs.existsSync(cwdFilePath) || !fs.existsSync(stagedFilePath))
119
+ return false
120
+
121
+ const cwdHash = hashfile(cwdFilePath)
122
+ const stagingHash = hashfile(stagedFilePath)
123
+
124
+ return cwdHash !== stagingHash
125
+ })
126
+
127
+
128
+ // output
129
+ console.log(chalk.bold.blue(`\nOn branch: main (default)`));
130
+
131
+ console.log(chalk.bold.green("\nChanges to be committed (files that are staged but not commited):"));
132
+ if(toBeCommitted.length > 0) {
133
+ toBeCommitted.forEach(f => console.log(chalk.green(`\t${f}`)));
134
+ }
135
+ else {
136
+ console.log(chalk.gray("\tNo changes added to commit"));
137
+ }
138
+
139
+ console.log(chalk.bold.yellow("\nChanges not staged for commit (files that are modified after adding to staging area):"));
140
+ if(modified.length > 0) {
141
+ modified.forEach(f => console.log(chalk.yellow(`\t${f}`)));
142
+ }
143
+ else {
144
+ console.log(chalk.gray("\tNo modified files detected"));
145
+ }
146
+
147
+ console.log(chalk.bold.red("\nUntracked files (files that are not staged or commited):"));
148
+ if(untracked.length > 0) {
149
+ untracked.forEach(f => console.log(chalk.red(`\t${f}`)));
150
+ }
151
+ else {
152
+ console.log(chalk.gray("\tNo untracked files"));
153
+ }
154
+
155
+ console.log(chalk.gray("\n(use 'jvcs add <file>' to stage changes)"));
156
+ console.log(chalk.gray("(use 'jvcs commit -m \"message\"' to commit changes)"));
157
+ console.log(chalk.gray("(use 'jvcs unstage <file>/<folder> to unstage a file/folder')"))
158
+ }
159
+
160
+ module.exports = statusCmd
@@ -10,6 +10,7 @@ function checkGlobalConfig() {
10
10
  function getGlobalConfig() {
11
11
  if(checkGlobalConfig()) {
12
12
  const configData = JSON.parse(fs.readFileSync(config,"utf-8"))
13
+ if(configData.username && configData.email && configData.token)
13
14
  return configData
14
15
  }
15
16
 
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const yargs = require("yargs");
4
4
  const { hideBin } = require("yargs/helpers");
@@ -11,8 +11,11 @@ const initCmd = require("./controllers/init")
11
11
  const addCmd = require("./controllers/add")
12
12
  const commitCmd = require("./controllers/commit")
13
13
  const unstageCmd = require("./controllers/unstage")
14
- const logCmd = require("./controllers/log")
15
-
14
+ const logCmd = require("./controllers/log");
15
+ const pushCmd = require("./controllers/push");
16
+ const statusCmd = require("./controllers/status")
17
+ const revertCmd = require("./controllers/revert")
18
+ //
16
19
  yargs(hideBin(process.argv))
17
20
  .scriptName("jvcs")
18
21
  .command(
@@ -115,7 +118,7 @@ yargs(hideBin(process.argv))
115
118
  )
116
119
  .command(
117
120
  "log",
118
- "show details of all commits",
121
+ chalk.blue("show details of all commits"),
119
122
  {},
120
123
  async ()=> {
121
124
  try {
@@ -126,6 +129,50 @@ yargs(hideBin(process.argv))
126
129
  }
127
130
  }
128
131
  )
132
+ .command(
133
+ "push",
134
+ chalk.blue("Push all the commits to remote"),
135
+ {},
136
+ async ()=> {
137
+ try {
138
+ await pushCmd()
139
+ }
140
+ catch(error) {
141
+ console.log(chalk.red(error))
142
+ }
143
+ }
144
+ )
145
+ .command(
146
+ "status",
147
+ chalk.blue("Check status of each file/folder"),
148
+ {},
149
+ async ()=> {
150
+ try {
151
+ await statusCmd()
152
+ }
153
+ catch(error) {
154
+ console.log(chalk.red(error))
155
+ }
156
+ }
157
+ )
158
+ .command(
159
+ "revert <commitId>",
160
+ chalk.blue("Replace your working directory with specific commit you made previously"),
161
+ (yargs)=> {
162
+ return yargs.positional("commitId", {
163
+ type: 'string',
164
+ describe: 'commitId to move your head'
165
+ })
166
+ },
167
+ async (argv)=> {
168
+ try {
169
+ await revertCmd(argv.commitId)
170
+ }
171
+ catch(error) {
172
+ console.log(chalk.red(error))
173
+ }
174
+ }
175
+ )
129
176
  .demandCommand(1, chalk.yellow("You need at least one command"))
130
177
  .help()
131
178
  .parse();
package/package.json CHANGED
@@ -1,13 +1,12 @@
1
1
  {
2
2
  "name": "jvcs",
3
- "version": "1.0.2",
3
+ "version": "1.0.4",
4
4
  "bin": {
5
5
  "jvcs": "./index.js"
6
6
  },
7
7
  "keywords": [],
8
8
  "author": "",
9
9
  "license": "ISC",
10
- "description": "",
11
10
  "dependencies": {
12
11
  "chalk": "^4.1.2",
13
12
  "crypto": "^1.0.1",
@@ -18,4 +17,4 @@
18
17
  "validator": "^13.15.20",
19
18
  "yargs": "^18.0.0"
20
19
  }
21
- }
20
+ }
@@ -1,98 +0,0 @@
1
- const fs = require("fs");
2
- const fsPromises = require("fs").promises;
3
- const path = require("path");
4
- const chalk = require("chalk");
5
- const { drive } = require("../config/drive-config");
6
- const { checkInitialization, checkRepoExists, getCLIConfig } = require("./utils");
7
-
8
- async function pullCmd(reponame) {
9
- try {
10
- if (!checkInitialization()) return;
11
- if (!checkRepoExists(reponame)) return;
12
-
13
- const repoPath = path.join(process.cwd(), `.${reponame}`);
14
- const commitFolder = path.join(repoPath, "commits");
15
- if (!fs.existsSync(commitFolder)) fs.mkdirSync(commitFolder, { recursive: true });
16
-
17
- const config = await getCLIConfig();
18
- if (!config) return console.log(chalk.red("Could not read CLI configuration."));
19
-
20
- console.log(chalk.blue("Pulling commits for user:"), chalk.green(config.username));
21
-
22
- // 1️⃣ Find user folder in Drive
23
- const userFolderRes = await drive.files.list({
24
- q: `name='${config.username}' and '1ahuoMCN_Ls5kGF2KPUGLbRZb9kGVMe0V' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
25
- fields: "files(id, name)"
26
- });
27
- if (!userFolderRes.data.files.length) {
28
- console.log(chalk.yellow("⚠ No user folder found on Drive."));
29
- return;
30
- }
31
- const userFolderId = userFolderRes.data.files[0].id;
32
-
33
- // 2️⃣ Find repo folder in Drive
34
- const repoFolderRes = await drive.files.list({
35
- q: `name='${reponame}' and '${userFolderId}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
36
- fields: "files(id, name)"
37
- });
38
- if (!repoFolderRes.data.files.length) {
39
- console.log(chalk.yellow(`⚠ Repository '${reponame}' not found on Drive.`));
40
- return;
41
- }
42
- const repoFolderId = repoFolderRes.data.files[0].id;
43
-
44
- // 3️⃣ Get commit folders in Drive
45
- const driveCommitsRes = await drive.files.list({
46
- q: `'${repoFolderId}' in parents and mimeType='application/vnd.google-apps.folder' and trashed=false`,
47
- fields: "files(name, id)"
48
- });
49
- const driveCommits = driveCommitsRes.data.files;
50
- if (!driveCommits.length) {
51
- console.log(chalk.yellow("⚠ No commits found on Drive."));
52
- return;
53
- }
54
-
55
- // 4️⃣ Get local commit folders
56
- const localCommits = await fsPromises.readdir(commitFolder);
57
-
58
- // 5️⃣ Download only missing commits
59
- for (const commit of driveCommits) {
60
- if (localCommits.includes(commit.name)) {
61
- console.log(chalk.gray(`✔ Commit '${commit.name}' already exists locally.`));
62
- continue;
63
- }
64
- const localCommitPath = path.join(commitFolder, commit.name);
65
- await fsPromises.mkdir(localCommitPath, { recursive: true });
66
- await downloadFolder(commit.id, localCommitPath);
67
- console.log(chalk.green(`⬇ Pulled new commit '${commit.name}' successfully.`));
68
- }
69
-
70
- console.log(chalk.blue("Pull complete! Local repo is now up to date."));
71
-
72
- } catch (error) {
73
- console.log(chalk.red("Error in pullCmd:"), error.message);
74
- }
75
- }
76
-
77
- // Recursive folder download helper
78
- async function downloadFolder(folderId, destPath) {
79
- const res = await drive.files.list({
80
- q: `'${folderId}' in parents and trashed=false`,
81
- fields: "files(id, name, mimeType)"
82
- });
83
- for (const file of res.data.files) {
84
- const localPath = path.join(destPath, file.name);
85
- if (file.mimeType === "application/vnd.google-apps.folder") {
86
- await fsPromises.mkdir(localPath, { recursive: true });
87
- await downloadFolder(file.id, localPath);
88
- } else {
89
- const dest = fs.createWriteStream(localPath);
90
- await drive.files.get({ fileId: file.id, alt: "media" }, { responseType: "stream" })
91
- .then(res => new Promise((resolve, reject) => {
92
- res.data.on("end", resolve).on("error", reject).pipe(dest);
93
- }));
94
- }
95
- }
96
- }
97
-
98
- module.exports = pullCmd;