reslop 0.2.4 → 0.2.6

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 (109) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/LICENSE +1 -1
  3. package/README.md +12 -13
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -12
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +48 -18
  9. package/lib/dashboard/git.js +128 -0
  10. package/lib/dashboard/model.js +332 -0
  11. package/lib/dashboard/npm.js +193 -0
  12. package/lib/dashboard/tree.js +278 -0
  13. package/lib/deps-diff.js +196 -404
  14. package/lib/deps.js +240 -399
  15. package/lib/detect.js +8 -18
  16. package/lib/diff/align.js +41 -113
  17. package/lib/diff/diff.js +12 -43
  18. package/lib/diff/display.js +25 -36
  19. package/lib/diff/edit.js +6 -20
  20. package/lib/diff/inline.js +138 -141
  21. package/lib/diff/items.js +33 -47
  22. package/lib/diff/parse.js +28 -56
  23. package/lib/diff/patch.js +12 -33
  24. package/lib/diff/unit.js +90 -206
  25. package/lib/editor.js +98 -55
  26. package/lib/files.js +86 -103
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +182 -344
  29. package/lib/git-reword-todo.js +5 -13
  30. package/lib/git-worktree.js +141 -212
  31. package/lib/git.js +21 -49
  32. package/lib/github.js +67 -181
  33. package/lib/gitlab.js +40 -90
  34. package/lib/highlight/bash.js +55 -177
  35. package/lib/highlight/core.js +157 -132
  36. package/lib/highlight/css.js +25 -58
  37. package/lib/highlight/csv.js +19 -37
  38. package/lib/highlight/dart.js +72 -213
  39. package/lib/highlight/dot.js +16 -26
  40. package/lib/highlight/html.js +42 -63
  41. package/lib/highlight/js.js +177 -506
  42. package/lib/highlight/json.js +17 -37
  43. package/lib/highlight/log.js +22 -35
  44. package/lib/highlight/txt.js +9 -22
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +62 -85
  47. package/lib/manifest.js +67 -130
  48. package/lib/npm-commands.js +119 -189
  49. package/lib/remote.js +141 -197
  50. package/lib/render/branches.js +66 -73
  51. package/lib/render/chrome.js +264 -323
  52. package/lib/render/commits.js +187 -500
  53. package/lib/render/dash-activity.js +212 -0
  54. package/lib/render/dash-blocks.js +245 -0
  55. package/lib/render/dash-table.js +290 -0
  56. package/lib/render/dashboard.js +62 -0
  57. package/lib/render/diff.js +123 -228
  58. package/lib/render/files.js +45 -99
  59. package/lib/render/frame.js +129 -183
  60. package/lib/render/notes.js +144 -160
  61. package/lib/render/npm.js +234 -112
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +131 -170
  64. package/lib/render/render.js +5 -14
  65. package/lib/render/report.js +43 -0
  66. package/lib/render/tiles.js +260 -0
  67. package/lib/render/unit.js +15 -28
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -38
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -78
  72. package/lib/report-test.js +256 -141
  73. package/lib/report-tools.js +96 -161
  74. package/lib/review-merge.js +114 -152
  75. package/lib/review.js +115 -189
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +89 -75
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +82 -63
  80. package/lib/session/branches.js +193 -247
  81. package/lib/session/change-actions.js +171 -214
  82. package/lib/session/commit.js +280 -342
  83. package/lib/session/compose.js +559 -674
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +164 -157
  86. package/lib/session/edit-click.js +57 -0
  87. package/lib/session/files-pane.js +85 -53
  88. package/lib/session/items.js +73 -104
  89. package/lib/session/lifecycle.js +403 -541
  90. package/lib/session/load.js +48 -68
  91. package/lib/session/navigation.js +96 -87
  92. package/lib/session/npm.js +409 -345
  93. package/lib/session/ops.js +196 -63
  94. package/lib/session/pointer.js +23 -15
  95. package/lib/session/progress.js +33 -21
  96. package/lib/session/review.js +33 -54
  97. package/lib/session/tasks.js +333 -0
  98. package/lib/session/templates.js +51 -59
  99. package/lib/session/terminal.js +85 -136
  100. package/lib/session/unit.js +3 -4
  101. package/lib/session/update.js +76 -97
  102. package/lib/session/view.js +52 -83
  103. package/lib/session/watch.js +208 -204
  104. package/lib/session.js +112 -237
  105. package/lib/update.js +45 -116
  106. package/lib/utilities.js +67 -89
  107. package/lib/wrap.js +33 -44
  108. package/package.json +1 -1
  109. package/lib/session/todos.js +0 -293
@@ -1,78 +1,111 @@
1
1
  'use strict';
2
2
 
3
+ const utilities = require('./utilities.js');
4
+ const { unquote } = utilities;
5
+
3
6
  const NOTICE_HEAD =
4
7
  'reslop: reduced output; passing results are omitted ' +
5
8
  'and similar problems are grouped';
6
9
 
7
- const NOTICE = [
8
- NOTICE_HEAD,
9
- 'If reduced output hides information needed for debugging,',
10
- 'rerun the same command with RESLOP_OUTPUT=raw.',
11
- ].join('\n');
10
+ const notice = (rawFile) => {
11
+ const target = rawFile
12
+ ? `the raw log \`.log/${rawFile}\``
13
+ : 'the matching raw log under `.log/`';
14
+ const hint = `Agent: if this report hides a needed detail, read ${target}.`;
15
+ return `${NOTICE_HEAD}\n${hint}`;
16
+ };
12
17
 
13
- const SUMMARY_ROWS = 6;
14
- const PROBLEM_FIELDS = 7;
18
+ const addRow = (rows, key, value) => {
19
+ if (value === null || value === undefined || value === '') return;
20
+ rows.push([key, `${value}`]);
21
+ };
15
22
 
16
- const writeRow = (rows, index, key, value) => {
17
- if (value === null || value === undefined || value === '') return index;
18
- rows[index] = [key, `${value}`];
19
- return index + 1;
23
+ const isComparison = (line) => {
24
+ const match = /^(.*) (?:!==|===|!=|==) (\S.*)$/.exec(line.trim());
25
+ return !!match && match[1].trim() !== '';
20
26
  };
21
27
 
22
- const summaryRows = (stats) => {
23
- const rows = new Array(SUMMARY_ROWS);
24
- let index = 0;
25
- index = writeRow(rows, index, 'passed', stats.passed);
26
- index = writeRow(rows, index, 'failed', stats.failed);
27
- index = writeRow(rows, index, 'skipped', stats.skipped);
28
- index = writeRow(rows, index, 'errors', stats.errors);
29
- index = writeRow(rows, index, 'warnings', stats.warnings);
30
- index = writeRow(rows, index, 'duration', stats.duration);
31
- rows.length = index;
32
- return rows;
28
+ const isDiffHeader = (line) => {
29
+ const text = line.trim().replace(/^[+-]\s*/, '');
30
+ return /^actual\s+[+-]?\s*expected$/.test(text);
33
31
  };
34
32
 
35
- const problemRows = (item) => {
36
- const extra = item.extra;
37
- let size = PROBLEM_FIELDS + extra.length;
38
- if (item.count > 1) size += 1;
39
- if (item.omitted > 0) size += 1;
40
- const rows = new Array(size);
41
- let index = 0;
42
- index = writeRow(rows, index, 'severity', item.severity);
43
- index = writeRow(rows, index, 'duration', item.duration);
44
- index = writeRow(rows, index, 'code', item.code);
45
- index = writeRow(rows, index, 'operator', item.operator);
46
- index = writeRow(rows, index, 'expected', item.expected);
47
- index = writeRow(rows, index, 'actual', item.actual);
48
- index = writeRow(rows, index, 'message', item.message);
49
- for (let i = 0; i < extra.length; i++) {
50
- const pair = extra[i];
51
- index = writeRow(rows, index, pair[0], pair[1]);
33
+ const isMarkedDiff = (line) => /^[+-](?:\s|$)/.test(line.trim());
34
+
35
+ const sameValue = (line, value) => {
36
+ if (!value) return false;
37
+ const text = line.trim();
38
+ return text === value || unquote(text) === value;
39
+ };
40
+
41
+ const startsDiff = (line, item) =>
42
+ isComparison(line) ||
43
+ isDiffHeader(line) ||
44
+ isMarkedDiff(line) ||
45
+ sameValue(line, item.expected) ||
46
+ sameValue(line, item.actual);
47
+
48
+ const displayMessage = (item) => {
49
+ const values = item.expected !== '' || item.actual !== '';
50
+ const lines = [];
51
+ for (const line of `${item.message}`.split('\n')) {
52
+ if (line.trim() === '') continue;
53
+ if (values && startsDiff(line, item)) break;
54
+ lines.push(line);
52
55
  }
53
- if (item.count > 1) index = writeRow(rows, index, 'count', item.count);
54
- if (item.omitted > 0) {
55
- index = writeRow(rows, index, 'omitted', item.omitted);
56
+ if (lines.length && lines[0].trimEnd().endsWith(':')) {
57
+ lines[0] = lines[0].trimEnd().slice(0, -1);
56
58
  }
57
- rows.length = index;
59
+ if (lines.length && lines[0].trim() === '') lines.shift();
60
+ return lines.join('\n');
61
+ };
62
+
63
+ const summaryRows = (stats) => {
64
+ const rows = [];
65
+ addRow(rows, 'passed', stats.passed);
66
+ addRow(rows, 'failed', stats.failed);
67
+ addRow(rows, 'skipped', stats.skipped);
68
+ addRow(rows, 'errors', stats.errors);
69
+ addRow(rows, 'warnings', stats.warnings);
70
+ addRow(rows, 'duration', stats.duration);
58
71
  return rows;
59
72
  };
60
73
 
61
- const markdownValue = (value) => {
62
- const text = `${value}`;
63
- if (!text.includes('\n')) return text;
64
- return `\n${text}`;
74
+ const problemRows = (item) => {
75
+ const rows = [];
76
+ if (item.severity !== 'error') addRow(rows, 'severity', item.severity);
77
+ addRow(rows, 'duration', item.duration);
78
+ addRow(rows, 'code', item.code);
79
+ addRow(rows, 'operator', item.operator);
80
+ addRow(rows, 'expected', item.expected);
81
+ addRow(rows, 'actual', item.actual);
82
+ for (const pair of item.extra) addRow(rows, pair[0], pair[1]);
83
+ if (item.count > 1) addRow(rows, 'count', item.count);
84
+ if (item.omitted > 0) addRow(rows, 'omitted', item.omitted);
85
+ return rows;
65
86
  };
66
87
 
88
+ const prefixLines = (prefix, text) =>
89
+ `${text}`
90
+ .replace(/\n+$/, '')
91
+ .split('\n')
92
+ .map((line) => `${prefix}${line}`);
93
+
94
+ const quoteLines = (text) => prefixLines('│ ', text);
95
+
67
96
  const markdownProblem = (item) => {
68
97
  const title = item.name || item.message || item.code || 'problem';
69
98
  const lines = ['', `## ${title}`, ''];
70
99
  for (const row of problemRows(item)) {
71
- lines.push(`- ${row[0]}: ${markdownValue(row[1])}`);
100
+ const value = row[1].includes('\n') ? `\n${row[1]}` : row[1];
101
+ lines.push(`- ${row[0]}: ${value}`);
72
102
  }
73
- for (const trace of item.traces) {
74
- if (!trace) continue;
75
- lines.push(`- trace:\n${trace}`);
103
+ const message = displayMessage(item);
104
+ if (message) lines.push('', ...prefixLines('> ', message), '');
105
+ if (item.trace) lines.push(`- trace:\n${item.trace}`);
106
+ if (item.related.length) {
107
+ lines.push('- related:');
108
+ for (const row of item.related) lines.push(` - ${row}`);
76
109
  }
77
110
  return lines;
78
111
  };
@@ -82,23 +115,33 @@ const markdownReport = (item) => {
82
115
  for (const row of summaryRows(item.summary)) {
83
116
  lines.push(`${row[0]}: ${row[1]}`);
84
117
  }
85
- for (const problem of item.problems) {
86
- lines.push(...markdownProblem(problem));
87
- }
118
+ for (const entry of item.problems) lines.push(...markdownProblem(entry));
88
119
  return lines;
89
120
  };
90
121
 
91
- const renderMarkdown = (doc) => {
92
- const lines = [NOTICE, '', `exit: ${doc.exit}`];
122
+ const renderDocument = (doc, rawFile) => {
123
+ const lines = [notice(rawFile)];
93
124
  for (const item of doc.reports) lines.push(...markdownReport(item));
94
125
  if (doc.stderr) lines.push('', '# stderr', '', doc.stderr);
126
+ lines.push('', `exit: ${doc.exit}`);
95
127
  return `${lines.join('\n').replace(/\n+$/, '')}\n`;
96
128
  };
97
129
 
98
- const renderDocument = (doc) => renderMarkdown(doc);
130
+ const loggedText = (text, rawFile) => {
131
+ const body = `${text ?? ''}`;
132
+ if (body.startsWith(NOTICE_HEAD)) return body;
133
+ const head = notice(rawFile);
134
+ if (!body.trim()) return `${head}\n`;
135
+ return `${head}\n\n${body}`;
136
+ };
99
137
 
100
138
  module.exports = {
101
- NOTICE,
102
- renderMarkdown,
139
+ NOTICE_HEAD,
140
+ notice,
141
+ summaryRows,
142
+ problemRows,
143
+ displayMessage,
144
+ quoteLines,
145
+ loggedText,
103
146
  renderDocument,
104
147
  };
package/lib/report-run.js CHANGED
@@ -7,20 +7,16 @@ const { spawn } = childProcess;
7
7
  const utilities = require('./utilities.js');
8
8
  const { spawnBase } = utilities;
9
9
  const npmCommands = require('./npm-commands.js');
10
- const { writeLog } = npmCommands;
10
+ const { nextLogFile, saveLogs } = npmCommands;
11
11
  const reportParse = require('./report-parse.js');
12
12
  const { toolHint, buildDocument } = reportParse;
13
13
  const reportRender = require('./report-render.js');
14
14
  const { renderDocument } = reportRender;
15
+ const runs = require('./runs.js');
16
+ const { RunRecorder, commandLabel, summarizeDocument } = runs;
15
17
 
16
- const OUTPUTS = new Set(['md', 'raw']);
17
-
18
- const outputMode = (env) => {
19
- const value = env.RESLOP_OUTPUT;
20
- if (value) return OUTPUTS.has(value) ? value : '';
21
- return 'md';
22
- };
23
-
18
+ const OUTPUTS = ['md', 'raw'];
19
+ const PIPE = ['ignore', 'pipe', 'pipe'];
24
20
  const CMD_SAFE = /^[\w./:=@+-]+$/;
25
21
 
26
22
  const quoteCmdArg = (arg) => {
@@ -35,34 +31,18 @@ const winCommand = (program, args) => {
35
31
  return `"${parts.join(' ')}"`;
36
32
  };
37
33
 
38
- const spawnCommand = (program, args, options) => {
39
- const { cwd, env, stdio } = options;
40
- const base = { cwd, env, stdio };
34
+ const spawnCommand = (command, options) => {
35
+ const { program, args } = command;
41
36
  if (process.platform !== 'win32') {
42
- return spawn(program, args, spawnBase(base));
37
+ return spawn(program, args, spawnBase(options));
43
38
  }
44
- const command = winCommand(program, args);
45
39
  const shell = process.env.comspec || 'cmd.exe';
46
- return spawn(
47
- shell,
48
- ['/d', '/s', '/c', command],
49
- spawnBase({ ...base, windowsVerbatimArguments: true }),
50
- );
40
+ const line = winCommand(program, args);
41
+ const opts = spawnBase({ ...options, windowsVerbatimArguments: true });
42
+ return spawn(shell, ['/d', '/s', '/c', line], opts);
51
43
  };
52
44
 
53
- const waitStatus = (child) =>
54
- new Promise((resolve) => {
55
- let settled = false;
56
- const finish = (status) => {
57
- if (settled) return;
58
- settled = true;
59
- resolve(status);
60
- };
61
- child.on('error', () => finish(1));
62
- child.on('close', (status) => finish(status === null ? 1 : status));
63
- });
64
-
65
- const collect = (child) =>
45
+ const collect = (child, onOut, onErr) =>
66
46
  new Promise((resolve) => {
67
47
  let out = '';
68
48
  let err = '';
@@ -76,91 +56,120 @@ const collect = (child) =>
76
56
  child.stdout.setEncoding('utf8');
77
57
  child.stdout.on('data', (chunk) => {
78
58
  out += chunk;
59
+ if (onOut) onOut(chunk);
79
60
  });
80
61
  }
81
62
  if (child.stderr) {
82
63
  child.stderr.setEncoding('utf8');
83
64
  child.stderr.on('data', (chunk) => {
84
65
  err += chunk;
66
+ if (onErr) onErr(chunk);
85
67
  });
86
68
  }
87
69
  child.on('error', (error) => {
88
70
  err += `${error.message}\n`;
89
71
  finish(1);
90
72
  });
91
- child.on('close', (status) => finish(status === null ? 1 : status));
73
+ child.on('close', (status) => finish(status ?? 1));
92
74
  });
93
75
 
94
- const commandArgs = (argv) => {
95
- if (argv[0] !== 't' || argv[1] !== '--' || !argv[2]) return null;
96
- return { program: argv[2], args: argv.slice(3) };
76
+ const beginRun = (cwd, command) =>
77
+ new RunRecorder(cwd, commandLabel(command.program, command.args));
78
+
79
+ const documentOf = (command, result, cwd) => {
80
+ const hint = toolHint(command.program, command.args);
81
+ const { stdout, stderr, status } = result;
82
+ return buildDocument(stdout, stderr, cwd, status, hint);
97
83
  };
98
84
 
99
- const usage = (stderr) => {
100
- stderr.write('reslop: missing command\n');
101
- stderr.write('Usage: reslop t -- <program> [args]\n');
102
- return 1;
85
+ const rawStreams = (stdout, stderr) => {
86
+ if (!stderr.trim()) return stdout;
87
+ const lead = stdout.endsWith('\n') || !stdout ? stdout : `${stdout}\n`;
88
+ return `${lead}# stderr\n${stderr}`;
103
89
  };
104
90
 
105
- const runInherit = async (spawnFn, program, args, options) => {
106
- let child;
91
+ const runInherit = async (command, cwd, env) => {
107
92
  try {
108
- child = spawnFn(program, args, { ...options, stdio: 'inherit' });
93
+ const child = spawnCommand(command, { cwd, env, stdio: 'inherit' });
94
+ return (await collect(child)).status;
109
95
  } catch {
110
96
  return 1;
111
97
  }
112
- return waitStatus(child);
113
98
  };
114
99
 
115
- const runCapture = async (proc, deps = {}) => {
116
- const env = proc.env || {};
117
- const argv = proc.argv.slice(2);
118
- const command = commandArgs(argv);
119
- if (!command) return usage(proc.stderr);
120
- const mode = outputMode(env);
121
- if (!mode) {
122
- proc.stderr.write(`reslop: unknown RESLOP_OUTPUT ${env.RESLOP_OUTPUT}\n`);
100
+ const runRaw = async (proc, command, cwd, env) => {
101
+ const recorder = beginRun(cwd, command);
102
+ let child;
103
+ try {
104
+ child = spawnCommand(command, { cwd, env, stdio: PIPE });
105
+ } catch {
106
+ recorder.finish(1);
123
107
  return 1;
124
108
  }
125
- const cwd = deps.cwd ? deps.cwd : proc.cwd();
126
- const spawnFn = deps.spawn || spawnCommand;
127
- const capture = env.RESLOP_CAPTURE === '1';
128
- if (mode === 'raw' || capture) {
129
- return runInherit(spawnFn, command.program, command.args, { cwd, env });
130
- }
131
- const childEnv = { ...env, RESLOP_CAPTURE: '1' };
109
+ const onOut = (chunk) => {
110
+ proc.stdout.write(chunk);
111
+ recorder.feed(chunk);
112
+ };
113
+ const onErr = (chunk) => proc.stderr.write(chunk);
114
+ const result = await collect(child, onOut, onErr);
115
+ const doc = documentOf(command, result, cwd);
116
+ recorder.finish(result.status, summarizeDocument(doc));
117
+ return result.status;
118
+ };
119
+
120
+ const runReport = async (proc, command, cwd, env) => {
121
+ const recorder = beginRun(cwd, command);
122
+ const fail = (error) => proc.stderr.write(`reslop: ${error.message}\n`);
132
123
  let child;
133
124
  try {
134
- child = spawnFn(command.program, command.args, {
135
- cwd,
136
- env: childEnv,
137
- stdio: ['ignore', 'pipe', 'pipe'],
138
- });
125
+ const childEnv = { ...env, RESLOP_CAPTURE: '1' };
126
+ child = spawnCommand(command, { cwd, env: childEnv, stdio: PIPE });
139
127
  } catch (error) {
140
- proc.stderr.write(`reslop: ${error.message}\n`);
128
+ fail(error);
129
+ recorder.finish(1);
141
130
  return 1;
142
131
  }
143
- const result = await collect(child);
144
- const hint = toolHint(command.program, command.args);
145
- const doc = buildDocument(
146
- result.stdout,
147
- result.stderr,
148
- cwd,
149
- result.status,
150
- hint,
151
- );
152
- const text = renderDocument(doc);
132
+ const result = await collect(child, (chunk) => recorder.feed(chunk));
133
+ const doc = documentOf(command, result, cwd);
134
+ let slot = null;
135
+ try {
136
+ slot = nextLogFile(cwd, path.basename(command.program));
137
+ } catch (error) {
138
+ fail(error);
139
+ }
140
+ const text = renderDocument(doc, slot ? slot.rawName : '');
141
+ recorder.finish(result.status, summarizeDocument(doc));
153
142
  proc.stdout.write(text);
143
+ if (!slot) return result.status;
154
144
  try {
155
- writeLog(cwd, path.basename(command.program), text);
145
+ await saveLogs(slot, text, rawStreams(result.stdout, result.stderr));
156
146
  } catch (error) {
157
- proc.stderr.write(`reslop: ${error.message}\n`);
147
+ fail(error);
158
148
  }
159
149
  return result.status;
160
150
  };
161
151
 
152
+ const runCapture = async (proc, deps = {}) => {
153
+ const argv = proc.argv.slice(2);
154
+ if (argv[1] !== '--' || !argv[2]) {
155
+ proc.stderr.write('reslop: missing command\n');
156
+ proc.stderr.write('Usage: reslop t -- <program> [args]\n');
157
+ return 1;
158
+ }
159
+ const command = { program: argv[2], args: argv.slice(3) };
160
+ const env = proc.env ?? {};
161
+ const mode = env.RESLOP_OUTPUT || 'md';
162
+ if (!OUTPUTS.includes(mode)) {
163
+ proc.stderr.write(`reslop: unknown RESLOP_OUTPUT ${env.RESLOP_OUTPUT}\n`);
164
+ return 1;
165
+ }
166
+ const cwd = deps.cwd ?? proc.cwd();
167
+ if (env.RESLOP_CAPTURE === '1') return runInherit(command, cwd, env);
168
+ if (mode === 'raw') return runRaw(proc, command, cwd, env);
169
+ return runReport(proc, command, cwd, env);
170
+ };
171
+
162
172
  module.exports = {
163
- outputMode,
164
173
  winCommand,
165
174
  runCapture,
166
175
  };