reslop 0.1.8 → 0.2.0

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 (95) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/README.md +27 -38
  3. package/bin/reslop.js +2 -2
  4. package/lib/ansi.js +6 -2
  5. package/lib/api.js +2 -2
  6. package/lib/capabilities.js +10 -14
  7. package/lib/cli.js +28 -47
  8. package/lib/clipboard.js +1 -1
  9. package/lib/deps-diff.js +712 -0
  10. package/lib/deps.js +556 -32
  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 +34 -8
  20. package/lib/{git/branches.js → git-branches.js} +103 -10
  21. package/lib/{git/dependencies.js → git-deps.js} +111 -38
  22. package/lib/git.js +592 -65
  23. package/lib/github.js +28 -197
  24. package/lib/gitlab.js +24 -195
  25. package/lib/highlight/bash.js +1 -3
  26. package/lib/highlight/css.js +1 -3
  27. package/lib/highlight/js.js +11 -14
  28. package/lib/highlight.js +6 -9
  29. package/lib/keys.js +22 -9
  30. package/lib/manifest.js +330 -0
  31. package/lib/remote.js +374 -0
  32. package/lib/render/branches.js +34 -66
  33. package/lib/render/chrome.js +57 -22
  34. package/lib/render/commits.js +85 -0
  35. package/lib/render/diff.js +16 -16
  36. package/lib/render/files.js +29 -37
  37. package/lib/render/frame.js +29 -13
  38. package/lib/render/notes.js +4 -3
  39. package/lib/render/primitives.js +46 -1
  40. package/lib/{render.js → render/render.js} +6 -7
  41. package/lib/render/unit.js +41 -0
  42. package/lib/review.js +750 -14
  43. package/lib/select.js +5 -10
  44. package/lib/session/accessors.js +9 -1
  45. package/lib/session/actions.js +93 -36
  46. package/lib/session/branches.js +130 -144
  47. package/lib/session/change-actions.js +206 -124
  48. package/lib/session/commit.js +217 -62
  49. package/lib/session/compose.js +158 -13
  50. package/lib/session/dispatch.js +109 -55
  51. package/lib/session/files-pane.js +145 -142
  52. package/lib/session/items.js +131 -34
  53. package/lib/session/lifecycle.js +82 -42
  54. package/lib/session/load.js +19 -33
  55. package/lib/session/navigation.js +15 -3
  56. package/lib/session/ops.js +28 -41
  57. package/lib/session/pointer.js +58 -54
  58. package/lib/session/review.js +23 -37
  59. package/lib/session/terminal.js +6 -12
  60. package/lib/session/todos.js +8 -12
  61. package/lib/session/unit.js +208 -0
  62. package/lib/session/update.js +33 -46
  63. package/lib/session/view.js +82 -11
  64. package/lib/session/watch.js +11 -19
  65. package/lib/session.js +29 -226
  66. package/lib/source.js +3 -3
  67. package/lib/update.js +40 -75
  68. package/lib/{git/proc.js → utilities.js} +75 -11
  69. package/lib/wrap.js +1 -3
  70. package/package.json +2 -2
  71. package/lib/deps/changes.js +0 -331
  72. package/lib/deps/items.js +0 -131
  73. package/lib/deps/manifest.js +0 -145
  74. package/lib/deps/merge.js +0 -120
  75. package/lib/deps/proposals.js +0 -164
  76. package/lib/deps/reports.js +0 -180
  77. package/lib/deps/rows.js +0 -177
  78. package/lib/deps/scan.js +0 -138
  79. package/lib/deps/usage.js +0 -68
  80. package/lib/deps/versions.js +0 -119
  81. package/lib/diff.js +0 -33
  82. package/lib/git/changes.js +0 -164
  83. package/lib/git/read.js +0 -253
  84. package/lib/git/run.js +0 -100
  85. package/lib/npm/commands.js +0 -100
  86. package/lib/remote/changes.js +0 -37
  87. package/lib/remote/headers.js +0 -36
  88. package/lib/remote/http.js +0 -122
  89. package/lib/remote/notes.js +0 -94
  90. package/lib/review/format.js +0 -434
  91. package/lib/review/storage.js +0 -150
  92. package/lib/review/store.js +0 -178
  93. package/lib/review/templates.js +0 -49
  94. package/lib/session/origins.js +0 -7
  95. package/lib/sys.js +0 -84
package/lib/files.js CHANGED
@@ -1,18 +1,17 @@
1
1
  'use strict';
2
2
 
3
+ const { listFilePath } = require('./manifest.js');
4
+
3
5
  const REVIEW_DIR = '.review';
4
6
  const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
5
7
  const TODO_FILE = 'TODOs';
6
8
  const REPO_TODOS_LABEL = 'Repository TODOs and Issues';
7
9
  const TOTAL_LABEL = 'total';
8
10
 
9
- const normalizePathSpec = (spec) =>
10
- `${spec ?? ''}`.replaceAll('\\', '/').replace(/\/+$/, '');
11
-
12
11
  const isPathInScope = (rel, paths) => {
13
12
  if (!paths.length) return true;
14
13
  for (const spec of paths) {
15
- const norm = normalizePathSpec(spec);
14
+ const norm = `${spec ?? ''}`.replaceAll('\\', '/').replace(/\/+$/, '');
16
15
  if (!norm || norm === '.') return true;
17
16
  if (rel === norm || rel.startsWith(`${norm}/`)) return true;
18
17
  }
@@ -24,14 +23,16 @@ const itemPath = (item) => {
24
23
  return item.file.newPath || item.file.oldPath || '';
25
24
  };
26
25
 
26
+ const listPath = (item) => listFilePath(itemPath(item));
27
+
27
28
  const isTodoItem = (item) => !!(item && item.origin === 'todo');
28
29
 
30
+ const isReadOnlyOrigin = (origin) => origin === 'commit' || origin === 'pr';
31
+
29
32
  const isTodosEntry = (entry) => !!(entry && entry.kind === 'todos');
30
33
 
31
34
  const isTotalEntry = (entry) => !!(entry && entry.kind === 'total');
32
35
 
33
- const repoTodosLabel = () => REPO_TODOS_LABEL;
34
-
35
36
  const makeTodoItem = () => ({
36
37
  origin: 'todo',
37
38
  file: {
@@ -72,6 +73,25 @@ const lineDelta = ({ hunk, blockId }) => {
72
73
  return delta;
73
74
  };
74
75
 
76
+ const countAgo = (n, unit) => {
77
+ const label = n === 1 ? unit : `${unit}s`;
78
+ return `${n} ${label} ago`;
79
+ };
80
+
81
+ const relativeAge = (thenMs, nowMs = Date.now()) => {
82
+ const diff = Math.max(0, Math.round((nowMs - thenMs) / 1000));
83
+ if (diff < 90) return countAgo(diff, 'second');
84
+ const minutes = Math.round(diff / 60);
85
+ if (minutes < 90) return countAgo(minutes, 'minute');
86
+ const hours = Math.round(minutes / 60);
87
+ if (hours < 36) return countAgo(hours, 'hour');
88
+ const days = Math.round(hours / 24);
89
+ if (days < 14) return countAgo(days, 'day');
90
+ if (days < 70) return countAgo(Math.round(days / 7), 'week');
91
+ if (days < 365) return countAgo(Math.round(days / 30), 'month');
92
+ return countAgo(Math.round(days / 365), 'year');
93
+ };
94
+
75
95
  const addLineDelta = (group, origin, delta) => {
76
96
  group.added += delta.added;
77
97
  group.removed += delta.removed;
@@ -116,7 +136,8 @@ const fileEntries = (items) => {
116
136
  for (let i = 0; i < items.length; i++) {
117
137
  const item = items[i];
118
138
  if (isTodoItem(item)) continue;
119
- const rel = itemPath(item);
139
+ const rel = listPath(item);
140
+ if (!rel) continue;
120
141
  if (!groups.has(rel)) {
121
142
  groups.set(rel, {
122
143
  path: rel,
@@ -132,10 +153,12 @@ const fileEntries = (items) => {
132
153
  unstagedRemoved: 0,
133
154
  staged: 0,
134
155
  unstaged: 0,
156
+ date: '',
135
157
  });
136
158
  }
137
159
  const group = groups.get(rel);
138
160
  const { origin } = item;
161
+ if (item.date && !group.date) group.date = item.date;
139
162
  group.origins.push(origin);
140
163
  group.remaining += 1;
141
164
  addLineDelta(group, origin, lineDelta(item));
@@ -149,6 +172,7 @@ const fileEntries = (items) => {
149
172
  for (const group of entries) {
150
173
  group.status = fileStatus(group.origins);
151
174
  }
175
+ entries.sort((left, right) => left.path.localeCompare(right.path, 'en'));
152
176
  return entries;
153
177
  };
154
178
 
@@ -197,12 +221,14 @@ module.exports = {
197
221
  TOTAL_LABEL,
198
222
  isPathInScope,
199
223
  itemPath,
224
+ listPath,
200
225
  isTodoItem,
226
+ isReadOnlyOrigin,
201
227
  isTodosEntry,
202
228
  isTotalEntry,
203
- repoTodosLabel,
204
229
  makeTodoItem,
205
230
  fileStatus,
206
231
  fileEntries,
207
232
  fileTotals,
233
+ relativeAge,
208
234
  };
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const { oneLine, requireOk, runGit, runGitAsync } = require('./run.js');
3
+ const { oneLine, requireOk, runGit, runGitAsync } = require('./git.js');
4
4
 
5
5
  const BRANCH_FORMAT = [
6
6
  '%(HEAD)',
@@ -26,6 +26,100 @@ const lastMessage = (top) => {
26
26
  return (result.stdout ?? '').replace(/\s+$/, '');
27
27
  };
28
28
 
29
+ const COMMIT_FORMAT = ['%H', '%h', '%an', '%ar', '%s'].join('%x1f');
30
+ const COMMIT_LIMIT = 256;
31
+ const UNIT = '\x1f';
32
+
33
+ const parseCommitLine = (line, index) => {
34
+ const parts = line.split(UNIT);
35
+ if (parts.length < 5) return null;
36
+ const sha = (parts[0] ?? '').trim();
37
+ if (!sha) return null;
38
+ return {
39
+ sha,
40
+ shortSha: (parts[1] ?? '').trim() || sha.slice(0, 7),
41
+ author: (parts[2] ?? '').trim(),
42
+ date: (parts[3] ?? '').trim(),
43
+ subject: parts.slice(4).join(UNIT).trim(),
44
+ head: index === 0,
45
+ };
46
+ };
47
+
48
+ const listCommits = (top) => {
49
+ const args = [
50
+ 'log',
51
+ `--format=${COMMIT_FORMAT}`,
52
+ `--max-count=${COMMIT_LIMIT}`,
53
+ ];
54
+ const result = requireOk(runGit(args, top), 'log failed');
55
+ const commits = [];
56
+ for (const line of result.stdout.split('\n')) {
57
+ if (!line) continue;
58
+ const entry = parseCommitLine(line, commits.length);
59
+ if (entry) commits.push(entry);
60
+ }
61
+ return commits;
62
+ };
63
+
64
+ const namedCommit = (sha) => {
65
+ const commit = `${sha ?? ''}`.trim();
66
+ if (!commit) throw new Error('empty commit');
67
+ return commit;
68
+ };
69
+
70
+ const resolveCommit = (top, sha) => {
71
+ const result = requireOk(runGit(['rev-parse', sha], top), 'rev-parse failed');
72
+ return (result.stdout ?? '').trim();
73
+ };
74
+
75
+ const dropHeadCommit = (top) => {
76
+ requireOk(runGit(['reset', '--soft', 'HEAD~1'], top), 'drop failed');
77
+ };
78
+
79
+ const rebaseFailed = (result) => {
80
+ if (result.status === 0) return '';
81
+ return result.stderr || result.stdout || 'rebase failed';
82
+ };
83
+
84
+ const dropOlderCommit = (top, sha) => {
85
+ const onto = `${sha}^`;
86
+ const result = runGit(['rebase', '--onto', onto, sha], top);
87
+ const failed = rebaseFailed(result);
88
+ if (!failed) return;
89
+ runGit(['rebase', '--abort'], top);
90
+ throw new Error(oneLine(failed));
91
+ };
92
+
93
+ const dropOlderCommitAsync = async (top, sha) => {
94
+ const onto = `${sha}^`;
95
+ const result = await runGitAsync(['rebase', '--onto', onto, sha], top);
96
+ const failed = rebaseFailed(result);
97
+ if (!failed) return;
98
+ await runGitAsync(['rebase', '--abort'], top);
99
+ throw new Error(oneLine(failed));
100
+ };
101
+
102
+ const dropCommit = (top, sha) => {
103
+ const spec = namedCommit(sha);
104
+ const target = resolveCommit(top, spec);
105
+ const parent = runGit(['rev-parse', '--verify', `${target}^`], top);
106
+ if (parent.status !== 0) throw new Error('cannot drop root commit');
107
+ const head = resolveCommit(top, 'HEAD');
108
+ if (target === head) return void dropHeadCommit(top);
109
+ dropOlderCommit(top, target);
110
+ };
111
+
112
+ const dropCommitAsync = async (top, sha) => {
113
+ const spec = namedCommit(sha);
114
+ const target = resolveCommit(top, spec);
115
+ const parentArgs = ['rev-parse', '--verify', `${target}^`];
116
+ const parent = await runGitAsync(parentArgs, top);
117
+ if (parent.status !== 0) throw new Error('cannot drop root commit');
118
+ const head = resolveCommit(top, 'HEAD');
119
+ if (target === head) return void dropHeadCommit(top);
120
+ await dropOlderCommitAsync(top, target);
121
+ };
122
+
29
123
  const parseTrack = (text) => {
30
124
  const raw = `${text ?? ''}`.trim().replace(/^\[|\]$/g, '');
31
125
  if (!raw) return { ahead: 0, behind: 0, gone: false };
@@ -89,10 +183,11 @@ const listBranches = (top) => {
89
183
  const names = new Set();
90
184
  for (const entry of branches) names.add(entry.name);
91
185
  const primary = defaultBranch(top, names);
92
- const listed = [];
93
- for (const entry of branches) {
94
- const isDefault = entry.name === primary;
95
- listed.push({ ...entry, isDefault });
186
+ const { length } = branches;
187
+ const listed = new Array(length);
188
+ for (let i = 0; i < length; i++) {
189
+ const entry = branches[i];
190
+ listed[i] = { ...entry, isDefault: entry.name === primary };
96
191
  }
97
192
  return listed;
98
193
  };
@@ -125,11 +220,6 @@ const createBranchAsync = async (top, name) => {
125
220
  requireOk(await runGitAsync(args, top), 'checkout failed');
126
221
  };
127
222
 
128
- const rebaseFailed = (result) => {
129
- if (result.status === 0) return '';
130
- return result.stderr || result.stdout || 'rebase failed';
131
- };
132
-
133
223
  const rebaseBranch = (top, onto) => {
134
224
  const branch = namedBranch(onto);
135
225
  const result = runGit(['rebase', branch], top);
@@ -272,6 +362,7 @@ const commitChangesAsync = async (top, kind, message) => {
272
362
 
273
363
  module.exports = {
274
364
  lastMessage,
365
+ listCommits,
275
366
  listBranches,
276
367
  checkoutBranch,
277
368
  checkoutBranchAsync,
@@ -279,6 +370,8 @@ module.exports = {
279
370
  createBranchAsync,
280
371
  rebaseBranch,
281
372
  rebaseBranchAsync,
373
+ dropCommit,
374
+ dropCommitAsync,
282
375
  dropBranch,
283
376
  dropBranchAsync,
284
377
  pullChanges,
@@ -3,21 +3,55 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
- const deps = require('../deps.js');
6
+ const deps = require('./deps.js');
7
7
  const { foldDepItems, mergeDepFile, depFileMeta } = deps;
8
8
  const { parseAuditReport, parseOutdatedReport } = deps;
9
9
  const { mergeProposedItems, MANIFEST, LOCKFILE } = deps;
10
- const scan = require('../deps/scan.js');
11
- const { collectUsedNames, collectExportMeta } = scan;
12
- const { itemPath, isPathInScope } = require('../files.js');
13
- const { abortError } = require('./proc.js');
14
- const { oneLine, requireOk, runGit } = require('./run.js');
15
- const read = require('./read.js');
16
- const { gitText, worktreeText, readDepSides } = read;
17
- const changes = require('./changes.js');
18
- const npm = require('../npm/commands.js');
19
- const { proposedNpmPlan, runNpm, runNpmAsync } = npm;
20
- const { runNpmJson, runNpmJsonAsync, AUDIT_MS } = npm;
10
+ const { collectUsedNames, collectExportMeta } = deps;
11
+ const { itemPath, isPathInScope } = require('./files.js');
12
+ const proc = require('./utilities.js');
13
+ const { abortError, npmBin, npmOpts, runProc, runProcSync } = proc;
14
+ const git = require('./git.js');
15
+ const { oneLine, requireOk, runGit } = git;
16
+ const { gitText, worktreeText, readDepSides, revertOnePath } = git;
17
+
18
+ const AUDIT_MS = 15000;
19
+ const INSTALL_MS = 120000;
20
+
21
+ const npmProcOptions = (cwd, extra = {}) => ({
22
+ cwd,
23
+ encoding: proc.DEFAULT_ENCODING,
24
+ maxBuffer: proc.DEFAULT_MAX_BUFFER,
25
+ timeout: extra.timeout,
26
+ signal: extra.signal,
27
+ shell: npmOpts().shell,
28
+ });
29
+
30
+ const runNpm = (cwd, args) =>
31
+ runProcSync(npmBin(), args, npmProcOptions(cwd, { timeout: INSTALL_MS }));
32
+
33
+ const runNpmAsync = (cwd, args, extra = {}) =>
34
+ runProc(
35
+ npmBin(),
36
+ args,
37
+ npmProcOptions(cwd, { timeout: INSTALL_MS, signal: extra.signal }),
38
+ );
39
+
40
+ const runNpmJson = (top, args, timeout) => {
41
+ const result = runProcSync(npmBin(), args, npmProcOptions(top, { timeout }));
42
+ if (result.error) return '';
43
+ return result.stdout || '';
44
+ };
45
+
46
+ const runNpmJsonAsync = async (top, args, timeout, signal) => {
47
+ const result = await runProc(
48
+ npmBin(),
49
+ args,
50
+ npmProcOptions(top, { timeout, signal }),
51
+ );
52
+ if (result.error) return '';
53
+ return result.stdout || '';
54
+ };
21
55
 
22
56
  const depPaths = (item) => item.dep?.files ?? [];
23
57
 
@@ -25,15 +59,59 @@ const origDepItems = (item) => item.dep?.items ?? [];
25
59
 
26
60
  const depChange = (item) => item.dep?.change ?? null;
27
61
 
62
+ const firstDepRel = (files, kind) => {
63
+ for (const rel of files) {
64
+ const meta = depFileMeta(rel);
65
+ if (meta && meta.kind === kind) return rel;
66
+ }
67
+ return '';
68
+ };
69
+
70
+ const proposedNpmPlan = (top, item) => {
71
+ const change = depChange(item);
72
+ if (!change) return null;
73
+ const files = depPaths(item);
74
+ const lockOnly = change.section === 'resolved';
75
+ const removing = !!change.unused;
76
+ const manifest = firstDepRel(files, 'manifest');
77
+ const lockRel = firstDepRel(files, 'lockfile');
78
+ const picked = lockOnly ? lockRel : manifest;
79
+ const target = picked || files[0] || '';
80
+ const meta = depFileMeta(target);
81
+ if (!meta) return null;
82
+ const cwd = meta.dir === '.' ? top : path.join(top, meta.dir);
83
+ let npmArgs = ['i'];
84
+ if (removing) npmArgs = ['uninstall', change.name];
85
+ else if (lockOnly) npmArgs = ['audit', 'fix'];
86
+ const fail = removing ? 'npm uninstall failed' : 'npm i failed';
87
+ const dirLock = meta.dir === '.' ? LOCKFILE : `${meta.dir}/${LOCKFILE}`;
88
+ const lockPath = lockRel || dirLock;
89
+ const prepareManifest = !lockOnly && !removing && !!manifest;
90
+ return {
91
+ cwd,
92
+ npmArgs,
93
+ fail,
94
+ lockOnly,
95
+ lockPath,
96
+ manifest,
97
+ prepareManifest,
98
+ runner: item.dep.install,
99
+ };
100
+ };
101
+
28
102
  const hasProposed = (item) => {
29
103
  const change = depChange(item);
30
104
  return !!(change && change.propose);
31
105
  };
32
106
 
33
107
  const relatedFiles = (items) => {
34
- const files = [];
108
+ let size = 0;
109
+ for (const item of items) size += depPaths(item).length;
110
+ const files = new Array(size);
111
+ let i = 0;
35
112
  for (const item of items) {
36
- for (const file of depPaths(item)) files.push(file);
113
+ const paths = depPaths(item);
114
+ for (let j = 0; j < paths.length; j++) files[i++] = paths[j];
37
115
  }
38
116
  return files;
39
117
  };
@@ -230,10 +308,7 @@ const captureFile = (top, rel) => {
230
308
 
231
309
  const restoreFile = (top, saved) => {
232
310
  const abs = path.join(top, saved.rel);
233
- if (saved.existed) {
234
- fs.writeFileSync(abs, saved.text);
235
- return;
236
- }
311
+ if (saved.existed) return void fs.writeFileSync(abs, saved.text);
237
312
  try {
238
313
  fs.unlinkSync(abs);
239
314
  } catch {
@@ -242,8 +317,9 @@ const restoreFile = (top, saved) => {
242
317
  };
243
318
 
244
319
  const captureFiles = (top, rels) => {
245
- const owned = [];
246
- for (const rel of rels) owned.push(captureFile(top, rel));
320
+ const { length } = rels;
321
+ const owned = new Array(length);
322
+ for (let i = 0; i < length; i++) owned[i] = captureFile(top, rels[i]);
247
323
  return owned;
248
324
  };
249
325
 
@@ -299,9 +375,6 @@ const ownedRelsOf = (plan) => {
299
375
  return rels;
300
376
  };
301
377
 
302
- const resultFailed = (result) =>
303
- !!(result && (result.error || result.status !== 0));
304
-
305
378
  const errorFromResult = (result, fail) => {
306
379
  if (!result) return new Error(fail);
307
380
  if (result.error) {
@@ -338,7 +411,9 @@ const stagePlan = (top, plan) => {
338
411
  };
339
412
 
340
413
  const finishPlan = (top, plan, result, stage) => {
341
- if (resultFailed(result)) throw errorFromResult(result, plan.fail);
414
+ if (result && (result.error || result.status !== 0)) {
415
+ throw errorFromResult(result, plan.fail);
416
+ }
342
417
  const stageFn = stage ?? stagePlan;
343
418
  stageFn(top, plan);
344
419
  };
@@ -409,10 +484,7 @@ const applyDepChange = (top, item, mode) => {
409
484
 
410
485
  const addItem = (top, item) => {
411
486
  if (item.origin === 'staged' || item.origin === 'commit') return;
412
- if (depChange(item)) {
413
- applyDepChange(top, item, 'add');
414
- return;
415
- }
487
+ if (depChange(item)) return void applyDepChange(top, item, 'add');
416
488
  const files = depPaths(item);
417
489
  if (files.length) {
418
490
  requireOk(runGit(['add', '--', ...files], top));
@@ -430,10 +502,7 @@ const addItemAsync = async (top, item) => {
430
502
 
431
503
  const unstageItem = (top, item) => {
432
504
  if (item.origin !== 'staged') return;
433
- if (depChange(item)) {
434
- applyDepChange(top, item, 'unstage');
435
- return;
436
- }
505
+ if (depChange(item)) return void applyDepChange(top, item, 'unstage');
437
506
  const files = depPaths(item);
438
507
  if (files.length) {
439
508
  requireOk(runGit(['restore', '--staged', '--', ...files], top));
@@ -441,10 +510,7 @@ const unstageItem = (top, item) => {
441
510
  };
442
511
 
443
512
  const revertDepItem = (top, item) => {
444
- if (depChange(item)) {
445
- applyDepChange(top, item, 'revert');
446
- return;
447
- }
513
+ if (depChange(item)) return void applyDepChange(top, item, 'revert');
448
514
  const byPath = new Map();
449
515
  for (const orig of origDepItems(item)) {
450
516
  const rel = itemPath(orig);
@@ -453,7 +519,7 @@ const revertDepItem = (top, item) => {
453
519
  else byPath.set(rel, [orig]);
454
520
  }
455
521
  for (const rel of byPath.keys()) {
456
- changes.revertOnePath(top, rel, byPath.get(rel));
522
+ revertOnePath(top, rel, byPath.get(rel));
457
523
  }
458
524
  };
459
525
 
@@ -463,7 +529,7 @@ const revertFile = (top, rel, items) => {
463
529
  for (const file of paths) {
464
530
  const group = itemsForPath(file, items);
465
531
  const targets = group.length ? group : items;
466
- changes.revertOnePath(top, file, targets);
532
+ revertOnePath(top, file, targets);
467
533
  }
468
534
  };
469
535
 
@@ -486,4 +552,11 @@ module.exports = {
486
552
  unstageItem,
487
553
  revertDepItem,
488
554
  revertFile,
555
+ AUDIT_MS,
556
+ INSTALL_MS,
557
+ runNpm,
558
+ runNpmAsync,
559
+ runNpmJson,
560
+ runNpmJsonAsync,
561
+ proposedNpmPlan,
489
562
  };