diffx-cli 0.4.1 → 0.4.3
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/cli.mjs +0 -0
- package/package.json +2 -2
- package/dist/cli.js +0 -220
package/dist/cli.mjs
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diffx-cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Local code review tool for git diffs with a GitHub PR-like web UI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"cli"
|
|
20
20
|
],
|
|
21
21
|
"bin": {
|
|
22
|
-
"diffx": "./dist/cli.
|
|
22
|
+
"diffx": "./dist/cli.mjs"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"dist"
|
package/dist/cli.js
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { parseArgs } from "node:util";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
|
-
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
5
|
-
import getPort from "get-port";
|
|
6
|
-
import { execSync } from "node:child_process";
|
|
7
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
8
|
-
import { readFile } from "node:fs/promises";
|
|
9
|
-
import { Hono } from "hono";
|
|
10
|
-
import { serve } from "@hono/node-server";
|
|
11
|
-
import { homedir } from "node:os";
|
|
12
|
-
//#region src/git.ts
|
|
13
|
-
function isGitRepo() {
|
|
14
|
-
try {
|
|
15
|
-
execSync("git rev-parse --is-inside-work-tree", { stdio: "pipe" });
|
|
16
|
-
return true;
|
|
17
|
-
} catch {
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function getRepoRoot() {
|
|
22
|
-
return execSync("git rev-parse --show-toplevel", { encoding: "utf-8" }).trim();
|
|
23
|
-
}
|
|
24
|
-
function getRepoName() {
|
|
25
|
-
return basename(getRepoRoot());
|
|
26
|
-
}
|
|
27
|
-
function getBranchName() {
|
|
28
|
-
try {
|
|
29
|
-
return execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
|
|
30
|
-
} catch {
|
|
31
|
-
return "";
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function getCustomGitDiff(args) {
|
|
35
|
-
return execSync([
|
|
36
|
-
"git",
|
|
37
|
-
"diff",
|
|
38
|
-
...args
|
|
39
|
-
].join(" "), {
|
|
40
|
-
encoding: "utf-8",
|
|
41
|
-
maxBuffer: 50 * 1024 * 1024
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
function getGitDiff(options = {}) {
|
|
45
|
-
const parts = [];
|
|
46
|
-
const unstaged = execSync("git diff", {
|
|
47
|
-
encoding: "utf-8",
|
|
48
|
-
maxBuffer: 50 * 1024 * 1024
|
|
49
|
-
});
|
|
50
|
-
if (unstaged) parts.push(unstaged);
|
|
51
|
-
if (options.staged) {
|
|
52
|
-
const staged = execSync("git diff --staged", {
|
|
53
|
-
encoding: "utf-8",
|
|
54
|
-
maxBuffer: 50 * 1024 * 1024
|
|
55
|
-
});
|
|
56
|
-
if (staged) parts.push(staged);
|
|
57
|
-
}
|
|
58
|
-
if (options.untracked) {
|
|
59
|
-
const untrackedPatch = getUntrackedFilesDiff();
|
|
60
|
-
if (untrackedPatch) parts.push(untrackedPatch);
|
|
61
|
-
}
|
|
62
|
-
return parts.join("\n");
|
|
63
|
-
}
|
|
64
|
-
function getUntrackedFilesDiff() {
|
|
65
|
-
const root = getRepoRoot();
|
|
66
|
-
const output = execSync("git ls-files --others --exclude-standard", {
|
|
67
|
-
encoding: "utf-8",
|
|
68
|
-
maxBuffer: 50 * 1024 * 1024
|
|
69
|
-
}).trim();
|
|
70
|
-
if (!output) return "";
|
|
71
|
-
const files = output.split("\n");
|
|
72
|
-
const patches = [];
|
|
73
|
-
for (const file of files) try {
|
|
74
|
-
const lines = readFileSync(join(root, file), "utf-8").split("\n");
|
|
75
|
-
const diffLines = lines.map((line) => `+${line}`);
|
|
76
|
-
const patch = [
|
|
77
|
-
`diff --git a/${file} b/${file}`,
|
|
78
|
-
"new file mode 100644",
|
|
79
|
-
"index 0000000..0000001",
|
|
80
|
-
"--- /dev/null",
|
|
81
|
-
`+++ b/${file}`,
|
|
82
|
-
`@@ -0,0 +1,${lines.length} @@`,
|
|
83
|
-
...diffLines
|
|
84
|
-
].join("\n");
|
|
85
|
-
patches.push(patch);
|
|
86
|
-
} catch {}
|
|
87
|
-
return patches.length > 0 ? "\n" + patches.join("\n") : "";
|
|
88
|
-
}
|
|
89
|
-
//#endregion
|
|
90
|
-
//#region src/settings.ts
|
|
91
|
-
const CONFIG_DIR = join(homedir(), ".config", "diffx");
|
|
92
|
-
const SETTINGS_FILE = join(CONFIG_DIR, "settings.json");
|
|
93
|
-
const DEFAULTS = {
|
|
94
|
-
staged: true,
|
|
95
|
-
untracked: true,
|
|
96
|
-
diffStyle: "split"
|
|
97
|
-
};
|
|
98
|
-
function loadSettings() {
|
|
99
|
-
try {
|
|
100
|
-
const data = readFileSync(SETTINGS_FILE, "utf-8");
|
|
101
|
-
return {
|
|
102
|
-
...DEFAULTS,
|
|
103
|
-
...JSON.parse(data)
|
|
104
|
-
};
|
|
105
|
-
} catch {
|
|
106
|
-
return { ...DEFAULTS };
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function saveSettings(settings) {
|
|
110
|
-
const merged = {
|
|
111
|
-
...loadSettings(),
|
|
112
|
-
...settings
|
|
113
|
-
};
|
|
114
|
-
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
115
|
-
writeFileSync(SETTINGS_FILE, JSON.stringify(merged, null, 2));
|
|
116
|
-
return merged;
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
//#region src/server.ts
|
|
120
|
-
const MIME_TYPES = {
|
|
121
|
-
".html": "text/html",
|
|
122
|
-
".js": "application/javascript",
|
|
123
|
-
".css": "text/css",
|
|
124
|
-
".json": "application/json",
|
|
125
|
-
".svg": "image/svg+xml",
|
|
126
|
-
".png": "image/png",
|
|
127
|
-
".ico": "image/x-icon"
|
|
128
|
-
};
|
|
129
|
-
function createApp(clientDir, customDiffArgs) {
|
|
130
|
-
const app = new Hono();
|
|
131
|
-
const isCustomMode = !!customDiffArgs;
|
|
132
|
-
app.get("/api/diff", (c) => {
|
|
133
|
-
let patch;
|
|
134
|
-
if (isCustomMode) patch = getCustomGitDiff(customDiffArgs);
|
|
135
|
-
else patch = getGitDiff({
|
|
136
|
-
staged: c.req.query("staged") === "true",
|
|
137
|
-
untracked: c.req.query("untracked") === "true"
|
|
138
|
-
});
|
|
139
|
-
const repoName = getRepoName();
|
|
140
|
-
const branch = getBranchName();
|
|
141
|
-
return c.json({
|
|
142
|
-
patch,
|
|
143
|
-
repoName,
|
|
144
|
-
branch,
|
|
145
|
-
customMode: isCustomMode
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
app.get("/api/settings", (c) => {
|
|
149
|
-
return c.json(loadSettings());
|
|
150
|
-
});
|
|
151
|
-
app.put("/api/settings", async (c) => {
|
|
152
|
-
const settings = saveSettings(await c.req.json());
|
|
153
|
-
return c.json(settings);
|
|
154
|
-
});
|
|
155
|
-
app.get("/*", async (c) => {
|
|
156
|
-
let filePath = c.req.path;
|
|
157
|
-
if (filePath === "/") filePath = "/index.html";
|
|
158
|
-
const fullPath = join(clientDir, filePath);
|
|
159
|
-
try {
|
|
160
|
-
const content = await readFile(fullPath);
|
|
161
|
-
const contentType = MIME_TYPES[extname(fullPath)] || "application/octet-stream";
|
|
162
|
-
return new Response(content, { headers: { "Content-Type": contentType } });
|
|
163
|
-
} catch {
|
|
164
|
-
const indexContent = await readFile(join(clientDir, "index.html"));
|
|
165
|
-
return new Response(indexContent, { headers: { "Content-Type": "text/html" } });
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
return app;
|
|
169
|
-
}
|
|
170
|
-
function startServer(options) {
|
|
171
|
-
const app = createApp(options.clientDir, options.customDiffArgs);
|
|
172
|
-
return new Promise((resolve) => {
|
|
173
|
-
serve({
|
|
174
|
-
fetch: app.fetch,
|
|
175
|
-
port: options.port
|
|
176
|
-
}, (info) => {
|
|
177
|
-
resolve({ port: info.port });
|
|
178
|
-
});
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
//#endregion
|
|
182
|
-
//#region src/cli.ts
|
|
183
|
-
const { values, positionals } = parseArgs({
|
|
184
|
-
options: {
|
|
185
|
-
port: {
|
|
186
|
-
type: "string",
|
|
187
|
-
short: "p"
|
|
188
|
-
},
|
|
189
|
-
"no-open": {
|
|
190
|
-
type: "boolean",
|
|
191
|
-
default: false
|
|
192
|
-
}
|
|
193
|
-
},
|
|
194
|
-
allowPositionals: true
|
|
195
|
-
});
|
|
196
|
-
const customDiffArgs = positionals.length > 0 ? positionals : void 0;
|
|
197
|
-
if (!isGitRepo()) {
|
|
198
|
-
console.error("Error: not inside a git repository");
|
|
199
|
-
process.exit(1);
|
|
200
|
-
}
|
|
201
|
-
const port = await getPort({ port: values.port ? parseInt(values.port, 10) : 3433 });
|
|
202
|
-
const clientDir = resolve(dirname(fileURLToPath(import.meta.url)), "client");
|
|
203
|
-
const { existsSync } = await import("node:fs");
|
|
204
|
-
const { port: actualPort } = await startServer({
|
|
205
|
-
port,
|
|
206
|
-
clientDir: existsSync(clientDir) ? clientDir : resolve(process.cwd(), "dist/client"),
|
|
207
|
-
customDiffArgs
|
|
208
|
-
});
|
|
209
|
-
console.log(`diffx server running at http://localhost:${actualPort}`);
|
|
210
|
-
if (!values["no-open"]) {
|
|
211
|
-
const openModule = await import("open");
|
|
212
|
-
const url = `http://localhost:${actualPort}`;
|
|
213
|
-
openModule.default(url);
|
|
214
|
-
}
|
|
215
|
-
process.on("SIGINT", () => {
|
|
216
|
-
console.log("\nShutting down...");
|
|
217
|
-
process.exit(0);
|
|
218
|
-
});
|
|
219
|
-
//#endregion
|
|
220
|
-
export {};
|