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.
- package/CHANGELOG.md +27 -1
- package/README.md +19 -4
- package/bin/reslop.js +4 -2
- package/lib/ansi.js +120 -4
- package/lib/api.js +2 -1
- package/lib/cli.js +21 -9
- package/lib/clipboard.js +6 -3
- package/lib/deps-diff.js +61 -115
- package/lib/deps.js +57 -54
- package/lib/detect.js +4 -2
- package/lib/diff/align.js +2 -1
- package/lib/diff/diff.js +6 -3
- package/lib/diff/display.js +2 -1
- package/lib/diff/inline.js +2 -1
- package/lib/diff/patch.js +2 -1
- package/lib/diff/unit.js +4 -2
- package/lib/editor.js +22 -4
- package/lib/files.js +38 -12
- package/lib/git-branches.js +150 -165
- package/lib/git-deps.js +20 -60
- package/lib/git-worktree.js +508 -0
- package/lib/git.js +23 -580
- package/lib/github.js +2 -1
- package/lib/gitlab.js +2 -1
- package/lib/highlight/bash.js +2 -1
- package/lib/highlight/css.js +2 -1
- package/lib/highlight/csv.js +2 -1
- package/lib/highlight/dot.js +2 -1
- package/lib/highlight/html.js +2 -1
- package/lib/highlight/json.js +2 -1
- package/lib/highlight/log.js +2 -1
- package/lib/highlight/txt.js +2 -1
- package/lib/highlight.js +2 -1
- package/lib/keys.js +12 -6
- package/lib/manifest.js +4 -2
- package/lib/npm-commands.js +592 -0
- package/lib/remote.js +20 -13
- package/lib/render/branches.js +25 -41
- package/lib/render/chrome.js +163 -128
- package/lib/render/commits.js +727 -65
- package/lib/render/diff.js +130 -47
- package/lib/render/files.js +4 -12
- package/lib/render/frame.js +41 -13
- package/lib/render/notes.js +4 -11
- package/lib/render/npm.js +216 -0
- package/lib/render/present.js +2 -1
- package/lib/render/primitives.js +34 -50
- package/lib/render/render.js +2 -1
- package/lib/render/unit.js +14 -11
- package/lib/report-model.js +129 -0
- package/lib/report-parse.js +89 -0
- package/lib/report-render.js +104 -0
- package/lib/report-run.js +166 -0
- package/lib/report-test.js +507 -0
- package/lib/report-tools.js +337 -0
- package/lib/review-merge.js +2 -1
- package/lib/review.js +6 -2
- package/lib/session/actions.js +77 -44
- package/lib/session/branches.js +13 -49
- package/lib/session/change-actions.js +2 -1
- package/lib/session/commit.js +113 -53
- package/lib/session/compose.js +83 -14
- package/lib/session/dispatch.js +42 -22
- package/lib/session/files-pane.js +16 -21
- package/lib/session/items.js +6 -3
- package/lib/session/lifecycle.js +12 -5
- package/lib/session/load.js +4 -2
- package/lib/session/navigation.js +2 -0
- package/lib/session/npm.js +388 -0
- package/lib/session/ops.js +15 -1
- package/lib/session/pointer.js +30 -3
- package/lib/session/review.js +2 -1
- package/lib/session/terminal.js +4 -2
- package/lib/session/update.js +2 -1
- package/lib/session/view.js +41 -4
- package/lib/session.js +44 -22
- package/lib/source.js +6 -3
- package/lib/update.js +5 -9
- package/lib/utilities.js +4 -2
- package/lib/wrap.js +3 -1
- 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
|
+
};
|
package/lib/review-merge.js
CHANGED
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
|
|
7
|
-
const {
|
|
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);
|
package/lib/session/actions.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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] ??
|
|
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,
|
package/lib/session/branches.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const accessors = require('./accessors.js');
|
|
4
|
+
const { stateView } = accessors;
|
|
4
5
|
|
|
5
|
-
const
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
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';
|