diff-easy 0.1.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/LICENSE +22 -0
- package/README.md +142 -0
- package/dist/cli/cmux.d.ts +9 -0
- package/dist/cli/cmux.js +274 -0
- package/dist/cli/comment.d.ts +2 -0
- package/dist/cli/comment.js +91 -0
- package/dist/cli/github.d.ts +84 -0
- package/dist/cli/github.js +364 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +397 -0
- package/dist/cli/turns.d.ts +14 -0
- package/dist/cli/turns.js +333 -0
- package/dist/cli/utils.d.ts +27 -0
- package/dist/cli/utils.js +204 -0
- package/dist/client/assets/arc-CHYjdFTk.js +1 -0
- package/dist/client/assets/architecture-7EHR7CIX-weHBYDgM.js +1 -0
- package/dist/client/assets/architectureDiagram-3BPJPVTR-DRa2msuJ.js +36 -0
- package/dist/client/assets/array-BifhSqXX.js +1 -0
- package/dist/client/assets/blockDiagram-GPEHLZMM-BTdKsXy5.js +132 -0
- package/dist/client/assets/c4Diagram-AAUBKEIU-CwTvCkOg.js +10 -0
- package/dist/client/assets/channel-Cv40nQW1.js +1 -0
- package/dist/client/assets/chunk-2J33WTMH-BFsjrTtc.js +1 -0
- package/dist/client/assets/chunk-3OPIFGDE-V_xVw1dq.js +62 -0
- package/dist/client/assets/chunk-4BX2VUAB-DoLO6LLy.js +1 -0
- package/dist/client/assets/chunk-55IACEB6-CHgBwbdB.js +1 -0
- package/dist/client/assets/chunk-5ZQYHXKU-DMixAgQG.js +2 -0
- package/dist/client/assets/chunk-727SXJPM-C-3FZTmm.js +206 -0
- package/dist/client/assets/chunk-AQP2D5EJ-DpQZhXt-.js +231 -0
- package/dist/client/assets/chunk-BSJP7CBP-BnO9E_G4.js +1 -0
- package/dist/client/assets/chunk-CSCIHK7Q-BxItphyO.js +124 -0
- package/dist/client/assets/chunk-FMBD7UC4-4hEcfvfT.js +15 -0
- package/dist/client/assets/chunk-KSCS5N6A-DnArkpOI.js +10 -0
- package/dist/client/assets/chunk-L5ZTLDWV-DQoYJ1BW.js +1 -0
- package/dist/client/assets/chunk-LZXEDZCA-CdCevBX-.js +2 -0
- package/dist/client/assets/chunk-ND2GUHAM-Dt89U_nU.js +1 -0
- package/dist/client/assets/chunk-NNHCCRGN-DlpIbxXb.js +159 -0
- package/dist/client/assets/chunk-NZK2D7GU-C2Ev_f_w.js +1 -0
- package/dist/client/assets/chunk-O5CBEL6O-BeZ3MFnJ.js +70 -0
- package/dist/client/assets/chunk-QZHKN3VN-BmDOSWHp.js +1 -0
- package/dist/client/assets/chunk-WU5MYG2G-gLuInTk4.js +1 -0
- package/dist/client/assets/chunk-XPW4576I-DPbDc8_T.js +32 -0
- package/dist/client/assets/classDiagram-4FO5ZUOK-DOpVKUrb.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-DOpVKUrb.js +1 -0
- package/dist/client/assets/cose-bilkent-S5V4N54A-CCPyalms.js +1 -0
- package/dist/client/assets/cytoscape.esm-FqbQrHcz.js +321 -0
- package/dist/client/assets/dagre-BM42HDAG-6jrYMjX3.js +4 -0
- package/dist/client/assets/dagre-Bx709z4p.js +1 -0
- package/dist/client/assets/defaultLocale-C8Fc0cco.js +1 -0
- package/dist/client/assets/diagram-2AECGRRQ-dCQ_KoXl.js +43 -0
- package/dist/client/assets/diagram-5GNKFQAL-B3Xj4-XE.js +10 -0
- package/dist/client/assets/diagram-KO2AKTUF-DaGgF2yL.js +3 -0
- package/dist/client/assets/diagram-LMA3HP47-CF_c4tPT.js +24 -0
- package/dist/client/assets/diagram-OG6HWLK6-BgBHbJkT.js +24 -0
- package/dist/client/assets/dist-7wVkzZne.js +1 -0
- package/dist/client/assets/erDiagram-TEJ5UH35-BZgX1grR.js +85 -0
- package/dist/client/assets/eventmodeling-FCH6USID-BaI6RNKr.js +1 -0
- package/dist/client/assets/flowDiagram-I6XJVG4X-BFNUa12V.js +162 -0
- package/dist/client/assets/ganttDiagram-6RSMTGT7-C8t6BThI.js +292 -0
- package/dist/client/assets/gitGraph-WXDBUCRP-BlicfDkM.js +1 -0
- package/dist/client/assets/gitGraphDiagram-PVQCEYII-BmnIXuXP.js +106 -0
- package/dist/client/assets/graphlib-B8gBHxth.js +1 -0
- package/dist/client/assets/index-BfVtbKTZ.css +2 -0
- package/dist/client/assets/index-p8zzfjdu.js +87 -0
- package/dist/client/assets/info-J43DQDTF-DHLG38N3.js +1 -0
- package/dist/client/assets/infoDiagram-5YYISTIA-oQ-lq-45.js +2 -0
- package/dist/client/assets/init-D6jRqBbL.js +1 -0
- package/dist/client/assets/ishikawaDiagram-YF4QCWOH-DrODlBLD.js +70 -0
- package/dist/client/assets/journeyDiagram-JHISSGLW-Bxg5wCQz.js +139 -0
- package/dist/client/assets/kanban-definition-UN3LZRKU-DAdvUDIB.js +89 -0
- package/dist/client/assets/katex-Vhh-h91d.js +257 -0
- package/dist/client/assets/line-D8kNd46u.js +1 -0
- package/dist/client/assets/linear-jwGZSrg4.js +1 -0
- package/dist/client/assets/mermaid-parser.core-PVPfUfyU.js +4 -0
- package/dist/client/assets/mermaid.core-DwdP6CFg.js +9 -0
- package/dist/client/assets/mindmap-definition-RKZ34NQL-DeOoL56Q.js +96 -0
- package/dist/client/assets/ordinal-hYBb2elL.js +1 -0
- package/dist/client/assets/packet-YPE3B663-BBEaxdJP.js +1 -0
- package/dist/client/assets/path-BWPyau1x.js +1 -0
- package/dist/client/assets/pie-LRSECV5Y-fvTRFm-2.js +1 -0
- package/dist/client/assets/pieDiagram-4H26LBE5-oep1px2r.js +30 -0
- package/dist/client/assets/prism-bash-D6zCJ74D.js +1 -0
- package/dist/client/assets/prism-clojure-_z-zoNcE.js +1 -0
- package/dist/client/assets/prism-csharp-XPn2bw0d.js +1 -0
- package/dist/client/assets/prism-dart-CDv16Fbu.js +1 -0
- package/dist/client/assets/prism-elixir-DhHhV5Rj.js +1 -0
- package/dist/client/assets/prism-haskell-5hq1Nh9c.js +1 -0
- package/dist/client/assets/prism-hcl-B6Z0Ouoo.js +1 -0
- package/dist/client/assets/prism-java-6wtusvIl.js +1 -0
- package/dist/client/assets/prism-markup-templating-KkSMBj6Y.js +1 -0
- package/dist/client/assets/prism-nix-C88dTX93.js +1 -0
- package/dist/client/assets/prism-perl-Lsbi34sH.js +1 -0
- package/dist/client/assets/prism-php-Co4fM40K.js +1 -0
- package/dist/client/assets/prism-protobuf-yzSHlhwy.js +1 -0
- package/dist/client/assets/prism-ruby-DFCASkuD.js +1 -0
- package/dist/client/assets/prism-scala-VkM3wCOL.js +1 -0
- package/dist/client/assets/prism-solidity-DTom4k5p.js +1 -0
- package/dist/client/assets/prism-sql-CYhVDMEE.js +1 -0
- package/dist/client/assets/prism-vim-BgpkjCK5.js +1 -0
- package/dist/client/assets/quadrantDiagram-W4KKPZXB-D9l66CHb.js +7 -0
- package/dist/client/assets/radar-GUYGQ44K-DAU5EPVd.js +1 -0
- package/dist/client/assets/requirementDiagram-4Y6WPE33-helpQlwb.js +84 -0
- package/dist/client/assets/rough.esm-CSKSodPl.js +1 -0
- package/dist/client/assets/sankeyDiagram-5OEKKPKP-C9sNuuvA.js +40 -0
- package/dist/client/assets/sequenceDiagram-3UESZ5HK-BP0bhCwQ.js +162 -0
- package/dist/client/assets/src-CDAPGf2l.js +1 -0
- package/dist/client/assets/stateDiagram-AJRCARHV-CsFizpla.js +1 -0
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-Bvn8tanl.js +1 -0
- package/dist/client/assets/timeline-definition-PNZ67QCA-BnkmHs1F.js +120 -0
- package/dist/client/assets/treeView-BLDUP644-DD9HZO5W.js +1 -0
- package/dist/client/assets/treemap-LRROVOQU-BBKuQ1lo.js +1 -0
- package/dist/client/assets/vennDiagram-CIIHVFJN-BPFTHflF.js +34 -0
- package/dist/client/assets/wardley-L42UT6IY-y88gDkwb.js +1 -0
- package/dist/client/assets/wardleyDiagram-YWT4CUSO-BlcA7IVh.js +78 -0
- package/dist/client/assets/xychartDiagram-2RQKCTM6-DX5vUCRH.js +7 -0
- package/dist/client/favicon.png +0 -0
- package/dist/client/index.html +14 -0
- package/dist/client/logo.png +0 -0
- package/dist/client/site-data/blobs/080c0e6/cHVibGljL3NpdGUtZGF0YS9vZy1pbWFnZS5wbmc.png +0 -0
- package/dist/client/site-data/blobs/55f23a1/bGFuZGluZy9wdWJsaWMvZGlmaXQvbG9nby5wbmc.png +0 -0
- package/dist/client/site-data/blobs/66ff7c6/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/blobs/e6977fe/cHVibGljL2xvZ28ucG5n.png +0 -0
- package/dist/client/site-data/manifest.json +1 -0
- package/dist/client/site-data/og-image.png +0 -0
- package/dist/client/site-data/snapshots/55f23a1...080c0e6.json +1 -0
- package/dist/client/site-data/snapshots/66ff7c6...e6977fe.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f-comments.json +1 -0
- package/dist/client/site-data/snapshots/7d40fd4...a72112f.json +1 -0
- package/dist/server/file-watcher.d.ts +33 -0
- package/dist/server/file-watcher.js +340 -0
- package/dist/server/generated-file-check.d.ts +17 -0
- package/dist/server/generated-file-check.js +280 -0
- package/dist/server/git-diff.d.ts +94 -0
- package/dist/server/git-diff.js +909 -0
- package/dist/server/grouping/cache.d.ts +3 -0
- package/dist/server/grouping/cache.js +22 -0
- package/dist/server/grouping/claudeCli.d.ts +10 -0
- package/dist/server/grouping/claudeCli.js +95 -0
- package/dist/server/grouping/digest.d.ts +15 -0
- package/dist/server/grouping/digest.js +38 -0
- package/dist/server/grouping/reconcile.d.ts +6 -0
- package/dist/server/grouping/reconcile.js +34 -0
- package/dist/server/grouping/types.d.ts +9 -0
- package/dist/server/grouping/types.js +1 -0
- package/dist/server/server.d.ts +37 -0
- package/dist/server/server.js +1283 -0
- package/dist/shared/grouping.d.ts +4 -0
- package/dist/shared/grouping.js +170 -0
- package/dist/types/diff.d.ts +263 -0
- package/dist/types/diff.js +1 -0
- package/dist/types/watch.d.ts +59 -0
- package/dist/types/watch.js +8 -0
- package/dist/utils/commentFormatting.d.ts +6 -0
- package/dist/utils/commentFormatting.js +90 -0
- package/dist/utils/commentImports.d.ts +11 -0
- package/dist/utils/commentImports.js +386 -0
- package/dist/utils/createId.d.ts +1 -0
- package/dist/utils/createId.js +5 -0
- package/dist/utils/diffMode.d.ts +3 -0
- package/dist/utils/diffMode.js +13 -0
- package/dist/utils/diffSelection.d.ts +13 -0
- package/dist/utils/diffSelection.js +39 -0
- package/dist/utils/editorOptions.d.ts +60 -0
- package/dist/utils/editorOptions.js +171 -0
- package/dist/utils/fileUtils.d.ts +12 -0
- package/dist/utils/fileUtils.js +21 -0
- package/dist/utils/hubConfig.d.ts +32 -0
- package/dist/utils/hubConfig.js +51 -0
- package/dist/utils/suggestionUtils.d.ts +19 -0
- package/dist/utils/suggestionUtils.js +31 -0
- package/dist/utils/turnLog.d.ts +47 -0
- package/dist/utils/turnLog.js +133 -0
- package/package.json +105 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { simpleGit } from 'simple-git';
|
|
5
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
6
|
+
import { startServer } from '../server/server.js';
|
|
7
|
+
import { createDiffSelection } from '../utils/diffSelection.js';
|
|
8
|
+
import { DiffMode } from '../types/watch.js';
|
|
9
|
+
import { shouldReadStdin, findUntrackedFiles, markFilesIntentToAdd, promptUser, parseCommentOptions, validateDiffArguments, getGitRoot, readStdin, } from './utils.js';
|
|
10
|
+
import { createCommentCommand } from './comment.js';
|
|
11
|
+
import { getPrPatch, getPrCommentImports, getPrDetails } from './github.js';
|
|
12
|
+
import { createSnapshotCommand, createInitCommand } from './turns.js';
|
|
13
|
+
import { createCmuxCommand } from './cmux.js';
|
|
14
|
+
import { addHubRoots } from '../utils/hubConfig.js';
|
|
15
|
+
function createHubCommand() {
|
|
16
|
+
return new Command('hub')
|
|
17
|
+
.description('Run an always-on diff-easy; pick a folder or PR to review from the UI')
|
|
18
|
+
.option('--port <port>', 'preferred port (default 4966)', parseInt)
|
|
19
|
+
.option('--host <host>', 'host address to bind', '')
|
|
20
|
+
.option('--no-open', 'do not automatically open the browser')
|
|
21
|
+
.option('--root <path>', 'workspace root to scan for repos (repeatable)', (value, previous) => [...previous, value], [])
|
|
22
|
+
.action(async (options) => {
|
|
23
|
+
if (options.root.length > 0) {
|
|
24
|
+
addHubRoots(options.root);
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const { url } = await startServer({
|
|
28
|
+
hub: true,
|
|
29
|
+
keepAlive: true,
|
|
30
|
+
preferredPort: options.port ?? 4966,
|
|
31
|
+
host: options.host || undefined,
|
|
32
|
+
openBrowser: options.open !== false,
|
|
33
|
+
});
|
|
34
|
+
console.log(`\n๐๏ธ diff-easy hub running at ${url}`);
|
|
35
|
+
console.log(' Pick a folder or paste a PR URL in the UI. Press Ctrl+C to stop.\n');
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
console.error(`Error starting hub: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
function isSpecialArg(arg) {
|
|
44
|
+
return arg === 'working' || arg === 'staged' || arg === '.';
|
|
45
|
+
}
|
|
46
|
+
function resolveDiffSelection(commitish, compareWith, mergeBase) {
|
|
47
|
+
let baseCommitish;
|
|
48
|
+
if (compareWith) {
|
|
49
|
+
baseCommitish = compareWith;
|
|
50
|
+
}
|
|
51
|
+
else if (commitish === 'working') {
|
|
52
|
+
baseCommitish = 'staged';
|
|
53
|
+
}
|
|
54
|
+
else if (isSpecialArg(commitish)) {
|
|
55
|
+
baseCommitish = 'HEAD';
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
baseCommitish = commitish + '^';
|
|
59
|
+
}
|
|
60
|
+
return createDiffSelection(baseCommitish, commitish, mergeBase ? 'merge-base' : undefined);
|
|
61
|
+
}
|
|
62
|
+
function determineDiffMode(selection, compareWith) {
|
|
63
|
+
const { targetCommitish } = selection;
|
|
64
|
+
// If comparing specific commits/branches (not involving HEAD), no watching needed
|
|
65
|
+
// Exception: allow watching when targetCommitish is '.' even with compareWith
|
|
66
|
+
if (compareWith && targetCommitish !== 'HEAD' && targetCommitish !== '.') {
|
|
67
|
+
return DiffMode.SPECIFIC;
|
|
68
|
+
}
|
|
69
|
+
if (targetCommitish === 'working') {
|
|
70
|
+
return DiffMode.WORKING;
|
|
71
|
+
}
|
|
72
|
+
if (targetCommitish === 'staged') {
|
|
73
|
+
return DiffMode.STAGED;
|
|
74
|
+
}
|
|
75
|
+
if (targetCommitish === '.') {
|
|
76
|
+
return DiffMode.DOT;
|
|
77
|
+
}
|
|
78
|
+
// Default mode: HEAD^ vs HEAD or HEAD vs other commits (watch for HEAD changes)
|
|
79
|
+
return DiffMode.DEFAULT;
|
|
80
|
+
}
|
|
81
|
+
async function startBackgroundProcess() {
|
|
82
|
+
const scriptPath = process.argv[1];
|
|
83
|
+
if (!scriptPath) {
|
|
84
|
+
throw new Error('Unable to determine diff-easy entrypoint for background process');
|
|
85
|
+
}
|
|
86
|
+
const childArgs = process.argv.slice(2).filter((arg) => arg !== '--background');
|
|
87
|
+
if (!childArgs.includes('--keep-alive')) {
|
|
88
|
+
childArgs.push('--keep-alive');
|
|
89
|
+
}
|
|
90
|
+
if (!childArgs.includes('--no-open')) {
|
|
91
|
+
childArgs.push('--no-open');
|
|
92
|
+
}
|
|
93
|
+
const child = spawn(process.execPath, [scriptPath, ...childArgs], {
|
|
94
|
+
detached: true,
|
|
95
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
96
|
+
env: {
|
|
97
|
+
...process.env,
|
|
98
|
+
[BACKGROUND_CHILD_ENV]: '1',
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
child.stdout.setEncoding('utf8');
|
|
102
|
+
child.stderr.setEncoding('utf8');
|
|
103
|
+
await new Promise((resolve, reject) => {
|
|
104
|
+
const timeout = setTimeout(() => {
|
|
105
|
+
reject(new Error('Timed out while starting background diff-easy server'));
|
|
106
|
+
}, 10_000);
|
|
107
|
+
let stderr = '';
|
|
108
|
+
let settled = false;
|
|
109
|
+
const finish = (callback) => {
|
|
110
|
+
if (settled) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
settled = true;
|
|
114
|
+
clearTimeout(timeout);
|
|
115
|
+
callback();
|
|
116
|
+
};
|
|
117
|
+
child.stdout.on('data', (chunk) => {
|
|
118
|
+
const line = chunk
|
|
119
|
+
.split(/\r?\n/u)
|
|
120
|
+
.map((value) => value.trim())
|
|
121
|
+
.find((value) => value.length > 0);
|
|
122
|
+
if (!line) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
finish(() => {
|
|
126
|
+
console.log(line);
|
|
127
|
+
child.unref();
|
|
128
|
+
resolve();
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
child.stderr.on('data', (chunk) => {
|
|
132
|
+
stderr += chunk;
|
|
133
|
+
});
|
|
134
|
+
child.once('error', (error) => {
|
|
135
|
+
finish(() => {
|
|
136
|
+
reject(error);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
child.once('exit', (code) => {
|
|
140
|
+
finish(() => {
|
|
141
|
+
const trimmedStderr = stderr.trim();
|
|
142
|
+
reject(new Error(trimmedStderr || `Background diff-easy server exited early (code ${code ?? 'unknown'})`));
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const BACKGROUND_CHILD_ENV = 'DIFIT_BACKGROUND_CHILD';
|
|
148
|
+
const program = new Command();
|
|
149
|
+
program
|
|
150
|
+
.name('diff-easy')
|
|
151
|
+
.description('A lightweight Git diff viewer with GitHub-like interface')
|
|
152
|
+
.version(pkg.version, '-v, --version', 'output the version number')
|
|
153
|
+
.enablePositionalOptions()
|
|
154
|
+
.addCommand(createCommentCommand())
|
|
155
|
+
.addCommand(createSnapshotCommand())
|
|
156
|
+
.addCommand(createInitCommand())
|
|
157
|
+
.addCommand(createCmuxCommand())
|
|
158
|
+
.addCommand(createHubCommand())
|
|
159
|
+
.argument('[commit-ish]', 'Git commit, tag, branch, HEAD~n reference, or "working"/"staged"/"." (default "." = all uncommitted changes)', '.')
|
|
160
|
+
.argument('[compare-with]', 'Optional: Compare with this commit/branch (shows diff between commit-ish and compare-with)')
|
|
161
|
+
.option('--port <port>', 'preferred port (auto-assigned if occupied)', parseInt)
|
|
162
|
+
.option('--host <host>', 'host address to bind', '')
|
|
163
|
+
.option('--no-open', 'do not automatically open browser')
|
|
164
|
+
.option('--comment <json>', 'inject initial review comments (repeatable, accepts a JSON object or array)', (value, previous) => [...previous, value], [])
|
|
165
|
+
.option('--pr <url>', 'GitHub PR URL to review (e.g., https://github.com/owner/repo/pull/123)')
|
|
166
|
+
.option('--clean', 'start with a clean slate by clearing all existing comments')
|
|
167
|
+
.option('--include-untracked', 'automatically include untracked files in diff')
|
|
168
|
+
.option('--keep-alive', 'keep server running even after browser disconnects')
|
|
169
|
+
.option('--background', 'keep the server running in the background and output JSON info')
|
|
170
|
+
.option('--context <lines>', 'number of context lines shown around each change', parseInt)
|
|
171
|
+
.option('--merge-base', 'resolve the base revision with git merge-base before diffing (Git revision mode only)')
|
|
172
|
+
.action(async (commitish, compareWith, options) => {
|
|
173
|
+
try {
|
|
174
|
+
const isBackgroundChild = process.env[BACKGROUND_CHILD_ENV] === '1';
|
|
175
|
+
const backgroundMode = options.background || isBackgroundChild;
|
|
176
|
+
let stdinDiff;
|
|
177
|
+
let stdinReviewLabel = 'diff from stdin';
|
|
178
|
+
let manualCommentImports = [];
|
|
179
|
+
let commentImports = [];
|
|
180
|
+
let prTitle;
|
|
181
|
+
let prBody;
|
|
182
|
+
if (options.context !== undefined &&
|
|
183
|
+
(!Number.isInteger(options.context) || options.context < 0)) {
|
|
184
|
+
console.error('Error: --context must be a non-negative integer');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
if (options.background && !isBackgroundChild) {
|
|
188
|
+
await startBackgroundProcess();
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
manualCommentImports = parseCommentOptions(options.comment);
|
|
193
|
+
commentImports = manualCommentImports;
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Invalid --comment value'}`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
if (backgroundMode) {
|
|
200
|
+
options.keepAlive = true;
|
|
201
|
+
options.open = false;
|
|
202
|
+
}
|
|
203
|
+
if (options.pr) {
|
|
204
|
+
if (program.args.length > 0) {
|
|
205
|
+
console.error('Error: --pr option cannot be used with positional arguments');
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
if (options.mergeBase) {
|
|
209
|
+
console.error('Error: --merge-base option cannot be used with --pr');
|
|
210
|
+
process.exit(1);
|
|
211
|
+
}
|
|
212
|
+
if (options.context !== undefined) {
|
|
213
|
+
console.error('Error: --context option cannot be used with --pr');
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
stdinDiff = getPrPatch(options.pr);
|
|
218
|
+
stdinReviewLabel = options.pr;
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
console.error(`Error resolving PR: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const prCommentImports = await getPrCommentImports(options.pr);
|
|
226
|
+
commentImports = [...prCommentImports, ...manualCommentImports];
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
console.warn(`Warning: Failed to load PR review comments: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
230
|
+
}
|
|
231
|
+
try {
|
|
232
|
+
const prDetails = await getPrDetails(options.pr);
|
|
233
|
+
prTitle = prDetails?.title || undefined;
|
|
234
|
+
prBody = prDetails?.body || undefined;
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
console.warn(`Warning: Failed to load PR title/body: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
else {
|
|
241
|
+
// Check if we should read from stdin
|
|
242
|
+
const readFromStdin = shouldReadStdin({
|
|
243
|
+
commitish,
|
|
244
|
+
hasPositionalArgs: program.args.length > 0,
|
|
245
|
+
hasPrOption: false,
|
|
246
|
+
});
|
|
247
|
+
if (readFromStdin) {
|
|
248
|
+
if (options.context !== undefined) {
|
|
249
|
+
console.error('Error: --context option cannot be used with stdin diff');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
if (options.mergeBase) {
|
|
253
|
+
console.error('Error: --merge-base option cannot be used with stdin diff');
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
// Read unified diff from stdin
|
|
257
|
+
stdinDiff = await readStdin();
|
|
258
|
+
if (!stdinDiff.trim()) {
|
|
259
|
+
console.error('Error: No diff content received from stdin');
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
if (stdinDiff) {
|
|
265
|
+
// Start server with stdin diff (including --pr patch)
|
|
266
|
+
const { url, port } = await startServer({
|
|
267
|
+
stdinDiff,
|
|
268
|
+
preferredPort: options.port,
|
|
269
|
+
host: options.host,
|
|
270
|
+
openBrowser: options.open,
|
|
271
|
+
clearComments: options.clean,
|
|
272
|
+
keepAlive: options.keepAlive,
|
|
273
|
+
...(commentImports.length > 0 ? { commentImports } : {}),
|
|
274
|
+
prTitle,
|
|
275
|
+
prBody,
|
|
276
|
+
});
|
|
277
|
+
if (backgroundMode) {
|
|
278
|
+
console.log(JSON.stringify({ port, url, pid: process.pid }));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
console.log(`\n๐ diff-easy server started on ${url}`);
|
|
282
|
+
console.log(`๐ Reviewing: ${stdinReviewLabel}`);
|
|
283
|
+
if (options.keepAlive) {
|
|
284
|
+
console.log('๐ Keep-alive mode: server will stay running after browser disconnects');
|
|
285
|
+
}
|
|
286
|
+
console.log('\nPress Ctrl+C to stop the server');
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
// Detect git root
|
|
290
|
+
let repoPath;
|
|
291
|
+
try {
|
|
292
|
+
repoPath = getGitRoot();
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// If not in a git repository, fall back to process.cwd()
|
|
296
|
+
repoPath = undefined;
|
|
297
|
+
}
|
|
298
|
+
const selection = resolveDiffSelection(commitish, compareWith, options.mergeBase);
|
|
299
|
+
if (options.mergeBase && isSpecialArg(selection.baseCommitish)) {
|
|
300
|
+
console.error(`Error: --merge-base requires a commit-ish base, but resolved base was "${selection.baseCommitish}"`);
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
if (selection.targetCommitish === 'working' || selection.targetCommitish === '.') {
|
|
304
|
+
const git = simpleGit(repoPath);
|
|
305
|
+
if (isBackgroundChild && !options.includeUntracked) {
|
|
306
|
+
// Skip interactive prompts in detached background mode.
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
await handleUntrackedFiles(git, options.includeUntracked);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const validation = validateDiffArguments(selection.targetCommitish, compareWith);
|
|
313
|
+
if (!validation.valid) {
|
|
314
|
+
console.error(`Error: ${validation.error}`);
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
const { url, port, isEmpty } = await startServer({
|
|
318
|
+
selection,
|
|
319
|
+
preferredPort: options.port,
|
|
320
|
+
host: options.host,
|
|
321
|
+
openBrowser: options.open,
|
|
322
|
+
clearComments: options.clean,
|
|
323
|
+
keepAlive: options.keepAlive,
|
|
324
|
+
contextLines: options.context,
|
|
325
|
+
diffMode: determineDiffMode(selection, compareWith),
|
|
326
|
+
repoPath,
|
|
327
|
+
...(commentImports.length > 0 ? { commentImports } : {}),
|
|
328
|
+
});
|
|
329
|
+
if (backgroundMode) {
|
|
330
|
+
console.log(JSON.stringify({ port, url, pid: process.pid }));
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
console.log(`\n๐ diff-easy server started on ${url}`);
|
|
334
|
+
console.log(`๐ Reviewing: ${selection.targetCommitish}`);
|
|
335
|
+
if (options.keepAlive) {
|
|
336
|
+
console.log('๐ Keep-alive mode: server will stay running after browser disconnects');
|
|
337
|
+
}
|
|
338
|
+
if (options.clean) {
|
|
339
|
+
console.log('๐งน Starting with a clean slate - all existing comments will be cleared');
|
|
340
|
+
}
|
|
341
|
+
if (isEmpty) {
|
|
342
|
+
console.log('\n! \x1b[33mNo differences found. Browser will not open automatically.\x1b[0m');
|
|
343
|
+
console.log(` Server is running at ${url} if you want to check manually.\n`);
|
|
344
|
+
}
|
|
345
|
+
else if (options.open) {
|
|
346
|
+
console.log('๐ Opening browser...\n');
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
console.log('๐ก Use --open to automatically open browser\n');
|
|
350
|
+
}
|
|
351
|
+
process.on('SIGINT', async () => {
|
|
352
|
+
console.log('\n๐ Shutting down diff-easy server...');
|
|
353
|
+
// Try to fetch comments before shutting down
|
|
354
|
+
try {
|
|
355
|
+
const response = await fetch(`http://localhost:${port}/api/comments-output`);
|
|
356
|
+
if (response.ok) {
|
|
357
|
+
const data = await response.text();
|
|
358
|
+
if (data.trim()) {
|
|
359
|
+
console.log(data);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
catch {
|
|
364
|
+
// Silently ignore fetch errors during shutdown
|
|
365
|
+
}
|
|
366
|
+
process.exit(0);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
console.error('Error:', error instanceof Error ? error.message : 'Unknown error');
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
void program.parseAsync();
|
|
375
|
+
async function handleUntrackedFiles(git, addAutomatically) {
|
|
376
|
+
const files = await findUntrackedFiles(git);
|
|
377
|
+
if (files.length === 0) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const shouldAdd = addAutomatically || (await promptUserToIncludeUntracked(files));
|
|
381
|
+
if (shouldAdd) {
|
|
382
|
+
await markFilesIntentToAdd(git, files);
|
|
383
|
+
console.log('โ
Files added with --intent-to-add');
|
|
384
|
+
const filesAsArgs = files.join(' ');
|
|
385
|
+
console.log(` ๐ก To undo this, run \`git reset -- ${filesAsArgs}\``);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
console.log('i Untracked files will not be shown in diff');
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
async function promptUserToIncludeUntracked(files) {
|
|
392
|
+
console.log(`\n๐ Found ${files.length} untracked file(s):`);
|
|
393
|
+
for (const file of files) {
|
|
394
|
+
console.log(` - ${file}`);
|
|
395
|
+
}
|
|
396
|
+
return await promptUser('\nโ Would you like to include these untracked files in the diff review? (Y/n): ');
|
|
397
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* Best-effort live-refresh ping. Capped so it never slows the agent's turn.
|
|
4
|
+
*
|
|
5
|
+
* A viewer advertises itself in one of two ways: a per-repo `server.json` (a
|
|
6
|
+
* standalone `difit` opened on this repo) or the global `hub.json` (the
|
|
7
|
+
* always-on hub, which serves many repos and never writes a per-repo file).
|
|
8
|
+
* Ping every live advertisement so the timeline refreshes regardless of which
|
|
9
|
+
* one the user has open โ without this, hub users only saw new turns after a
|
|
10
|
+
* manual refresh.
|
|
11
|
+
*/
|
|
12
|
+
export declare function notifyServer(repoRoot: string): Promise<void>;
|
|
13
|
+
export declare function createSnapshotCommand(): Command;
|
|
14
|
+
export declare function createInitCommand(): Command;
|