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
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sai Mageshvar (diff-easy)
|
|
4
|
+
Copyright (c) 2025 @yoshiko-pg/difit (original difit project this is based on)
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# diff-easy
|
|
2
|
+
|
|
3
|
+
A local, browser-based **git diff viewer built for AI-agent workflows**. It runs a
|
|
4
|
+
small web server on your machine (view it in any browser — or Cmux's inline
|
|
5
|
+
browser) and adds two things on top of a plain diff view:
|
|
6
|
+
|
|
7
|
+
1. **Filter by filename / type, and search within the diff.** Filter the file
|
|
8
|
+
list by substring, by extension chips (`.ts`, `.tsx`, `.css`, …), and by change
|
|
9
|
+
status; search diff _content_ across all files with highlight + next/prev.
|
|
10
|
+
2. **Review the diff produced by each AI-agent turn.** A capture hook snapshots
|
|
11
|
+
your working tree at every Claude Code turn boundary, so you can browse a full
|
|
12
|
+
timeline of turns and see exactly what each one changed — with **live refresh**
|
|
13
|
+
as the agent works.
|
|
14
|
+
|
|
15
|
+
diff-easy is built on a vendored copy of [difit](https://github.com/yoshiko-pg/difit)
|
|
16
|
+
(MIT). See [`NOTICE.md`](./NOTICE.md) for attribution.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# from a clone of this repo
|
|
22
|
+
corepack enable # provides pnpm
|
|
23
|
+
pnpm install
|
|
24
|
+
pnpm run build
|
|
25
|
+
npm link # makes `diff-easy` available on your PATH (optional)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
If you don't `npm link`, run it via `node dist/cli/index.js …` (or `pnpm start`).
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
Run inside any git repository:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
diff-easy # default: ALL uncommitted changes (staged + unstaged) vs HEAD
|
|
36
|
+
diff-easy working # unstaged changes only
|
|
37
|
+
diff-easy staged # staged changes only
|
|
38
|
+
diff-easy HEAD # the last commit (HEAD^..HEAD)
|
|
39
|
+
diff-easy HEAD~3 HEAD # a commit range
|
|
40
|
+
diff-easy --port 9100 # pick a port (handy with Cmux per-workspace ports)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
It prints a `http://localhost:<port>` URL and opens your browser. In **Cmux**,
|
|
44
|
+
enable "open http/https in the cmux browser" (Settings) and the URL opens right in
|
|
45
|
+
the inline browser pane — or run `cmux browser open <url>`.
|
|
46
|
+
|
|
47
|
+
### Filter & search
|
|
48
|
+
|
|
49
|
+
- **File list** (left): type in _Filter files…_, click extension chips to narrow by
|
|
50
|
+
type, and use the status chips (Added / Modified / …) when more than one status
|
|
51
|
+
is present.
|
|
52
|
+
- **In-diff search**: press **Cmd/Ctrl+F** (or click the search icon). Enter steps
|
|
53
|
+
to the next match, Shift+Enter the previous; toggle case sensitivity. Matches are
|
|
54
|
+
highlighted across every file.
|
|
55
|
+
|
|
56
|
+
## Use from CMUX
|
|
57
|
+
|
|
58
|
+
Run this in a repo's CMUX workspace to open/close the hub — scoped to that repo —
|
|
59
|
+
in a browser split pane beside your terminal:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
diff-easy cmux toggle # open the split for $PWD if closed, close it if open
|
|
63
|
+
diff-easy cmux open # just open (re-points the same pane; never closes)
|
|
64
|
+
diff-easy cmux open --no-open # ensure the hub is up and print the URL
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`toggle` starts the always-on hub if needed and opens the diff-easy split scoped
|
|
68
|
+
via a `?repo=` deep-link; running it again closes that split. As the agent edits
|
|
69
|
+
code, the diff (and the agent-turn timeline) update live beside your terminal.
|
|
70
|
+
|
|
71
|
+
The hub is shared and shows one repo at a time, so toggling in another workspace
|
|
72
|
+
re-points it to that repo. All three accept `--port <n>` (default `4966`; also
|
|
73
|
+
honors `$DIFIT_HUB_PORT`).
|
|
74
|
+
|
|
75
|
+
All accept `--port <n>` (default `4966`; also honors `$DIFIT_HUB_PORT`).
|
|
76
|
+
|
|
77
|
+
## Per-turn diffs (Claude Code)
|
|
78
|
+
|
|
79
|
+
Install the capture hooks once:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
diff-easy init # installs into ~/.claude/settings.json (works in every repo)
|
|
83
|
+
diff-easy init --project # or commit them to ./.claude/settings.json for a single repo
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This adds two Claude Code hooks — `UserPromptSubmit` (turn start) and `Stop`
|
|
87
|
+
(turn end). On each boundary, `diff-easy snapshot` records the **entire working
|
|
88
|
+
tree** as a git snapshot (capturing edits from _both_ the Edit/Write tools and
|
|
89
|
+
Bash commands like `sed`/`mv`). Snapshots are anchored under `refs/difit/snapshots`
|
|
90
|
+
so they survive `git gc`, and turn metadata is written to `.difit-turns/log.json`.
|
|
91
|
+
The hook is a no-op outside a git repo and never blocks the agent.
|
|
92
|
+
|
|
93
|
+
Then, in the viewer, use the **Agent turns** dropdown (top toolbar) to:
|
|
94
|
+
|
|
95
|
+
- pick **Working dir** for the live diff, or any **Turn N** to see exactly what that
|
|
96
|
+
turn changed (its diff is `git diff <snapshotBefore> <snapshotAfter>`);
|
|
97
|
+
- stay on **Live** to auto-advance to each new turn as the agent finishes it
|
|
98
|
+
(powered by an SSE ping from the `Stop` hook).
|
|
99
|
+
|
|
100
|
+
Capture is decoupled from viewing: turns are recorded whether or not the viewer is
|
|
101
|
+
open, so you can review after the fact.
|
|
102
|
+
|
|
103
|
+
### Where it writes & data retention
|
|
104
|
+
|
|
105
|
+
- **`<repo>/.difit-turns/`** — `log.json` (turn metadata) and `server.json`
|
|
106
|
+
(live-refresh port). Local-only and self-ignored from version control.
|
|
107
|
+
- **`<repo>/.git`** — snapshot commits/trees/blobs, anchored by the single ref
|
|
108
|
+
`refs/difit/snapshots`. Never touches your branches or normal history. Git
|
|
109
|
+
deduplicates unchanged file content across snapshots.
|
|
110
|
+
- **OS temp dir (`$TMPDIR`)** — a short-lived git index file per snapshot,
|
|
111
|
+
deleted immediately (nothing persists there).
|
|
112
|
+
|
|
113
|
+
**Retention:** each session keeps its **most recent 50 turns**
|
|
114
|
+
(`MAX_TURNS_PER_SESSION`). Older turns are dropped from the log, and their
|
|
115
|
+
snapshot commits stop being referenced by `refs/difit/snapshots`, so a routine
|
|
116
|
+
`git gc` reclaims them — keeping `.git` bounded. To wipe all snapshots manually:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
git update-ref -d refs/difit/snapshots && git gc --prune=now && rm -rf .difit-turns
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Develop
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
pnpm run build # build CLI + client
|
|
126
|
+
pnpm run build:cli # CLI only (type-checks the server/CLI)
|
|
127
|
+
npx tsc --noEmit -p tsconfig.json # type-check the client
|
|
128
|
+
pnpm test # run tests
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Key diff-easy additions over difit:
|
|
132
|
+
|
|
133
|
+
- `src/client/components/FileList.tsx` — extension/status filter chips
|
|
134
|
+
- `src/client/hooks/useDiffSearch.ts`, `src/client/components/DiffSearch.tsx` —
|
|
135
|
+
in-diff search (CSS Custom Highlight API)
|
|
136
|
+
- `src/cli/turns.ts`, `src/utils/turnLog.ts` — `snapshot` / `init` commands + log
|
|
137
|
+
- `src/client/hooks/useTurns.ts`, `src/client/components/TurnTimeline.tsx` — turn UI
|
|
138
|
+
- `src/server/server.ts` — `/api/turns`, `/events/turn-complete`, `server.json`
|
|
139
|
+
|
|
140
|
+
## License
|
|
141
|
+
|
|
142
|
+
MIT — see [`LICENSE`](./LICENSE) and [`NOTICE.md`](./NOTICE.md).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
export declare const DEFAULT_HUB_PORT = 4966;
|
|
3
|
+
export declare function resolveHubPort(flagPort: number | undefined, env: NodeJS.ProcessEnv): number;
|
|
4
|
+
export declare function hubOrigin(port: number): string;
|
|
5
|
+
export declare function buildHubUrl(port: number, repoPath: string): string;
|
|
6
|
+
export declare function shouldReuseSurface(storedRef: string | null, surfaceUrl: string | null, origin: string): boolean;
|
|
7
|
+
export declare function isInsideCmux(env: NodeJS.ProcessEnv): boolean;
|
|
8
|
+
/** Builds the `diff-easy cmux` subcommand (open/toggle). */
|
|
9
|
+
export declare function createCmuxCommand(): Command;
|
package/dist/cli/cmux.js
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { execFileSync, spawn } from 'child_process';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
|
|
3
|
+
import { get as httpGet } from 'http';
|
|
4
|
+
import { tmpdir } from 'os';
|
|
5
|
+
import { dirname, join } from 'path';
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
export const DEFAULT_HUB_PORT = 4966;
|
|
8
|
+
export function resolveHubPort(flagPort, env) {
|
|
9
|
+
if (typeof flagPort === 'number' && Number.isFinite(flagPort))
|
|
10
|
+
return flagPort;
|
|
11
|
+
const fromEnv = env.DIFIT_HUB_PORT ? Number.parseInt(env.DIFIT_HUB_PORT, 10) : Number.NaN;
|
|
12
|
+
return Number.isFinite(fromEnv) ? fromEnv : DEFAULT_HUB_PORT;
|
|
13
|
+
}
|
|
14
|
+
export function hubOrigin(port) {
|
|
15
|
+
return `http://localhost:${port}`;
|
|
16
|
+
}
|
|
17
|
+
export function buildHubUrl(port, repoPath) {
|
|
18
|
+
return `${hubOrigin(port)}/?repo=${encodeURIComponent(repoPath)}`;
|
|
19
|
+
}
|
|
20
|
+
export function shouldReuseSurface(storedRef, surfaceUrl, origin) {
|
|
21
|
+
return Boolean(storedRef) && Boolean(surfaceUrl?.startsWith(origin));
|
|
22
|
+
}
|
|
23
|
+
export function isInsideCmux(env) {
|
|
24
|
+
return Boolean(env.CMUX_WORKSPACE_ID);
|
|
25
|
+
}
|
|
26
|
+
// ---- surface state (per-workspace pane reuse) ----
|
|
27
|
+
function surfaceStatePath(workspaceId) {
|
|
28
|
+
return join(tmpdir(), 'diff-easy-cmux', `${workspaceId.replace(/[^\w:-]/g, '_')}.surface`);
|
|
29
|
+
}
|
|
30
|
+
function readStoredSurface(workspaceId) {
|
|
31
|
+
try {
|
|
32
|
+
return readFileSync(surfaceStatePath(workspaceId), 'utf8').trim() || null;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function writeStoredSurface(workspaceId, ref) {
|
|
39
|
+
const p = surfaceStatePath(workspaceId);
|
|
40
|
+
if (!existsSync(dirname(p)))
|
|
41
|
+
mkdirSync(dirname(p), { recursive: true });
|
|
42
|
+
writeFileSync(p, `${ref}\n`);
|
|
43
|
+
}
|
|
44
|
+
function clearStoredSurface(workspaceId) {
|
|
45
|
+
try {
|
|
46
|
+
rmSync(surfaceStatePath(workspaceId), { force: true });
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// best-effort
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// ---- hub liveness ----
|
|
53
|
+
async function isHubUp(port) {
|
|
54
|
+
return await new Promise((resolve) => {
|
|
55
|
+
let settled = false;
|
|
56
|
+
const finish = (up) => {
|
|
57
|
+
if (settled)
|
|
58
|
+
return;
|
|
59
|
+
settled = true;
|
|
60
|
+
req.destroy();
|
|
61
|
+
resolve(up);
|
|
62
|
+
};
|
|
63
|
+
const req = httpGet(`${hubOrigin(port)}/api/workspaces`, (res) => {
|
|
64
|
+
res.resume();
|
|
65
|
+
finish(true);
|
|
66
|
+
});
|
|
67
|
+
req.setTimeout(500, () => finish(false));
|
|
68
|
+
req.on('error', () => finish(false));
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
async function waitForHub(port, timeoutMs) {
|
|
72
|
+
const deadline = Date.now() + timeoutMs;
|
|
73
|
+
while (Date.now() < deadline) {
|
|
74
|
+
if (await isHubUp(port))
|
|
75
|
+
return true;
|
|
76
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
function startHubDetached(port) {
|
|
81
|
+
const scriptPath = process.argv[1];
|
|
82
|
+
if (!scriptPath) {
|
|
83
|
+
throw new Error('Unable to determine difit entrypoint for hub background process');
|
|
84
|
+
}
|
|
85
|
+
const child = spawn(process.execPath, [scriptPath, 'hub', '--no-open', '--port', String(port)], {
|
|
86
|
+
detached: true,
|
|
87
|
+
stdio: 'ignore',
|
|
88
|
+
});
|
|
89
|
+
child.unref();
|
|
90
|
+
}
|
|
91
|
+
/** Ensure the hub answers on `port`, starting it detached if needed. */
|
|
92
|
+
async function ensureHub(port) {
|
|
93
|
+
if (await isHubUp(port))
|
|
94
|
+
return true;
|
|
95
|
+
console.log(`diff-easy: starting hub on port ${port}…`);
|
|
96
|
+
startHubDetached(port);
|
|
97
|
+
if (await waitForHub(port, 5000))
|
|
98
|
+
return true;
|
|
99
|
+
console.error(`diff-easy: hub did not come up on port ${port}. Try \`diff-easy hub --port ${port}\`.`);
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
// ---- cmux browser surface control ----
|
|
103
|
+
/** Read a CMUX browser surface's current URL, or null if it doesn't resolve. */
|
|
104
|
+
function cmuxSurfaceUrl(ref) {
|
|
105
|
+
try {
|
|
106
|
+
return (execFileSync('cmux', ['browser', '--surface', ref, 'url'], {
|
|
107
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
108
|
+
})
|
|
109
|
+
.toString()
|
|
110
|
+
.trim() || null);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Open a new browser split and return the created surface ref (best-effort). */
|
|
117
|
+
function cmuxOpenSplit(url) {
|
|
118
|
+
try {
|
|
119
|
+
const out = execFileSync('cmux', ['browser', 'open-split', url, '--focus', 'true'], {
|
|
120
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
121
|
+
}).toString();
|
|
122
|
+
const match = out.match(/surface:\d+/);
|
|
123
|
+
return match ? match[0] : null;
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Re-point an existing browser surface at `url` and bring it forward. Returns
|
|
131
|
+
* false if the surface can't be navigated (e.g. it was closed), so the caller
|
|
132
|
+
* can fall back to opening a fresh split. Note: `cmux browser navigate` does
|
|
133
|
+
* NOT accept `--focus`; focusing is a separate best-effort call.
|
|
134
|
+
*/
|
|
135
|
+
function cmuxNavigate(ref, url) {
|
|
136
|
+
try {
|
|
137
|
+
execFileSync('cmux', ['browser', '--surface', ref, 'navigate', url], {
|
|
138
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
try {
|
|
145
|
+
execFileSync('cmux', ['browser', '--surface', ref, 'focus-webview'], {
|
|
146
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
// focus is best-effort — the navigate already succeeded
|
|
151
|
+
}
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
/** Close a CMUX browser surface (best-effort). */
|
|
155
|
+
function cmuxCloseSurface(ref) {
|
|
156
|
+
try {
|
|
157
|
+
execFileSync('cmux', ['browser', '--surface', ref, 'tab', 'close'], {
|
|
158
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// best-effort
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
function isGitWorkTree(cwd) {
|
|
166
|
+
try {
|
|
167
|
+
return (execFileSync('git', ['-C', cwd, 'rev-parse', '--is-inside-work-tree'], {
|
|
168
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
169
|
+
})
|
|
170
|
+
.toString()
|
|
171
|
+
.trim() === 'true');
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// ---- open / close / toggle ----
|
|
178
|
+
/** Open (or re-point) the diff-easy browser split for `cwd` in this workspace. */
|
|
179
|
+
function openPane(port, cwd, workspaceId) {
|
|
180
|
+
const url = buildHubUrl(port, cwd);
|
|
181
|
+
const stored = readStoredSurface(workspaceId);
|
|
182
|
+
if (shouldReuseSurface(stored, stored ? cmuxSurfaceUrl(stored) : null, hubOrigin(port)) &&
|
|
183
|
+
cmuxNavigate(stored, url)) {
|
|
184
|
+
console.log(`diff-easy: re-pointed diff-easy pane to ${cwd}`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const ref = cmuxOpenSplit(url);
|
|
188
|
+
if (ref)
|
|
189
|
+
writeStoredSurface(workspaceId, ref);
|
|
190
|
+
console.log(`diff-easy: opened diff-easy split pane for ${cwd}`);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Close the diff-easy pane if it's currently open on our hub. Returns true when
|
|
194
|
+
* a pane was closed. Clears a stale stored ref either way.
|
|
195
|
+
*/
|
|
196
|
+
function closePane(workspaceId, port) {
|
|
197
|
+
const stored = readStoredSurface(workspaceId);
|
|
198
|
+
const open = shouldReuseSurface(stored, stored ? cmuxSurfaceUrl(stored) : null, hubOrigin(port));
|
|
199
|
+
if (open) {
|
|
200
|
+
cmuxCloseSurface(stored);
|
|
201
|
+
clearStoredSurface(workspaceId);
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (stored)
|
|
205
|
+
clearStoredSurface(workspaceId);
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
function warnIfNotGit(cwd) {
|
|
209
|
+
if (!isGitWorkTree(cwd)) {
|
|
210
|
+
console.warn(`diff-easy: ${cwd} is not a git repository — the hub can't scope to it.`);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function notInsideCmuxMessage(port, cwd) {
|
|
214
|
+
console.log(`diff-easy: not inside CMUX. Open this URL to review ${cwd}:\n ${buildHubUrl(port, cwd)}`);
|
|
215
|
+
}
|
|
216
|
+
async function runCmuxOpen(options) {
|
|
217
|
+
const port = resolveHubPort(options.port, process.env);
|
|
218
|
+
const cwd = process.cwd();
|
|
219
|
+
warnIfNotGit(cwd);
|
|
220
|
+
if (!(await ensureHub(port)))
|
|
221
|
+
process.exit(1);
|
|
222
|
+
if (options.open === false) {
|
|
223
|
+
console.log(buildHubUrl(port, cwd));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const workspaceId = process.env.CMUX_WORKSPACE_ID;
|
|
227
|
+
if (!isInsideCmux(process.env) || !workspaceId) {
|
|
228
|
+
notInsideCmuxMessage(port, cwd);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
openPane(port, cwd, workspaceId);
|
|
232
|
+
}
|
|
233
|
+
async function runCmuxToggle(options) {
|
|
234
|
+
const port = resolveHubPort(options.port, process.env);
|
|
235
|
+
const cwd = process.cwd();
|
|
236
|
+
const workspaceId = process.env.CMUX_WORKSPACE_ID;
|
|
237
|
+
if (!isInsideCmux(process.env) || !workspaceId) {
|
|
238
|
+
warnIfNotGit(cwd);
|
|
239
|
+
if (!(await ensureHub(port)))
|
|
240
|
+
process.exit(1);
|
|
241
|
+
notInsideCmuxMessage(port, cwd);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Open → close; closed → open.
|
|
245
|
+
if (closePane(workspaceId, port)) {
|
|
246
|
+
console.log('diff-easy: closed diff-easy pane');
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
warnIfNotGit(cwd);
|
|
250
|
+
if (!(await ensureHub(port)))
|
|
251
|
+
process.exit(1);
|
|
252
|
+
openPane(port, cwd, workspaceId);
|
|
253
|
+
}
|
|
254
|
+
/** Builds the `diff-easy cmux` subcommand (open/toggle). */
|
|
255
|
+
export function createCmuxCommand() {
|
|
256
|
+
const cmux = new Command('cmux').description('CMUX integration: open/toggle diff-easy for the current repo in a browser split pane');
|
|
257
|
+
const portOption = (value) => Number.parseInt(value, 10);
|
|
258
|
+
cmux
|
|
259
|
+
.command('open', { isDefault: true })
|
|
260
|
+
.description('Ensure the hub is running and open it (scoped to $PWD) in a CMUX browser split pane')
|
|
261
|
+
.option('--port <port>', 'hub port (default 4966; also honors $DIFIT_HUB_PORT)', portOption)
|
|
262
|
+
.option('--no-open', 'ensure the hub is up and print the URL, without opening a pane')
|
|
263
|
+
.action(async (options) => {
|
|
264
|
+
await runCmuxOpen(options);
|
|
265
|
+
});
|
|
266
|
+
cmux
|
|
267
|
+
.command('toggle')
|
|
268
|
+
.description('Toggle the diff-easy browser split for $PWD (open if closed, close if open)')
|
|
269
|
+
.option('--port <port>', 'hub port (default 4966; also honors $DIFIT_HUB_PORT)', portOption)
|
|
270
|
+
.action(async (options) => {
|
|
271
|
+
await runCmuxToggle(options);
|
|
272
|
+
});
|
|
273
|
+
return cmux;
|
|
274
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { Command, Option } from 'commander';
|
|
2
|
+
import { parseCommentImportValue } from '../utils/commentImports.js';
|
|
3
|
+
import { detectStdinSource, readStdin } from './utils.js';
|
|
4
|
+
async function parseCommentAddInput(json) {
|
|
5
|
+
if (typeof json === 'string') {
|
|
6
|
+
return json;
|
|
7
|
+
}
|
|
8
|
+
if (detectStdinSource() === 'tty') {
|
|
9
|
+
throw new Error('Provide comment JSON as an argument or via stdin');
|
|
10
|
+
}
|
|
11
|
+
const stdin = await readStdin();
|
|
12
|
+
if (!stdin.trim()) {
|
|
13
|
+
throw new Error('No comment JSON received from stdin');
|
|
14
|
+
}
|
|
15
|
+
return stdin;
|
|
16
|
+
}
|
|
17
|
+
export function createCommentCommand() {
|
|
18
|
+
const comment = new Command('comment').description('Add or retrieve comments on a running difit server');
|
|
19
|
+
comment
|
|
20
|
+
.command('add')
|
|
21
|
+
.description('Add comments to a running difit server')
|
|
22
|
+
.argument('[json]', 'comment import JSON (object or array)')
|
|
23
|
+
.requiredOption('--port <port>', 'port of the running difit server', parseInt)
|
|
24
|
+
.action(async (json, opts) => {
|
|
25
|
+
try {
|
|
26
|
+
const input = await parseCommentAddInput(json);
|
|
27
|
+
const imports = parseCommentImportValue(input);
|
|
28
|
+
const response = await fetch(`http://localhost:${opts.port}/api/comment-imports`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: { 'Content-Type': 'application/json' },
|
|
31
|
+
body: JSON.stringify(imports),
|
|
32
|
+
});
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const errorBody = (await response.json().catch(() => ({})));
|
|
35
|
+
console.error(`Error: ${errorBody.error ?? 'Failed to add comments'}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const result = (await response.json());
|
|
39
|
+
console.log(JSON.stringify({
|
|
40
|
+
success: result.success ?? true,
|
|
41
|
+
importId: result.importId,
|
|
42
|
+
count: result.count ?? imports.length,
|
|
43
|
+
warnings: result.warnings ?? [],
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof TypeError && error.message.includes('fetch failed')) {
|
|
48
|
+
console.error(`Error: Cannot connect to difit server on port ${opts.port}. Is the server running?`);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
52
|
+
}
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
comment
|
|
57
|
+
.command('get')
|
|
58
|
+
.description('Retrieve comments from a running difit server')
|
|
59
|
+
.requiredOption('--port <port>', 'port of the running difit server', parseInt)
|
|
60
|
+
.addOption(new Option('--format <format>', 'output format').choices(['text', 'json']).default('text'))
|
|
61
|
+
.action(async (opts) => {
|
|
62
|
+
try {
|
|
63
|
+
const endpoint = opts.format === 'json' ? '/api/comments-json' : '/api/comments-output';
|
|
64
|
+
const response = await fetch(`http://localhost:${opts.port}${endpoint}`);
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
console.error('Error: Failed to retrieve comments');
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
if (opts.format === 'json') {
|
|
70
|
+
const data = await response.json();
|
|
71
|
+
console.log(JSON.stringify(data));
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const text = await response.text();
|
|
75
|
+
if (text.trim()) {
|
|
76
|
+
console.log(text);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
if (error instanceof TypeError && error.message.includes('fetch failed')) {
|
|
82
|
+
console.error(`Error: Cannot connect to difit server on port ${opts.port}. Is the server running?`);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.error(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
86
|
+
}
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
return comment;
|
|
91
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { CommentImport } from '../types/diff.js';
|
|
2
|
+
interface PullRequestInfo {
|
|
3
|
+
owner: string;
|
|
4
|
+
repo: string;
|
|
5
|
+
pullNumber: number;
|
|
6
|
+
hostname: string;
|
|
7
|
+
}
|
|
8
|
+
interface GitHubReviewThreadAuthor {
|
|
9
|
+
login?: string | null;
|
|
10
|
+
}
|
|
11
|
+
interface GitHubReviewThreadCommentNode {
|
|
12
|
+
id?: string | null;
|
|
13
|
+
body?: string | null;
|
|
14
|
+
createdAt?: string | null;
|
|
15
|
+
updatedAt?: string | null;
|
|
16
|
+
author?: GitHubReviewThreadAuthor | null;
|
|
17
|
+
}
|
|
18
|
+
interface GitHubReviewThreadNode {
|
|
19
|
+
id?: string | null;
|
|
20
|
+
isResolved?: boolean | null;
|
|
21
|
+
isOutdated?: boolean | null;
|
|
22
|
+
subjectType?: string | null;
|
|
23
|
+
path?: string | null;
|
|
24
|
+
diffSide?: string | null;
|
|
25
|
+
startDiffSide?: string | null;
|
|
26
|
+
line?: number | null;
|
|
27
|
+
startLine?: number | null;
|
|
28
|
+
originalLine?: number | null;
|
|
29
|
+
originalStartLine?: number | null;
|
|
30
|
+
comments?: {
|
|
31
|
+
nodes?: GitHubReviewThreadCommentNode[] | null;
|
|
32
|
+
} | null;
|
|
33
|
+
}
|
|
34
|
+
interface GitHubReviewThreadsPageInfo {
|
|
35
|
+
hasNextPage?: boolean | null;
|
|
36
|
+
endCursor?: string | null;
|
|
37
|
+
}
|
|
38
|
+
interface GitHubReviewThreadsConnection {
|
|
39
|
+
nodes?: GitHubReviewThreadNode[] | null;
|
|
40
|
+
pageInfo?: GitHubReviewThreadsPageInfo | null;
|
|
41
|
+
}
|
|
42
|
+
interface GitHubReviewThreadsGraphqlResponse {
|
|
43
|
+
data?: {
|
|
44
|
+
repository?: {
|
|
45
|
+
pullRequest?: {
|
|
46
|
+
reviewThreads?: GitHubReviewThreadsConnection | null;
|
|
47
|
+
} | null;
|
|
48
|
+
} | null;
|
|
49
|
+
} | null;
|
|
50
|
+
errors?: Array<{
|
|
51
|
+
message?: string | null;
|
|
52
|
+
}> | null;
|
|
53
|
+
}
|
|
54
|
+
interface PrCommentImportsPage {
|
|
55
|
+
commentImports: CommentImport[];
|
|
56
|
+
pageInfo: {
|
|
57
|
+
hasNextPage: boolean;
|
|
58
|
+
endCursor: string | null;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
export interface PullRequestDetails {
|
|
62
|
+
title: string;
|
|
63
|
+
body: string;
|
|
64
|
+
}
|
|
65
|
+
interface GitHubPrDetailsGraphqlResponse {
|
|
66
|
+
data?: {
|
|
67
|
+
repository?: {
|
|
68
|
+
pullRequest?: {
|
|
69
|
+
title?: string | null;
|
|
70
|
+
body?: string | null;
|
|
71
|
+
} | null;
|
|
72
|
+
} | null;
|
|
73
|
+
} | null;
|
|
74
|
+
errors?: Array<{
|
|
75
|
+
message?: string | null;
|
|
76
|
+
}> | null;
|
|
77
|
+
}
|
|
78
|
+
export declare function parseGitHubPrUrl(url: string): PullRequestInfo | null;
|
|
79
|
+
export declare function getPrPatch(prArg: string): string;
|
|
80
|
+
export declare function parsePrCommentImportsResponse(response: GitHubReviewThreadsGraphqlResponse): PrCommentImportsPage;
|
|
81
|
+
export declare function getPrCommentImports(prArg: string): Promise<CommentImport[]>;
|
|
82
|
+
export declare function parsePrDetailsResponse(response: GitHubPrDetailsGraphqlResponse): PullRequestDetails | null;
|
|
83
|
+
export declare function getPrDetails(prArg: string): Promise<PullRequestDetails | null>;
|
|
84
|
+
export {};
|