reslop 0.2.5 → 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 (105) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/LICENSE +1 -1
  3. package/README.md +11 -11
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -8
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +39 -47
  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 +197 -402
  14. package/lib/deps.js +113 -240
  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 +76 -144
  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 +79 -178
  25. package/lib/editor.js +52 -69
  26. package/lib/files.js +71 -89
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +147 -272
  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 +155 -141
  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 +14 -14
  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 +19 -25
  44. package/lib/highlight/txt.js +7 -10
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +49 -82
  47. package/lib/manifest.js +67 -126
  48. package/lib/npm-commands.js +120 -169
  49. package/lib/remote.js +140 -190
  50. package/lib/render/branches.js +63 -61
  51. package/lib/render/chrome.js +220 -333
  52. package/lib/render/commits.js +160 -361
  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 -258
  58. package/lib/render/files.js +40 -85
  59. package/lib/render/frame.js +78 -117
  60. package/lib/render/notes.js +139 -210
  61. package/lib/render/npm.js +213 -177
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +72 -174
  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 -31
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -37
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -77
  72. package/lib/report-test.js +221 -99
  73. package/lib/report-tools.js +96 -154
  74. package/lib/review-merge.js +33 -41
  75. package/lib/review.js +68 -129
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +53 -91
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +61 -43
  80. package/lib/session/branches.js +166 -222
  81. package/lib/session/change-actions.js +49 -119
  82. package/lib/session/commit.js +274 -355
  83. package/lib/session/compose.js +556 -714
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +162 -170
  86. package/lib/session/files-pane.js +76 -44
  87. package/lib/session/items.js +6 -17
  88. package/lib/session/lifecycle.js +393 -466
  89. package/lib/session/load.js +1 -14
  90. package/lib/session/navigation.js +2 -2
  91. package/lib/session/npm.js +403 -420
  92. package/lib/session/ops.js +5 -13
  93. package/lib/session/pointer.js +16 -14
  94. package/lib/session/progress.js +39 -30
  95. package/lib/session/review.js +1 -3
  96. package/lib/session/tasks.js +276 -235
  97. package/lib/session/templates.js +51 -59
  98. package/lib/session/terminal.js +85 -132
  99. package/lib/session/view.js +47 -81
  100. package/lib/session/watch.js +196 -212
  101. package/lib/session.js +81 -161
  102. package/lib/update.js +44 -111
  103. package/lib/utilities.js +46 -88
  104. package/lib/wrap.js +29 -55
  105. package/package.json +1 -1
@@ -36,7 +36,7 @@ const writeManifest = (root, pkg) => {
36
36
  };
37
37
 
38
38
  const scriptEntries = (pkg) => {
39
- const scripts = pkg && pkg.scripts;
39
+ const scripts = pkg.scripts;
40
40
  if (!scripts || typeof scripts !== 'object') return [];
41
41
  const entries = [];
42
42
  for (const name of Object.keys(scripts)) {
@@ -56,11 +56,8 @@ const binNames = (pkg) => {
56
56
  return base ? [base] : [];
57
57
  }
58
58
  if (!bin || typeof bin !== 'object' || Array.isArray(bin)) return [];
59
- const names = [];
60
- for (const name of Object.keys(bin)) {
61
- if (typeof bin[name] === 'string' && bin[name]) names.push(name);
62
- }
63
- return names;
59
+ const isPath = (name) => typeof bin[name] === 'string' && bin[name] !== '';
60
+ return Object.keys(bin).filter(isPath);
64
61
  };
65
62
 
66
63
  const declaredNames = (pkg) => {
@@ -68,7 +65,7 @@ const declaredNames = (pkg) => {
68
65
  for (const section of SECTIONS) {
69
66
  const map = pkg[section];
70
67
  if (!map || typeof map !== 'object') continue;
71
- for (const name of Object.keys(map)) names.push(name);
68
+ names.push(...Object.keys(map));
72
69
  }
73
70
  return names;
74
71
  };
@@ -107,25 +104,50 @@ const isPassingTest = (line) => {
107
104
  if (!text) return false;
108
105
  if (/^\s*[✔✓√] /.test(plain)) return true;
109
106
  if (/^ok \d+ /.test(text)) return true;
110
- if (/^\s*PASS\b/.test(plain)) return true;
111
- return false;
107
+ return /^\s*PASS\b/.test(plain);
112
108
  };
113
109
 
114
- const isNodeFrame = (line) => stripAnsi(line).includes('(node:');
115
-
116
110
  const indentStack = (line) => {
117
- const plain = stripAnsi(line);
118
- if (!/^\s*at\s/.test(plain)) return line;
119
- const lead = line.match(/^\s*/)?.[0] ?? '';
120
- const body = lead ? line.slice(lead.length) : line;
121
- return ` ${body}`;
111
+ if (!/^\s*at\s/.test(stripAnsi(line))) return line;
112
+ return ` ${line.trimStart()}`;
122
113
  };
123
114
 
124
115
  const withoutNodeFrame = (line) => {
125
- if (!isNodeFrame(line)) return line;
126
116
  const plain = stripAnsi(line);
127
- if (/\{\s*$/.test(plain)) return '{';
128
- return null;
117
+ if (!plain.includes('(node:')) return line;
118
+ return /\{\s*$/.test(plain) ? '{' : null;
119
+ };
120
+
121
+ const withSep = (dir) => {
122
+ const sep = dir.includes('\\') ? '\\' : '/';
123
+ if (dir.endsWith(sep)) return dir;
124
+ return `${dir}${sep}`;
125
+ };
126
+
127
+ const stripOne = (text, dir) => {
128
+ if (!dir) return text;
129
+ let next = text.split(withSep(dir)).join('');
130
+ if (next.includes(dir)) next = next.split(dir).join('.');
131
+ return next;
132
+ };
133
+
134
+ const relativize = (text, root) => {
135
+ if (!root) return text;
136
+ const slash = root.replaceAll('\\', '/');
137
+ const back = slash.replaceAll('/', '\\');
138
+ const resolved = path.resolve(root).replaceAll('\\', '/');
139
+ const resolvedBack = resolved.replaceAll('/', '\\');
140
+ let next = text;
141
+ for (const dir of [slash, back, resolved, resolvedBack]) {
142
+ next = stripOne(next, dir);
143
+ }
144
+ return next;
145
+ };
146
+
147
+ const traceLine = (line, root) => {
148
+ const next = withoutNodeFrame(line);
149
+ if (next === null) return null;
150
+ return relativize(indentStack(next), root);
129
151
  };
130
152
 
131
153
  const objectEnd = (lines, start) => {
@@ -162,19 +184,18 @@ const showValue = (value) => {
162
184
  };
163
185
 
164
186
  const parseObject = (lines) => {
165
- const body = lines.slice(1, -1);
166
187
  try {
167
188
  const value = JSON.parse(lines.join('\n'));
168
189
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
169
190
  return null;
170
191
  }
171
- const entries = [];
172
- for (const key of Object.keys(value)) {
173
- entries.push([key, showValue(value[key])]);
174
- }
192
+ const entries = Object.keys(value).map((key) => [
193
+ key,
194
+ showValue(value[key]),
195
+ ]);
175
196
  return entries.length ? entries : null;
176
197
  } catch {
177
- return parseInspect(body);
198
+ return parseInspect(lines.slice(1, -1));
178
199
  }
179
200
  };
180
201
 
@@ -182,18 +203,22 @@ const TABLE_KEY = '\x1b[900m';
182
203
  const TABLE_VAL = '\x1b[901m';
183
204
 
184
205
  const formatTable = (entries) => {
185
- let keyW = 0;
206
+ const prepared = [];
207
+ let keyWidth = 0;
186
208
  for (const entry of entries) {
187
- keyW = Math.max(keyW, visibleWidth(entry[0]));
209
+ const lines = `${entry[1]}`.split('\n').filter((line) => line.trim());
210
+ if (!lines.length) continue;
211
+ prepared.push([entry[0], lines]);
212
+ keyWidth = Math.max(keyWidth, visibleWidth(entry[0]));
188
213
  }
189
214
  const rows = [];
190
- for (const entry of entries) {
191
- const parts = `${entry[1]}`.split('\n');
192
- for (let i = 0; i < parts.length; i++) {
215
+ for (const entry of prepared) {
216
+ const lines = entry[1];
217
+ for (let i = 0; i < lines.length; i++) {
193
218
  const name = i === 0 ? entry[0] : '';
194
- const pad = ' '.repeat(Math.max(0, keyW - visibleWidth(name)));
219
+ const pad = ' '.repeat(Math.max(0, keyWidth - visibleWidth(name)));
195
220
  const key = ` ${pad}${name} `;
196
- const value = ` ${parts[i]} `;
221
+ const value = ` ${lines[i]} `;
197
222
  rows.push(`${TABLE_KEY}${key}${TABLE_VAL} ${value}`);
198
223
  }
199
224
  }
@@ -204,67 +229,30 @@ const foldObjects = (lines) => {
204
229
  const out = [];
205
230
  for (let i = 0; i < lines.length; i++) {
206
231
  const end = objectEnd(lines, i);
207
- if (end < 0) {
208
- out.push(lines[i]);
209
- continue;
210
- }
211
- const entries = parseObject(lines.slice(i, end + 1));
232
+ const entries = end < 0 ? null : parseObject(lines.slice(i, end + 1));
212
233
  if (!entries) {
213
234
  out.push(lines[i]);
214
235
  continue;
215
236
  }
216
237
  if (out.length && out[out.length - 1] !== '') out.push('');
217
- for (const row of formatTable(entries)) out.push(row);
238
+ out.push(...formatTable(entries));
218
239
  i = end;
219
240
  }
220
241
  return out;
221
242
  };
222
243
 
223
- const withSep = (dir) => {
224
- const sep = dir.includes('\\') ? '\\' : '/';
225
- if (dir.endsWith(sep)) return dir;
226
- return `${dir}${sep}`;
227
- };
228
-
229
- const stripOne = (text, dir) => {
230
- if (!dir) return text;
231
- let next = text.split(withSep(dir)).join('');
232
- if (next.includes(dir)) next = next.split(dir).join('.');
233
- return next;
234
- };
235
-
236
- const relativize = (text, root) => {
237
- if (!root) return text;
238
- const slash = root.replaceAll('\\', '/');
239
- const back = slash.replaceAll('/', '\\');
240
- const resolved = path.resolve(root).replaceAll('\\', '/');
241
- const resolvedBack = resolved.replaceAll('/', '\\');
242
- let next = text;
243
- for (const dir of [slash, back, resolved, resolvedBack]) {
244
- next = stripOne(next, dir);
245
- }
246
- return next;
247
- };
248
-
249
244
  const reduceOutput = (text, root, status) => {
250
- const lines = `${text ?? ''}`.split(/\r?\n/);
251
245
  const kept = [];
252
- for (const line of lines) {
246
+ for (const line of `${text ?? ''}`.split(/\r?\n/)) {
253
247
  if (isPassingTest(line)) continue;
254
- const next = withoutNodeFrame(line);
255
- if (next === null) continue;
256
- kept.push(relativize(indentStack(next), root));
248
+ const next = traceLine(line, root);
249
+ if (next !== null) kept.push(next);
257
250
  }
258
251
  const folded = foldObjects(kept);
259
252
  while (folded.length && folded[folded.length - 1] === '') folded.pop();
260
- if (status === '') {
261
- if (!folded.length) return '';
262
- return `${folded.join('\n')}\n`;
263
- }
264
- const code = status === undefined || status === null ? 'null' : status;
265
- const exit = `exit ${code}`;
266
- if (!folded.length) return `${exit}\n`;
267
- return `${folded.join('\n')}\n${exit}\n`;
253
+ if (status !== '') folded.push(`exit ${status ?? 'null'}`);
254
+ if (!folded.length) return '';
255
+ return `${folded.join('\n')}\n`;
268
256
  };
269
257
 
270
258
  const logFileName = (command, date, index) => {
@@ -308,10 +296,9 @@ const readLogNames = (dir) => {
308
296
 
309
297
  const staleLogFiles = (root, now = new Date()) => {
310
298
  const dir = path.join(root, LOG_DIR);
311
- const names = readLogNames(dir);
312
299
  const files = [];
313
300
  let bytes = 0;
314
- for (const name of names) {
301
+ for (const name of readLogNames(dir)) {
315
302
  if (!name.endsWith('.log') || !isStaleLog(name, now)) continue;
316
303
  const full = path.join(dir, name);
317
304
  let stat;
@@ -334,31 +321,22 @@ const removeStaleLogs = (root, now = new Date()) => {
334
321
  return found.bytes;
335
322
  };
336
323
 
337
- const scaled = (value) => {
338
- if (value >= 10) return `${Math.round(value)}`;
339
- return `${Math.round(value * 10) / 10}`;
340
- };
341
-
342
- const formatSize = (bytes) => {
343
- const size = bytes > 0 ? bytes : 0;
344
- if (size < 1024) return `${size}b`;
345
- const kb = size / 1024;
346
- if (kb < 1024) return `${scaled(kb)}k`;
347
- return `${scaled(kb / 1024)}m`;
324
+ const logSlot = (dir, name) => {
325
+ const rawName = name.replace(/\.log$/, '.raw.log');
326
+ const full = path.join(dir, name);
327
+ const rawFull = path.join(dir, rawName);
328
+ return { name, full, dir, rawName, rawFull };
348
329
  };
349
330
 
350
331
  const nextLogFile = (root, command, now = new Date()) => {
351
332
  const date = dateStamp(now);
352
333
  const dir = path.join(root, LOG_DIR);
353
- let index = 1;
354
- let name = logFileName(command, date, index);
355
- let full = path.join(dir, name);
356
- while (index < 100 && fs.existsSync(full)) {
357
- index += 1;
358
- name = logFileName(command, date, index);
359
- full = path.join(dir, name);
334
+ let slot = null;
335
+ for (let index = 1; index <= 100; index++) {
336
+ slot = logSlot(dir, logFileName(command, date, index));
337
+ if (!fs.existsSync(slot.full) && !fs.existsSync(slot.rawFull)) break;
360
338
  }
361
- return { name, full, dir };
339
+ return slot;
362
340
  };
363
341
 
364
342
  const logText = (text) =>
@@ -367,22 +345,12 @@ const logText = (text) =>
367
345
  .map((line) => line.trimEnd())
368
346
  .join('\n');
369
347
 
370
- const writeLog = (root, command, text, now) => {
371
- const file = nextLogFile(root, command, now);
372
- fs.mkdirSync(file.dir, { recursive: true });
373
- fs.writeFileSync(file.full, logText(text));
374
- return file.name;
375
- };
376
-
377
- const parseScriptLine = (text) => {
378
- const raw = `${text ?? ''}`.trim();
379
- const at = raw.indexOf(': ');
380
- if (at <= 0) return null;
381
- const name = raw.slice(0, at).trim();
382
- const command = raw.slice(at + 2).trim();
383
- if (!name || !command) return null;
384
- if (!/^[\w:.-]+$/.test(name)) return null;
385
- return { name, command };
348
+ const saveLogs = async (slot, reduced, raw) => {
349
+ await fs.promises.mkdir(slot.dir, { recursive: true });
350
+ await Promise.all([
351
+ fs.promises.writeFile(slot.full, logText(reduced)),
352
+ fs.promises.writeFile(slot.rawFull, logText(raw)),
353
+ ]);
386
354
  };
387
355
 
388
356
  const orderedScripts = (pkg) => {
@@ -393,47 +361,25 @@ const orderedScripts = (pkg) => {
393
361
  return scripts;
394
362
  };
395
363
 
396
- const upsertScript = (pkg, prev, next) => {
397
- const scripts = orderedScripts(pkg);
398
- const names = Object.keys(scripts);
399
- const ordered = {};
400
- if (prev && names.includes(prev)) {
401
- for (const key of names) {
402
- if (key === prev) ordered[next.name] = next.command;
403
- else if (key !== next.name) ordered[key] = scripts[key];
404
- }
405
- } else {
406
- for (const key of names) ordered[key] = scripts[key];
407
- ordered[next.name] = next.command;
408
- }
409
- pkg.scripts = ordered;
410
- };
411
-
412
- const dropScript = (pkg, name) => {
413
- const scripts = orderedScripts(pkg);
414
- if (!Object.hasOwn(scripts, name)) return false;
364
+ const pickScripts = (scripts, names) => {
415
365
  const ordered = {};
416
- for (const key of Object.keys(scripts)) {
417
- if (key !== name) ordered[key] = scripts[key];
418
- }
419
- pkg.scripts = ordered;
420
- return true;
366
+ for (const key of names) ordered[key] = scripts[key];
367
+ return ordered;
421
368
  };
422
369
 
423
- const moveScript = (pkg, name, delta) => {
370
+ const upsertScript = (pkg, prev, next) => {
424
371
  const scripts = orderedScripts(pkg);
425
372
  const names = Object.keys(scripts);
426
- const at = names.indexOf(name);
427
- if (at < 0) return false;
428
- const next = at + delta;
429
- if (next < 0 || next >= names.length) return false;
430
- const swap = names[at];
431
- names[at] = names[next];
432
- names[next] = swap;
373
+ if (!prev || !names.includes(prev)) {
374
+ pkg.scripts = { ...scripts, [next.name]: next.command };
375
+ return;
376
+ }
433
377
  const ordered = {};
434
- for (const key of names) ordered[key] = scripts[key];
378
+ for (const key of names) {
379
+ if (key === prev) ordered[next.name] = next.command;
380
+ else if (key !== next.name) ordered[key] = scripts[key];
381
+ }
435
382
  pkg.scripts = ordered;
436
- return true;
437
383
  };
438
384
 
439
385
  const saveScript = (root, prev, next) => {
@@ -444,23 +390,30 @@ const saveScript = (root, prev, next) => {
444
390
 
445
391
  const removeScript = (root, name) => {
446
392
  const pkg = readManifest(root);
447
- if (!pkg || !dropScript(pkg, name)) return false;
393
+ if (!pkg) return false;
394
+ const scripts = orderedScripts(pkg);
395
+ if (!Object.hasOwn(scripts, name)) return false;
396
+ const names = Object.keys(scripts).filter((key) => key !== name);
397
+ pkg.scripts = pickScripts(scripts, names);
448
398
  writeManifest(root, pkg);
449
399
  return true;
450
400
  };
451
401
 
452
402
  const reorderScript = (root, name, delta) => {
453
403
  const pkg = readManifest(root);
454
- if (!pkg || !moveScript(pkg, name, delta)) return false;
404
+ if (!pkg) return false;
405
+ const scripts = orderedScripts(pkg);
406
+ const names = Object.keys(scripts);
407
+ const at = names.indexOf(name);
408
+ const next = at + delta;
409
+ if (at < 0 || next < 0 || next >= names.length) return false;
410
+ names[at] = names[next];
411
+ names[next] = name;
412
+ pkg.scripts = pickScripts(scripts, names);
455
413
  writeManifest(root, pkg);
456
414
  return true;
457
415
  };
458
416
 
459
- const npmArgs = (entry) => {
460
- if (entry.kind === 'bin') return ['exec', '--', entry.name];
461
- return ['run', entry.name];
462
- };
463
-
464
417
  const signalPid = (pid, signal) => {
465
418
  try {
466
419
  process.kill(pid, signal);
@@ -471,7 +424,7 @@ const signalPid = (pid, signal) => {
471
424
  };
472
425
 
473
426
  const killTree = (proc) => {
474
- const pid = proc && proc.pid;
427
+ const pid = proc.pid;
475
428
  if (!pid) return;
476
429
  if (process.platform === 'win32') {
477
430
  const killer = spawn(
@@ -508,6 +461,8 @@ const startNpm = (cwd, entry, onData, onClose) => {
508
461
  settled = true;
509
462
  onClose({ status, text: raw });
510
463
  };
464
+ const isBin = entry.kind === 'bin';
465
+ const args = isBin ? ['exec', '--', entry.name] : ['run', entry.name];
511
466
  let child;
512
467
  try {
513
468
  const opts = npmOpts({
@@ -515,7 +470,7 @@ const startNpm = (cwd, entry, onData, onClose) => {
515
470
  env: commandEnv(),
516
471
  detached: process.platform !== 'win32',
517
472
  });
518
- child = spawn(npmBin(), npmArgs(entry), opts);
473
+ child = spawn(npmBin(), args, opts);
519
474
  } catch (error) {
520
475
  raw = `${error.message}\n`;
521
476
  finish(1);
@@ -526,13 +481,10 @@ const startNpm = (cwd, entry, onData, onClose) => {
526
481
  raw += chunk;
527
482
  onData(raw);
528
483
  };
529
- if (child.stdout) {
530
- child.stdout.setEncoding('utf8');
531
- child.stdout.on('data', push);
532
- }
533
- if (child.stderr) {
534
- child.stderr.setEncoding('utf8');
535
- child.stderr.on('data', push);
484
+ for (const stream of [child.stdout, child.stderr]) {
485
+ if (!stream) continue;
486
+ stream.setEncoding('utf8');
487
+ stream.on('data', push);
536
488
  }
537
489
  child.on('error', (error) => {
538
490
  if (!stopped) raw += `${error.message}\n`;
@@ -552,20 +504,19 @@ module.exports = {
552
504
  listCommands,
553
505
  reduceOutput,
554
506
  logFileName,
555
- writeLog,
507
+ nextLogFile,
508
+ saveLogs,
556
509
  staleLogFiles,
557
510
  removeStaleLogs,
558
- formatSize,
559
- parseScriptLine,
560
511
  saveScript,
561
512
  removeScript,
562
513
  reorderScript,
563
514
  startNpm,
564
515
  commandEnv,
565
516
  isPassingTest,
566
- withoutNodeFrame,
567
- indentStack,
517
+ traceLine,
568
518
  relativize,
569
519
  TABLE_KEY,
570
520
  TABLE_VAL,
521
+ formatTable,
571
522
  };