jvcs 1.4.4 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,44 @@
1
+ const AppState = require("./state")
2
+ const getDiff = require("./diffEngine")
3
+ const chalk = require("chalk")
4
+ const { checkGlobalConfig, getGlobalConfig, checkforjvcs } = require("./utility")
5
+
6
+ async function diff(mode, options = {}) {
7
+
8
+ if(!checkGlobalConfig()) {
9
+ console.log(chalk.red("No existing session found. Please login or signup."))
10
+ console.log(chalk.green("jvcs --help for help"))
11
+ return
12
+ }
13
+
14
+ let configData = getGlobalConfig()
15
+
16
+ if(!configData) {
17
+ console.log(chalk.red("No existing session found. Please login or signup."))
18
+ console.log(chalk.green("jvcs --help for help"))
19
+ return
20
+ }
21
+
22
+ if(!checkforjvcs()) {
23
+ console.log(chalk.red("Repository is not initialized or is deleted. Please create it."))
24
+ return
25
+ }
26
+
27
+ try {
28
+
29
+ const files = getDiff(mode, options)
30
+ if(!files || files.length === 0) {
31
+ console.log(chalk.yellow("No differences found."))
32
+ return
33
+ }
34
+
35
+ const appState = new AppState (mode, files)
36
+ startUI(appState)
37
+ }
38
+ catch(error) {
39
+ console.log(chalk.red("Diff Error: " + (error.message || error)))
40
+ }
41
+
42
+ }
43
+
44
+ module.exports = diff
@@ -0,0 +1,178 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+
4
+ const JVCS_ROOT = path.join(process.cwd(), ".jvcs")
5
+
6
+ const IGNORE_FOLDERS = [
7
+ ".jvcs",
8
+ "node_modules",
9
+ ".git"
10
+ ]
11
+
12
+ const IGNORE_FILES = [
13
+ ".DS_Store",
14
+ "jvcs_hashcode.json",
15
+ "meta.json",
16
+ "HEAD",
17
+ "config.json"
18
+ ]
19
+
20
+ function fileExists(filepath) {
21
+ return fs.existsSync(filepath)
22
+ }
23
+
24
+ function readFileSafe(filepath) {
25
+
26
+ if(!fileExists(filepath)) return ""
27
+ return fs.readFileSync(filepath, "utf-8")
28
+ }
29
+
30
+ function shouldIgnoreFile(fileName) {
31
+ return IGNORE_FILES.includes(fileName)
32
+ }
33
+
34
+ function shouldIgnoreFolder(folderName) {
35
+ return IGNORE_FOLDERS.includes(folderName)
36
+ }
37
+
38
+ function loadStageSnapshot() {
39
+ const stagePath = path.join(JVCS_ROOT, "staging")
40
+ return loadDirectoryRecursive(stagePath)
41
+ }
42
+
43
+ function loadDirectoryRecursive(dirPath, baseDir = dirPath, snapshot = {}) {
44
+
45
+ if(!fileExists(dirPath)) return snapshot
46
+
47
+ const entries = fs.readdirSync(dirPath)
48
+
49
+ for(const entry of entries) {
50
+ if(shouldIgnoreFile(entry)) continue
51
+ if(shouldIgnoreFolder(entry)) continue
52
+
53
+ const fullPath = path.join(dirPath, entry)
54
+ const stat = fs.statSync(fullPath)
55
+
56
+ if(stat.isDirectory()) {
57
+ loadDirectoryRecursive(fullPath, baseDir, snapshot)
58
+ }
59
+ else if (stat.isFile()) {
60
+ const relativePath = path.relative(baseDir, fullPath)
61
+ snapshot[relativePath] = readFileSafe(fullPath)
62
+ }
63
+ }
64
+
65
+ return snapshot
66
+ }
67
+
68
+ function loadStageSnapshot() {
69
+ const stagePath = path.join(JVCS_ROOT, "staging")
70
+ return loadDirectoryRecursive(stagePath)
71
+ }
72
+
73
+ function loadWorkingDirectorySnapshot() {
74
+ return loadDirectoryRecursive(process.cwd(), process.cwd())
75
+ }
76
+
77
+ function calculateLineDiff(leftContent, rightContent) {
78
+
79
+ const leftLines = leftContent.split("\n")
80
+ const rightLines = rightContent.split("\n")
81
+
82
+ let added = 0
83
+ let removed = 0
84
+
85
+ const max = Math.max(leftLines.length, rightLines.length)
86
+
87
+ for (let i = 0; i < max; i++) {
88
+ if (leftLines[i] !== rightLines[i]) {
89
+ if(leftLines[i] === undefined) {
90
+ added++
91
+ }
92
+ else if(rightLines[i] === undefined) {
93
+ removed++
94
+ }
95
+ else {
96
+ added++
97
+ removed++
98
+ }
99
+ }
100
+ }
101
+
102
+ return { added, removed }
103
+ }
104
+
105
+ function buildDiffObject(filePath, leftContent, rightContent) {
106
+ let status = "modified"
107
+
108
+ if(!leftContent && rightContent) {
109
+ status = "added"
110
+ }
111
+ else if(leftContent && !rightContent) {
112
+ status = "deleted"
113
+ }
114
+
115
+ return {
116
+ path: filePath,
117
+ status,
118
+ leftContent,
119
+ rightContent,
120
+ stats: calculateLineDiff(leftContent, rightContent)
121
+ }
122
+ }
123
+
124
+ function getDiff(mode, options = {}) {
125
+
126
+ let leftSnapshot = {}
127
+ let rightSnapshot = {}
128
+
129
+ if(mode === "commit-vs-commit") {
130
+ const { commitA, commitB } = options
131
+
132
+ if(!commitA || !commitB) {
133
+ throw new Error("commit-vs-commit requires commitA and commitB")
134
+ }
135
+
136
+ leftSnapshot = loadCommitSnapshot(commitA)
137
+ rightSnapshot = loadCommitSnapshot(commitB)
138
+ }
139
+ else if(mode === "commit-vs-stage") {
140
+ const { commitId } = options
141
+
142
+ if(!commitId) {
143
+ throw new Error("commit-vs-stage requires commitId")
144
+ }
145
+
146
+ leftSnapshot = loadCommitSnapshot(commitId)
147
+ rightSnapshot = loadStageSnapshot()
148
+ }
149
+ else if(mode === "stage-vs-cwd") {
150
+ leftSnapshot = loadStageSnapshot()
151
+ rightSnapshot = loadWorkingDirectorySnapshot()
152
+ }
153
+ else {
154
+ throw new Error("Invalid diff mode")
155
+ }
156
+
157
+ const allFiles = new Set([
158
+ ...Object.keys(leftSnapshot),
159
+ ...Object.keys(rightSnapshot)
160
+ ])
161
+
162
+ const diffResults = []
163
+
164
+ for (const filePath of allFiles) {
165
+ const leftContent = leftSnapshot[filePath] || ""
166
+ const rightContent = rightSnapshot[filePath] || ""
167
+
168
+ if(leftContent !== rightContent) {
169
+ diffResults.push(
170
+ buildDiffObject(filePath, leftContent, rightContent)
171
+ )
172
+ }
173
+ }
174
+
175
+ return diffResults
176
+ }
177
+
178
+ module.exports = getDiff
@@ -0,0 +1,32 @@
1
+ class AppState {
2
+
3
+ constructor(mode, files) {
4
+ this.mode = mode
5
+ this.files = files || []
6
+ this.screen = "FILE_LIST"
7
+ this.selectedIndex = 0
8
+ this.activeTab = 0
9
+ }
10
+
11
+ selectFile(index) {
12
+ this.selectedIndex = index
13
+ }
14
+
15
+ switchTab(tabIndex) {
16
+ this.activeTab = tabIndex
17
+ }
18
+
19
+ goToFileView() {
20
+ this.screen = "FILE_VIEW"
21
+ }
22
+
23
+ goToListView() {
24
+ this.screen = "FILE_LIST"
25
+ }
26
+
27
+ getCurrentFile() {
28
+ return this.files[this.selectedIndex]
29
+ }
30
+ }
31
+
32
+ module.exports = AppState
@@ -0,0 +1,216 @@
1
+ const blessed = require("blessed")
2
+
3
+ function startUI(state) {
4
+
5
+ const screen = blessed.screen({
6
+ smartCSR: true,
7
+ title: "JVCS Diff Viewer"
8
+ })
9
+
10
+ // ---------------------------------------
11
+ // HEADER
12
+ // ---------------------------------------
13
+
14
+ const header = blessed.box({
15
+ top: 0,
16
+ height: 1,
17
+ width: "100%",
18
+ content: ` JVCS DIFF VIEW (${state.mode}) | ↑↓ Navigate | Enter Open | Esc Back | Tab Switch | Q Quit `,
19
+ style: {
20
+ fg: "black",
21
+ bg: "cyan"
22
+ }
23
+ })
24
+
25
+ screen.append(header)
26
+
27
+ // ---------------------------------------
28
+ // FILE LIST VIEW
29
+ // ---------------------------------------
30
+
31
+ const fileList = blessed.list({
32
+ top: 1,
33
+ left: 0,
34
+ width: "100%",
35
+ height: "100%-1",
36
+ keys: true,
37
+ vi: true,
38
+ border: "line",
39
+ label: " Changed Files ",
40
+ style: {
41
+ selected: {
42
+ bg: "blue"
43
+ }
44
+ }
45
+ })
46
+
47
+ screen.append(fileList)
48
+
49
+ function renderFileList() {
50
+ const items = state.files.map((file, index) => {
51
+ return `${file.status.toUpperCase()} ${file.path}`
52
+ })
53
+
54
+ fileList.setItems(items)
55
+ fileList.select(state.selectedIndex)
56
+ fileList.focus()
57
+ screen.render()
58
+ }
59
+
60
+ // ---------------------------------------
61
+ // FILE VIEW (3 PANELS)
62
+ // ---------------------------------------
63
+
64
+ const leftBox = blessed.box({
65
+ top: 1,
66
+ left: 0,
67
+ width: "33%",
68
+ height: "100%-1",
69
+ border: "line",
70
+ label: " LEFT ",
71
+ scrollable: true,
72
+ alwaysScroll: true,
73
+ keys: true,
74
+ vi: true
75
+ })
76
+
77
+ const rightBox = blessed.box({
78
+ top: 1,
79
+ left: "33%",
80
+ width: "34%",
81
+ height: "100%-1",
82
+ border: "line",
83
+ label: " RIGHT ",
84
+ scrollable: true,
85
+ alwaysScroll: true,
86
+ keys: true,
87
+ vi: true
88
+ })
89
+
90
+ const aiBox = blessed.box({
91
+ top: 1,
92
+ left: "67%",
93
+ width: "33%",
94
+ height: "100%-1",
95
+ border: "line",
96
+ label: " AI ANALYSIS ",
97
+ scrollable: true,
98
+ alwaysScroll: true,
99
+ keys: true,
100
+ vi: true
101
+ })
102
+
103
+ function renderFileView() {
104
+
105
+ const file = state.getCurrentFile()
106
+ if (!file) return
107
+
108
+ leftBox.setContent(file.leftContent || "")
109
+ rightBox.setContent(file.rightContent || "")
110
+
111
+ aiBox.setContent(`
112
+ Summary:
113
+ Changes detected in file.
114
+
115
+ Status: ${file.status}
116
+
117
+ Lines Added: ${file.stats.added}
118
+ Lines Removed: ${file.stats.removed}
119
+
120
+ (Real AI analysis will appear here later)
121
+ `)
122
+
123
+ screen.append(leftBox)
124
+ screen.append(rightBox)
125
+ screen.append(aiBox)
126
+
127
+ updateActiveTabHighlight()
128
+
129
+ screen.render()
130
+ }
131
+
132
+ function destroyFileView() {
133
+ leftBox.detach()
134
+ rightBox.detach()
135
+ aiBox.detach()
136
+ screen.render()
137
+ }
138
+
139
+ function updateActiveTabHighlight() {
140
+
141
+ leftBox.style.border.fg = "white"
142
+ rightBox.style.border.fg = "white"
143
+ aiBox.style.border.fg = "white"
144
+
145
+ if (state.activeTab === 0) {
146
+ leftBox.style.border.fg = "yellow"
147
+ leftBox.focus()
148
+ }
149
+ else if (state.activeTab === 1) {
150
+ rightBox.style.border.fg = "yellow"
151
+ rightBox.focus()
152
+ }
153
+ else {
154
+ aiBox.style.border.fg = "yellow"
155
+ aiBox.focus()
156
+ }
157
+
158
+ screen.render()
159
+ }
160
+
161
+ // ---------------------------------------
162
+ // KEYBOARD CONTROLS
163
+ // ---------------------------------------
164
+
165
+ screen.key(["q", "C-c"], () => {
166
+ return process.exit(0)
167
+ })
168
+
169
+ // Navigate list
170
+ fileList.key(["up"], () => {
171
+ if (state.selectedIndex > 0) {
172
+ state.selectFile(state.selectedIndex - 1)
173
+ renderFileList()
174
+ }
175
+ })
176
+
177
+ fileList.key(["down"], () => {
178
+ if (state.selectedIndex < state.files.length - 1) {
179
+ state.selectFile(state.selectedIndex + 1)
180
+ renderFileList()
181
+ }
182
+ })
183
+
184
+ // Enter → Open file view
185
+ fileList.key(["enter"], () => {
186
+ state.goToFileView()
187
+ fileList.detach()
188
+ renderFileView()
189
+ })
190
+
191
+ // ESC → Back to list
192
+ screen.key(["escape"], () => {
193
+ if (state.screen === "FILE_VIEW") {
194
+ state.goToListView()
195
+ destroyFileView()
196
+ screen.append(fileList)
197
+ renderFileList()
198
+ }
199
+ })
200
+
201
+ // TAB → switch active panel
202
+ screen.key(["tab"], () => {
203
+ if (state.screen === "FILE_VIEW") {
204
+ state.switchTab((state.activeTab + 1) % 3)
205
+ updateActiveTabHighlight()
206
+ }
207
+ })
208
+
209
+ // ---------------------------------------
210
+ // INITIAL RENDER
211
+ // ---------------------------------------
212
+
213
+ renderFileList()
214
+ }
215
+
216
+ module.exports = startUI
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");
@@ -16,6 +16,7 @@ const pushCmd = require("./controllers/push");
16
16
  const statusCmd = require("./controllers/status")
17
17
  const revertCmd = require("./controllers/revert")
18
18
  const cloneCmd = require("./controllers/clone")
19
+ const diff = require("./controllers/diff-engine/diff")
19
20
  // file changed
20
21
 
21
22
  yargs(hideBin(process.argv))
@@ -157,6 +158,65 @@ yargs(hideBin(process.argv))
157
158
  }
158
159
  }
159
160
  )
161
+ .command(
162
+ "diff",
163
+ chalk.blue(`
164
+ Compare different states of your repository.
165
+
166
+ Modes:
167
+ stage-vs-cwd
168
+ commit-vs-stage
169
+ commit-vs-commit
170
+
171
+ Example:
172
+ jvcs diff --mode stage-vs-cwd
173
+ jvcs diff --mode commit-vs-stage --commitId <id>
174
+ jvcs diff --mode commit-vs-commit --commitA <id> --commitB <id>
175
+ `),
176
+ (yargs) => {
177
+ return yargs
178
+ .option("mode", {
179
+ type: "string",
180
+ describe: "Diff mode",
181
+ choices: ["stage-vs-cwd", "commit-vs-stage", "commit-vs-commit"],
182
+ demandOption: true
183
+ })
184
+ .option("commitId", {
185
+ type: "string",
186
+ describe: "Commit ID (for commit-vs-stage)"
187
+ })
188
+ .option("commitA", {
189
+ type: "string",
190
+ describe: "First commit ID (for commit-vs-commit)"
191
+ })
192
+ .option("commitB", {
193
+ type: "string",
194
+ describe: "Second commit ID (for commit-vs-commit)"
195
+ })
196
+ },
197
+ async (argv) => {
198
+ try {
199
+ const { mode, commitId, commitA, commitB } = argv
200
+
201
+ if(mode === "commit-vs-stage" && !commitId) {
202
+ throw new Error("commit-vs-stage requires --commitId")
203
+ }
204
+
205
+ if(mode === "commit-vs-commit" && (!commitA || !commitB)) {
206
+ throw new Error("commit-vs-commit requires --commitA and --commitB")
207
+ }
208
+
209
+ await diff(mode, {
210
+ commitId,
211
+ commitA,
212
+ commitB
213
+ })
214
+ }
215
+ catch (error) {
216
+ console.log(chalk.red(error.message || error))
217
+ }
218
+ }
219
+ )
160
220
  .command(
161
221
  "revert <commitId>",
162
222
  chalk.blue("Replace your working directory with specific commit you made previously"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jvcs",
3
- "version": "1.4.4",
3
+ "version": "1.4.5",
4
4
  "bin": {
5
5
  "jvcs": "index.js"
6
6
  },
@@ -8,6 +8,7 @@
8
8
  "author": "",
9
9
  "license": "ISC",
10
10
  "dependencies": {
11
+ "blessed": "^0.1.81",
11
12
  "chalk": "^4.1.2",
12
13
  "dotenv": "^17.2.3",
13
14
  "googleapis": "^164.1.0",