reslop 0.2.5 → 0.2.7
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/CHANGELOG.md +22 -1
- package/LICENSE +1 -1
- package/README.md +11 -11
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +72 -19
- package/lib/api.js +1 -1
- package/lib/capabilities.js +8 -8
- package/lib/cli.js +61 -75
- package/lib/clipboard.js +39 -47
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +357 -0
- package/lib/dashboard/npm.js +290 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +197 -402
- package/lib/deps.js +113 -240
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +76 -144
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +79 -178
- package/lib/editor.js +52 -69
- package/lib/files.js +79 -91
- package/lib/find.js +61 -0
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +148 -272
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +161 -214
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +155 -141
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +14 -14
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +19 -25
- package/lib/highlight/txt.js +7 -10
- package/lib/highlight.js +17 -46
- package/lib/keys.js +75 -83
- package/lib/manifest.js +67 -126
- package/lib/npm-commands.js +120 -169
- package/lib/remote.js +140 -190
- package/lib/render/branches.js +63 -61
- package/lib/render/chrome.js +412 -347
- package/lib/render/commits.js +160 -361
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +496 -0
- package/lib/render/dash-table.js +295 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -258
- package/lib/render/files.js +60 -86
- package/lib/render/frame.js +82 -117
- package/lib/render/notes.js +150 -210
- package/lib/render/npm.js +213 -177
- package/lib/render/packages.js +93 -0
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +77 -172
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/repos.js +226 -0
- package/lib/render/search.js +25 -0
- package/lib/render/tiles.js +355 -0
- package/lib/render/unit.js +15 -31
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -37
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -77
- package/lib/report-test.js +221 -99
- package/lib/report-tools.js +96 -154
- package/lib/review-merge.js +33 -41
- package/lib/review.js +69 -129
- package/lib/runs.js +225 -0
- package/lib/select.js +53 -91
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +189 -65
- package/lib/session/branches.js +164 -222
- package/lib/session/change-actions.js +49 -119
- package/lib/session/commit.js +298 -352
- package/lib/session/compose.js +572 -715
- package/lib/session/dashboard.js +530 -0
- package/lib/session/dispatch.js +197 -171
- package/lib/session/files-pane.js +92 -45
- package/lib/session/find.js +94 -0
- package/lib/session/items.js +6 -17
- package/lib/session/lifecycle.js +420 -466
- package/lib/session/load.js +1 -14
- package/lib/session/navigation.js +4 -2
- package/lib/session/npm.js +403 -420
- package/lib/session/ops.js +5 -13
- package/lib/session/packages.js +143 -0
- package/lib/session/pointer.js +30 -14
- package/lib/session/progress.js +39 -30
- package/lib/session/review.js +1 -3
- package/lib/session/tasks.js +277 -236
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -132
- package/lib/session/view.js +59 -80
- package/lib/session/watch.js +201 -212
- package/lib/session/workspace.js +455 -0
- package/lib/session.js +95 -161
- package/lib/update.js +44 -111
- package/lib/utilities.js +46 -88
- package/lib/workspace.js +166 -0
- package/lib/wrap.js +29 -55
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased][unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.7][] - 2026-10-03
|
|
6
|
+
|
|
7
|
+
- Add tiling view for multi-repository environment
|
|
8
|
+
- Improve navigation and hotkeys
|
|
9
|
+
- Add npm and dependencies screen
|
|
10
|
+
- Rework tasks: regroup into feature, bug, debt, research, and security
|
|
11
|
+
- Show tasks progress
|
|
12
|
+
- Search file paths in the files and diff screens
|
|
13
|
+
|
|
14
|
+
## [0.2.6][] - 2026-10-02
|
|
15
|
+
|
|
16
|
+
- Add a live dashboard as the start screen
|
|
17
|
+
- Compact related failure contexts in test runnter logs
|
|
18
|
+
- Save the raw command output beside the reduced log
|
|
19
|
+
- Improve test results rendering
|
|
20
|
+
- Update the dashboard from file system events, without timers
|
|
21
|
+
- Record `reslop t` runs under `.log/.runs/` show progress on the dashboard
|
|
22
|
+
- Implement tasks screen: backlog, issues, bugs, features
|
|
23
|
+
|
|
5
24
|
## [0.2.5][] - 2026-09-30
|
|
6
25
|
|
|
7
26
|
- Remove UI and filesystem polling timers, refresh on events
|
|
@@ -153,7 +172,9 @@
|
|
|
153
172
|
- Feedback and todos that produce a repair plan for an agent
|
|
154
173
|
- Intra-line highlighting in unified, mixed, and side-by-side layouts
|
|
155
174
|
|
|
156
|
-
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.
|
|
175
|
+
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.7...HEAD
|
|
176
|
+
[0.2.7]: https://github.com/tshemsedinov/reslop/compare/v0.2.6...v0.2.7
|
|
177
|
+
[0.2.6]: https://github.com/tshemsedinov/reslop/compare/v0.2.5...v0.2.6
|
|
157
178
|
[0.2.5]: https://github.com/tshemsedinov/reslop/compare/v0.2.4...v0.2.5
|
|
158
179
|
[0.2.4]: https://github.com/tshemsedinov/reslop/compare/v0.2.3...v0.2.4
|
|
159
180
|
[0.2.3]: https://github.com/tshemsedinov/reslop/compare/v0.2.1...v0.2.3
|
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026 Timur Shemsedinov
|
|
3
|
+
Copyright (c) 2026 Timur Shemsedinov and other contributors (see github)
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/README.md
CHANGED
|
@@ -17,21 +17,19 @@ Open a change, leave findings and a backlog, and prepare a repair plan for the a
|
|
|
17
17
|
Review → Plan → Repair → Verify
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
+
- Live dashboard: files, diffs, tasks, branches, commits, run, and npm, updated in real time
|
|
20
21
|
- Leave feedback, a project backlog, and in-place code proposals as a repair plan
|
|
21
22
|
- Review uncommitted git diffs, a given commit, a GitHub PR, or a GitLab MR
|
|
22
23
|
- File-scope review mode: not just diffs, with editor mode
|
|
23
24
|
- Stage, unstage, or revert each contiguous block of diff lines
|
|
24
25
|
- Auto-reload local diffs when files change, keeping the current screen
|
|
25
26
|
- Import GitHub PR and GitLab MR review comments into the local plan for AI
|
|
26
|
-
- Commits list: brief and full, view a commit's diff, commit, amend,
|
|
27
|
-
- Branches: checkout, create, rebase,
|
|
27
|
+
- Commits list: brief and full, view a commit's diff, commit, amend, apply, edit, fixup, and delete
|
|
28
|
+
- Branches: checkout, create, rebase, delete, pull, and push
|
|
28
29
|
- npm scripts and bins: run, edit, and reorder; double-click to run; clean logs older than 5 days
|
|
29
|
-
- Long lines wrap in the diff view; the editor scrolls horizontally
|
|
30
30
|
- Reduced test and lint output for agents: failures and a short summary, not passing noise
|
|
31
|
-
- Review
|
|
32
|
-
-
|
|
33
|
-
- Code highlighting and intra-line diffs: unified, mixed, and side-by-side
|
|
34
|
-
- Auto-update patch and minor releases; confirm a new major
|
|
31
|
+
- Review, renew, audit and update npm dependencies in `package.json` and the lockfile
|
|
32
|
+
- Auto-update reslop patch and minor releases; confirm a new major
|
|
35
33
|
- Work with git, npm and fetch in background
|
|
36
34
|
|
|
37
35
|
## Install
|
|
@@ -45,8 +43,8 @@ Requires Node.js `>=18.15.0`.
|
|
|
45
43
|
|
|
46
44
|
## Usage
|
|
47
45
|
|
|
48
|
-
- `reslop`
|
|
49
|
-
- `reslop path/file` show only that path or file
|
|
46
|
+
- `reslop` live dashboard of this repository; press a block key to open its screen
|
|
47
|
+
- `reslop path/file` show only that path or file (starts on the file list)
|
|
50
48
|
- `reslop 7ac260c` show that commit (read-only)
|
|
51
49
|
- `reslop https://github.com/metarhia/metacom/pull/555` GitHub/GitLab PR/MR
|
|
52
50
|
- `reslop -n` start a new review even if the latest is still editing
|
|
@@ -61,10 +59,12 @@ Prefix each program in an npm script with `reslop t --`. Keep `&&` between those
|
|
|
61
59
|
"test": "reslop t -- npm run -s lint && reslop t -- node --test"
|
|
62
60
|
```
|
|
63
61
|
|
|
64
|
-
The report is Markdown: failures, diagnostics, and a short summary. Passing results are omitted and similar problems are grouped. The
|
|
62
|
+
The report is Markdown: failures, diagnostics, and a short summary. Passing results are omitted and similar problems are grouped. The reduced report and the raw output are both saved under `.log/`. The report names the raw file for an agent to read when a detail is missing. Set `RESLOP_OUTPUT=raw` to pass the command through unchanged. The review screen runs scripts in raw mode and applies its own filter once.
|
|
65
63
|
|
|
66
64
|
Run the full check with `npm t`. Run one or more test files with `reslop t -- node --test <files>`. New reviews include those two commands in the agent instructions.
|
|
67
65
|
|
|
66
|
+
`reslop t` records each run in `.log/.runs/` so that the dashboard can show commands started by an agent in another terminal.
|
|
67
|
+
|
|
68
68
|
Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
|
|
69
69
|
|
|
70
70
|
- `editing`: still writing the review in reslop
|
|
@@ -87,5 +87,5 @@ Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
|
|
|
87
87
|
|
|
88
88
|
## License
|
|
89
89
|
|
|
90
|
-
Copyright (c) 2026 Timur Shemsedinov.
|
|
90
|
+
Copyright (c) 2026 Timur Shemsedinov and other contributors (see github).
|
|
91
91
|
This is [MIT](./LICENSE) licensed software.
|
package/bin/reslop.js
CHANGED
|
@@ -16,12 +16,7 @@ const fail = (reason) => {
|
|
|
16
16
|
process.exit(1);
|
|
17
17
|
};
|
|
18
18
|
|
|
19
|
-
process.on('uncaughtException',
|
|
20
|
-
process.on('unhandledRejection',
|
|
19
|
+
process.on('uncaughtException', fail);
|
|
20
|
+
process.on('unhandledRejection', fail);
|
|
21
21
|
|
|
22
|
-
run(process).then(
|
|
23
|
-
(code) => {
|
|
24
|
-
process.exit(code);
|
|
25
|
-
},
|
|
26
|
-
(error) => fail(error),
|
|
27
|
-
);
|
|
22
|
+
run(process).then((code) => process.exit(code), fail);
|
package/lib/ansi.js
CHANGED
|
@@ -75,6 +75,10 @@ const DARK_UI = {
|
|
|
75
75
|
warnFg: [0xe8, 0xc8, 0x4a],
|
|
76
76
|
mutedFg: [0x7a, 0x7a, 0x8a],
|
|
77
77
|
shaFg: [0x56, 0x9c, 0xd6],
|
|
78
|
+
pkgDepFg: [0xaf, 0xe0, 0xb9],
|
|
79
|
+
pkgDevFg: [0x9a, 0xc4, 0xe6],
|
|
80
|
+
pkgFg: [0xc8, 0xc8, 0xd2],
|
|
81
|
+
pkgChainFg: [0x55, 0x55, 0x62],
|
|
78
82
|
shaDarkFg: [0x15, 0x4a, 0x8c],
|
|
79
83
|
stagedDelLineFg: [0xa0, 0xa0, 0xa8],
|
|
80
84
|
stagedDelLineBg: [0x20, 0x20, 0x26],
|
|
@@ -84,6 +88,30 @@ const DARK_UI = {
|
|
|
84
88
|
stagedAddLineBg: [0x26, 0x26, 0x2e],
|
|
85
89
|
stagedAddCharFg: [0xff, 0xff, 0xff],
|
|
86
90
|
stagedAddCharBg: [0x4e, 0x4e, 0x58],
|
|
91
|
+
dashBg: [0x00, 0x00, 0x00],
|
|
92
|
+
dashTileBg: [0x26, 0x26, 0x2a],
|
|
93
|
+
dashRowBg: [0x1c, 0x1c, 0x1f],
|
|
94
|
+
dashHeadBg: [0x70, 0x70, 0x7c],
|
|
95
|
+
dashDimHeadBg: [0x30, 0x30, 0x38],
|
|
96
|
+
dashDimTileBg: [0x12, 0x12, 0x14],
|
|
97
|
+
dashDimRowBg: [0x0c, 0x0c, 0x0e],
|
|
98
|
+
dashHeadMuted: [0xf2, 0xf2, 0xfa],
|
|
99
|
+
dashDelFg: [0xff, 0xd2, 0xd2],
|
|
100
|
+
heatTrack: [0x3a, 0x3c, 0x48],
|
|
101
|
+
heat1: [0x1c, 0x3a, 0x32],
|
|
102
|
+
heat2: [0x2a, 0x5a, 0x38],
|
|
103
|
+
heat3: [0x8a, 0x6a, 0x18],
|
|
104
|
+
heat4: [0xa0, 0x3a, 0x28],
|
|
105
|
+
heatFg: [0xf4, 0xf4, 0xf8],
|
|
106
|
+
heatBlink: [0xf2, 0xe2, 0x8a],
|
|
107
|
+
heatBlinkFg: [0x1a, 0x16, 0x08],
|
|
108
|
+
dashKeyFg: [0xff, 0xff, 0xff],
|
|
109
|
+
taskHeadFg: [0xff, 0xff, 0xff],
|
|
110
|
+
taskHeadBg: [0x3c, 0x3c, 0x48],
|
|
111
|
+
searchFg: [0x1a, 0x16, 0x08],
|
|
112
|
+
searchBg: [0x8a, 0x6a, 0x18],
|
|
113
|
+
searchOnFg: [0x1a, 0x16, 0x08],
|
|
114
|
+
searchOnBg: [0xf2, 0xe2, 0x8a],
|
|
87
115
|
};
|
|
88
116
|
|
|
89
117
|
const DARK_CODE = {
|
|
@@ -167,6 +195,10 @@ const LIGHT_UI = {
|
|
|
167
195
|
warnFg: [0x9a, 0x67, 0x00],
|
|
168
196
|
mutedFg: [0x6e, 0x77, 0x81],
|
|
169
197
|
shaFg: [0x05, 0x50, 0xae],
|
|
198
|
+
pkgDepFg: [0x3c, 0xa8, 0x5a],
|
|
199
|
+
pkgDevFg: [0x3d, 0x7e, 0xc8],
|
|
200
|
+
pkgFg: [0x9a, 0xa2, 0xac],
|
|
201
|
+
pkgChainFg: [0x3a, 0x42, 0x4c],
|
|
170
202
|
shaDarkFg: [0x0b, 0x3d, 0x70],
|
|
171
203
|
stagedDelLineFg: [0x57, 0x60, 0x6a],
|
|
172
204
|
stagedDelLineBg: [0xf3, 0xf4, 0xf6],
|
|
@@ -176,6 +208,30 @@ const LIGHT_UI = {
|
|
|
176
208
|
stagedAddLineBg: [0xea, 0xec, 0xef],
|
|
177
209
|
stagedAddCharFg: [0x0f, 0x14, 0x19],
|
|
178
210
|
stagedAddCharBg: [0xcc, 0xd1, 0xd7],
|
|
211
|
+
dashBg: [0xff, 0xff, 0xff],
|
|
212
|
+
dashTileBg: [0xe4, 0xe7, 0xeb],
|
|
213
|
+
dashRowBg: [0xd9, 0xdc, 0xe1],
|
|
214
|
+
dashHeadBg: [0xe8, 0xeb, 0xef],
|
|
215
|
+
dashDimHeadBg: [0xb0, 0xb6, 0xbe],
|
|
216
|
+
dashDimTileBg: [0xc4, 0xc8, 0xce],
|
|
217
|
+
dashDimRowBg: [0xb0, 0xb4, 0xba],
|
|
218
|
+
dashHeadMuted: [0x3e, 0x46, 0x52],
|
|
219
|
+
dashDelFg: [0xd0, 0x3a, 0x44],
|
|
220
|
+
heatTrack: [0xc5, 0xca, 0xd3],
|
|
221
|
+
heat1: [0xc8, 0xe6, 0xd4],
|
|
222
|
+
heat2: [0x7d, 0xc8, 0x96],
|
|
223
|
+
heat3: [0xe6, 0xb4, 0x3c],
|
|
224
|
+
heat4: [0xe0, 0x78, 0x68],
|
|
225
|
+
heatFg: [0x1f, 0x23, 0x28],
|
|
226
|
+
heatBlink: [0xff, 0xf3, 0xb0],
|
|
227
|
+
heatBlinkFg: [0x1a, 0x16, 0x08],
|
|
228
|
+
dashKeyFg: [0x00, 0x00, 0x00],
|
|
229
|
+
taskHeadFg: [0xff, 0xff, 0xff],
|
|
230
|
+
taskHeadBg: [0x6e, 0x77, 0x81],
|
|
231
|
+
searchFg: [0x1a, 0x16, 0x08],
|
|
232
|
+
searchBg: [0xf0, 0xc8, 0x6a],
|
|
233
|
+
searchOnFg: [0x1a, 0x16, 0x08],
|
|
234
|
+
searchOnBg: [0xff, 0xe1, 0x4a],
|
|
179
235
|
};
|
|
180
236
|
|
|
181
237
|
const LIGHT_CODE = {
|
|
@@ -233,26 +289,16 @@ const THEME_NAMES = Object.keys(PALETTES);
|
|
|
233
289
|
const applyColors = (into, from) => {
|
|
234
290
|
for (const key of Object.keys(from)) {
|
|
235
291
|
const src = from[key];
|
|
236
|
-
|
|
237
|
-
if (!dst) {
|
|
238
|
-
dst = [0, 0, 0];
|
|
239
|
-
into[key] = dst;
|
|
240
|
-
}
|
|
292
|
+
const dst = into[key];
|
|
241
293
|
dst[0] = src[0];
|
|
242
294
|
dst[1] = src[1];
|
|
243
295
|
dst[2] = src[2];
|
|
244
296
|
}
|
|
245
|
-
return into;
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const cloneColors = (colors) => {
|
|
249
|
-
const into = Array.isArray(colors) ? [] : {};
|
|
250
|
-
return applyColors(into, colors);
|
|
251
297
|
};
|
|
252
298
|
|
|
253
|
-
const THEME =
|
|
254
|
-
const CODE_FG =
|
|
255
|
-
const LOG_COL_FG =
|
|
299
|
+
const THEME = structuredClone(DARK_UI);
|
|
300
|
+
const CODE_FG = structuredClone(DARK_CODE);
|
|
301
|
+
const LOG_COL_FG = structuredClone(DARK_LOG_COLS);
|
|
256
302
|
let currentTheme = 'dark';
|
|
257
303
|
|
|
258
304
|
const setTheme = (name) => {
|
|
@@ -339,22 +385,27 @@ const visibleWidth = (text) => {
|
|
|
339
385
|
return width;
|
|
340
386
|
};
|
|
341
387
|
|
|
342
|
-
const truncateVisible = (text, width) => {
|
|
388
|
+
const truncateVisible = (text, width, mark = '') => {
|
|
343
389
|
if (width <= 0) return '';
|
|
344
|
-
if (visibleWidth(text) <= width) return text;
|
|
345
390
|
const plain = stripAnsi(text);
|
|
391
|
+
if (visibleWidth(plain) <= width) return text;
|
|
392
|
+
const markW = mark ? visibleWidth(mark) : 0;
|
|
393
|
+
const fits = markW > 0 && markW <= width;
|
|
394
|
+
const limit = fits ? width - markW : width;
|
|
346
395
|
const parts = graphemes(plain);
|
|
347
396
|
let used = 0;
|
|
348
397
|
let out = '';
|
|
349
398
|
for (const ch of parts) {
|
|
350
399
|
const w = graphemeWidth(ch, used);
|
|
351
|
-
if (used + w >
|
|
400
|
+
if (used + w > limit) break;
|
|
352
401
|
out += ch;
|
|
353
402
|
used += w;
|
|
354
403
|
}
|
|
355
|
-
return out;
|
|
404
|
+
return fits ? `${out}${mark}` : out;
|
|
356
405
|
};
|
|
357
406
|
|
|
407
|
+
const trimVisible = (text, width) => truncateVisible(text, width, '…');
|
|
408
|
+
|
|
358
409
|
const csiLength = (text, index) => {
|
|
359
410
|
if (text[index] !== ESC || text[index + 1] !== '[') return 0;
|
|
360
411
|
let at = index + 2;
|
|
@@ -459,7 +510,7 @@ const foregroundOn = (text, bgRgb) => {
|
|
|
459
510
|
const kept = keepFgParams(sgrParams(seq));
|
|
460
511
|
const reset = kept.includes(0);
|
|
461
512
|
const rest = kept.filter((code) => code !== 0);
|
|
462
|
-
if (reset) out +=
|
|
513
|
+
if (reset) out += RESET;
|
|
463
514
|
out += ground;
|
|
464
515
|
if (rest.length) out += `${ESC}[${rest.join(';')}m`;
|
|
465
516
|
}
|
|
@@ -487,6 +538,8 @@ module.exports = {
|
|
|
487
538
|
graphemeWidth,
|
|
488
539
|
visibleWidth,
|
|
489
540
|
truncateVisible,
|
|
541
|
+
trimVisible,
|
|
542
|
+
firstGrapheme,
|
|
490
543
|
clipAnsi,
|
|
491
544
|
foregroundOn,
|
|
492
545
|
};
|
package/lib/api.js
CHANGED
package/lib/capabilities.js
CHANGED
|
@@ -50,12 +50,14 @@ const BRANCH_METHODS = [
|
|
|
50
50
|
'pushAsync',
|
|
51
51
|
];
|
|
52
52
|
|
|
53
|
+
const VIEW_CAPS = { read: true, changes: false, branches: false, review: true };
|
|
54
|
+
|
|
53
55
|
const SOURCE_CAPS = {
|
|
54
56
|
local: { read: true, changes: true, branches: true, review: true },
|
|
55
|
-
commit:
|
|
56
|
-
pr:
|
|
57
|
-
mr:
|
|
58
|
-
readonly:
|
|
57
|
+
commit: VIEW_CAPS,
|
|
58
|
+
pr: VIEW_CAPS,
|
|
59
|
+
mr: VIEW_CAPS,
|
|
60
|
+
readonly: VIEW_CAPS,
|
|
59
61
|
};
|
|
60
62
|
|
|
61
63
|
const withReadOnly = (caps, readOnly) => {
|
|
@@ -88,12 +90,10 @@ const sessionCapabilities = (options) => {
|
|
|
88
90
|
if (options.capabilities) return withReadOnly(options.capabilities, readOnly);
|
|
89
91
|
if (options.rev) return capabilitiesFor('commit', { readOnly });
|
|
90
92
|
const source = options.change && options.change.source;
|
|
91
|
-
if (
|
|
92
|
-
return capabilitiesFor(source, { readOnly });
|
|
93
|
-
}
|
|
93
|
+
if (SOURCE_CAPS[source]) return capabilitiesFor(source, { readOnly });
|
|
94
94
|
const repoCaps = options.repo && options.repo.capabilities;
|
|
95
95
|
if (repoCaps) return withReadOnly(repoCaps, readOnly);
|
|
96
|
-
return capabilitiesFor(
|
|
96
|
+
return capabilitiesFor('local', { readOnly });
|
|
97
97
|
};
|
|
98
98
|
|
|
99
99
|
module.exports = {
|
package/lib/cli.js
CHANGED
|
@@ -21,6 +21,8 @@ const ansi = require('./ansi.js');
|
|
|
21
21
|
const { setTheme } = ansi;
|
|
22
22
|
const reportRun = require('./report-run.js');
|
|
23
23
|
const { runCapture } = reportRun;
|
|
24
|
+
const workspace = require('./workspace.js');
|
|
25
|
+
const { workspaceFromArgs } = workspace;
|
|
24
26
|
|
|
25
27
|
const HEX_REV = /^[0-9a-f]{4,40}$/i;
|
|
26
28
|
const HEAD_REV = /^HEAD([~^].*)?$/i;
|
|
@@ -44,12 +46,8 @@ const REMOTE_CHANGE = {
|
|
|
44
46
|
},
|
|
45
47
|
};
|
|
46
48
|
|
|
47
|
-
const errorMessage = (reason) =>
|
|
48
|
-
|
|
49
|
-
if (reason === null || reason === undefined) return '';
|
|
50
|
-
if (typeof reason === 'string') return reason;
|
|
51
|
-
return `${reason}`;
|
|
52
|
-
};
|
|
49
|
+
const errorMessage = (reason) =>
|
|
50
|
+
reason instanceof Error ? reason.message : `${reason ?? ''}`;
|
|
53
51
|
|
|
54
52
|
const fail = (stderr, reason) => {
|
|
55
53
|
stderr.write(`reslop: ${errorMessage(reason)}\n`);
|
|
@@ -62,81 +60,58 @@ const parseArgv = (argv) => {
|
|
|
62
60
|
let readOnly = false;
|
|
63
61
|
let theme = 'dark';
|
|
64
62
|
for (const arg of argv) {
|
|
65
|
-
if (arg === '-n')
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
readOnly = true;
|
|
71
|
-
continue;
|
|
72
|
-
}
|
|
73
|
-
if (arg === '-light') {
|
|
74
|
-
theme = 'light';
|
|
75
|
-
continue;
|
|
76
|
-
}
|
|
77
|
-
if (arg.startsWith('-')) {
|
|
78
|
-
throw new Error(`unknown option ${arg}`);
|
|
79
|
-
}
|
|
80
|
-
paths.push(arg);
|
|
63
|
+
if (arg === '-n') newReview = true;
|
|
64
|
+
else if (arg === '-r') readOnly = true;
|
|
65
|
+
else if (arg === '-light') theme = 'light';
|
|
66
|
+
else if (arg.startsWith('-')) throw new Error(`unknown option ${arg}`);
|
|
67
|
+
else paths.push(arg);
|
|
81
68
|
}
|
|
82
69
|
return { paths, newReview, readOnly, theme };
|
|
83
70
|
};
|
|
84
71
|
|
|
85
|
-
const looksLikeRev = (spec) =>
|
|
86
|
-
|
|
87
|
-
if (HEX_REV.test(spec)) return true;
|
|
88
|
-
if (HEAD_REV.test(spec)) return true;
|
|
89
|
-
return RANGE_REV.test(spec);
|
|
90
|
-
};
|
|
72
|
+
const looksLikeRev = (spec) =>
|
|
73
|
+
HEX_REV.test(spec) || HEAD_REV.test(spec) || RANGE_REV.test(spec);
|
|
91
74
|
|
|
92
75
|
const resolveScope = async (cwd, paths, repo) => {
|
|
93
76
|
if (!paths.length) return { rev: null, paths };
|
|
94
77
|
const first = paths[0];
|
|
95
|
-
if (await exists(path.resolve(cwd, first))) {
|
|
96
|
-
return { rev: null, paths };
|
|
97
|
-
}
|
|
78
|
+
if (await exists(path.resolve(cwd, first))) return { rev: null, paths };
|
|
98
79
|
const resolved = repo.resolveRev(cwd, first);
|
|
99
80
|
if (resolved) {
|
|
100
|
-
if (paths.length > 1)
|
|
101
|
-
throw new Error('use a commit or a path, not both');
|
|
102
|
-
}
|
|
81
|
+
if (paths.length > 1) throw new Error('use a commit or a path, not both');
|
|
103
82
|
return { rev: resolved, paths: [] };
|
|
104
83
|
}
|
|
105
|
-
if (looksLikeRev(first)) {
|
|
106
|
-
throw new Error(`bad revision ${first}`);
|
|
107
|
-
}
|
|
84
|
+
if (looksLikeRev(first)) throw new Error(`bad revision ${first}`);
|
|
108
85
|
return { rev: null, paths };
|
|
109
86
|
};
|
|
110
87
|
|
|
111
|
-
const
|
|
112
|
-
cwd: deps.cwd ?? proc.cwd(),
|
|
113
|
-
stdin: proc.stdin,
|
|
114
|
-
stdout: proc.stdout,
|
|
115
|
-
color: proc.stdout.isTTY === true && !env.NO_COLOR,
|
|
116
|
-
startPane: 'files',
|
|
117
|
-
newReview: args.newReview,
|
|
118
|
-
readOnly: args.readOnly,
|
|
119
|
-
audit: true,
|
|
120
|
-
...extra,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
const openLocalSession = async (proc, deps, args, selected, env) => {
|
|
124
|
-
const cwd = deps.cwd ?? proc.cwd();
|
|
88
|
+
const localOptions = async (deps, args, selected, cwd) => {
|
|
125
89
|
const rawRepo = deps.repo ?? createGitRepo();
|
|
90
|
+
const found = workspaceFromArgs(cwd, selected.paths);
|
|
91
|
+
if (found) {
|
|
92
|
+
const capabilities = capabilitiesFor('local', { readOnly: args.readOnly });
|
|
93
|
+
return {
|
|
94
|
+
cwd: found.root,
|
|
95
|
+
repo: attachCapabilities(rawRepo, capabilities),
|
|
96
|
+
paths: [],
|
|
97
|
+
rev: null,
|
|
98
|
+
capabilities,
|
|
99
|
+
startPane: 'repos',
|
|
100
|
+
workspace: found,
|
|
101
|
+
repoName: path.basename(found.root),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
126
104
|
const scope = await resolveScope(cwd, selected.paths, rawRepo);
|
|
127
105
|
const kind = scope.rev ? 'commit' : 'local';
|
|
128
106
|
const capabilities = capabilitiesFor(kind, { readOnly: args.readOnly });
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
repo,
|
|
107
|
+
const direct = scope.rev || scope.paths.length;
|
|
108
|
+
return {
|
|
109
|
+
repo: attachCapabilities(rawRepo, capabilities),
|
|
132
110
|
paths: scope.paths,
|
|
133
111
|
rev: scope.rev,
|
|
134
112
|
capabilities,
|
|
113
|
+
startPane: direct ? 'files' : 'dashboard',
|
|
135
114
|
};
|
|
136
|
-
const options = sessionOptions(proc, deps, args, env, extra);
|
|
137
|
-
const session = new Session(options);
|
|
138
|
-
session.ensureRepo();
|
|
139
|
-
return session;
|
|
140
115
|
};
|
|
141
116
|
|
|
142
117
|
const remoteRepo = (deps, selected, env, cwd) => {
|
|
@@ -160,26 +135,32 @@ const remoteRepo = (deps, selected, env, cwd) => {
|
|
|
160
135
|
});
|
|
161
136
|
};
|
|
162
137
|
|
|
163
|
-
const
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
const repo = remoteRepo(deps, selected, env, cwd);
|
|
169
|
-
const extra = { repo, paths: selected.paths, capabilities };
|
|
170
|
-
const options = sessionOptions(proc, deps, args, env, extra);
|
|
171
|
-
const session = new Session(options);
|
|
172
|
-
session.ensureRepo();
|
|
173
|
-
return session;
|
|
174
|
-
};
|
|
138
|
+
const remoteOptions = (deps, args, selected, cwd, env) => ({
|
|
139
|
+
repo: remoteRepo(deps, selected, env, cwd),
|
|
140
|
+
paths: selected.paths,
|
|
141
|
+
capabilities: capabilitiesFor(selected.kind, { readOnly: args.readOnly }),
|
|
142
|
+
});
|
|
175
143
|
|
|
176
144
|
const loadSession = async (proc, deps, args) => {
|
|
177
145
|
const env = proc.env ?? {};
|
|
146
|
+
const cwd = deps.cwd ?? proc.cwd();
|
|
178
147
|
const selected = selectChangeSource(args.paths);
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
148
|
+
const extra = REMOTE_CHANGE[selected.kind]
|
|
149
|
+
? remoteOptions(deps, args, selected, cwd, env)
|
|
150
|
+
: await localOptions(deps, args, selected, cwd);
|
|
151
|
+
const session = new Session({
|
|
152
|
+
cwd,
|
|
153
|
+
stdin: proc.stdin,
|
|
154
|
+
stdout: proc.stdout,
|
|
155
|
+
color: proc.stdout.isTTY === true && !env.NO_COLOR,
|
|
156
|
+
startPane: 'files',
|
|
157
|
+
newReview: args.newReview,
|
|
158
|
+
readOnly: args.readOnly,
|
|
159
|
+
audit: true,
|
|
160
|
+
...extra,
|
|
161
|
+
});
|
|
162
|
+
if (session.pane !== 'repos') session.ensureRepo();
|
|
163
|
+
return session;
|
|
183
164
|
};
|
|
184
165
|
|
|
185
166
|
const runUi = async (session, proc) => {
|
|
@@ -196,7 +177,7 @@ const run = async (proc, deps = {}) => {
|
|
|
196
177
|
const stderr = proc.stderr;
|
|
197
178
|
let args;
|
|
198
179
|
try {
|
|
199
|
-
args = parseArgv(
|
|
180
|
+
args = parseArgv(argv);
|
|
200
181
|
} catch (error) {
|
|
201
182
|
fail(stderr, error);
|
|
202
183
|
stderr.write(`${USAGE}\n`);
|
|
@@ -211,6 +192,10 @@ const run = async (proc, deps = {}) => {
|
|
|
211
192
|
}
|
|
212
193
|
const tty = proc.stdin.isTTY === true && stdout.isTTY === true;
|
|
213
194
|
if (tty) return runUi(session, proc);
|
|
195
|
+
if (session.pane === 'repos') {
|
|
196
|
+
stderr.write('reslop: interactive terminal required\n');
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
214
199
|
try {
|
|
215
200
|
await session.loadReady();
|
|
216
201
|
} catch (error) {
|
|
@@ -228,5 +213,6 @@ module.exports = {
|
|
|
228
213
|
errorMessage,
|
|
229
214
|
parseArgv,
|
|
230
215
|
resolveScope,
|
|
216
|
+
loadSession,
|
|
231
217
|
run,
|
|
232
218
|
};
|
package/lib/clipboard.js
CHANGED
|
@@ -6,62 +6,46 @@ const { spawnSync } = childProcess;
|
|
|
6
6
|
const ansi = require('./ansi.js');
|
|
7
7
|
const { ESC } = ansi;
|
|
8
8
|
const utilities = require('./utilities.js');
|
|
9
|
-
const {
|
|
9
|
+
const { spawnBase } = utilities;
|
|
10
10
|
|
|
11
11
|
const CLIP_TIMEOUT_MS = 1000;
|
|
12
12
|
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
const COPY_TOOLS = {
|
|
14
|
+
win32: [{ cmd: 'clip', args: [] }],
|
|
15
|
+
darwin: [{ cmd: 'pbcopy', args: [] }],
|
|
16
|
+
unix: [
|
|
17
|
+
{ cmd: 'wl-copy', args: [] },
|
|
18
|
+
{ cmd: 'xclip', args: ['-selection', 'clipboard'] },
|
|
19
|
+
{ cmd: 'xsel', args: ['--clipboard', '--input'] },
|
|
20
|
+
],
|
|
16
21
|
};
|
|
17
22
|
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
cmd,
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (result.error) return false;
|
|
29
|
-
return result.status === 0;
|
|
23
|
+
const PASTE_TOOLS = {
|
|
24
|
+
win32: [
|
|
25
|
+
{ cmd: 'powershell', args: ['-NoProfile', '-Command', 'Get-Clipboard'] },
|
|
26
|
+
],
|
|
27
|
+
darwin: [{ cmd: 'pbpaste', args: [] }],
|
|
28
|
+
unix: [
|
|
29
|
+
{ cmd: 'wl-paste', args: ['--no-newline'] },
|
|
30
|
+
{ cmd: 'xclip', args: ['-selection', 'clipboard', '-o'] },
|
|
31
|
+
{ cmd: 'xsel', args: ['--clipboard', '--output'] },
|
|
32
|
+
],
|
|
30
33
|
};
|
|
31
34
|
|
|
32
|
-
const UNIX_PASTE = [
|
|
33
|
-
{ cmd: 'wl-paste', args: ['--no-newline'] },
|
|
34
|
-
{ cmd: 'xclip', args: ['-selection', 'clipboard', '-o'] },
|
|
35
|
-
{ cmd: 'xsel', args: ['--clipboard', '--output'] },
|
|
36
|
-
];
|
|
37
|
-
|
|
38
35
|
let copied = '';
|
|
39
36
|
|
|
40
|
-
const
|
|
41
|
-
if (platform === 'win32') {
|
|
42
|
-
const args = ['-NoProfile', '-Command', 'Get-Clipboard'];
|
|
43
|
-
return [{ cmd: 'powershell', args }];
|
|
44
|
-
}
|
|
45
|
-
if (platform === 'darwin') return [{ cmd: 'pbpaste', args: [] }];
|
|
46
|
-
return UNIX_PASTE;
|
|
47
|
-
};
|
|
37
|
+
const platformTools = (tools) => tools[process.platform] ?? tools.unix;
|
|
48
38
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
cmd,
|
|
52
|
-
args,
|
|
53
|
-
spawnBase({ encoding: 'utf8', timeout: CLIP_TIMEOUT_MS }),
|
|
39
|
+
const runTool = (tool, input) =>
|
|
40
|
+
spawnSync(
|
|
41
|
+
tool.cmd,
|
|
42
|
+
tool.args,
|
|
43
|
+
spawnBase({ input, encoding: 'utf8', timeout: CLIP_TIMEOUT_MS }),
|
|
54
44
|
);
|
|
55
|
-
if (result.error || result.status !== 0) return '';
|
|
56
|
-
return result.stdout || '';
|
|
57
|
-
};
|
|
58
45
|
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (text) return text;
|
|
63
|
-
}
|
|
64
|
-
return '';
|
|
46
|
+
const osc52 = (text) => {
|
|
47
|
+
const b64 = Buffer.from(text, 'utf8').toString('base64');
|
|
48
|
+
return `${ESC}]52;c;${b64}\x07`;
|
|
65
49
|
};
|
|
66
50
|
|
|
67
51
|
const copyText = (text, stdout) => {
|
|
@@ -72,12 +56,20 @@ const copyText = (text, stdout) => {
|
|
|
72
56
|
stdout.write(osc52(text));
|
|
73
57
|
ok = true;
|
|
74
58
|
}
|
|
75
|
-
for (const tool of
|
|
76
|
-
|
|
59
|
+
for (const tool of platformTools(COPY_TOOLS)) {
|
|
60
|
+
const result = runTool(tool, text);
|
|
61
|
+
if (!result.error && result.status === 0) return true;
|
|
77
62
|
}
|
|
78
63
|
return ok;
|
|
79
64
|
};
|
|
80
65
|
|
|
81
|
-
const pasteText = () =>
|
|
66
|
+
const pasteText = () => {
|
|
67
|
+
for (const tool of platformTools(PASTE_TOOLS)) {
|
|
68
|
+
const result = runTool(tool);
|
|
69
|
+
if (result.error || result.status !== 0) continue;
|
|
70
|
+
if (result.stdout) return result.stdout;
|
|
71
|
+
}
|
|
72
|
+
return copied;
|
|
73
|
+
};
|
|
82
74
|
|
|
83
75
|
module.exports = { osc52, copyText, pasteText };
|