reslop 0.2.1 → 0.2.4

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 (81) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +19 -4
  3. package/bin/reslop.js +4 -2
  4. package/lib/ansi.js +120 -4
  5. package/lib/api.js +2 -1
  6. package/lib/cli.js +21 -9
  7. package/lib/clipboard.js +6 -3
  8. package/lib/deps-diff.js +61 -115
  9. package/lib/deps.js +57 -54
  10. package/lib/detect.js +4 -2
  11. package/lib/diff/align.js +2 -1
  12. package/lib/diff/diff.js +6 -3
  13. package/lib/diff/display.js +2 -1
  14. package/lib/diff/inline.js +2 -1
  15. package/lib/diff/patch.js +2 -1
  16. package/lib/diff/unit.js +4 -2
  17. package/lib/editor.js +22 -4
  18. package/lib/files.js +38 -12
  19. package/lib/git-branches.js +150 -165
  20. package/lib/git-deps.js +20 -60
  21. package/lib/git-worktree.js +508 -0
  22. package/lib/git.js +23 -580
  23. package/lib/github.js +2 -1
  24. package/lib/gitlab.js +2 -1
  25. package/lib/highlight/bash.js +2 -1
  26. package/lib/highlight/css.js +2 -1
  27. package/lib/highlight/csv.js +2 -1
  28. package/lib/highlight/dot.js +2 -1
  29. package/lib/highlight/html.js +2 -1
  30. package/lib/highlight/json.js +2 -1
  31. package/lib/highlight/log.js +2 -1
  32. package/lib/highlight/txt.js +2 -1
  33. package/lib/highlight.js +2 -1
  34. package/lib/keys.js +12 -6
  35. package/lib/manifest.js +4 -2
  36. package/lib/npm-commands.js +592 -0
  37. package/lib/remote.js +20 -13
  38. package/lib/render/branches.js +25 -41
  39. package/lib/render/chrome.js +163 -128
  40. package/lib/render/commits.js +727 -65
  41. package/lib/render/diff.js +130 -47
  42. package/lib/render/files.js +4 -12
  43. package/lib/render/frame.js +41 -13
  44. package/lib/render/notes.js +4 -11
  45. package/lib/render/npm.js +216 -0
  46. package/lib/render/present.js +2 -1
  47. package/lib/render/primitives.js +34 -50
  48. package/lib/render/render.js +2 -1
  49. package/lib/render/unit.js +14 -11
  50. package/lib/report-model.js +129 -0
  51. package/lib/report-parse.js +89 -0
  52. package/lib/report-render.js +104 -0
  53. package/lib/report-run.js +166 -0
  54. package/lib/report-test.js +507 -0
  55. package/lib/report-tools.js +337 -0
  56. package/lib/review-merge.js +2 -1
  57. package/lib/review.js +6 -2
  58. package/lib/session/actions.js +77 -44
  59. package/lib/session/branches.js +13 -49
  60. package/lib/session/change-actions.js +2 -1
  61. package/lib/session/commit.js +113 -53
  62. package/lib/session/compose.js +83 -14
  63. package/lib/session/dispatch.js +42 -22
  64. package/lib/session/files-pane.js +16 -21
  65. package/lib/session/items.js +6 -3
  66. package/lib/session/lifecycle.js +12 -5
  67. package/lib/session/load.js +4 -2
  68. package/lib/session/navigation.js +2 -0
  69. package/lib/session/npm.js +388 -0
  70. package/lib/session/ops.js +15 -1
  71. package/lib/session/pointer.js +30 -3
  72. package/lib/session/review.js +2 -1
  73. package/lib/session/terminal.js +4 -2
  74. package/lib/session/update.js +2 -1
  75. package/lib/session/view.js +41 -4
  76. package/lib/session.js +44 -22
  77. package/lib/source.js +6 -3
  78. package/lib/update.js +5 -9
  79. package/lib/utilities.js +4 -2
  80. package/lib/wrap.js +3 -1
  81. package/package.json +6 -6
@@ -0,0 +1,337 @@
1
+ 'use strict';
2
+
3
+ const deps = require('./deps.js');
4
+ const { parseAuditReport, parseOutdatedReport } = deps;
5
+ const model = require('./report-model.js');
6
+
7
+ const { summary, problem, report, userTrace } = model;
8
+
9
+ const place = (file, line, column, root) =>
10
+ userTrace(`${file}:${line}:${column}`, root);
11
+
12
+ const parseEslintJson = (data, root) => {
13
+ const rows = Array.isArray(data) ? data : data && data.results;
14
+ if (!Array.isArray(rows) || !rows.length) return null;
15
+ if (!rows[0] || !Array.isArray(rows[0].messages)) return null;
16
+ const stats = summary();
17
+ let errors = 0;
18
+ let warnings = 0;
19
+ const problems = [];
20
+ for (const file of rows) {
21
+ const messages = file.messages || [];
22
+ for (const item of messages) {
23
+ const severity = item.severity === 1 ? 'warning' : 'error';
24
+ if (severity === 'warning') warnings += 1;
25
+ else errors += 1;
26
+ problems.push(
27
+ problem({
28
+ severity,
29
+ message: item.message || '',
30
+ code: item.ruleId || '',
31
+ trace: place(
32
+ file.filePath || '',
33
+ item.line || 0,
34
+ item.column || 0,
35
+ root,
36
+ ),
37
+ }),
38
+ );
39
+ }
40
+ }
41
+ stats.errors = errors;
42
+ stats.warnings = warnings;
43
+ return report('eslint', { summary: stats, problems });
44
+ };
45
+
46
+ const eslintLine = (line) => {
47
+ const match = /^\s*(\d+):(\d+)\s+(error|warning)\s+(.*)$/.exec(line);
48
+ if (!match) return null;
49
+ const rest = match[4];
50
+ const ruleAt = rest.lastIndexOf(' ');
51
+ const message = ruleAt < 0 ? rest : rest.slice(0, ruleAt);
52
+ const code = ruleAt < 0 ? '' : rest.slice(ruleAt + 2).trim();
53
+ return {
54
+ line: match[1],
55
+ column: match[2],
56
+ severity: match[3],
57
+ message: message.trim(),
58
+ code,
59
+ };
60
+ };
61
+
62
+ const isPathLine = (line) => {
63
+ const text = line.trim();
64
+ if (!text || text.startsWith('✖')) return false;
65
+ if (eslintLine(text)) return false;
66
+ return text.includes('/') || text.includes('\\') || /\.\w+$/.test(text);
67
+ };
68
+
69
+ const parseEslintStylish = (text, root) => {
70
+ const lines = `${text ?? ''}`.split(/\r?\n/);
71
+ const problems = [];
72
+ let file = '';
73
+ let errors = 0;
74
+ let warnings = 0;
75
+ let seen = false;
76
+ for (const line of lines) {
77
+ const item = eslintLine(line);
78
+ if (item) {
79
+ seen = true;
80
+ if (item.severity === 'warning') warnings += 1;
81
+ else errors += 1;
82
+ problems.push(
83
+ problem({
84
+ severity: item.severity,
85
+ message: item.message,
86
+ code: item.code,
87
+ trace: place(file, item.line, item.column, root),
88
+ }),
89
+ );
90
+ continue;
91
+ }
92
+ if (/✖\s+\d+\s+problems?\b/.test(line)) seen = true;
93
+ if (isPathLine(line)) file = line.trim();
94
+ }
95
+ if (!seen) return null;
96
+ const stats = summary();
97
+ stats.errors = errors;
98
+ stats.warnings = warnings;
99
+ return report('eslint', { summary: stats, problems });
100
+ };
101
+
102
+ const tscLine = (line) => {
103
+ const match = /^(.*)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.*)$/.exec(
104
+ line.trim(),
105
+ );
106
+ if (!match) return null;
107
+ return {
108
+ file: match[1],
109
+ line: match[2],
110
+ column: match[3],
111
+ code: match[4],
112
+ message: match[5],
113
+ };
114
+ };
115
+
116
+ const parseTsc = (text, root) => {
117
+ const problems = [];
118
+ for (const line of `${text ?? ''}`.split(/\r?\n/)) {
119
+ const item = tscLine(line);
120
+ if (!item) continue;
121
+ problems.push(
122
+ problem({
123
+ message: item.message,
124
+ code: item.code,
125
+ trace: place(item.file, item.line, item.column, root),
126
+ }),
127
+ );
128
+ }
129
+ if (!problems.length) return null;
130
+ const stats = summary();
131
+ stats.errors = problems.length;
132
+ return report('tsc', { summary: stats, problems });
133
+ };
134
+
135
+ const prettierFile = (line) => {
136
+ const warn = /^\[(?:warn|error)\]\s+(\S+)$/.exec(line.trim());
137
+ if (warn && !warn[1].startsWith('Code')) return warn[1];
138
+ return '';
139
+ };
140
+
141
+ const parsePrettier = (text, root) => {
142
+ const lines = `${text ?? ''}`.split(/\r?\n/);
143
+ const problems = [];
144
+ let seen = false;
145
+ for (const line of lines) {
146
+ if (line.includes('Code style issues')) seen = true;
147
+ if (line.includes('All matched files use Prettier')) seen = true;
148
+ const file = prettierFile(line);
149
+ if (!file) continue;
150
+ seen = true;
151
+ problems.push(
152
+ problem({
153
+ message: 'format',
154
+ trace: userTrace(file, root),
155
+ }),
156
+ );
157
+ }
158
+ if (!seen) return null;
159
+ const stats = summary();
160
+ stats.errors = problems.length;
161
+ return report('prettier', { summary: stats, problems });
162
+ };
163
+
164
+ const auditExtra = (item) => {
165
+ const size = (item.range ? 1 : 0) + (item.fix ? 1 : 0);
166
+ const extra = new Array(size);
167
+ let index = 0;
168
+ if (item.range) {
169
+ extra[index] = ['range', item.range];
170
+ index += 1;
171
+ }
172
+ if (item.fix) extra[index] = ['fix', item.fix];
173
+ return extra;
174
+ };
175
+
176
+ const auditProblems = (text) => {
177
+ const audit = parseAuditReport(text);
178
+ const problems = new Array(audit.size);
179
+ let index = 0;
180
+ for (const item of audit.values()) {
181
+ problems[index] = problem({
182
+ severity: item.severity || 'error',
183
+ name: item.name,
184
+ message: item.title || '',
185
+ extra: auditExtra(item),
186
+ });
187
+ index += 1;
188
+ }
189
+ return problems;
190
+ };
191
+
192
+ const parseAudit = (text) => {
193
+ const problems = auditProblems(text);
194
+ if (!problems.length) return null;
195
+ const stats = summary();
196
+ stats.errors = problems.length;
197
+ return report('audit', { summary: stats, problems });
198
+ };
199
+
200
+ const behind = (item) => {
201
+ if (!item.current) return true;
202
+ if (item.wanted && item.wanted !== item.current) return true;
203
+ if (item.latest && item.latest !== item.current) return true;
204
+ return false;
205
+ };
206
+
207
+ const parseOutdated = (text) => {
208
+ const outdated = parseOutdatedReport(text);
209
+ const problems = [];
210
+ for (const [name, item] of outdated) {
211
+ if (!behind(item)) continue;
212
+ problems.push(
213
+ problem({
214
+ severity: 'warning',
215
+ name,
216
+ message: `${item.current} ${item.wanted} ${item.latest}`.trim(),
217
+ extra: [
218
+ ['current', item.current],
219
+ ['wanted', item.wanted],
220
+ ['latest', item.latest],
221
+ ],
222
+ }),
223
+ );
224
+ }
225
+ if (!problems.length) return null;
226
+ const stats = summary();
227
+ stats.warnings = problems.length;
228
+ return report('outdated', { summary: stats, problems });
229
+ };
230
+
231
+ const pushLs = (problems, text) => {
232
+ const message = `${text ?? ''}`.trim();
233
+ if (!message) return;
234
+ problems.push(problem({ message }));
235
+ };
236
+
237
+ const walkLs = (node, problems) => {
238
+ if (!node || typeof node !== 'object') return;
239
+ if (Array.isArray(node.problems)) {
240
+ for (const item of node.problems) pushLs(problems, item);
241
+ }
242
+ if (node.missing) {
243
+ pushLs(problems, `missing ${node.required || node.version || ''}`);
244
+ }
245
+ if (node.invalid) pushLs(problems, `invalid ${node.version || ''}`);
246
+ if (node.extraneous) pushLs(problems, `extraneous ${node.version || ''}`);
247
+ const deps = node.dependencies;
248
+ if (!deps || typeof deps !== 'object') return;
249
+ for (const name of Object.keys(deps)) walkLs(deps[name], problems);
250
+ };
251
+
252
+ const parseLs = (data) => {
253
+ if (!data || typeof data !== 'object' || Array.isArray(data)) return null;
254
+ if (!data.dependencies && !Array.isArray(data.problems)) return null;
255
+ const problems = [];
256
+ walkLs(data, problems);
257
+ if (!problems.length && !Array.isArray(data.problems)) return null;
258
+ const stats = summary();
259
+ stats.errors = problems.length;
260
+ return report('ls', { summary: stats, problems });
261
+ };
262
+
263
+ const parseToolJson = (data, root) => {
264
+ const eslint = parseEslintJson(data, root);
265
+ if (eslint) return [eslint];
266
+ const vulns = data && (data.vulnerabilities || data.advisories);
267
+ const audit = vulns ? parseAudit(JSON.stringify(data)) : null;
268
+ if (audit) return [audit];
269
+ const ls = parseLs(data);
270
+ if (ls) return [ls];
271
+ const outdated = parseOutdated(JSON.stringify(data));
272
+ if (outdated) return [outdated];
273
+ return null;
274
+ };
275
+
276
+ const parseAuditText = (text) => {
277
+ if (`${text ?? ''}`.trim().startsWith('{')) return null;
278
+ const lines = `${text ?? ''}`.split(/\r?\n/);
279
+ const problems = [];
280
+ for (let index = 0; index < lines.length; index++) {
281
+ const match = /^Severity:\s+(\w+)\s*$/.exec(lines[index].trim());
282
+ if (!match) continue;
283
+ const name = (lines[index - 1] || '').trim();
284
+ problems.push(
285
+ problem({
286
+ severity: match[1].toLowerCase(),
287
+ name,
288
+ message: name,
289
+ }),
290
+ );
291
+ }
292
+ if (!problems.length) return null;
293
+ const stats = summary();
294
+ stats.errors = problems.length;
295
+ return report('audit', { summary: stats, problems });
296
+ };
297
+
298
+ const TOOL_SLOTS = 4;
299
+
300
+ const parseToolText = (text, root) => {
301
+ const reports = new Array(TOOL_SLOTS);
302
+ let index = 0;
303
+ const eslint = parseEslintStylish(text, root);
304
+ if (eslint) {
305
+ reports[index] = eslint;
306
+ index += 1;
307
+ }
308
+ const tsc = parseTsc(text, root);
309
+ if (tsc) {
310
+ reports[index] = tsc;
311
+ index += 1;
312
+ }
313
+ const prettier = parsePrettier(text, root);
314
+ if (prettier) {
315
+ reports[index] = prettier;
316
+ index += 1;
317
+ }
318
+ const audit = parseAuditText(text);
319
+ if (audit) {
320
+ reports[index] = audit;
321
+ index += 1;
322
+ }
323
+ reports.length = index;
324
+ return reports;
325
+ };
326
+
327
+ module.exports = {
328
+ parseEslintJson,
329
+ parseEslintStylish,
330
+ parseTsc,
331
+ parsePrettier,
332
+ parseAudit,
333
+ parseOutdated,
334
+ parseLs,
335
+ parseToolJson,
336
+ parseToolText,
337
+ };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { TODO_FILE } = require('./files.js');
3
+ const files = require('./files.js');
4
+ const { TODO_FILE } = files;
4
5
 
5
6
  const flatText = (text) => `${text ?? ''}`.trim().replace(/\n/g, ' ');
6
7
 
package/lib/review.js CHANGED
@@ -3,8 +3,10 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
- const { TODO_FILE, REVIEW_DIR } = require('./files.js');
7
- const { captureBaseline, mergeReview } = require('./review-merge.js');
6
+ const files = require('./files.js');
7
+ const { TODO_FILE, REVIEW_DIR } = files;
8
+ const reviewMerge = require('./review-merge.js');
9
+ const { captureBaseline, mergeReview } = reviewMerge;
8
10
 
9
11
  const createStore = (reviewPath, templates = []) => ({
10
12
  reviewPath,
@@ -240,6 +242,8 @@ const AGENT_INSTRUCTIONS = [
240
242
  '- If not `editing`, set `status` to `partial` if some remain, ' +
241
243
  'or `done` if all are `[x]`',
242
244
  '- Do not redo items already marked `[x]`',
245
+ '- Run the full check with `npm t`',
246
+ '- Run specific test files with `reslop t -- node --test <files>`',
243
247
  ].join('\n');
244
248
 
245
249
  const toInt = (text) => parseInt(text, 10);
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const LIST_PANES = ['files', 'branches', 'commits', 'npm'];
4
+
3
5
  const ACTIONS = {
4
6
  add: ['a'],
5
7
  unstage: ['u'],
@@ -33,21 +35,40 @@ const COMMIT_ACTIONS = {
33
35
  reword: ['r'],
34
36
  fixup: ['f'],
35
37
  drop: ['d'],
38
+ view: ['v'],
36
39
  pull: ['p'],
37
40
  push: ['s'],
38
41
  };
39
42
 
43
+ const NPM_ACTIONS = {
44
+ npm: ['n'],
45
+ npmEdit: ['e'],
46
+ npmNew: ['n'],
47
+ npmDrop: ['d'],
48
+ npmLogs: ['c'],
49
+ npmStop: ['escape'],
50
+ npmRerun: ['r'],
51
+ };
52
+
40
53
  const ACTION_HOT = {
41
54
  prev: '←',
42
55
  next: '→',
56
+ npmStop: 'esc',
43
57
  };
44
58
 
45
59
  const ACTION_LABEL = {
46
60
  revert: 'drop',
47
61
  check: 'x',
48
62
  layout: 'mode',
63
+ view: 'view',
49
64
  code: 'edit',
50
65
  newBranch: 'new',
66
+ npmEdit: 'edit',
67
+ npmNew: 'new',
68
+ npmDrop: 'delete',
69
+ npmLogs: 'cleanup',
70
+ npmStop: 'esc',
71
+ npmRerun: 're-run',
51
72
  quit: 'q',
52
73
  prev: '←',
53
74
  next: '→',
@@ -106,21 +127,8 @@ const BRANCHES_DISABLED = [
106
127
  'prev',
107
128
  'next',
108
129
  ];
109
- const COMMITS_DISABLED = [
110
- 'add',
111
- 'unstage',
112
- 'revert',
113
- 'layout',
114
- 'feedback',
115
- 'todo',
116
- 'code',
117
- 'check',
118
- 'branch',
119
- 'file',
120
- 'diff',
121
- 'prev',
122
- 'next',
123
- ];
130
+ const COMMITS_DISABLED = BRANCHES_DISABLED.filter((id) => id !== 'commit');
131
+ const NPM_DISABLED = [...BRANCHES_DISABLED, 'pull', 'push'];
124
132
 
125
133
  const isTodosTarget = (item) =>
126
134
  !!(item && (item.kind === 'todos' || item.origin === 'todo'));
@@ -143,8 +151,16 @@ const isFixupCommit = (item) =>
143
151
  `${item && item.subject ? item.subject : ''}`.startsWith('fixup!');
144
152
 
145
153
  const commitWriteDisabled = (item) => {
146
- if (!item || !item.sha) return ['amend', 'apply', 'reword', 'fixup', 'drop'];
147
- return [];
154
+ const pending = Boolean(item && item.pending);
155
+ const unstaged = Boolean(item && item.unstagedCurrent);
156
+ if (!item || !item.sha) {
157
+ const off = ['reword', 'fixup', 'drop'];
158
+ if (pending) return off;
159
+ return ['amend', 'apply', ...off];
160
+ }
161
+ if (unstaged) return [];
162
+ if (isFixupCommit(item)) return ['amend'];
163
+ return ['amend', 'apply'];
148
164
  };
149
165
 
150
166
  const commitGitDisabled = (item) => {
@@ -164,6 +180,7 @@ const disabledActions = (pane, item) => {
164
180
  if (pane === 'commits') {
165
181
  return [...COMMITS_DISABLED, ...commitWriteDisabled(item)];
166
182
  }
183
+ if (pane === 'npm') return NPM_DISABLED;
167
184
  if (onTodos) return TODO_DISABLED;
168
185
  return [...DIFF_DISABLED, ...diffGitDisabled(item)];
169
186
  };
@@ -190,41 +207,50 @@ const SCROLL = {
190
207
  delete: 'removeTodo',
191
208
  };
192
209
 
193
- const KEY_TO_ACTION = {};
210
+ const keysForActions = (actions) => {
211
+ const keys = {};
212
+ for (const [id, shortcuts] of Object.entries(actions)) {
213
+ for (const key of shortcuts) keys[key] = id;
214
+ }
215
+ return keys;
216
+ };
217
+
194
218
  const PANE_ONLY = ['branch', 'file', 'diff'];
195
- const FILES_KEYS = {};
196
- for (const key of ACTIONS.branch) FILES_KEYS[key] = 'branch';
197
- for (const id of BRANCH_REMOTE) {
198
- for (const key of BRANCH_ACTIONS[id]) FILES_KEYS[key] = id;
199
- }
200
- const BRANCH_KEYS = {};
201
- for (const id of Object.keys(BRANCH_ACTIONS)) {
202
- for (const key of BRANCH_ACTIONS[id]) BRANCH_KEYS[key] = id;
203
- }
204
- const COMMIT_KEYS = {};
205
- for (const id of Object.keys(COMMIT_ACTIONS)) {
206
- for (const key of COMMIT_ACTIONS[id]) COMMIT_KEYS[key] = id;
207
- }
219
+ const FILES_KEYS = keysForActions({
220
+ branch: ACTIONS.branch,
221
+ pull: BRANCH_ACTIONS.pull,
222
+ push: BRANCH_ACTIONS.push,
223
+ npm: NPM_ACTIONS.npm,
224
+ });
225
+ const BRANCH_KEYS = keysForActions(BRANCH_ACTIONS);
226
+ const COMMIT_KEYS = keysForActions(COMMIT_ACTIONS);
227
+ const NPM_KEYS = {
228
+ e: 'npmEdit',
229
+ n: 'npmNew',
230
+ d: 'npmDrop',
231
+ c: 'npmLogs',
232
+ };
208
233
  const PANE_KEYS = {
209
234
  files: FILES_KEYS,
210
235
  branches: BRANCH_KEYS,
211
236
  commits: COMMIT_KEYS,
237
+ npm: NPM_KEYS,
212
238
  };
213
239
 
214
- for (const id of ACTION_IDS) {
215
- if (PANE_ONLY.includes(id)) continue;
216
- for (const key of ACTIONS[id]) KEY_TO_ACTION[key] = id;
217
- }
218
-
219
- for (const key of Object.keys(SCROLL)) KEY_TO_ACTION[key] = SCROLL[key];
220
- for (const key of Object.keys(HIDDEN_KEYS)) {
221
- KEY_TO_ACTION[key] = HIDDEN_KEYS[key];
222
- }
240
+ const KEY_TO_ACTION = {
241
+ ...keysForActions(
242
+ Object.fromEntries(
243
+ Object.entries(ACTIONS).filter(([id]) => !PANE_ONLY.includes(id)),
244
+ ),
245
+ ),
246
+ ...SCROLL,
247
+ ...HIDDEN_KEYS,
248
+ };
223
249
 
224
250
  const actionFromKey = (key, pane, fileScope) => {
225
- const listPane =
226
- pane === 'files' || pane === 'branches' || pane === 'commits';
227
- if (listPane && (key === 'left' || key === 'right')) return null;
251
+ if (LIST_PANES.includes(pane) && (key === 'left' || key === 'right')) {
252
+ return null;
253
+ }
228
254
  const paneKeys = PANE_KEYS[pane];
229
255
  if (paneKeys && paneKeys[key]) return paneKeys[key];
230
256
  if (pane === 'files') {
@@ -235,7 +261,11 @@ const actionFromKey = (key, pane, fileScope) => {
235
261
  };
236
262
 
237
263
  const actionKeys = (id) =>
238
- ACTIONS[id] ?? BRANCH_ACTIONS[id] ?? COMMIT_ACTIONS[id] ?? [];
264
+ ACTIONS[id] ??
265
+ BRANCH_ACTIONS[id] ??
266
+ COMMIT_ACTIONS[id] ??
267
+ NPM_ACTIONS[id] ??
268
+ [];
239
269
 
240
270
  const actionHot = (id) => ACTION_HOT[id] ?? actionKeys(id)[0];
241
271
 
@@ -323,10 +353,12 @@ const promptFromChoices = (choices) =>
323
353
  choices.map((choice) => `${choice.lead}${choice.label}`).join('');
324
354
 
325
355
  module.exports = {
356
+ LIST_PANES,
326
357
  ACTIONS,
327
358
  ACTION_IDS,
328
359
  BRANCH_ACTIONS,
329
360
  COMMIT_ACTIONS,
361
+ NPM_ACTIONS,
330
362
  ACTION_HOT,
331
363
  ACTION_LABEL,
332
364
  DIFF_DISABLED,
@@ -338,6 +370,7 @@ module.exports = {
338
370
  TODO_DISABLED,
339
371
  BRANCHES_DISABLED,
340
372
  COMMITS_DISABLED,
373
+ NPM_DISABLED,
341
374
  SCROLL,
342
375
  PANE_ONLY,
343
376
  disabledActions,
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const { stateView } = require('./accessors.js');
3
+ const accessors = require('./accessors.js');
4
+ const { stateView } = accessors;
4
5
 
5
- const { pickRepoWrite } = require('./ops.js');
6
+ const ops = require('./ops.js');
7
+ const { pickRepoWrite } = ops;
6
8
 
7
9
  const selectedBranch = (api) => {
8
10
  const cursor = api.ui.nav.branchCursor;
@@ -95,12 +97,6 @@ const onCheckoutBranch = (api) => {
95
97
  if (!api.ui.status) api.ui.status = done;
96
98
  };
97
99
 
98
- const rebaseAsync = (api, onto, done, write) => {
99
- const run = (top) => write(top, onto);
100
- const after = () => refreshAfterWrite(api, { doneStatus: done });
101
- return void api.ui.ops.runBusy('rebasing', done, run, after);
102
- };
103
-
104
100
  const onRebaseBranch = (api) => {
105
101
  if (api.ui.nav.pane !== 'branches') return;
106
102
  if (!api.ui.commits.canCommit()) return;
@@ -108,17 +104,9 @@ const onRebaseBranch = (api) => {
108
104
  if (!entry || entry.current) return;
109
105
  const onto = entry.name;
110
106
  const done = `rebased onto ${onto}`;
111
- const uiOpen = api.ui.uiOpen;
112
- const picked = pickRepoWrite(api.ui.repo, 'rebase', uiOpen);
113
- if (picked.async) {
114
- return void rebaseAsync(api, onto, done, picked.write);
115
- }
116
- const ok = api.ui.ops.runRepo(done, (top) => {
117
- api.ui.repo.rebase(top, onto);
118
- });
119
- if (!ok) return;
120
- refreshAfterWrite(api);
121
- if (!api.ui.status) api.ui.status = done;
107
+ api.ui.ops.runWrite('rebase', [onto], 'rebasing', done, (extra) =>
108
+ refreshAfterWrite(api, extra),
109
+ );
122
110
  };
123
111
 
124
112
  const onDropBranch = (api) => {
@@ -137,29 +125,15 @@ const cancelDropBranch = (api) => {
137
125
  api.ui.status = '';
138
126
  };
139
127
 
140
- const dropAsync = (api, name, done, write) => {
141
- const run = (top) => write(top, name);
142
- const after = () => refreshAfterWrite(api, { doneStatus: done });
143
- return void api.ui.ops.runBusy('dropping', done, run, after);
144
- };
145
-
146
128
  const confirmDropBranch = (api) => {
147
129
  const name = api.state.dropName;
148
130
  api.ui.mode = 'review';
149
131
  api.state.dropName = '';
150
132
  if (!name) return;
151
133
  const done = `dropped ${name}`;
152
- const uiOpen = api.ui.uiOpen;
153
- const picked = pickRepoWrite(api.ui.repo, 'drop', uiOpen);
154
- if (picked.async) {
155
- return void dropAsync(api, name, done, picked.write);
156
- }
157
- const ok = api.ui.ops.runRepo(done, (top) => {
158
- api.ui.repo.drop(top, name);
159
- });
160
- if (!ok) return;
161
- refreshAfterWrite(api);
162
- if (!api.ui.status) api.ui.status = done;
134
+ api.ui.ops.runWrite('drop', [name], 'dropping', done, (extra) =>
135
+ refreshAfterWrite(api, extra),
136
+ );
163
137
  };
164
138
 
165
139
  const onNewBranch = (api) => {
@@ -201,19 +175,9 @@ const finishCreateBranch = (api) => {
201
175
 
202
176
  const onPull = (api) => {
203
177
  if (!api.ui.commits.canCommit()) return;
204
- const repo = api.ui.repo;
205
- const picked = pickRepoWrite(repo, 'pull', api.ui.uiOpen);
206
- if (picked.async) {
207
- const after = () => refreshAfterWrite(api);
208
- const write = (top) => picked.write(top);
209
- return void api.ui.ops.runBusy('pulling', 'pulled', write, after);
210
- }
211
- const ok = api.ui.ops.runRepo('pulled', (top) => {
212
- repo.pull(top);
213
- });
214
- if (!ok) return;
215
- refreshAfterWrite(api);
216
- if (!api.ui.status) api.ui.status = 'pulled';
178
+ api.ui.ops.runWrite('pull', [], 'pulling', 'pulled', (extra) =>
179
+ refreshAfterWrite(api, extra),
180
+ );
217
181
  };
218
182
 
219
183
  const startPush = (api, force) => {
@@ -4,7 +4,8 @@ const files = require('../files.js');
4
4
  const { listPath, isTodoItem, isTodosEntry, isReadOnlyOrigin } = files;
5
5
  const items = require('./items.js');
6
6
  const { needsReload, npmBusyKind, sameBlock } = items;
7
- const { hunkKey } = require('../diff/diff.js');
7
+ const diff = require('../diff/diff.js');
8
+ const { hunkKey } = diff;
8
9
 
9
10
  const nextUnstageOrigin = (item) => {
10
11
  if (item.file.isNew === true) return 'untracked';