diffprism 0.34.0 → 0.35.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/dist/bin.js +126 -224
- package/dist/chunk-DHCVZGHE.js +501 -0
- package/dist/chunk-JSBRDJBE.js +30 -0
- package/dist/{chunk-NJKYNMAQ.js → chunk-LUUR6LNP.js} +317 -1802
- package/dist/chunk-QGWYCEJN.js +448 -0
- package/dist/mcp-server.js +75 -280
- package/dist/src-AMCPIYDZ.js +19 -0
- package/dist/src-JMPTSU3P.js +27 -0
- package/package.json +1 -1
- package/ui-dist/assets/index-BfqEajZq.js +325 -0
- package/ui-dist/assets/{index-RCLz30rX.css → index-vG-fI3wH.css} +1 -1
- package/ui-dist/index.html +2 -2
- package/ui-dist/assets/index-1PUjTjRT.js +0 -324
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
// packages/git/src/local.ts
|
|
2
|
+
import { execSync } from "child_process";
|
|
3
|
+
import { readFileSync } from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
function getGitDiff(ref, options) {
|
|
6
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
7
|
+
try {
|
|
8
|
+
execSync("git --version", { cwd, stdio: "pipe" });
|
|
9
|
+
} catch {
|
|
10
|
+
throw new Error(
|
|
11
|
+
"git is not available. Please install git and make sure it is on your PATH."
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
try {
|
|
15
|
+
execSync("git rev-parse --is-inside-work-tree", { cwd, stdio: "pipe" });
|
|
16
|
+
} catch {
|
|
17
|
+
throw new Error(
|
|
18
|
+
`The directory "${cwd}" is not inside a git repository.`
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
let command;
|
|
22
|
+
let includeUntracked = false;
|
|
23
|
+
switch (ref) {
|
|
24
|
+
case "staged":
|
|
25
|
+
command = "git diff --staged --no-color";
|
|
26
|
+
break;
|
|
27
|
+
case "unstaged":
|
|
28
|
+
command = "git diff --no-color";
|
|
29
|
+
includeUntracked = true;
|
|
30
|
+
break;
|
|
31
|
+
case "all":
|
|
32
|
+
command = "git diff HEAD --no-color";
|
|
33
|
+
includeUntracked = true;
|
|
34
|
+
break;
|
|
35
|
+
default:
|
|
36
|
+
command = `git diff --no-color ${ref}`;
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
let output;
|
|
40
|
+
try {
|
|
41
|
+
output = execSync(command, {
|
|
42
|
+
cwd,
|
|
43
|
+
encoding: "utf-8",
|
|
44
|
+
maxBuffer: 50 * 1024 * 1024
|
|
45
|
+
// 50 MB
|
|
46
|
+
});
|
|
47
|
+
} catch (err) {
|
|
48
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
49
|
+
throw new Error(`git diff failed: ${message}`);
|
|
50
|
+
}
|
|
51
|
+
if (includeUntracked) {
|
|
52
|
+
output += getUntrackedDiffs(cwd);
|
|
53
|
+
}
|
|
54
|
+
return output;
|
|
55
|
+
}
|
|
56
|
+
function getCurrentBranch(options) {
|
|
57
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
58
|
+
try {
|
|
59
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
60
|
+
cwd,
|
|
61
|
+
encoding: "utf-8",
|
|
62
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
63
|
+
}).trim();
|
|
64
|
+
} catch {
|
|
65
|
+
return "unknown";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
function listBranches(options) {
|
|
69
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
70
|
+
try {
|
|
71
|
+
const output = execSync(
|
|
72
|
+
"git branch -a --format='%(refname:short)' --sort=-committerdate",
|
|
73
|
+
{ cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
74
|
+
).trim();
|
|
75
|
+
if (!output) return { local: [], remote: [] };
|
|
76
|
+
const local = [];
|
|
77
|
+
const remote = [];
|
|
78
|
+
for (const line of output.split("\n")) {
|
|
79
|
+
const name = line.trim();
|
|
80
|
+
if (!name) continue;
|
|
81
|
+
if (name.endsWith("/HEAD")) continue;
|
|
82
|
+
if (name.includes("/")) {
|
|
83
|
+
remote.push(name);
|
|
84
|
+
} else {
|
|
85
|
+
local.push(name);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return { local, remote };
|
|
89
|
+
} catch {
|
|
90
|
+
return { local: [], remote: [] };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function listCommits(options) {
|
|
94
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
95
|
+
const limit = options?.limit ?? 50;
|
|
96
|
+
try {
|
|
97
|
+
const output = execSync(
|
|
98
|
+
`git log --format='%H<<>>%h<<>>%s<<>>%an<<>>%aI' -n ${limit}`,
|
|
99
|
+
{ cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
100
|
+
).trim();
|
|
101
|
+
if (!output) return [];
|
|
102
|
+
const commits = [];
|
|
103
|
+
for (const line of output.split("\n")) {
|
|
104
|
+
const parts = line.split("<<>>");
|
|
105
|
+
if (parts.length < 5) continue;
|
|
106
|
+
commits.push({
|
|
107
|
+
hash: parts[0],
|
|
108
|
+
shortHash: parts[1],
|
|
109
|
+
subject: parts[2],
|
|
110
|
+
author: parts[3],
|
|
111
|
+
date: parts[4]
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return commits;
|
|
115
|
+
} catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function detectWorktree(options) {
|
|
120
|
+
const cwd = options?.cwd ?? process.cwd();
|
|
121
|
+
try {
|
|
122
|
+
const gitDir = execSync("git rev-parse --git-dir", {
|
|
123
|
+
cwd,
|
|
124
|
+
encoding: "utf-8",
|
|
125
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
126
|
+
}).trim();
|
|
127
|
+
const gitCommonDir = execSync("git rev-parse --git-common-dir", {
|
|
128
|
+
cwd,
|
|
129
|
+
encoding: "utf-8",
|
|
130
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
131
|
+
}).trim();
|
|
132
|
+
const resolvedGitDir = path.resolve(cwd, gitDir);
|
|
133
|
+
const resolvedCommonDir = path.resolve(cwd, gitCommonDir);
|
|
134
|
+
const isWorktree = resolvedGitDir !== resolvedCommonDir;
|
|
135
|
+
if (!isWorktree) {
|
|
136
|
+
return { isWorktree: false };
|
|
137
|
+
}
|
|
138
|
+
const worktreePath = execSync("git rev-parse --show-toplevel", {
|
|
139
|
+
cwd,
|
|
140
|
+
encoding: "utf-8",
|
|
141
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
142
|
+
}).trim();
|
|
143
|
+
const mainWorktreePath = path.dirname(resolvedCommonDir);
|
|
144
|
+
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
145
|
+
cwd,
|
|
146
|
+
encoding: "utf-8",
|
|
147
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
148
|
+
}).trim();
|
|
149
|
+
return {
|
|
150
|
+
isWorktree: true,
|
|
151
|
+
worktreePath,
|
|
152
|
+
mainWorktreePath,
|
|
153
|
+
branch: branch === "HEAD" ? void 0 : branch
|
|
154
|
+
};
|
|
155
|
+
} catch {
|
|
156
|
+
return { isWorktree: false };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function getUntrackedDiffs(cwd) {
|
|
160
|
+
let untrackedList;
|
|
161
|
+
try {
|
|
162
|
+
untrackedList = execSync(
|
|
163
|
+
"git ls-files --others --exclude-standard",
|
|
164
|
+
{ cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
|
|
165
|
+
).trim();
|
|
166
|
+
} catch {
|
|
167
|
+
return "";
|
|
168
|
+
}
|
|
169
|
+
if (!untrackedList) return "";
|
|
170
|
+
const files = untrackedList.split("\n");
|
|
171
|
+
let result = "";
|
|
172
|
+
for (const file of files) {
|
|
173
|
+
const absPath = path.resolve(cwd, file);
|
|
174
|
+
let content;
|
|
175
|
+
try {
|
|
176
|
+
content = readFileSync(absPath, "utf-8");
|
|
177
|
+
} catch {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
const lines = content.split("\n");
|
|
181
|
+
const hasTrailingNewline = content.length > 0 && content[content.length - 1] === "\n";
|
|
182
|
+
const contentLines = hasTrailingNewline ? lines.slice(0, -1) : lines;
|
|
183
|
+
result += `diff --git a/${file} b/${file}
|
|
184
|
+
`;
|
|
185
|
+
result += "new file mode 100644\n";
|
|
186
|
+
result += "--- /dev/null\n";
|
|
187
|
+
result += `+++ b/${file}
|
|
188
|
+
`;
|
|
189
|
+
result += `@@ -0,0 +1,${contentLines.length} @@
|
|
190
|
+
`;
|
|
191
|
+
for (const line of contentLines) {
|
|
192
|
+
result += `+${line}
|
|
193
|
+
`;
|
|
194
|
+
}
|
|
195
|
+
if (!hasTrailingNewline) {
|
|
196
|
+
result += "\\n";
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// packages/git/src/parser.ts
|
|
203
|
+
import path2 from "path";
|
|
204
|
+
var EXTENSION_MAP = {
|
|
205
|
+
ts: "typescript",
|
|
206
|
+
tsx: "typescript",
|
|
207
|
+
js: "javascript",
|
|
208
|
+
jsx: "javascript",
|
|
209
|
+
py: "python",
|
|
210
|
+
rb: "ruby",
|
|
211
|
+
go: "go",
|
|
212
|
+
rs: "rust",
|
|
213
|
+
java: "java",
|
|
214
|
+
c: "c",
|
|
215
|
+
h: "c",
|
|
216
|
+
cpp: "cpp",
|
|
217
|
+
hpp: "cpp",
|
|
218
|
+
cc: "cpp",
|
|
219
|
+
cs: "csharp",
|
|
220
|
+
md: "markdown",
|
|
221
|
+
json: "json",
|
|
222
|
+
yaml: "yaml",
|
|
223
|
+
yml: "yaml",
|
|
224
|
+
html: "html",
|
|
225
|
+
css: "css",
|
|
226
|
+
scss: "scss",
|
|
227
|
+
sql: "sql",
|
|
228
|
+
sh: "shell",
|
|
229
|
+
bash: "shell",
|
|
230
|
+
zsh: "shell"
|
|
231
|
+
};
|
|
232
|
+
var FILENAME_MAP = {
|
|
233
|
+
Dockerfile: "dockerfile",
|
|
234
|
+
Makefile: "makefile"
|
|
235
|
+
};
|
|
236
|
+
function detectLanguage(filePath) {
|
|
237
|
+
const basename = path2.basename(filePath);
|
|
238
|
+
if (FILENAME_MAP[basename]) {
|
|
239
|
+
return FILENAME_MAP[basename];
|
|
240
|
+
}
|
|
241
|
+
const ext = basename.includes(".") ? basename.slice(basename.lastIndexOf(".") + 1) : "";
|
|
242
|
+
return EXTENSION_MAP[ext] ?? "text";
|
|
243
|
+
}
|
|
244
|
+
function stripPrefix(raw) {
|
|
245
|
+
if (raw === "/dev/null") return raw;
|
|
246
|
+
return raw.replace(/^[ab]\//, "");
|
|
247
|
+
}
|
|
248
|
+
function parseDiff(rawDiff, baseRef, headRef) {
|
|
249
|
+
if (!rawDiff.trim()) {
|
|
250
|
+
return { baseRef, headRef, files: [] };
|
|
251
|
+
}
|
|
252
|
+
const files = [];
|
|
253
|
+
const lines = rawDiff.split("\n");
|
|
254
|
+
let i = 0;
|
|
255
|
+
while (i < lines.length) {
|
|
256
|
+
if (!lines[i].startsWith("diff --git ")) {
|
|
257
|
+
i++;
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
let oldPath;
|
|
261
|
+
let newPath;
|
|
262
|
+
let status = "modified";
|
|
263
|
+
let binary = false;
|
|
264
|
+
let renameFrom;
|
|
265
|
+
let renameTo;
|
|
266
|
+
const diffLine = lines[i];
|
|
267
|
+
const gitPathMatch = diffLine.match(/^diff --git a\/(.*) b\/(.*)$/);
|
|
268
|
+
if (gitPathMatch) {
|
|
269
|
+
oldPath = gitPathMatch[1];
|
|
270
|
+
newPath = gitPathMatch[2];
|
|
271
|
+
}
|
|
272
|
+
i++;
|
|
273
|
+
while (i < lines.length && !lines[i].startsWith("diff --git ")) {
|
|
274
|
+
const line = lines[i];
|
|
275
|
+
if (line.startsWith("--- ")) {
|
|
276
|
+
const raw = line.slice(4);
|
|
277
|
+
oldPath = stripPrefix(raw);
|
|
278
|
+
if (raw === "/dev/null") {
|
|
279
|
+
status = "added";
|
|
280
|
+
}
|
|
281
|
+
} else if (line.startsWith("+++ ")) {
|
|
282
|
+
const raw = line.slice(4);
|
|
283
|
+
newPath = stripPrefix(raw);
|
|
284
|
+
if (raw === "/dev/null") {
|
|
285
|
+
status = "deleted";
|
|
286
|
+
}
|
|
287
|
+
} else if (line.startsWith("rename from ")) {
|
|
288
|
+
renameFrom = line.slice("rename from ".length);
|
|
289
|
+
status = "renamed";
|
|
290
|
+
} else if (line.startsWith("rename to ")) {
|
|
291
|
+
renameTo = line.slice("rename to ".length);
|
|
292
|
+
status = "renamed";
|
|
293
|
+
} else if (line.startsWith("new file mode")) {
|
|
294
|
+
status = "added";
|
|
295
|
+
} else if (line.startsWith("deleted file mode")) {
|
|
296
|
+
status = "deleted";
|
|
297
|
+
} else if (line.startsWith("Binary files") || line === "GIT binary patch") {
|
|
298
|
+
binary = true;
|
|
299
|
+
if (line.includes("/dev/null") && line.includes(" and b/")) {
|
|
300
|
+
status = "added";
|
|
301
|
+
} else if (line.includes("a/") && line.includes("/dev/null")) {
|
|
302
|
+
status = "deleted";
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (line.startsWith("@@ ")) {
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
i++;
|
|
309
|
+
}
|
|
310
|
+
const filePath = status === "deleted" ? oldPath ?? newPath ?? "unknown" : newPath ?? oldPath ?? "unknown";
|
|
311
|
+
const fileOldPath = status === "renamed" ? renameFrom ?? oldPath : oldPath;
|
|
312
|
+
const hunks = [];
|
|
313
|
+
let additions = 0;
|
|
314
|
+
let deletions = 0;
|
|
315
|
+
while (i < lines.length && !lines[i].startsWith("diff --git ")) {
|
|
316
|
+
const line = lines[i];
|
|
317
|
+
if (line.startsWith("@@ ")) {
|
|
318
|
+
const hunkMatch = line.match(
|
|
319
|
+
/^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/
|
|
320
|
+
);
|
|
321
|
+
if (!hunkMatch) {
|
|
322
|
+
i++;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const oldStart = parseInt(hunkMatch[1], 10);
|
|
326
|
+
const oldLines = hunkMatch[2] !== void 0 ? parseInt(hunkMatch[2], 10) : 1;
|
|
327
|
+
const newStart = parseInt(hunkMatch[3], 10);
|
|
328
|
+
const newLines = hunkMatch[4] !== void 0 ? parseInt(hunkMatch[4], 10) : 1;
|
|
329
|
+
const changes = [];
|
|
330
|
+
let oldLineNum = oldStart;
|
|
331
|
+
let newLineNum = newStart;
|
|
332
|
+
i++;
|
|
333
|
+
while (i < lines.length) {
|
|
334
|
+
const changeLine = lines[i];
|
|
335
|
+
if (changeLine.startsWith("@@ ") || changeLine.startsWith("diff --git ")) {
|
|
336
|
+
break;
|
|
337
|
+
}
|
|
338
|
+
if (changeLine.startsWith("\")) {
|
|
339
|
+
i++;
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
if (changeLine.startsWith("+")) {
|
|
343
|
+
changes.push({
|
|
344
|
+
type: "add",
|
|
345
|
+
lineNumber: newLineNum,
|
|
346
|
+
content: changeLine.slice(1)
|
|
347
|
+
});
|
|
348
|
+
newLineNum++;
|
|
349
|
+
additions++;
|
|
350
|
+
} else if (changeLine.startsWith("-")) {
|
|
351
|
+
changes.push({
|
|
352
|
+
type: "delete",
|
|
353
|
+
lineNumber: oldLineNum,
|
|
354
|
+
content: changeLine.slice(1)
|
|
355
|
+
});
|
|
356
|
+
oldLineNum++;
|
|
357
|
+
deletions++;
|
|
358
|
+
} else {
|
|
359
|
+
changes.push({
|
|
360
|
+
type: "context",
|
|
361
|
+
lineNumber: newLineNum,
|
|
362
|
+
content: changeLine.length > 0 ? changeLine.slice(1) : ""
|
|
363
|
+
});
|
|
364
|
+
oldLineNum++;
|
|
365
|
+
newLineNum++;
|
|
366
|
+
}
|
|
367
|
+
i++;
|
|
368
|
+
}
|
|
369
|
+
hunks.push({ oldStart, oldLines, newStart, newLines, changes });
|
|
370
|
+
} else {
|
|
371
|
+
i++;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const diffFile = {
|
|
375
|
+
path: filePath,
|
|
376
|
+
status,
|
|
377
|
+
hunks,
|
|
378
|
+
language: detectLanguage(filePath),
|
|
379
|
+
binary,
|
|
380
|
+
additions,
|
|
381
|
+
deletions
|
|
382
|
+
};
|
|
383
|
+
if (status === "renamed" && fileOldPath) {
|
|
384
|
+
diffFile.oldPath = fileOldPath;
|
|
385
|
+
}
|
|
386
|
+
files.push(diffFile);
|
|
387
|
+
}
|
|
388
|
+
return { baseRef, headRef, files };
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// packages/git/src/index.ts
|
|
392
|
+
function getDiff(ref, options) {
|
|
393
|
+
if (ref === "working-copy") {
|
|
394
|
+
return getWorkingCopyDiff(options);
|
|
395
|
+
}
|
|
396
|
+
const rawDiff = getGitDiff(ref, options);
|
|
397
|
+
let baseRef;
|
|
398
|
+
let headRef;
|
|
399
|
+
if (ref === "staged") {
|
|
400
|
+
baseRef = "HEAD";
|
|
401
|
+
headRef = "staged";
|
|
402
|
+
} else if (ref === "unstaged") {
|
|
403
|
+
baseRef = "staged";
|
|
404
|
+
headRef = "working tree";
|
|
405
|
+
} else if (ref.includes("..")) {
|
|
406
|
+
const [base, head] = ref.split("..");
|
|
407
|
+
baseRef = base;
|
|
408
|
+
headRef = head;
|
|
409
|
+
} else {
|
|
410
|
+
baseRef = ref;
|
|
411
|
+
headRef = "HEAD";
|
|
412
|
+
}
|
|
413
|
+
const diffSet = parseDiff(rawDiff, baseRef, headRef);
|
|
414
|
+
return { diffSet, rawDiff };
|
|
415
|
+
}
|
|
416
|
+
function getWorkingCopyDiff(options) {
|
|
417
|
+
const stagedRaw = getGitDiff("staged", options);
|
|
418
|
+
const unstagedRaw = getGitDiff("unstaged", options);
|
|
419
|
+
const stagedDiffSet = parseDiff(stagedRaw, "HEAD", "staged");
|
|
420
|
+
const unstagedDiffSet = parseDiff(unstagedRaw, "staged", "working tree");
|
|
421
|
+
const stagedFiles = stagedDiffSet.files.map((f) => ({
|
|
422
|
+
...f,
|
|
423
|
+
stage: "staged"
|
|
424
|
+
}));
|
|
425
|
+
const unstagedFiles = unstagedDiffSet.files.map((f) => ({
|
|
426
|
+
...f,
|
|
427
|
+
stage: "unstaged"
|
|
428
|
+
}));
|
|
429
|
+
const rawDiff = [stagedRaw, unstagedRaw].filter(Boolean).join("");
|
|
430
|
+
return {
|
|
431
|
+
diffSet: {
|
|
432
|
+
baseRef: "HEAD",
|
|
433
|
+
headRef: "working tree",
|
|
434
|
+
files: [...stagedFiles, ...unstagedFiles]
|
|
435
|
+
},
|
|
436
|
+
rawDiff
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
export {
|
|
441
|
+
getGitDiff,
|
|
442
|
+
getCurrentBranch,
|
|
443
|
+
listBranches,
|
|
444
|
+
listCommits,
|
|
445
|
+
detectWorktree,
|
|
446
|
+
parseDiff,
|
|
447
|
+
getDiff
|
|
448
|
+
};
|