diffity 0.1.3 → 0.1.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.
- package/README.md +94 -0
- package/dist/index.js +206 -24
- package/dist/ui/assets/{index-BUq0B-nJ.js → index-Dt_01AYh.js} +23 -23
- package/dist/ui/index.html +1 -1
- package/package.json +2 -2
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<img src="./packages/ui/public/brand.svg" width="80" />
|
|
2
|
+
|
|
3
|
+
# diffity
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/diffity)
|
|
6
|
+
[](https://github.com/kamranahmedse/diffity/blob/main/LICENSE)
|
|
7
|
+
|
|
8
|
+
Diffity is an agent-agnostic, GitHub-style diff viewer and code review tool.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g diffity
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
It works with Claude Code, Cursor, Codex, and any AI coding agent.
|
|
15
|
+
|
|
16
|
+
## See your diffs
|
|
17
|
+
|
|
18
|
+
Run `diffity` inside any git repo — your browser opens with a GitHub-style, syntax-highlighted diff.
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
diffity # working tree changes
|
|
22
|
+
diffity HEAD~1 # last commit
|
|
23
|
+
diffity HEAD~3 # last 3 commits
|
|
24
|
+
diffity main..feature # compare branches
|
|
25
|
+
diffity v1.0.0..v2.0.0 # compare tags
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For the working tree, you can leave comments, copy them into your agent with a button and ask it to resolve them. Alternatively, use the skills below to avoid this manual step and let your agent auto-solve them.
|
|
29
|
+
|
|
30
|
+
## AI code review
|
|
31
|
+
|
|
32
|
+
Install the skills for your coding agent (Claude Code, Cursor, Codex, etc.):
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npx skills add kamranahmedse/diffity
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Then use the slash commands:
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
# use this skill to open the browser with diff viewer
|
|
42
|
+
# you can review the code yourself and leave comments
|
|
43
|
+
/diffity-start
|
|
44
|
+
|
|
45
|
+
# once done, you can come back to the agent and use the
|
|
46
|
+
# below skill to ask agent to resolve your comments.
|
|
47
|
+
/diffity-resolve
|
|
48
|
+
|
|
49
|
+
# you can use this to have AI review your uncommitted
|
|
50
|
+
# changes and leave comments in the diff viewer
|
|
51
|
+
/diffity-review
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The review uses severity tags so you know what matters:
|
|
55
|
+
- `[must-fix]` — Bugs, security issues
|
|
56
|
+
- `[suggestion]` — Meaningful improvements
|
|
57
|
+
- `[nit]` — Style preferences
|
|
58
|
+
- `[question]` — Needs clarification
|
|
59
|
+
|
|
60
|
+
You can focus the review on what you care about: `/diffity-review security` or `/diffity-review performance`
|
|
61
|
+
|
|
62
|
+
## Multiple projects
|
|
63
|
+
|
|
64
|
+
Diffity supports running multiple projects simultaneously. Each gets its own port automatically:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Terminal 1 — starts on :5391
|
|
68
|
+
cd ~/projects/app && diffity
|
|
69
|
+
|
|
70
|
+
# Terminal 2 — starts on :5392
|
|
71
|
+
cd ~/projects/api && diffity
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
If you run `diffity` in a repo that already has a running instance, it opens the existing one instead of starting a new server. Use `--new` to kill the existing instance and start fresh.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
diffity list # show all running instances
|
|
78
|
+
diffity list --json # machine-readable output
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Options
|
|
82
|
+
|
|
83
|
+
```
|
|
84
|
+
--port <port> Custom port (default: auto-assigned from 5391)
|
|
85
|
+
--no-open Don't open browser
|
|
86
|
+
--dark Dark mode
|
|
87
|
+
--unified Unified view (default: split)
|
|
88
|
+
--quiet Minimal terminal output
|
|
89
|
+
--new Stop existing instance and start fresh
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
import { execSync as execSync3 } from "node:child_process";
|
|
6
|
-
import { rmSync, existsSync as
|
|
7
|
-
import { join as
|
|
8
|
-
import { homedir as
|
|
6
|
+
import { rmSync, existsSync as existsSync3 } from "node:fs";
|
|
7
|
+
import { join as join6 } from "node:path";
|
|
8
|
+
import { homedir as homedir3 } from "node:os";
|
|
9
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
9
10
|
import { createRequire } from "node:module";
|
|
10
11
|
import open from "open";
|
|
11
12
|
import pc2 from "picocolors";
|
|
@@ -196,8 +197,8 @@ function getRecentCommits(query) {
|
|
|
196
197
|
// src/server.ts
|
|
197
198
|
import { createServer } from "node:http";
|
|
198
199
|
import { createHash as createHash2 } from "node:crypto";
|
|
199
|
-
import { readFileSync as
|
|
200
|
-
import { join as
|
|
200
|
+
import { readFileSync as readFileSync3, existsSync as existsSync2 } from "node:fs";
|
|
201
|
+
import { join as join5, extname } from "node:path";
|
|
201
202
|
import { fileURLToPath } from "node:url";
|
|
202
203
|
import { dirname } from "node:path";
|
|
203
204
|
|
|
@@ -722,6 +723,116 @@ function handleReviewRoute(req, res, pathname, url) {
|
|
|
722
723
|
return !1;
|
|
723
724
|
}
|
|
724
725
|
|
|
726
|
+
// src/registry.ts
|
|
727
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync, existsSync, statSync, mkdirSync as mkdirSync2 } from "node:fs";
|
|
728
|
+
import { join as join4 } from "node:path";
|
|
729
|
+
import { homedir as homedir2 } from "node:os";
|
|
730
|
+
var DIFFITY_DIR = join4(homedir2(), ".diffity"), REGISTRY_PATH = join4(DIFFITY_DIR, "registry.json"), LOCK_PATH = join4(DIFFITY_DIR, "registry.lock"), LOCK_STALE_MS = 5e3, LOCK_TIMEOUT_MS = 3e3, BASE_PORT = 5391, MAX_PORT_ATTEMPTS = 10;
|
|
731
|
+
function isProcessAlive(pid) {
|
|
732
|
+
try {
|
|
733
|
+
return process.kill(pid, 0), !0;
|
|
734
|
+
} catch {
|
|
735
|
+
return !1;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function acquireLock() {
|
|
739
|
+
mkdirSync2(DIFFITY_DIR, { recursive: !0 });
|
|
740
|
+
let start = Date.now();
|
|
741
|
+
for (; ; )
|
|
742
|
+
try {
|
|
743
|
+
writeFileSync2(LOCK_PATH, String(process.pid), { flag: "wx" });
|
|
744
|
+
return;
|
|
745
|
+
} catch {
|
|
746
|
+
try {
|
|
747
|
+
let stat = statSync(LOCK_PATH);
|
|
748
|
+
if (Date.now() - stat.mtimeMs > LOCK_STALE_MS) {
|
|
749
|
+
try {
|
|
750
|
+
unlinkSync(LOCK_PATH);
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
} catch {
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
if (Date.now() - start > LOCK_TIMEOUT_MS) {
|
|
759
|
+
try {
|
|
760
|
+
unlinkSync(LOCK_PATH);
|
|
761
|
+
} catch {
|
|
762
|
+
}
|
|
763
|
+
try {
|
|
764
|
+
writeFileSync2(LOCK_PATH, String(process.pid), { flag: "wx" });
|
|
765
|
+
return;
|
|
766
|
+
} catch {
|
|
767
|
+
throw new Error("Could not acquire registry lock");
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
let wait = Date.now() + 50;
|
|
771
|
+
for (; Date.now() < wait; )
|
|
772
|
+
;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
function releaseLock() {
|
|
776
|
+
try {
|
|
777
|
+
unlinkSync(LOCK_PATH);
|
|
778
|
+
} catch {
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function withLock(fn) {
|
|
782
|
+
acquireLock();
|
|
783
|
+
try {
|
|
784
|
+
return fn();
|
|
785
|
+
} finally {
|
|
786
|
+
releaseLock();
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
function readRegistryRaw() {
|
|
790
|
+
if (!existsSync(REGISTRY_PATH))
|
|
791
|
+
return [];
|
|
792
|
+
try {
|
|
793
|
+
return JSON.parse(readFileSync2(REGISTRY_PATH, "utf-8"));
|
|
794
|
+
} catch {
|
|
795
|
+
return [];
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
function writeRegistryRaw(entries) {
|
|
799
|
+
mkdirSync2(DIFFITY_DIR, { recursive: !0 }), writeFileSync2(REGISTRY_PATH, JSON.stringify(entries, null, 2));
|
|
800
|
+
}
|
|
801
|
+
function cleanStaleEntries(entries) {
|
|
802
|
+
return entries.filter((entry) => isProcessAlive(entry.pid));
|
|
803
|
+
}
|
|
804
|
+
function readRegistry() {
|
|
805
|
+
return withLock(() => {
|
|
806
|
+
let entries = readRegistryRaw(), clean = cleanStaleEntries(entries);
|
|
807
|
+
return clean.length !== entries.length && writeRegistryRaw(clean), clean;
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
function registerInstance(entry) {
|
|
811
|
+
withLock(() => {
|
|
812
|
+
let filtered = cleanStaleEntries(readRegistryRaw()).filter((e) => e.pid !== entry.pid);
|
|
813
|
+
filtered.push(entry), writeRegistryRaw(filtered);
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
function deregisterInstance(pid) {
|
|
817
|
+
withLock(() => {
|
|
818
|
+
let filtered = readRegistryRaw().filter((e) => e.pid !== pid);
|
|
819
|
+
writeRegistryRaw(filtered);
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
function findInstanceForRepo(repoHash) {
|
|
823
|
+
let match = readRegistry().find((e) => e.repoHash === repoHash);
|
|
824
|
+
return match || null;
|
|
825
|
+
}
|
|
826
|
+
function findAvailablePort() {
|
|
827
|
+
let entries = readRegistry(), usedPorts = new Set(entries.map((e) => e.port));
|
|
828
|
+
for (let i = 0; i < MAX_PORT_ATTEMPTS; i++) {
|
|
829
|
+
let candidate = BASE_PORT + i;
|
|
830
|
+
if (!usedPorts.has(candidate))
|
|
831
|
+
return candidate;
|
|
832
|
+
}
|
|
833
|
+
return 0;
|
|
834
|
+
}
|
|
835
|
+
|
|
725
836
|
// src/server.ts
|
|
726
837
|
var __dirname = dirname(fileURLToPath(import.meta.url)), MIME_TYPES = {
|
|
727
838
|
".html": "text/html",
|
|
@@ -739,11 +850,11 @@ function sendError2(res, status, message) {
|
|
|
739
850
|
res.writeHead(status, { "Content-Type": "application/json" }), res.end(JSON.stringify({ error: message }));
|
|
740
851
|
}
|
|
741
852
|
function serveStatic(res, filePath) {
|
|
742
|
-
if (!
|
|
853
|
+
if (!existsSync2(filePath)) {
|
|
743
854
|
sendError2(res, 404, "Not found");
|
|
744
855
|
return;
|
|
745
856
|
}
|
|
746
|
-
let ext = extname(filePath), mime = MIME_TYPES[ext] || "application/octet-stream", content =
|
|
857
|
+
let ext = extname(filePath), mime = MIME_TYPES[ext] || "application/octet-stream", content = readFileSync3(filePath);
|
|
747
858
|
res.writeHead(200, { "Content-Type": mime }), res.end(content);
|
|
748
859
|
}
|
|
749
860
|
function descriptionForRef(ref) {
|
|
@@ -780,7 +891,7 @@ function readBody2(req) {
|
|
|
780
891
|
});
|
|
781
892
|
}
|
|
782
893
|
function startServer(options) {
|
|
783
|
-
let { port, diffArgs, description, effectiveRef } = options, sessionId = null, reviewsEnabled = isActionableRef(effectiveRef);
|
|
894
|
+
let { port, portIsExplicit, diffArgs, description, effectiveRef, registryInfo } = options, sessionId = null, reviewsEnabled = isActionableRef(effectiveRef);
|
|
784
895
|
reviewsEnabled && effectiveRef && (sessionId = findOrCreateSession(effectiveRef).id);
|
|
785
896
|
let includeUntracked = diffArgs.length === 0;
|
|
786
897
|
function enrichWithLineCounts(diff, baseRef) {
|
|
@@ -801,7 +912,7 @@ function startServer(options) {
|
|
|
801
912
|
}
|
|
802
913
|
return raw;
|
|
803
914
|
}
|
|
804
|
-
let uiDir =
|
|
915
|
+
let uiDir = join5(__dirname, "ui"), server = createServer(async (req, res) => {
|
|
805
916
|
let url = new URL(req.url || "/", `http://localhost:${port}`), pathname = url.pathname;
|
|
806
917
|
if (res.setHeader("Access-Control-Allow-Origin", "*"), res.setHeader("Access-Control-Allow-Methods", "GET, POST, PATCH, DELETE, OPTIONS"), res.setHeader("Access-Control-Allow-Headers", "Content-Type"), req.method === "OPTIONS") {
|
|
807
918
|
res.writeHead(204), res.end();
|
|
@@ -927,17 +1038,28 @@ function startServer(options) {
|
|
|
927
1038
|
}
|
|
928
1039
|
if (handleReviewRoute(req, res, pathname, url))
|
|
929
1040
|
return;
|
|
930
|
-
let filePath =
|
|
931
|
-
|
|
932
|
-
}), closeFn = () =>
|
|
1041
|
+
let filePath = join5(uiDir, pathname === "/" ? "index.html" : pathname);
|
|
1042
|
+
existsSync2(filePath) || (filePath = join5(uiDir, "index.html")), serveStatic(res, filePath);
|
|
1043
|
+
}), closeFn = () => {
|
|
1044
|
+
deregisterInstance(process.pid), server.close();
|
|
1045
|
+
};
|
|
933
1046
|
return new Promise((resolve, reject) => {
|
|
934
|
-
let retries = 0, maxRetries =
|
|
935
|
-
err.code === "EADDRINUSE" && retries < maxRetries ? (retries++, server.close(), setTimeout(() => server.listen(
|
|
1047
|
+
let currentPort = port, retries = 0, maxRetries = portIsExplicit ? 0 : 10, onError = (err) => {
|
|
1048
|
+
err.code === "EADDRINUSE" && retries < maxRetries ? (retries++, server.close(), currentPort++, setTimeout(() => server.listen(currentPort), 200)) : err.code === "EADDRINUSE" && portIsExplicit ? reject(new Error(`Port ${port} is already in use`)) : reject(err);
|
|
936
1049
|
};
|
|
937
1050
|
server.on("error", onError), server.on("listening", () => {
|
|
938
1051
|
let addr = server.address();
|
|
939
|
-
addr && typeof addr != "string" &&
|
|
940
|
-
|
|
1052
|
+
addr && typeof addr != "string" && (registryInfo && registerInstance({
|
|
1053
|
+
pid: process.pid,
|
|
1054
|
+
port: addr.port,
|
|
1055
|
+
repoRoot: registryInfo.repoRoot,
|
|
1056
|
+
repoHash: registryInfo.repoHash,
|
|
1057
|
+
repoName: registryInfo.repoName,
|
|
1058
|
+
ref: effectiveRef || "work",
|
|
1059
|
+
description: description || "Unstaged changes",
|
|
1060
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1061
|
+
}), resolve({ port: addr.port, close: closeFn }));
|
|
1062
|
+
}), server.listen(currentPort);
|
|
941
1063
|
});
|
|
942
1064
|
}
|
|
943
1065
|
|
|
@@ -1020,7 +1142,7 @@ Examples:
|
|
|
1020
1142
|
|
|
1021
1143
|
// src/index.ts
|
|
1022
1144
|
var require2 = createRequire(import.meta.url), pkg = require2("../package.json"), program = new Command();
|
|
1023
|
-
program.name("diffity").description("GitHub-style git diff viewer in the browser").version(pkg.version).argument("[refs...]", "Git refs to diff (e.g. HEAD~3, main, main..feature)").option("--port <port>", "Port to use", "5391").option("--no-open", "Do not open browser automatically").option("--quiet", "Minimal terminal output").option("--dark", "Open in dark mode (default: light)").option("--unified", "Open in unified view (default: split)").addHelpText("after", `
|
|
1145
|
+
program.name("diffity").description("GitHub-style git diff viewer in the browser").version(pkg.version).argument("[refs...]", "Git refs to diff (e.g. HEAD~3, main, main..feature)").option("--port <port>", "Port to use (default: auto-assigned from 5391)", "5391").option("--no-open", "Do not open browser automatically").option("--quiet", "Minimal terminal output").option("--dark", "Open in dark mode (default: light)").option("--unified", "Open in unified view (default: split)").option("--new", "Stop existing instance and start fresh").addHelpText("after", `
|
|
1024
1146
|
Examples:
|
|
1025
1147
|
$ diffity Working tree changes
|
|
1026
1148
|
$ diffity HEAD~1 Last commit vs working tree
|
|
@@ -1037,29 +1159,89 @@ Examples:
|
|
|
1037
1159
|
let ref = refs[0];
|
|
1038
1160
|
ref.includes("..") ? (diffArgs.push(ref), description = ref) : (diffArgs.push(ref), description = `Changes from ${ref}`);
|
|
1039
1161
|
} else refs.length === 2 ? (diffArgs.push(`${refs[0]}..${refs[1]}`), description = `${refs[0]}..${refs[1]}`) : description = "Unstaged changes";
|
|
1040
|
-
let
|
|
1162
|
+
let effectiveRef;
|
|
1041
1163
|
refs.length > 0 ? effectiveRef = refs.length === 2 ? `${refs[0]}..${refs[1]}` : refs[0] : effectiveRef = "work";
|
|
1164
|
+
let repoRoot = getRepoRoot(), repoHash = createHash3("sha256").update(repoRoot).digest("hex").slice(0, 12), repoName = getRepoName(), existing = findInstanceForRepo(repoHash);
|
|
1165
|
+
if (existing)
|
|
1166
|
+
if (opts.new) {
|
|
1167
|
+
try {
|
|
1168
|
+
process.kill(existing.pid, "SIGTERM");
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
deregisterInstance(existing.pid), opts.quiet || console.log(pc2.dim(` Stopped existing instance (pid ${existing.pid})`));
|
|
1172
|
+
} else {
|
|
1173
|
+
let urlParams = new URLSearchParams({ ref: effectiveRef });
|
|
1174
|
+
opts.dark && urlParams.set("theme", "dark"), opts.unified && urlParams.set("view", "unified");
|
|
1175
|
+
let url = `http://localhost:${existing.port}/?${urlParams.toString()}`;
|
|
1176
|
+
opts.quiet || (console.log(""), console.log(pc2.bold(" diffity")), console.log(` ${pc2.dim("Already running for this repo")}`), console.log(""), console.log(` ${pc2.green("\u2192")} ${pc2.cyan(url)}`), console.log("")), opts.open !== !1 && await open(url);
|
|
1177
|
+
return;
|
|
1178
|
+
}
|
|
1179
|
+
let explicitPort = program.getOptionValueSource("port") === "cli", port = explicitPort ? parseInt(opts.port, 10) : findAvailablePort();
|
|
1042
1180
|
try {
|
|
1043
|
-
let { port: actualPort, close } = await startServer({
|
|
1181
|
+
let { port: actualPort, close } = await startServer({
|
|
1182
|
+
port,
|
|
1183
|
+
portIsExplicit: explicitPort,
|
|
1184
|
+
diffArgs,
|
|
1185
|
+
description,
|
|
1186
|
+
effectiveRef,
|
|
1187
|
+
registryInfo: { repoRoot, repoHash, repoName }
|
|
1188
|
+
}), urlParams = new URLSearchParams({ ref: effectiveRef });
|
|
1044
1189
|
opts.dark && urlParams.set("theme", "dark"), opts.unified && urlParams.set("view", "unified");
|
|
1045
1190
|
let url = `http://localhost:${actualPort}/?${urlParams.toString()}`;
|
|
1046
1191
|
opts.quiet || (console.log(""), console.log(pc2.bold(" diffity")), console.log(` ${pc2.dim(description)}`), console.log(""), console.log(` ${pc2.green("\u2192")} ${pc2.cyan(url)}`), console.log(` ${pc2.dim("Press Ctrl+C to stop")}`), console.log("")), process.on("SIGINT", () => {
|
|
1047
1192
|
opts.quiet || console.log(pc2.dim(`
|
|
1048
|
-
Shutting down...`)), close(), process.exit(0);
|
|
1193
|
+
Shutting down...`)), deregisterInstance(process.pid), close(), process.exit(0);
|
|
1049
1194
|
}), process.on("SIGTERM", () => {
|
|
1050
|
-
close(), process.exit(0);
|
|
1195
|
+
deregisterInstance(process.pid), close(), process.exit(0);
|
|
1051
1196
|
}), opts.open !== !1 && await open(url);
|
|
1052
1197
|
} catch (err) {
|
|
1053
1198
|
console.error(pc2.red(`Failed to start server: ${err}`)), process.exit(1);
|
|
1054
1199
|
}
|
|
1055
1200
|
});
|
|
1201
|
+
program.command("list").description("List all running diffity instances").option("--json", "Output as JSON").action((opts) => {
|
|
1202
|
+
let entries = readRegistry();
|
|
1203
|
+
if (opts.json) {
|
|
1204
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
if (entries.length === 0) {
|
|
1208
|
+
console.log(pc2.dim("No running diffity instances."));
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
console.log(""), console.log(
|
|
1212
|
+
` ${pc2.dim("PORT")} ${pc2.dim("PID".padEnd(8))}${pc2.dim("REPO".padEnd(22))}${pc2.dim("REF".padEnd(22))}${pc2.dim("STARTED")}`
|
|
1213
|
+
);
|
|
1214
|
+
for (let entry of entries) {
|
|
1215
|
+
let ago = getTimeAgo(entry.startedAt);
|
|
1216
|
+
console.log(
|
|
1217
|
+
` ${String(entry.port).padEnd(7)}${String(entry.pid).padEnd(8)}${entry.repoName.slice(0, 20).padEnd(22)}${entry.ref.slice(0, 20).padEnd(22)}${pc2.dim(ago)}`
|
|
1218
|
+
);
|
|
1219
|
+
}
|
|
1220
|
+
console.log("");
|
|
1221
|
+
});
|
|
1222
|
+
function getTimeAgo(isoDate) {
|
|
1223
|
+
let diff = Date.now() - new Date(isoDate).getTime(), seconds = Math.floor(diff / 1e3);
|
|
1224
|
+
if (seconds < 60)
|
|
1225
|
+
return "just now";
|
|
1226
|
+
let minutes = Math.floor(seconds / 60);
|
|
1227
|
+
if (minutes < 60)
|
|
1228
|
+
return `${minutes}m ago`;
|
|
1229
|
+
let hours = Math.floor(minutes / 60);
|
|
1230
|
+
return hours < 24 ? `${hours}h ago` : `${Math.floor(hours / 24)}d ago`;
|
|
1231
|
+
}
|
|
1056
1232
|
program.command("prune").description("Remove all diffity data (database, sessions) for all repos").action(() => {
|
|
1057
|
-
let dir =
|
|
1058
|
-
if (!
|
|
1233
|
+
let dir = join6(homedir3(), ".diffity");
|
|
1234
|
+
if (!existsSync3(dir)) {
|
|
1059
1235
|
console.log(pc2.dim("Nothing to prune."));
|
|
1060
1236
|
return;
|
|
1061
1237
|
}
|
|
1062
|
-
|
|
1238
|
+
let running = readRegistry();
|
|
1239
|
+
for (let entry of running)
|
|
1240
|
+
try {
|
|
1241
|
+
process.kill(entry.pid, "SIGTERM");
|
|
1242
|
+
} catch {
|
|
1243
|
+
}
|
|
1244
|
+
running.length > 0 && console.log(pc2.dim(` Stopped ${running.length} running instance${running.length > 1 ? "s" : ""}.`)), rmSync(dir, { recursive: !0, force: !0 }), console.log(pc2.green("Pruned all diffity data (~/.diffity)."));
|
|
1063
1245
|
});
|
|
1064
1246
|
program.command("update").description("Update diffity to the latest version").action(() => {
|
|
1065
1247
|
try {
|