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 CHANGED
File without changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffx-cli",
3
- "version": "0.4.1",
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.js"
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 {};