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
package/lib/files.js CHANGED
@@ -8,7 +8,37 @@ const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
8
8
  const TASKS_FILE = 'TODOs';
9
9
  const REPO_TASKS_LABEL =
10
10
  'Project Tasks: Backlog, Issues, Bug Reports, Feature Requests';
11
+ const TASK_KINDS = [
12
+ { id: 'backlog', title: 'Backlog' },
13
+ { id: 'issues', title: 'Issues' },
14
+ { id: 'bugs', title: 'Bug Reports' },
15
+ { id: 'features', title: 'Feature Requests' },
16
+ ];
17
+ const TASK_KIND_IDS = new Set(TASK_KINDS.map((kind) => kind.id));
18
+
19
+ const taskKind = (todo) => {
20
+ const kind = todo && todo.kind;
21
+ return TASK_KIND_IDS.has(kind) ? kind : 'backlog';
22
+ };
23
+
24
+ const taskTitle = (id) => {
25
+ for (const kind of TASK_KINDS) {
26
+ if (kind.id === id) return kind.title;
27
+ }
28
+ return TASK_KINDS[0].title;
29
+ };
11
30
  const TOTAL_LABEL = 'total';
31
+ const AGE_UNIT = {
32
+ second: 's',
33
+ minute: 'm',
34
+ hour: 'h',
35
+ day: 'd',
36
+ week: 'w',
37
+ month: 'mo',
38
+ year: 'y',
39
+ };
40
+ const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
41
+ const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
12
42
 
13
43
  const isPathInScope = (rel, paths) => {
14
44
  if (!paths.length) return true;
@@ -20,20 +50,17 @@ const isPathInScope = (rel, paths) => {
20
50
  return false;
21
51
  };
22
52
 
23
- const itemPath = (item) => {
24
- if (!item || !item.file) return '';
25
- return item.file.newPath || item.file.oldPath || '';
26
- };
53
+ const itemPath = (item) => item?.file?.newPath || item?.file?.oldPath || '';
27
54
 
28
55
  const listPath = (item) => listFilePath(itemPath(item));
29
56
 
30
- const isTaskItem = (item) => !!(item && item.origin === 'task');
57
+ const isTaskItem = (item) => item?.origin === 'task';
31
58
 
32
59
  const isReadOnlyOrigin = (origin) => origin === 'commit' || origin === 'pr';
33
60
 
34
- const isTasksEntry = (entry) => !!(entry && entry.kind === 'tasks');
61
+ const isTasksEntry = (entry) => entry?.kind === 'tasks';
35
62
 
36
- const isTotalEntry = (entry) => !!(entry && entry.kind === 'total');
63
+ const isTotalEntry = (entry) => entry?.kind === 'total';
37
64
 
38
65
  const makeTaskItem = () => ({
39
66
  origin: 'task',
@@ -53,52 +80,30 @@ const makeTaskItem = () => ({
53
80
  });
54
81
 
55
82
  const fileStatus = (origins) => {
56
- const present = new Set(origins);
57
- const seen = ORIGIN_ORDER.filter((origin) => present.has(origin));
83
+ const seen = ORIGIN_ORDER.filter((origin) => origins.includes(origin));
58
84
  const key = seen.join('+');
59
- if (!key) return '';
60
- if (key === 'staged+unstaged') return 'partial';
61
- return key;
85
+ return key === 'staged+unstaged' ? 'partial' : key;
62
86
  };
63
87
 
64
88
  const lineDelta = ({ hunk, blockId }) => {
65
89
  const delta = { added: 0, removed: 0 };
66
- if (!hunk) return delta;
67
- const { lines } = hunk;
68
- if (!lines) return delta;
90
+ if (!hunk || !hunk.lines) return delta;
69
91
  const scoped = typeof blockId === 'number';
70
- for (const { type, blockId: lineBlock } of lines) {
71
- if (scoped && lineBlock !== blockId) continue;
72
- if (type === 'add') delta.added += 1;
73
- else if (type === 'del') delta.removed += 1;
92
+ for (const line of hunk.lines) {
93
+ if (scoped && line.blockId !== blockId) continue;
94
+ if (line.type === 'add') delta.added += 1;
95
+ else if (line.type === 'del') delta.removed += 1;
74
96
  }
75
97
  return delta;
76
98
  };
77
99
 
78
- const AGE_UNIT = {
79
- second: 's',
80
- minute: 'm',
81
- hour: 'h',
82
- day: 'd',
83
- week: 'w',
84
- month: 'mo',
85
- year: 'y',
86
- };
87
-
88
- const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
89
- const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
90
-
91
100
  const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
92
101
 
93
102
  const shortAge = (text) => {
94
103
  const raw = `${text ?? ''}`.trim();
95
- if (!raw) return '';
96
104
  if (/^yesterday$/i.test(raw)) return '1d ago';
97
105
  const match = AGE_AGO.exec(raw);
98
- if (match) {
99
- const unit = AGE_UNIT[match[2].toLowerCase()];
100
- if (unit) return `${match[1]}${unit} ago`;
101
- }
106
+ if (match) return shortUnit(match[1], match[2].toLowerCase());
102
107
  const brief = AGE_SHORT.exec(raw);
103
108
  if (brief) return `${brief[1]}${brief[2].toLowerCase()} ago`;
104
109
  return raw;
@@ -118,6 +123,18 @@ const relativeAge = (thenMs, nowMs = Date.now()) => {
118
123
  return shortUnit(Math.round(days / 365), 'year');
119
124
  };
120
125
 
126
+ const emptyCounts = () => ({
127
+ remaining: 0,
128
+ added: 0,
129
+ removed: 0,
130
+ stagedAdded: 0,
131
+ stagedRemoved: 0,
132
+ unstagedAdded: 0,
133
+ unstagedRemoved: 0,
134
+ staged: 0,
135
+ unstaged: 0,
136
+ });
137
+
121
138
  const addLineDelta = (group, origin, delta) => {
122
139
  group.added += delta.added;
123
140
  group.removed += delta.removed;
@@ -141,19 +158,12 @@ const originLineDelta = (entry) => {
141
158
  }
142
159
  const added = entry.added ?? 0;
143
160
  const removed = entry.removed ?? 0;
144
- if ((entry.status ?? '') === 'staged') {
145
- return {
146
- stagedAdded: added,
147
- stagedRemoved: removed,
148
- unstagedAdded: 0,
149
- unstagedRemoved: 0,
150
- };
151
- }
161
+ const staged = entry.status === 'staged';
152
162
  return {
153
- stagedAdded: 0,
154
- stagedRemoved: 0,
155
- unstagedAdded: added,
156
- unstagedRemoved: removed,
163
+ stagedAdded: staged ? added : 0,
164
+ stagedRemoved: staged ? removed : 0,
165
+ unstagedAdded: staged ? 0 : added,
166
+ unstagedRemoved: staged ? 0 : removed,
157
167
  };
158
168
  };
159
169
 
@@ -165,22 +175,8 @@ const fileEntries = (items) => {
165
175
  const rel = listPath(item);
166
176
  if (!rel) continue;
167
177
  if (!groups.has(rel)) {
168
- groups.set(rel, {
169
- path: rel,
170
- origins: [],
171
- firstIndex: i,
172
- openIndex: i,
173
- remaining: 0,
174
- added: 0,
175
- removed: 0,
176
- stagedAdded: 0,
177
- stagedRemoved: 0,
178
- unstagedAdded: 0,
179
- unstagedRemoved: 0,
180
- staged: 0,
181
- unstaged: 0,
182
- date: '',
183
- });
178
+ const fields = { origins: [], firstIndex: i, openIndex: i };
179
+ groups.set(rel, { path: rel, ...fields, ...emptyCounts(), date: '' });
184
180
  }
185
181
  const group = groups.get(rel);
186
182
  const { origin } = item;
@@ -195,47 +191,30 @@ const fileEntries = (items) => {
195
191
  }
196
192
  }
197
193
  const entries = [...groups.values()];
198
- for (const group of entries) {
199
- group.status = fileStatus(group.origins);
200
- }
194
+ for (const group of entries) group.status = fileStatus(group.origins);
201
195
  entries.sort((left, right) => left.path.localeCompare(right.path, 'en'));
202
196
  return entries;
203
197
  };
204
198
 
205
199
  const fileTotals = (files) => {
206
- const total = {
207
- path: TOTAL_LABEL,
208
- kind: 'total',
209
- status: '',
210
- remaining: 0,
211
- added: 0,
212
- removed: 0,
213
- stagedAdded: 0,
214
- stagedRemoved: 0,
215
- unstagedAdded: 0,
216
- unstagedRemoved: 0,
217
- staged: 0,
218
- unstaged: 0,
219
- };
200
+ const total = { path: TOTAL_LABEL, kind: 'total', status: '' };
201
+ Object.assign(total, emptyCounts());
220
202
  for (const entry of files) {
221
203
  if (isTasksEntry(entry) || isTotalEntry(entry)) continue;
222
204
  const remaining = entry.remaining ?? 0;
223
205
  const staged = entry.staged ?? 0;
224
- const added = entry.added ?? 0;
225
- const removed = entry.removed ?? 0;
226
206
  const lines = originLineDelta(entry);
227
207
  const listed = entry.unstaged ?? Math.max(0, remaining - staged);
228
- const untracked = (entry.status ?? '') === 'untracked';
229
- const unstaged = untracked && listed === 0 ? remaining : listed;
208
+ const untracked = entry.status === 'untracked';
230
209
  total.remaining += remaining;
231
- total.added += added;
232
- total.removed += removed;
210
+ total.added += entry.added ?? 0;
211
+ total.removed += entry.removed ?? 0;
233
212
  total.stagedAdded += lines.stagedAdded;
234
213
  total.stagedRemoved += lines.stagedRemoved;
235
214
  total.unstagedAdded += lines.unstagedAdded;
236
215
  total.unstagedRemoved += lines.unstagedRemoved;
237
216
  total.staged += staged;
238
- total.unstaged += unstaged;
217
+ total.unstaged += untracked && listed === 0 ? remaining : listed;
239
218
  }
240
219
  return total;
241
220
  };
@@ -244,6 +223,9 @@ module.exports = {
244
223
  REVIEW_DIR,
245
224
  TASKS_FILE,
246
225
  REPO_TASKS_LABEL,
226
+ TASK_KINDS,
227
+ taskKind,
228
+ taskTitle,
247
229
  TOTAL_LABEL,
248
230
  isPathInScope,
249
231
  itemPath,