reslop 0.2.0 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +13 -2
  3. package/lib/ansi.js +263 -6
  4. package/lib/api.js +2 -0
  5. package/lib/capabilities.js +6 -0
  6. package/lib/cli.js +13 -2
  7. package/lib/deps.js +43 -3
  8. package/lib/git-branches.js +228 -17
  9. package/lib/git-reword-todo.js +20 -0
  10. package/lib/git.js +20 -3
  11. package/lib/keys.js +10 -4
  12. package/lib/npm-commands.js +513 -0
  13. package/lib/render/branches.js +23 -58
  14. package/lib/render/chrome.js +156 -99
  15. package/lib/render/commits.js +643 -31
  16. package/lib/render/diff.js +4 -4
  17. package/lib/render/frame.js +51 -5
  18. package/lib/render/notes.js +22 -5
  19. package/lib/render/npm.js +228 -0
  20. package/lib/render/primitives.js +48 -34
  21. package/lib/render/render.js +3 -3
  22. package/lib/report-model.js +127 -0
  23. package/lib/report-parse.js +94 -0
  24. package/lib/report-render.js +87 -0
  25. package/lib/report-run.js +164 -0
  26. package/lib/report-test.js +506 -0
  27. package/lib/report-tools.js +311 -0
  28. package/lib/review-merge.js +270 -0
  29. package/lib/review.js +38 -6
  30. package/lib/session/actions.js +81 -5
  31. package/lib/session/commit.js +187 -19
  32. package/lib/session/compose.js +96 -32
  33. package/lib/session/dispatch.js +36 -2
  34. package/lib/session/files-pane.js +36 -6
  35. package/lib/session/lifecycle.js +116 -16
  36. package/lib/session/navigation.js +1 -0
  37. package/lib/session/npm.js +335 -0
  38. package/lib/session/pointer.js +12 -1
  39. package/lib/session/todos.js +29 -1
  40. package/lib/session/view.js +14 -3
  41. package/lib/session/watch.js +120 -8
  42. package/lib/session.js +11 -2
  43. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## [Unreleased][unreleased]
4
4
 
5
+ ## [0.2.3][] - 2026-09-27
6
+
7
+ - Add brief and full commit lists, toggled with `v`
8
+ - Select a commit from the list to view its diff
9
+ - Edit a multi-line commit message
10
+ - Propose unused removals only for libraries that export code
11
+ - Improve TUI/TUX, multiple small fixes
12
+ - Implement npm command runner with termination and re-run
13
+ - Filter tests output to reduce AI costs: `npm t` and other scripts
14
+ - Save script logs under `.log/`
15
+ - Group similar problems in test and other output
16
+
17
+ ## [0.2.1][] - 2026-09-23
18
+
19
+ - Type a new commit message at the top of the list
20
+ - Improve `fixup!` functions: create and apply
21
+ - Reword the selected commit message with `r`
22
+ - Add light theme: `-light` option and `l` hotkey to switch
23
+ - Support checkbox state in TODOs, UI for state change
24
+ - Reload the file list when leaving todos, branches, or commits
25
+ - Let agents run `editing` reviews and leave `done` files unchanged
26
+ - Skip npm audit and outdated unless package files changed
27
+ - Sync and merge TODOs with file instead of overwriting it
28
+
5
29
  ## [0.2.0][] - 2026-09-22
6
30
 
7
31
  - Add a full-size file editor
@@ -105,7 +129,9 @@
105
129
  - Feedback and todos that produce a repair plan for an agent
106
130
  - Intra-line highlighting in unified, mixed, and side-by-side layouts
107
131
 
108
- [unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.0...HEAD
132
+ [unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.3...HEAD
133
+ [0.2.3]: https://github.com/tshemsedinov/reslop/compare/v0.2.1...v0.2.3
134
+ [0.2.1]: https://github.com/tshemsedinov/reslop/compare/v0.2.0...v0.2.1
109
135
  [0.2.0]: https://github.com/tshemsedinov/reslop/compare/v0.1.9...v0.2.0
110
136
  [0.1.9]: https://github.com/tshemsedinov/reslop/compare/v0.1.8...v0.1.9
111
137
  [0.1.8]: https://github.com/tshemsedinov/reslop/compare/v0.1.7...v0.1.8
package/README.md CHANGED
@@ -24,7 +24,7 @@ Review → Plan → Repair → Verify
24
24
  - Stage, unstage, or revert each contiguous block of diff lines
25
25
  - Auto-reload local diffs when files change, keeping the current screen
26
26
  - Import GitHub PR and GitLab MR review comments into the local plan for AI
27
- - Commits list: commit, amend, fixup, drop, pull, and push
27
+ - Commits list: commit, amend, fixup, reword, drop, pull, and push
28
28
  - Branches: checkout, create, rebase, drop, pull, and push
29
29
  - Review each npm dependency once across `package.json` and the lockfile
30
30
  - Propose unused removals, npm audit fixes, and outdated updates as diffs
@@ -39,6 +39,7 @@ npm i -g reslop
39
39
  ```
40
40
 
41
41
  Works on Linux, macOS, and Windows. On Windows use Windows Terminal.
42
+ Requires Node.js `>=18.15.0`.
42
43
 
43
44
  ## Usage
44
45
 
@@ -48,6 +49,17 @@ Works on Linux, macOS, and Windows. On Windows use Windows Terminal.
48
49
  - `reslop https://github.com/metarhia/metacom/pull/555` GitHub/GitLab PR/MR
49
50
  - `reslop -n` start a new review even if the latest is still editing
50
51
  - `reslop -r` read-only mode
52
+ - `reslop -light` light color theme (default dark)
53
+ - `reslop t -- <program> [args]` run one program and print a reduced report
54
+
55
+ Prefix each program in an npm script with `reslop t --`. Keep `&&` between those wraps, so each program is captured on its own and a failing step does not start the next one:
56
+
57
+ ```json
58
+ "lint": "reslop t -- eslint . && reslop t -- prettier -c \"**/*.js\"",
59
+ "test": "reslop t -- npm run -s lint && reslop t -- node --test"
60
+ ```
61
+
62
+ The report is Markdown: failures, diagnostics, and a short summary. Passing results are omitted and similar problems are grouped. The same text is saved under `.log/`. Set `RESLOP_OUTPUT=raw` to pass the command through unchanged. The review screen runs scripts in raw mode and applies its own filter once.
51
63
 
52
64
  Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
53
65
 
@@ -64,7 +76,6 @@ Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
64
76
  - Send anonymized code blocks for expert review
65
77
  - Send questions and the repair plan to experts for approval
66
78
  - Ask experts
67
- - Verify the codebase
68
79
  - Apply refactoring skills
69
80
  - Call agents, harnesses, and IDEs to execute prepared plans
70
81
  - Squash, cherry-pick, and interactive rebase
package/lib/ansi.js CHANGED
@@ -40,7 +40,7 @@ const WIDE_CP = [
40
40
  0x1fae9, 0x1faf0, 0x1faf8, 0x20000, 0x2fffd, 0x30000, 0x3fffd,
41
41
  ];
42
42
 
43
- const THEME = {
43
+ const DARK_UI = {
44
44
  delLineFg: [0xcc, 0x7a, 0x7a],
45
45
  delLineBg: [0x3a, 0x1a, 0x1a],
46
46
  delCharFg: [0xff, 0xff, 0xff],
@@ -60,7 +60,6 @@ const THEME = {
60
60
  headerDirFg: [0x6e, 0x6e, 0x6e],
61
61
  headerSlashFg: [0x8a, 0x8a, 0x8a],
62
62
  headerFileFg: [0x4e, 0x4e, 0x4e],
63
- headerMetaFg: [0x5a, 0x5a, 0x5a],
64
63
  chromeFg: [0xd0, 0xd0, 0xe0],
65
64
  chromeBg: [0x18, 0x18, 0x24],
66
65
  noteBg: [0x24, 0x24, 0x36],
@@ -87,7 +86,7 @@ const THEME = {
87
86
  stagedAddCharBg: [0x4e, 0x4e, 0x58],
88
87
  };
89
88
 
90
- const CODE_FG = {
89
+ const DARK_CODE = {
91
90
  keyword: [197, 134, 192],
92
91
  control: [197, 134, 192],
93
92
  storage: [86, 156, 214],
@@ -122,7 +121,7 @@ const CODE_FG = {
122
121
  logDebug: [140, 160, 180],
123
122
  };
124
123
 
125
- const LOG_COL_FG = [
124
+ const DARK_LOG_COLS = [
126
125
  [210, 210, 220],
127
126
  [120, 180, 220],
128
127
  [180, 160, 220],
@@ -133,6 +132,142 @@ const LOG_COL_FG = [
133
132
  [200, 170, 140],
134
133
  ];
135
134
 
135
+ const LIGHT_UI = {
136
+ delLineFg: [0xb3, 0x1d, 0x28],
137
+ delLineBg: [0xff, 0xeb, 0xe9],
138
+ delCharFg: [0x1f, 0x23, 0x28],
139
+ delCharBg: [0xff, 0xc1, 0xc0],
140
+ addLineFg: [0x1a, 0x7f, 0x37],
141
+ addLineBg: [0xe6, 0xff, 0xec],
142
+ addCharFg: [0x1f, 0x23, 0x28],
143
+ addCharBg: [0xab, 0xf2, 0xbc],
144
+ ctxFg: [0x57, 0x60, 0x6a],
145
+ ctxBg: [0xff, 0xff, 0xff],
146
+ splitGutterFg: [0xea, 0xee, 0xf2],
147
+ splitGutterBg: [0xea, 0xee, 0xf2],
148
+ headerFg: [0x24, 0x29, 0x2f],
149
+ headerBg: [0xd8, 0xde, 0xe4],
150
+ headerChromeFg: [0x42, 0x4a, 0x53],
151
+ headerRepoFg: [0x24, 0x29, 0x2f],
152
+ headerDirFg: [0x57, 0x60, 0x6a],
153
+ headerSlashFg: [0x8c, 0x95, 0x9f],
154
+ headerFileFg: [0x1f, 0x23, 0x28],
155
+ chromeFg: [0x24, 0x29, 0x2f],
156
+ chromeBg: [0xf0, 0xf2, 0xf5],
157
+ noteBg: [0xe8, 0xf0, 0xfe],
158
+ buttonFg: [0x57, 0x60, 0x6a],
159
+ buttonBg: [0xe1, 0xe5, 0xea],
160
+ buttonHotFg: [0x0f, 0x14, 0x19],
161
+ currentBg: [0xac, 0xe1, 0xc2],
162
+ checkFg: [0x1f, 0x23, 0x28],
163
+ checkBg: [0xc8, 0xcf, 0xd8],
164
+ checkDoneFg: [0x0f, 0x3d, 0x1f],
165
+ checkDoneBg: [0xab, 0xf2, 0xbc],
166
+ errorFg: [0xcf, 0x22, 0x2e],
167
+ warnFg: [0x9a, 0x67, 0x00],
168
+ mutedFg: [0x6e, 0x77, 0x81],
169
+ shaFg: [0x05, 0x50, 0xae],
170
+ shaDarkFg: [0x0b, 0x3d, 0x70],
171
+ stagedDelLineFg: [0x57, 0x60, 0x6a],
172
+ stagedDelLineBg: [0xf3, 0xf4, 0xf6],
173
+ stagedDelCharFg: [0x1f, 0x23, 0x28],
174
+ stagedDelCharBg: [0xd8, 0xdc, 0xe1],
175
+ stagedAddLineFg: [0x42, 0x4a, 0x53],
176
+ stagedAddLineBg: [0xea, 0xec, 0xef],
177
+ stagedAddCharFg: [0x0f, 0x14, 0x19],
178
+ stagedAddCharBg: [0xcc, 0xd1, 0xd7],
179
+ };
180
+
181
+ const LIGHT_CODE = {
182
+ keyword: [175, 0, 219],
183
+ control: [175, 0, 219],
184
+ storage: [0, 0, 255],
185
+ literal: [0, 0, 255],
186
+ string: [163, 21, 21],
187
+ template: [163, 21, 21],
188
+ escape: [238, 0, 0],
189
+ interpolation: [0, 16, 128],
190
+ number: [9, 134, 88],
191
+ comment: [0, 128, 0],
192
+ function: [121, 94, 38],
193
+ method: [121, 94, 38],
194
+ className: [38, 127, 153],
195
+ type: [38, 127, 153],
196
+ interface: [38, 127, 153],
197
+ property: [0, 16, 128],
198
+ variable: [0, 16, 128],
199
+ parameter: [0, 16, 128],
200
+ constant: [0, 112, 193],
201
+ operator: [60, 60, 60],
202
+ punct: [60, 60, 60],
203
+ regex: [129, 31, 63],
204
+ decorator: [121, 94, 38],
205
+ tag: [128, 0, 0],
206
+ attr: [229, 0, 0],
207
+ plain: [31, 31, 31],
208
+ punctDim: [140, 140, 140],
209
+ logDate: [60, 60, 70],
210
+ logError: [205, 49, 49],
211
+ logWarn: [160, 110, 0],
212
+ logInfo: [0, 90, 180],
213
+ logDebug: [100, 110, 125],
214
+ };
215
+
216
+ const LIGHT_LOG_COLS = [
217
+ [60, 60, 70],
218
+ [0, 95, 150],
219
+ [110, 70, 170],
220
+ [30, 120, 60],
221
+ [150, 95, 20],
222
+ [170, 50, 90],
223
+ [20, 120, 120],
224
+ [130, 90, 50],
225
+ ];
226
+
227
+ const PALETTES = {
228
+ dark: { ui: DARK_UI, code: DARK_CODE, logCols: DARK_LOG_COLS },
229
+ light: { ui: LIGHT_UI, code: LIGHT_CODE, logCols: LIGHT_LOG_COLS },
230
+ };
231
+ const THEME_NAMES = Object.keys(PALETTES);
232
+
233
+ const applyColors = (into, from) => {
234
+ for (const key of Object.keys(from)) {
235
+ const src = from[key];
236
+ let dst = into[key];
237
+ if (!dst) {
238
+ dst = [0, 0, 0];
239
+ into[key] = dst;
240
+ }
241
+ dst[0] = src[0];
242
+ dst[1] = src[1];
243
+ dst[2] = src[2];
244
+ }
245
+ return into;
246
+ };
247
+
248
+ const cloneColors = (colors) => {
249
+ const into = Array.isArray(colors) ? [] : {};
250
+ return applyColors(into, colors);
251
+ };
252
+
253
+ const THEME = cloneColors(DARK_UI);
254
+ const CODE_FG = cloneColors(DARK_CODE);
255
+ const LOG_COL_FG = cloneColors(DARK_LOG_COLS);
256
+ let currentTheme = 'dark';
257
+
258
+ const setTheme = (name) => {
259
+ if (!Object.hasOwn(PALETTES, name)) {
260
+ throw new Error(`unknown theme ${name}`);
261
+ }
262
+ const palette = PALETTES[name];
263
+ applyColors(THEME, palette.ui);
264
+ applyColors(CODE_FG, palette.code);
265
+ applyColors(LOG_COL_FG, palette.logCols);
266
+ currentTheme = name;
267
+ };
268
+
269
+ const themeName = () => currentTheme;
270
+
136
271
  const codeFg = (style) => {
137
272
  if (typeof style !== 'string') return CODE_FG.plain;
138
273
  if (Object.hasOwn(CODE_FG, style)) return CODE_FG[style];
@@ -143,9 +278,14 @@ const codeFg = (style) => {
143
278
  return CODE_FG.plain;
144
279
  };
145
280
 
146
- const fg = (rgb) => `${ESC}[38;2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
281
+ const channels = (rgb) => {
282
+ if (typeof rgb === 'number') return `${rgb};${rgb};${rgb}`;
283
+ return `${rgb[0]};${rgb[1]};${rgb[2]}`;
284
+ };
285
+
286
+ const fg = (rgb) => `${ESC}[38;2;${channels(rgb)}m`;
147
287
 
148
- const bg = (rgb) => `${ESC}[48;2;${rgb[0]};${rgb[1]};${rgb[2]}m`;
288
+ const bg = (rgb) => `${ESC}[48;2;${channels(rgb)}m`;
149
289
 
150
290
  const seq = (fgRgb, bgRgb) => `${fg(fgRgb)}${bg(bgRgb)}`;
151
291
 
@@ -215,6 +355,117 @@ const truncateVisible = (text, width) => {
215
355
  return out;
216
356
  };
217
357
 
358
+ const csiLength = (text, index) => {
359
+ if (text[index] !== ESC || text[index + 1] !== '[') return 0;
360
+ let at = index + 2;
361
+ while (at < text.length) {
362
+ const code = text.charCodeAt(at);
363
+ at += 1;
364
+ if (code >= 64 && code <= 126) return at - index;
365
+ }
366
+ return 0;
367
+ };
368
+
369
+ const firstGrapheme = (text) => {
370
+ const next = SEGMENTER.segment(text)[Symbol.iterator]().next();
371
+ if (next.done || !next.value) return '';
372
+ return next.value.segment;
373
+ };
374
+
375
+ const clipAnsi = (text, width) => {
376
+ const raw = `${text ?? ''}`;
377
+ if (width <= 0) return '';
378
+ if (visibleWidth(raw) <= width) return raw;
379
+ let used = 0;
380
+ let out = '';
381
+ let index = 0;
382
+ while (index < raw.length && used < width) {
383
+ const csi = csiLength(raw, index);
384
+ if (csi) {
385
+ out += raw.slice(index, index + csi);
386
+ index += csi;
387
+ continue;
388
+ }
389
+ const ch = firstGrapheme(raw.slice(index));
390
+ if (!ch) break;
391
+ const w = graphemeWidth(ch, used);
392
+ if (used + w > width) break;
393
+ out += ch;
394
+ used += w;
395
+ index += ch.length;
396
+ }
397
+ if (stripAnsi(out) !== stripAnsi(raw)) out += RESET;
398
+ return out;
399
+ };
400
+
401
+ const sgrParams = (seq) => {
402
+ const inner = seq.slice(2, -1);
403
+ if (!inner) return [0];
404
+ return inner.split(';').map((part) => (part === '' ? 0 : Number(part)));
405
+ };
406
+
407
+ const colorSpan = (params, index) => {
408
+ const mode = params[index + 1];
409
+ if (mode === 5) return 2;
410
+ if (mode === 2) return 4;
411
+ return 0;
412
+ };
413
+
414
+ const keepFgParams = (params) => {
415
+ const kept = [];
416
+ for (let i = 0; i < params.length; i++) {
417
+ const code = params[i];
418
+ if (code === 38 || code === 48) {
419
+ const extra = colorSpan(params, i);
420
+ if (code === 38 && extra) {
421
+ kept.push(code, ...params.slice(i + 1, i + 1 + extra));
422
+ }
423
+ i += extra;
424
+ continue;
425
+ }
426
+ if (code >= 40 && code <= 49) continue;
427
+ if (code >= 100 && code <= 107) continue;
428
+ if (code === 7 || code === 27) continue;
429
+ kept.push(code);
430
+ }
431
+ return kept;
432
+ };
433
+
434
+ const foregroundOn = (text, bgRgb) => {
435
+ const raw = `${text ?? ''}`;
436
+ const ground = bg(bgRgb);
437
+ let out = '';
438
+ let index = 0;
439
+ while (index < raw.length) {
440
+ if (raw[index] !== ESC) {
441
+ const next = raw.indexOf(ESC, index);
442
+ const end = next < 0 ? raw.length : next;
443
+ out += raw.slice(index, end);
444
+ index = end;
445
+ continue;
446
+ }
447
+ const csi = csiLength(raw, index);
448
+ if (!csi) {
449
+ out += raw[index];
450
+ index += 1;
451
+ continue;
452
+ }
453
+ const seq = raw.slice(index, index + csi);
454
+ index += csi;
455
+ if (!seq.endsWith('m')) {
456
+ out += seq;
457
+ continue;
458
+ }
459
+ const kept = keepFgParams(sgrParams(seq));
460
+ const reset = kept.includes(0);
461
+ const rest = kept.filter((code) => code !== 0);
462
+ if (reset) out += `${ESC}[0m`;
463
+ out += ground;
464
+ if (rest.length) out += `${ESC}[${rest.join(';')}m`;
465
+ }
466
+ return out;
467
+ };
468
+
218
469
  module.exports = {
219
470
  ESC,
220
471
  RESET,
@@ -222,6 +473,10 @@ module.exports = {
222
473
  EL,
223
474
  THEME,
224
475
  CODE_FG,
476
+ PALETTES,
477
+ THEME_NAMES,
478
+ setTheme,
479
+ themeName,
225
480
  codeFg,
226
481
  fg,
227
482
  bg,
@@ -232,4 +487,6 @@ module.exports = {
232
487
  graphemeWidth,
233
488
  visibleWidth,
234
489
  truncateVisible,
490
+ clipAnsi,
491
+ foregroundOn,
235
492
  };
package/lib/api.js CHANGED
@@ -16,6 +16,8 @@ const cli = require('./cli.js');
16
16
  module.exports = {
17
17
  THEME: ansi.THEME,
18
18
  CODE_FG: ansi.CODE_FG,
19
+ THEME_NAMES: ansi.THEME_NAMES,
20
+ setTheme: ansi.setTheme,
19
21
  ACTIONS: keys.ACTIONS,
20
22
  diffChars: diff.diffChars,
21
23
  parseDiff: diff.parseDiff,
@@ -18,6 +18,7 @@ const CHANGE_METHODS = [
18
18
  'revertFile',
19
19
  'edit',
20
20
  'writeFile',
21
+ 'stagePath',
21
22
  ];
22
23
 
23
24
  const BRANCH_METHODS = [
@@ -25,6 +26,7 @@ const BRANCH_METHODS = [
25
26
  'commitAsync',
26
27
  'hasStaged',
27
28
  'lastMessage',
29
+ 'commitMessage',
28
30
  'listBranches',
29
31
  'listCommits',
30
32
  'currentBranch',
@@ -38,6 +40,10 @@ const BRANCH_METHODS = [
38
40
  'dropAsync',
39
41
  'dropCommit',
40
42
  'dropCommitAsync',
43
+ 'applyFixup',
44
+ 'applyFixupAsync',
45
+ 'reword',
46
+ 'rewordAsync',
41
47
  'pull',
42
48
  'pullAsync',
43
49
  'push',
package/lib/cli.js CHANGED
@@ -10,12 +10,15 @@ const { gitlabToken, loadMergeRequest } = require('./gitlab.js');
10
10
  const { selectChangeSource, createLoadedSource } = require('./source.js');
11
11
  const { Session } = require('./session.js');
12
12
  const { capabilitiesFor, attachCapabilities } = require('./capabilities.js');
13
+ const { setTheme } = require('./ansi.js');
14
+ const { runCapture } = require('./report-run.js');
13
15
 
14
16
  const HEX_REV = /^[0-9a-f]{4,40}$/i;
15
17
  const HEAD_REV = /^HEAD([~^].*)?$/i;
16
18
  const RANGE_REV = /[~^]/;
17
19
 
18
- const USAGE = 'Usage: reslop [-n] [-r] [path | commit | pr-url | mr-url]';
20
+ const USAGE =
21
+ 'Usage: reslop [-n] [-r] [-light] [path | commit | pr-url | mr-url]';
19
22
 
20
23
  const REMOTE_CHANGE = {
21
24
  pr: {
@@ -48,6 +51,7 @@ const parseArgv = (argv) => {
48
51
  const paths = [];
49
52
  let newReview = false;
50
53
  let readOnly = false;
54
+ let theme = 'dark';
51
55
  for (const arg of argv) {
52
56
  if (arg === '-n') {
53
57
  newReview = true;
@@ -57,12 +61,16 @@ const parseArgv = (argv) => {
57
61
  readOnly = true;
58
62
  continue;
59
63
  }
64
+ if (arg === '-light') {
65
+ theme = 'light';
66
+ continue;
67
+ }
60
68
  if (arg.startsWith('-')) {
61
69
  throw new Error(`unknown option ${arg}`);
62
70
  }
63
71
  paths.push(arg);
64
72
  }
65
- return { paths, newReview, readOnly };
73
+ return { paths, newReview, readOnly, theme };
66
74
  };
67
75
 
68
76
  const looksLikeRev = (spec) => {
@@ -173,6 +181,8 @@ const runUi = async (session, proc) => {
173
181
  };
174
182
 
175
183
  const run = async (proc, deps = {}) => {
184
+ const argv = proc.argv.slice(2);
185
+ if (argv[0] === 't') return runCapture(proc, deps);
176
186
  const stdout = proc.stdout;
177
187
  const stderr = proc.stderr;
178
188
  let args;
@@ -183,6 +193,7 @@ const run = async (proc, deps = {}) => {
183
193
  stderr.write(`${USAGE}\n`);
184
194
  return 1;
185
195
  }
196
+ setTheme(args.theme);
186
197
  let session;
187
198
  try {
188
199
  session = await loadSession(proc, deps, args);
package/lib/deps.js CHANGED
@@ -73,6 +73,46 @@ const pkgHasExportEntry = (pkg) => {
73
73
  return Object.keys(exported).length > 0;
74
74
  };
75
75
 
76
+ const isTypeKey = (key) => key === 'types' || key === 'typings';
77
+
78
+ const isRuntimeFile = (value) => {
79
+ if (typeof value !== 'string' || !value) return false;
80
+ return !value.endsWith('.d.ts');
81
+ };
82
+
83
+ const hasRuntimeExport = (value) => {
84
+ if (isRuntimeFile(value)) return true;
85
+ if (Array.isArray(value)) {
86
+ for (const item of value) {
87
+ if (hasRuntimeExport(item)) return true;
88
+ }
89
+ return false;
90
+ }
91
+ if (!value || typeof value !== 'object') return false;
92
+ const keys = Object.keys(value);
93
+ for (const key of keys) {
94
+ if (isTypeKey(key)) continue;
95
+ if (hasRuntimeExport(value[key])) return true;
96
+ }
97
+ return false;
98
+ };
99
+
100
+ const pkgHasBin = (pkg) => {
101
+ if (!pkg || typeof pkg !== 'object') return false;
102
+ const bin = pkg.bin;
103
+ if (typeof bin === 'string') return !!bin;
104
+ if (!bin || typeof bin !== 'object' || Array.isArray(bin)) return false;
105
+ return Object.keys(bin).length > 0;
106
+ };
107
+
108
+ const pkgExportsLibrary = (pkg) => {
109
+ if (!pkg || typeof pkg !== 'object') return false;
110
+ if (pkgHasBin(pkg)) return false;
111
+ if (isRuntimeFile(pkg.main)) return true;
112
+ if (isRuntimeFile(pkg.module)) return true;
113
+ return hasRuntimeExport(pkg.exports);
114
+ };
115
+
76
116
  const SKIP_WALK = ['node_modules', '.git', '.review', 'coverage', 'dist'];
77
117
 
78
118
  const collectUsedFromManifest = (used, rel) => {
@@ -156,8 +196,8 @@ const readDepPkg = (root, pkgDir, name) => {
156
196
 
157
197
  const depHasExportEntry = (root, pkgDir, name) => {
158
198
  const pkg = readDepPkg(root, pkgDir, name);
159
- if (!pkg) return true;
160
- return pkgHasExportEntry(pkg);
199
+ if (!pkg) return false;
200
+ return pkgExportsLibrary(pkg);
161
201
  };
162
202
 
163
203
  const collectExportEntries = (root, pkgDir, names) => {
@@ -349,7 +389,7 @@ const markOutdatedChanges = (changes, outdated) =>
349
389
  markChanges(changes, outdated, 'outdated');
350
390
 
351
391
  const depExports = (exportEntries, name) => {
352
- if (!exportEntries || !exportEntries.has(name)) return true;
392
+ if (!exportEntries || !exportEntries.has(name)) return false;
353
393
  return exportEntries.get(name) === true;
354
394
  };
355
395