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,6 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const { oneLine, requireOk, runGit, runGitAsync } = require('./run.js');
3
+ const fs = require('node:fs');
4
+ const os = require('node:os');
5
+ const path = require('node:path');
6
+
7
+ const { oneLine, requireOk, runGit, runGitAsync } = require('./git.js');
4
8
 
5
9
  const BRANCH_FORMAT = [
6
10
  '%(HEAD)',
@@ -20,12 +24,15 @@ const PUSH_REJECTED = [
20
24
  'updates were rejected',
21
25
  ];
22
26
 
23
- const lastMessage = (top) => {
24
- const result = runGit(['log', '-1', '--format=%B'], top);
27
+ const commitMessage = (top, sha) => {
28
+ const spec = `${sha ?? ''}`.trim() || 'HEAD';
29
+ const result = runGit(['log', '-1', '--format=%B', spec], top);
25
30
  if (result.status !== 0) return '';
26
31
  return (result.stdout ?? '').replace(/\s+$/, '');
27
32
  };
28
33
 
34
+ const lastMessage = (top) => commitMessage(top, 'HEAD');
35
+
29
36
  const COMMIT_FORMAT = ['%H', '%h', '%an', '%ar', '%s'].join('%x1f');
30
37
  const COMMIT_LIMIT = 256;
31
38
  const UNIT = '\x1f';
@@ -120,6 +127,177 @@ const dropCommitAsync = async (top, sha) => {
120
127
  await dropOlderCommitAsync(top, target);
121
128
  };
122
129
 
130
+ const FIXUP_MARK = 'fixup!';
131
+ const AUTOSQUASH_ARGS = [
132
+ '-c',
133
+ 'sequence.editor=:',
134
+ 'rebase',
135
+ '--interactive',
136
+ '--autosquash',
137
+ ];
138
+
139
+ const isFixupSubject = (subject) =>
140
+ `${subject ?? ''}`.trim().startsWith(FIXUP_MARK);
141
+
142
+ const fixupRest = (subject) => {
143
+ let text = `${subject ?? ''}`.trim();
144
+ while (text.startsWith(FIXUP_MARK)) {
145
+ text = text.slice(FIXUP_MARK.length).trim();
146
+ }
147
+ return text;
148
+ };
149
+
150
+ const commitSubject = (top, sha) => {
151
+ const args = ['log', '-1', '--format=%s', sha];
152
+ const result = requireOk(runGit(args, top), 'log failed');
153
+ return (result.stdout ?? '').trim();
154
+ };
155
+
156
+ const ancestorWithSubject = (top, sha, want) => {
157
+ const args = ['log', `--format=${COMMIT_FORMAT}`, `${sha}^`];
158
+ const result = runGit(args, top);
159
+ if (result.status !== 0) return '';
160
+ for (const line of result.stdout.split('\n')) {
161
+ if (!line) continue;
162
+ const entry = parseCommitLine(line, 1);
163
+ if (entry && entry.subject === want) return entry.sha;
164
+ }
165
+ return '';
166
+ };
167
+
168
+ const applyAutosquash = (top, ontoArgs) => {
169
+ const result = runGit([...AUTOSQUASH_ARGS, ...ontoArgs], top);
170
+ const failed = rebaseFailed(result);
171
+ if (!failed) return;
172
+ runGit(['rebase', '--abort'], top);
173
+ throw new Error(oneLine(failed));
174
+ };
175
+
176
+ const applyAutosquashAsync = async (top, ontoArgs) => {
177
+ const result = await runGitAsync([...AUTOSQUASH_ARGS, ...ontoArgs], top);
178
+ const failed = rebaseFailed(result);
179
+ if (!failed) return;
180
+ await runGitAsync(['rebase', '--abort'], top);
181
+ throw new Error(oneLine(failed));
182
+ };
183
+
184
+ const applyFixupOnto = (top, original, applyRange) => {
185
+ const parent = runGit(['rev-parse', '--verify', `${original}^`], top);
186
+ if (parent.status !== 0) return applyRange(top, ['--root']);
187
+ return applyRange(top, [(parent.stdout ?? '').trim()]);
188
+ };
189
+
190
+ const applyFixup = (top, sha) => {
191
+ const spec = namedCommit(sha);
192
+ const commit = resolveCommit(top, spec);
193
+ const subject = commitSubject(top, commit);
194
+ if (!isFixupSubject(subject)) throw new Error('not a fixup commit');
195
+ const want = fixupRest(subject);
196
+ if (!want) throw new Error('no fixup target');
197
+ const original = ancestorWithSubject(top, commit, want);
198
+ if (!original) throw new Error('no fixup target');
199
+ applyFixupOnto(top, original, applyAutosquash);
200
+ };
201
+
202
+ const applyFixupAsync = async (top, sha) => {
203
+ const spec = namedCommit(sha);
204
+ const commit = resolveCommit(top, spec);
205
+ const subject = commitSubject(top, commit);
206
+ if (!isFixupSubject(subject)) throw new Error('not a fixup commit');
207
+ const want = fixupRest(subject);
208
+ if (!want) throw new Error('no fixup target');
209
+ const original = ancestorWithSubject(top, commit, want);
210
+ if (!original) throw new Error('no fixup target');
211
+ await applyFixupOnto(top, original, applyAutosquashAsync);
212
+ };
213
+
214
+ const messageBody = (message) => {
215
+ const text = `${message ?? ''}`;
216
+ return text.endsWith('\n') ? text : `${text}\n`;
217
+ };
218
+
219
+ const rewordHead = (top, message) => {
220
+ const args = ['commit', '--amend', '--only', '-F', '-'];
221
+ requireOk(runGit(args, top, messageBody(message)));
222
+ };
223
+
224
+ const REWORD_TODO = path.join(__dirname, 'git-reword-todo.js');
225
+
226
+ const prepareRewordEditor = (message) => {
227
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'reslop-reword-'));
228
+ const msgFile = path.join(dir, 'MSG');
229
+ fs.writeFileSync(msgFile, messageBody(message));
230
+ const node = JSON.stringify(process.execPath);
231
+ const script = JSON.stringify(REWORD_TODO);
232
+ return {
233
+ dir,
234
+ env: {
235
+ GIT_SEQUENCE_EDITOR: `${node} ${script}`,
236
+ RESLOP_REWORD_FILE: msgFile,
237
+ },
238
+ };
239
+ };
240
+
241
+ const rewordRange = (top, sha, env) => {
242
+ const parent = runGit(['rev-parse', '--verify', `${sha}^`], top);
243
+ const onto =
244
+ parent.status !== 0 ? ['--root'] : [(parent.stdout ?? '').trim()];
245
+ return runGit(['rebase', '--interactive', ...onto], top, undefined, { env });
246
+ };
247
+
248
+ const rewordRangeAsync = async (top, sha, env) => {
249
+ const parent = runGit(['rev-parse', '--verify', `${sha}^`], top);
250
+ const onto =
251
+ parent.status !== 0 ? ['--root'] : [(parent.stdout ?? '').trim()];
252
+ return runGitAsync(['rebase', '--interactive', ...onto], top, { env });
253
+ };
254
+
255
+ const finishRewordRebase = (top, result) => {
256
+ const failed = rebaseFailed(result);
257
+ if (!failed) return;
258
+ runGit(['rebase', '--abort'], top);
259
+ throw new Error(oneLine(failed));
260
+ };
261
+
262
+ const rewordOlder = (top, sha, message) => {
263
+ const prepared = prepareRewordEditor(message);
264
+ try {
265
+ const result = rewordRange(top, sha, prepared.env);
266
+ finishRewordRebase(top, result);
267
+ } finally {
268
+ fs.rmSync(prepared.dir, { recursive: true, force: true });
269
+ }
270
+ };
271
+
272
+ const rewordOlderAsync = async (top, sha, message) => {
273
+ const prepared = prepareRewordEditor(message);
274
+ try {
275
+ const result = await rewordRangeAsync(top, sha, prepared.env);
276
+ const failed = rebaseFailed(result);
277
+ if (!failed) return;
278
+ await runGitAsync(['rebase', '--abort'], top);
279
+ throw new Error(oneLine(failed));
280
+ } finally {
281
+ fs.rmSync(prepared.dir, { recursive: true, force: true });
282
+ }
283
+ };
284
+
285
+ const rewordCommit = (top, sha, message) => {
286
+ const spec = namedCommit(sha);
287
+ const commit = resolveCommit(top, spec);
288
+ const head = resolveCommit(top, 'HEAD');
289
+ if (commit === head) return void rewordHead(top, message);
290
+ rewordOlder(top, commit, message);
291
+ };
292
+
293
+ const rewordCommitAsync = async (top, sha, message) => {
294
+ const spec = namedCommit(sha);
295
+ const commit = resolveCommit(top, spec);
296
+ const head = resolveCommit(top, 'HEAD');
297
+ if (commit === head) return void rewordHead(top, message);
298
+ await rewordOlderAsync(top, commit, message);
299
+ };
300
+
123
301
  const parseTrack = (text) => {
124
302
  const raw = `${text ?? ''}`.trim().replace(/^\[|\]$/g, '');
125
303
  if (!raw) return { ahead: 0, behind: 0, gone: false };
@@ -183,10 +361,11 @@ const listBranches = (top) => {
183
361
  const names = new Set();
184
362
  for (const entry of branches) names.add(entry.name);
185
363
  const primary = defaultBranch(top, names);
186
- const listed = [];
187
- for (const entry of branches) {
188
- const isDefault = entry.name === primary;
189
- listed.push({ ...entry, isDefault });
364
+ const { length } = branches;
365
+ const listed = new Array(length);
366
+ for (let i = 0; i < length; i++) {
367
+ const entry = branches[i];
368
+ listed[i] = { ...entry, isDefault: entry.name === primary };
190
369
  }
191
370
  return listed;
192
371
  };
@@ -337,10 +516,6 @@ const hasStaged = (top) => {
337
516
  };
338
517
 
339
518
  const commitPlan = (kind, message) => {
340
- if (kind === 'fixup') {
341
- const target = (message ?? '').trim() || 'HEAD';
342
- return { args: ['commit', `--fixup=${target}`], body: undefined };
343
- }
344
519
  const args = ['commit'];
345
520
  if (kind === 'amend') args.push('--amend');
346
521
  args.push('-F', '-');
@@ -361,6 +536,7 @@ const commitChangesAsync = async (top, kind, message) => {
361
536
 
362
537
  module.exports = {
363
538
  lastMessage,
539
+ commitMessage,
364
540
  listCommits,
365
541
  listBranches,
366
542
  checkoutBranch,
@@ -371,6 +547,10 @@ module.exports = {
371
547
  rebaseBranchAsync,
372
548
  dropCommit,
373
549
  dropCommitAsync,
550
+ applyFixup,
551
+ applyFixupAsync,
552
+ rewordCommit,
553
+ rewordCommitAsync,
374
554
  dropBranch,
375
555
  dropBranchAsync,
376
556
  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
  };
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+
5
+ const file = process.argv[process.argv.length - 1];
6
+ const msg = process.env.RESLOP_REWORD_FILE;
7
+ if (!file || !msg) process.exit(1);
8
+ const text = fs.readFileSync(file, 'utf8');
9
+ const rows = text.split('\n');
10
+ const out = [];
11
+ let seen = false;
12
+ for (const row of rows) {
13
+ out.push(row);
14
+ if (seen) continue;
15
+ if (!/^(pick|p)\s+/.test(row)) continue;
16
+ const spec = JSON.stringify(msg);
17
+ out.push(`exec git commit --amend --only -F ${spec}`);
18
+ seen = true;
19
+ }
20
+ fs.writeFileSync(file, out.join('\n'));