reslop 0.1.9 → 0.2.1

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 (96) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +3 -1
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +148 -5
  5. package/lib/api.js +4 -2
  6. package/lib/capabilities.js +13 -14
  7. package/lib/cli.js +38 -49
  8. package/lib/clipboard.js +1 -1
  9. package/lib/{deps/changes.js → deps-diff.js} +309 -19
  10. package/lib/deps.js +498 -29
  11. package/lib/detect.js +1 -7
  12. package/lib/diff/align.js +7 -10
  13. package/lib/diff/diff.js +53 -0
  14. package/lib/diff/display.js +5 -4
  15. package/lib/diff/inline.js +26 -21
  16. package/lib/diff/items.js +6 -4
  17. package/lib/diff/patch.js +1 -3
  18. package/lib/diff/unit.js +392 -0
  19. package/lib/files.js +5 -8
  20. package/lib/{git/branches.js → git-branches.js} +191 -11
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git-reword-todo.js +20 -0
  23. package/lib/git.js +606 -68
  24. package/lib/github.js +28 -197
  25. package/lib/gitlab.js +24 -195
  26. package/lib/highlight/bash.js +1 -3
  27. package/lib/highlight/css.js +1 -3
  28. package/lib/highlight/js.js +11 -14
  29. package/lib/highlight.js +6 -9
  30. package/lib/keys.js +14 -8
  31. package/lib/manifest.js +330 -0
  32. package/lib/remote.js +374 -0
  33. package/lib/render/branches.js +18 -100
  34. package/lib/render/chrome.js +71 -14
  35. package/lib/render/commits.js +69 -60
  36. package/lib/render/diff.js +20 -20
  37. package/lib/render/files.js +6 -34
  38. package/lib/render/frame.js +36 -13
  39. package/lib/render/notes.js +26 -8
  40. package/lib/render/primitives.js +99 -11
  41. package/lib/{render.js → render/render.js} +8 -8
  42. package/lib/render/unit.js +41 -0
  43. package/lib/review-merge.js +270 -0
  44. package/lib/review.js +782 -14
  45. package/lib/select.js +5 -10
  46. package/lib/session/accessors.js +9 -1
  47. package/lib/session/actions.js +61 -18
  48. package/lib/session/branches.js +116 -128
  49. package/lib/session/change-actions.js +202 -123
  50. package/lib/session/commit.js +201 -115
  51. package/lib/session/compose.js +181 -15
  52. package/lib/session/dispatch.js +110 -60
  53. package/lib/session/files-pane.js +156 -148
  54. package/lib/session/items.js +22 -30
  55. package/lib/session/lifecycle.js +151 -54
  56. package/lib/session/load.js +19 -33
  57. package/lib/session/navigation.js +12 -1
  58. package/lib/session/ops.js +28 -41
  59. package/lib/session/pointer.js +57 -57
  60. package/lib/session/review.js +23 -37
  61. package/lib/session/terminal.js +6 -12
  62. package/lib/session/todos.js +37 -13
  63. package/lib/session/unit.js +208 -0
  64. package/lib/session/update.js +33 -46
  65. package/lib/session/view.js +78 -9
  66. package/lib/session/watch.js +128 -24
  67. package/lib/session.js +22 -246
  68. package/lib/source.js +3 -3
  69. package/lib/update.js +40 -75
  70. package/lib/{git/proc.js → utilities.js} +75 -11
  71. package/lib/wrap.js +1 -3
  72. package/package.json +2 -2
  73. package/lib/deps/items.js +0 -134
  74. package/lib/deps/manifest.js +0 -152
  75. package/lib/deps/merge.js +0 -120
  76. package/lib/deps/proposals.js +0 -164
  77. package/lib/deps/reports.js +0 -180
  78. package/lib/deps/rows.js +0 -176
  79. package/lib/deps/scan.js +0 -138
  80. package/lib/deps/usage.js +0 -68
  81. package/lib/deps/versions.js +0 -119
  82. package/lib/diff.js +0 -33
  83. package/lib/git/changes.js +0 -164
  84. package/lib/git/read.js +0 -253
  85. package/lib/git/run.js +0 -100
  86. package/lib/npm/commands.js +0 -100
  87. package/lib/remote/changes.js +0 -37
  88. package/lib/remote/headers.js +0 -36
  89. package/lib/remote/http.js +0 -122
  90. package/lib/remote/notes.js +0 -94
  91. package/lib/review/format.js +0 -434
  92. package/lib/review/storage.js +0 -150
  93. package/lib/review/store.js +0 -178
  94. package/lib/review/templates.js +0 -49
  95. package/lib/session/origins.js +0 -7
  96. package/lib/sys.js +0 -84
@@ -1,15 +1,16 @@
1
1
  'use strict';
2
2
 
3
- const { itemPath } = require('../files.js');
3
+ const { jsonParse } = require('metautil');
4
+ const { itemPath } = require('./files.js');
4
5
  const {
5
6
  countSides,
6
7
  formatHunkHeader,
7
8
  formatPatch,
8
- } = require('../diff/patch.js');
9
-
9
+ } = require('./diff/patch.js');
10
+ const { isCtxType } = require('./diff/diff.js');
10
11
  const manifest = require('./manifest.js');
11
12
  const { MANIFEST, LOCKFILE, SECTION_KEYS, depFileMeta, relOf } = manifest;
12
- const { parseJson, readSections, emptySectionMaps } = manifest;
13
+ const { listFilePath, readSections, emptySectionMaps } = manifest;
13
14
  const { lockRootSections, lockEntries, lockPackageCount } = manifest;
14
15
 
15
16
  const SECTION_ORDER = [...SECTION_KEYS, 'resolved', 'field'];
@@ -51,9 +52,6 @@ const fieldText = (value) => {
51
52
  return JSON.stringify(value);
52
53
  };
53
54
 
54
- const sameJson = (left, right) =>
55
- JSON.stringify(left) === JSON.stringify(right);
56
-
57
55
  const diffOtherFields = (oldPkg, newPkg) => {
58
56
  if (!oldPkg && !newPkg) return [];
59
57
  const oldObj = oldPkg ?? {};
@@ -66,7 +64,7 @@ const diffOtherFields = (oldPkg, newPkg) => {
66
64
  if (SECTION_KEYS.includes(name)) continue;
67
65
  const prev = oldObj[name];
68
66
  const next = newObj[name];
69
- if (sameJson(prev, next)) continue;
67
+ if (JSON.stringify(prev) === JSON.stringify(next)) continue;
70
68
  let action = 'changed';
71
69
  if (!Object.hasOwn(oldObj, name)) action = 'added';
72
70
  else if (!Object.hasOwn(newObj, name)) action = 'removed';
@@ -98,9 +96,6 @@ const lineInBlock = (item, line) => {
98
96
  return line.blockId === blockId;
99
97
  };
100
98
 
101
- const isSectionFence = (text) =>
102
- SECTION_OPEN.test(text) || SECTION_END.test(text);
103
-
104
99
  const hunkOnlyFoldedDeps = (item, names) => {
105
100
  if (!item.hunk || !names.size) return false;
106
101
  let folded = 0;
@@ -112,7 +107,7 @@ const hunkOnlyFoldedDeps = (item, names) => {
112
107
  folded += 1;
113
108
  continue;
114
109
  }
115
- if (isSectionFence(line.text)) continue;
110
+ if (SECTION_OPEN.test(line.text) || SECTION_END.test(line.text)) continue;
116
111
  return false;
117
112
  }
118
113
  return folded > 0;
@@ -228,12 +223,14 @@ const directNames = (oldMaps, newMaps) => {
228
223
 
229
224
  const mergeResolved = (changes, oldResolved, newResolved, oldMaps, newMaps) => {
230
225
  const seen = new Set();
231
- const merged = [];
232
- for (const change of changes) {
226
+ const { length } = changes;
227
+ const merged = new Array(length);
228
+ for (let i = 0; i < length; i++) {
229
+ const change = changes[i];
233
230
  seen.add(change.name);
234
231
  const resolvedFrom = oldResolved.get(change.name) ?? '';
235
232
  const resolvedTo = newResolved.get(change.name) ?? '';
236
- merged.push({ ...change, resolvedFrom, resolvedTo });
233
+ merged[i] = { ...change, resolvedFrom, resolvedTo };
237
234
  }
238
235
  const names = [...directNames(oldMaps, newMaps)];
239
236
  names.sort();
@@ -286,10 +283,10 @@ const readGroupSnapshot = (group, readSides) => {
286
283
  const lockRel = relOf(dir, LOCKFILE);
287
284
  const manifestSides = readSides(origin, manifestRel);
288
285
  const lockSides = readSides(origin, lockRel);
289
- const oldPkg = parseJson(manifestSides.oldText);
290
- const newPkg = parseJson(manifestSides.newText);
291
- const oldLock = parseJson(lockSides.oldText);
292
- const newLock = parseJson(lockSides.newText);
286
+ const oldPkg = jsonParse(manifestSides.oldText ?? '');
287
+ const newPkg = jsonParse(manifestSides.newText ?? '');
288
+ const oldLock = jsonParse(lockSides.oldText ?? '');
289
+ const newLock = jsonParse(lockSides.newText ?? '');
293
290
  const parsedManifest = !!(oldPkg || newPkg);
294
291
  return {
295
292
  origin,
@@ -410,7 +407,296 @@ const takeManifestItems = (summary) => {
410
407
  return taken;
411
408
  };
412
409
 
410
+ const DEP_CAPTION = 'Dependencies in package.json & package-lock.json';
411
+
412
+ const changeMarks = (change) => {
413
+ const marks = [];
414
+ if (change.unused) marks.push('unused');
415
+ if (change.outdated && !change.propose) marks.push('outdated');
416
+ if (change.audit) marks.push(change.audit.severity);
417
+ return marks;
418
+ };
419
+
420
+ const changeSources = (change) => {
421
+ const sources = [];
422
+ if (change.unused) {
423
+ sources.push('npm uninstall');
424
+ return sources;
425
+ }
426
+ if (change.propose && change.outdated) sources.push('npm outdated');
427
+ if (change.audit) sources.push('npm audit');
428
+ return sources;
429
+ };
430
+
431
+ const sectionTitle = (section, action, marks, sources) => {
432
+ const title = action === 'changed' ? 'version changed' : action;
433
+ const kind = section.replace(/ies$/, 'y');
434
+ let label = `${kind} ${title}`;
435
+ if (section === 'field') label = `package.json ${title}`;
436
+ if (section === 'resolved' && action === 'vulnerable') {
437
+ label = 'lockfile vulnerable';
438
+ } else if (section === 'resolved') {
439
+ label = `lockfile resolved ${title}`;
440
+ }
441
+ if (marks.includes('unused')) {
442
+ label = `${kind} unused`;
443
+ const extra = [];
444
+ for (const mark of marks) {
445
+ if (mark !== 'unused') extra.push(mark);
446
+ }
447
+ if (extra.length) label = `${label}, ${extra.join(', ')}`;
448
+ } else if (marks.length) {
449
+ label = `${label}, ${marks.join(', ')}`;
450
+ }
451
+ if (!sources.length) return label;
452
+ return `${sources.join(', ')}: ${label}`;
453
+ };
454
+
455
+ const entryText = (change, side) => {
456
+ const version = side === 'old' ? change.from : change.to;
457
+ return `"${change.name}": "${version}"`;
458
+ };
459
+
460
+ const ctxLine = (text) => {
461
+ const type = 'ctx';
462
+ return { type, text };
463
+ };
464
+
465
+ const warnLine = (text) => {
466
+ const type = 'warn';
467
+ return { type, text };
468
+ };
469
+
470
+ const noteLine = (text) => {
471
+ const type = 'note';
472
+ return { type, text };
473
+ };
474
+
475
+ const noteSepLine = () => {
476
+ const type = 'noteSep';
477
+ const text = '';
478
+ return { type, text };
479
+ };
480
+
481
+ const changeLine = (type, change, side) => {
482
+ const text = entryText(change, side);
483
+ return { type, text };
484
+ };
485
+
486
+ const pushChangeLines = (rows, change) => {
487
+ if (change.unused) {
488
+ const side = change.action === 'added' ? 'new' : 'old';
489
+ rows.push(changeLine('del', change, side));
490
+ return;
491
+ }
492
+ if (change.action === 'added') {
493
+ rows.push(changeLine('add', change, 'new'));
494
+ return;
495
+ }
496
+ if (change.action === 'removed') {
497
+ rows.push(changeLine('del', change, 'old'));
498
+ return;
499
+ }
500
+ if (change.from === change.to) {
501
+ rows.push(changeLine('ctx', change, 'old'));
502
+ return;
503
+ }
504
+ rows.push(changeLine('del', change, 'old'));
505
+ rows.push(changeLine('add', change, 'new'));
506
+ };
507
+
508
+ const lockCountText = (oldCount, newCount) => {
509
+ if (oldCount === newCount) return '';
510
+ return `lockfile packages ${oldCount} → ${newCount}`;
511
+ };
512
+
513
+ const auditNoteTexts = (audit) => {
514
+ if (!audit) return [];
515
+ const notes = [];
516
+ if (Array.isArray(audit.titles)) {
517
+ for (const note of audit.titles) {
518
+ if (note && !notes.includes(note)) notes.push(note);
519
+ }
520
+ } else if (audit.title) {
521
+ notes.push(audit.title);
522
+ }
523
+ const severity = audit.severity;
524
+ if (!notes.length) return [`npm audit ${severity}`];
525
+ const { length } = notes;
526
+ const texts = new Array(length);
527
+ for (let i = 0; i < length; i++) {
528
+ texts[i] = `npm audit ${severity} ${notes[i]}`;
529
+ }
530
+ return texts;
531
+ };
532
+
533
+ const pushAuditNotes = (rows, audit) => {
534
+ const texts = auditNoteTexts(audit);
535
+ if (!texts.length) return;
536
+ rows.push(ctxLine(''));
537
+ let first = true;
538
+ for (const text of texts) {
539
+ if (!first) rows.push(noteSepLine());
540
+ first = false;
541
+ rows.push(noteLine(text));
542
+ }
543
+ };
544
+
545
+ const buildRows = (change, oldCount, newCount) => {
546
+ const rows = [ctxLine(DEP_CAPTION), ctxLine('')];
547
+ if (change) {
548
+ const marks = changeMarks(change);
549
+ const sources = changeSources(change);
550
+ const title = sectionTitle(change.section, change.action, marks, sources);
551
+ const audited = sources.includes('npm audit');
552
+ rows.push(audited ? warnLine(title) : ctxLine(title));
553
+ rows.push(ctxLine(''));
554
+ pushChangeLines(rows, change);
555
+ pushAuditNotes(rows, change.audit);
556
+ }
557
+ const countLine = lockCountText(oldCount, newCount);
558
+ if (countLine) {
559
+ if (rows.at(-1).text !== '') rows.push(ctxLine(''));
560
+ rows.push(ctxLine(countLine));
561
+ }
562
+ return rows;
563
+ };
564
+
565
+ const toHunkLine = (row, blockId) => {
566
+ const type = row.type;
567
+ const text = row.text;
568
+ const noNl = false;
569
+ const id = isCtxType(type) ? null : blockId;
570
+ return { type, text, noNl, blockId: id };
571
+ };
572
+
573
+ const makeHunk = (rows, blockId) => {
574
+ const lines = rows.map((row) => toHunkLine(row, blockId));
575
+ const counts = countSides(lines);
576
+ const oldCount = counts.oldCount;
577
+ const newCount = counts.newCount;
578
+ const header = `@@ -1,${oldCount} +1,${newCount} @@`;
579
+ return { oldStart: 1, oldCount, newStart: 1, newCount, header, lines };
580
+ };
581
+
582
+ const uniquePaths = (items) => {
583
+ const files = [];
584
+ const seen = new Set();
585
+ for (const item of items) {
586
+ const rel = itemPath(item);
587
+ if (!rel || seen.has(rel)) continue;
588
+ seen.add(rel);
589
+ files.push(rel);
590
+ }
591
+ return files;
592
+ };
593
+
594
+ const filesForChange = (change, files) => {
595
+ const manifest = [];
596
+ const lockfile = [];
597
+ for (const rel of files) {
598
+ const meta = depFileMeta(rel);
599
+ if (!meta) continue;
600
+ if (meta.kind === 'manifest') manifest.push(rel);
601
+ else lockfile.push(rel);
602
+ }
603
+ if (change.section === 'field') {
604
+ if (manifest.length) return manifest;
605
+ return files;
606
+ }
607
+ if (change.section === 'resolved') {
608
+ if (lockfile.length) return lockfile;
609
+ return files;
610
+ }
611
+ if (change.propose) {
612
+ if (manifest.length) return manifest;
613
+ return files;
614
+ }
615
+ const both = [...manifest, ...lockfile];
616
+ if (both.length) return both;
617
+ return files;
618
+ };
619
+
620
+ const pickPrimary = (items) => {
621
+ for (const item of items) {
622
+ const rel = itemPath(item);
623
+ const meta = depFileMeta(rel);
624
+ if (meta && meta.kind === 'manifest') return item;
625
+ }
626
+ return items[0];
627
+ };
628
+
629
+ const makeDepItem = (origin, fileItems, hidden, change, extra, blockId) => {
630
+ const allFiles = uniquePaths(fileItems);
631
+ const files = change ? filesForChange(change, allFiles) : allFiles;
632
+ const primary = pickPrimary(fileItems);
633
+ const oldPath = listFilePath(itemPath(primary));
634
+ const newPath = oldPath;
635
+ const isNew = fileItems.some((item) => item.file && item.file.isNew);
636
+ const isDeleted = fileItems.every((item) => item.file && item.file.isDeleted);
637
+ let oldCount = 0;
638
+ let newCount = 0;
639
+ if (!change) {
640
+ oldCount = extra.oldCount ?? 0;
641
+ newCount = extra.newCount ?? 0;
642
+ }
643
+ const rows = buildRows(change, oldCount, newCount);
644
+ const hunk = makeHunk(rows, blockId);
645
+ const isBinary = false;
646
+ const preamble = [];
647
+ const hunks = [hunk];
648
+ const file = {
649
+ oldPath,
650
+ newPath,
651
+ isNew,
652
+ isDeleted,
653
+ isBinary,
654
+ preamble,
655
+ hunks,
656
+ };
657
+ const patchAdd = '';
658
+ const patchRevert = '';
659
+ const items = hidden;
660
+ const dep = { files, items, change };
661
+ const reload = !!(change && change.propose);
662
+ return { origin, file, hunk, blockId, patchAdd, patchRevert, dep, reload };
663
+ };
664
+
665
+ const makeDepItems = (origin, fileItems, hidden, summary) => {
666
+ const ordered = orderedChanges(summary.changes);
667
+ if (!ordered.length) {
668
+ const oldCount = summary.oldCount ?? 0;
669
+ const newCount = summary.newCount ?? 0;
670
+ if (oldCount === newCount) return [];
671
+ return [makeDepItem(origin, fileItems, hidden, null, summary, 0)];
672
+ }
673
+ const { length } = ordered;
674
+ const items = new Array(length);
675
+ for (let i = 0; i < length; i++) {
676
+ items[i] = makeDepItem(origin, fileItems, hidden, ordered[i], summary, i);
677
+ }
678
+ return items;
679
+ };
680
+
681
+ const stubFileItem = (rel, origin) => {
682
+ const file = {
683
+ oldPath: rel,
684
+ newPath: rel,
685
+ isNew: false,
686
+ isDeleted: false,
687
+ isBinary: false,
688
+ preamble: [],
689
+ hunks: [],
690
+ };
691
+ const hunk = null;
692
+ const blockId = 0;
693
+ const patchAdd = '';
694
+ const patchRevert = '';
695
+ return { origin, file, hunk, blockId, patchAdd, patchRevert };
696
+ };
697
+
413
698
  module.exports = {
699
+ DEP_CAPTION,
414
700
  diffSections,
415
701
  mergeResolved,
416
702
  orderedChanges,
@@ -419,4 +705,8 @@ module.exports = {
419
705
  takeManifestItems,
420
706
  foldedDepNames,
421
707
  stripFoldedDepItem,
708
+ buildRows,
709
+ makeHunk,
710
+ makeDepItems,
711
+ stubFileItem,
422
712
  };