reslop 0.2.4 → 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 (109) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/LICENSE +1 -1
  3. package/README.md +12 -13
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -12
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +48 -18
  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 +196 -404
  14. package/lib/deps.js +240 -399
  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 +138 -141
  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 +90 -206
  25. package/lib/editor.js +98 -55
  26. package/lib/files.js +86 -103
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +182 -344
  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 +157 -132
  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 +16 -26
  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 +22 -35
  44. package/lib/highlight/txt.js +9 -22
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +62 -85
  47. package/lib/manifest.js +67 -130
  48. package/lib/npm-commands.js +119 -189
  49. package/lib/remote.js +141 -197
  50. package/lib/render/branches.js +66 -73
  51. package/lib/render/chrome.js +264 -323
  52. package/lib/render/commits.js +187 -500
  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 -228
  58. package/lib/render/files.js +45 -99
  59. package/lib/render/frame.js +129 -183
  60. package/lib/render/notes.js +144 -160
  61. package/lib/render/npm.js +234 -112
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +131 -170
  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 -28
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -38
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -78
  72. package/lib/report-test.js +256 -141
  73. package/lib/report-tools.js +96 -161
  74. package/lib/review-merge.js +114 -152
  75. package/lib/review.js +115 -189
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +89 -75
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +82 -63
  80. package/lib/session/branches.js +193 -247
  81. package/lib/session/change-actions.js +171 -214
  82. package/lib/session/commit.js +280 -342
  83. package/lib/session/compose.js +559 -674
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +164 -157
  86. package/lib/session/edit-click.js +57 -0
  87. package/lib/session/files-pane.js +85 -53
  88. package/lib/session/items.js +73 -104
  89. package/lib/session/lifecycle.js +403 -541
  90. package/lib/session/load.js +48 -68
  91. package/lib/session/navigation.js +96 -87
  92. package/lib/session/npm.js +409 -345
  93. package/lib/session/ops.js +196 -63
  94. package/lib/session/pointer.js +23 -15
  95. package/lib/session/progress.js +33 -21
  96. package/lib/session/review.js +33 -54
  97. package/lib/session/tasks.js +333 -0
  98. package/lib/session/templates.js +51 -59
  99. package/lib/session/terminal.js +85 -136
  100. package/lib/session/unit.js +3 -4
  101. package/lib/session/update.js +76 -97
  102. package/lib/session/view.js +52 -83
  103. package/lib/session/watch.js +208 -204
  104. package/lib/session.js +112 -237
  105. package/lib/update.js +45 -116
  106. package/lib/utilities.js +67 -89
  107. package/lib/wrap.js +33 -44
  108. package/package.json +1 -1
  109. package/lib/session/todos.js +0 -293
package/lib/git-deps.js CHANGED
@@ -4,376 +4,232 @@ const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
6
  const deps = require('./deps.js');
7
- const { foldDepItems, mergeDepFile, depFileMeta } = deps;
7
+ const { foldDepItems, mergeProposedItems } = deps;
8
8
  const { parseAuditReport, parseOutdatedReport } = deps;
9
- const { mergeProposedItems, MANIFEST, LOCKFILE } = deps;
10
9
  const { collectUsedNames, collectExportMeta } = deps;
10
+ const manifest = require('./manifest.js');
11
+ const { MANIFEST, LOCKFILE, depFileMeta, mergeDepFile, relOf } = manifest;
11
12
  const files = require('./files.js');
12
13
  const { itemPath, isPathInScope } = files;
13
- const proc = require('./utilities.js');
14
- const { abortError, npmBin, npmOpts, runProc, runProcSync } = proc;
15
- const git = require('./git-worktree.js');
16
- const { oneLine, requireOk, runGit } = git;
17
- const { gitText, worktreeText, readDepSides, revertOnePath } = git;
18
- const { writeFile, writeIndex } = git;
14
+ const utilities = require('./utilities.js');
15
+ const { IS_WIN, abortError, npmBin, oneLine, runProc, runProcSync } = utilities;
16
+ const worktree = require('./git-worktree.js');
17
+ const { requireOk, runGit, gitText, worktreeText, readDepSides } = worktree;
18
+ const { revertOnePath, groupByPath, writeFile, writeIndex } = worktree;
19
19
 
20
20
  const AUDIT_MS = 15000;
21
21
  const INSTALL_MS = 120000;
22
22
 
23
- const npmProcOptions = (cwd, extra = {}) => ({
23
+ const NPM_REPORT = {
24
+ audit: {
25
+ file: LOCKFILE,
26
+ args: ['audit', '--json', '--package-lock-only'],
27
+ parse: parseAuditReport,
28
+ },
29
+ outdated: {
30
+ file: MANIFEST,
31
+ args: ['outdated', '--json', '--long'],
32
+ parse: parseOutdatedReport,
33
+ },
34
+ };
35
+
36
+ const npmOptions = (cwd, timeout, signal) => ({
24
37
  cwd,
25
- encoding: proc.DEFAULT_ENCODING,
26
- maxBuffer: proc.DEFAULT_MAX_BUFFER,
27
- timeout: extra.timeout,
28
- signal: extra.signal,
29
- shell: npmOpts().shell,
38
+ timeout,
39
+ signal,
40
+ shell: IS_WIN,
30
41
  });
31
42
 
32
43
  const runNpm = (cwd, args) =>
33
- runProcSync(npmBin(), args, npmProcOptions(cwd, { timeout: INSTALL_MS }));
44
+ runProcSync(npmBin(), args, npmOptions(cwd, INSTALL_MS));
34
45
 
35
- const runNpmAsync = (cwd, args, extra = {}) => {
36
- const bin = npmBin();
37
- const signal = extra.signal;
38
- const options = npmProcOptions(cwd, { timeout: INSTALL_MS, signal });
39
- return runProc(bin, args, options);
40
- };
46
+ const runNpmAsync = (cwd, args) =>
47
+ runProc(npmBin(), args, npmOptions(cwd, INSTALL_MS));
41
48
 
42
- const runNpmJson = (top, args, timeout) => {
43
- const result = runProcSync(npmBin(), args, npmProcOptions(top, { timeout }));
44
- if (result.error) return '';
45
- return result.stdout || '';
49
+ const npmOutput = (result) => (result.error ? '' : result.stdout);
50
+
51
+ const collectReport = (top, kind) => {
52
+ const report = NPM_REPORT[kind];
53
+ if (!fs.existsSync(path.join(top, report.file))) return null;
54
+ const options = npmOptions(top, AUDIT_MS);
55
+ return report.parse(npmOutput(runProcSync(npmBin(), report.args, options)));
46
56
  };
47
57
 
48
- const runNpmJsonAsync = async (top, args, timeout, signal) => {
49
- const result = await runProc(
50
- npmBin(),
51
- args,
52
- npmProcOptions(top, { timeout, signal }),
53
- );
54
- if (result.error) return '';
55
- return result.stdout || '';
58
+ const collectReportAsync = async (top, kind, signal) => {
59
+ const report = NPM_REPORT[kind];
60
+ if (!fs.existsSync(path.join(top, report.file))) return null;
61
+ const options = npmOptions(top, AUDIT_MS, signal);
62
+ const result = await runProc(npmBin(), report.args, options);
63
+ return report.parse(npmOutput(result));
56
64
  };
57
65
 
58
66
  const depPaths = (item) => item.dep?.files ?? [];
59
67
 
60
- const origDepItems = (item) => item.dep?.items ?? [];
61
-
62
68
  const depChange = (item) => item.dep?.change ?? null;
63
69
 
64
- const firstDepRel = (files, kind) => {
65
- for (const rel of files) {
66
- const meta = depFileMeta(rel);
67
- if (meta && meta.kind === kind) return rel;
68
- }
69
- return '';
70
- };
70
+ const hasProposed = (item) => !!depChange(item)?.propose;
71
+
72
+ const isStagedOrCommit = (item) =>
73
+ item.origin === 'staged' || item.origin === 'commit';
74
+
75
+ const firstDepRel = (rels, kind) =>
76
+ rels.find((rel) => depFileMeta(rel)?.kind === kind) ?? '';
71
77
 
72
78
  const proposedNpmPlan = (top, item) => {
73
79
  const change = depChange(item);
74
80
  if (!change) return null;
75
- const files = depPaths(item);
81
+ const rels = depPaths(item);
76
82
  const lockOnly = change.section === 'resolved';
77
83
  const removing = !!change.unused;
78
- const manifest = firstDepRel(files, 'manifest');
79
- const lockRel = firstDepRel(files, 'lockfile');
80
- const picked = lockOnly ? lockRel : manifest;
81
- const target = picked || files[0] || '';
82
- const meta = depFileMeta(target);
84
+ const manifestRel = firstDepRel(rels, 'manifest');
85
+ const lockRel = firstDepRel(rels, 'lockfile');
86
+ const meta = depFileMeta((lockOnly ? lockRel : manifestRel) || rels[0]);
83
87
  if (!meta) return null;
84
- const cwd = meta.dir === '.' ? top : path.join(top, meta.dir);
85
88
  let npmArgs = ['i'];
86
89
  if (removing) npmArgs = ['uninstall', change.name];
87
90
  else if (lockOnly) npmArgs = ['audit', 'fix'];
88
- const fail = removing ? 'npm uninstall failed' : 'npm i failed';
89
- const dirLock = meta.dir === '.' ? LOCKFILE : `${meta.dir}/${LOCKFILE}`;
90
- const lockPath = lockRel || dirLock;
91
- const prepareManifest = !lockOnly && !removing && !!manifest;
92
91
  return {
93
- cwd,
92
+ cwd: meta.dir === '.' ? top : path.join(top, meta.dir),
94
93
  npmArgs,
95
- fail,
94
+ fail: removing ? 'npm uninstall failed' : 'npm i failed',
96
95
  lockOnly,
97
- lockPath,
98
- manifest,
99
- prepareManifest,
96
+ lockPath: lockRel || relOf(meta.dir, LOCKFILE),
97
+ manifest: manifestRel,
98
+ prepareManifest: !lockOnly && !removing && !!manifestRel,
100
99
  runner: item.dep.install,
101
100
  };
102
101
  };
103
102
 
104
- const hasProposed = (item) => {
105
- const change = depChange(item);
106
- return !!(change && change.propose);
107
- };
108
-
109
103
  const itemsForPath = (rel, items) => {
110
104
  const group = [];
111
105
  for (const item of items) {
112
- if (item.dep) {
113
- for (const orig of origDepItems(item)) {
114
- if (itemPath(orig) === rel) group.push(orig);
115
- }
116
- continue;
106
+ const listed = item.dep ? item.dep.items : [item];
107
+ for (const entry of listed ?? []) {
108
+ if (itemPath(entry) === rel) group.push(entry);
117
109
  }
118
- if (itemPath(item) === rel) group.push(item);
119
110
  }
120
111
  return group;
121
112
  };
122
113
 
123
- const hasDepFile = (items) => {
124
- for (const item of items) {
125
- if (depFileMeta(itemPath(item))) return true;
126
- }
127
- return false;
128
- };
129
-
130
- const collectAudit = (top) => {
131
- const lock = path.join(top, LOCKFILE);
132
- if (!fs.existsSync(lock)) return null;
133
- const args = ['audit', '--json', '--package-lock-only'];
134
- const text = runNpmJson(top, args, AUDIT_MS);
135
- return parseAuditReport(text);
136
- };
137
-
138
- const collectOutdated = (top) => {
139
- const manifest = path.join(top, MANIFEST);
140
- if (!fs.existsSync(manifest)) return null;
141
- const args = ['outdated', '--json', '--long'];
142
- const text = runNpmJson(top, args, AUDIT_MS);
143
- return parseOutdatedReport(text);
144
- };
145
-
146
- const collectAuditAsync = async (top, extra, signal) => {
147
- if (extra.auditMap !== undefined) return extra.auditMap;
148
- const lock = path.join(top, LOCKFILE);
149
- if (!fs.existsSync(lock)) return null;
150
- const args = ['audit', '--json', '--package-lock-only'];
151
- const text = await runNpmJsonAsync(top, args, AUDIT_MS, signal);
152
- return parseAuditReport(text);
153
- };
154
-
155
- const collectOutdatedAsync = async (top, extra, signal) => {
156
- if (extra.outdatedMap !== undefined) return extra.outdatedMap;
157
- const manifest = path.join(top, MANIFEST);
158
- if (!fs.existsSync(manifest)) return null;
159
- const args = ['outdated', '--json', '--long'];
160
- const text = await runNpmJsonAsync(top, args, AUDIT_MS, signal);
161
- return parseOutdatedReport(text);
162
- };
163
-
164
- const pickNpmMap = (override, collect, top) => {
165
- if (override !== undefined) return override;
166
- return collect(top);
114
+ const extrasNeeds = (items, rev, extra) => {
115
+ const live = extra.audit === true && !rev;
116
+ const hasDepDiff = items.some((item) => depFileMeta(itemPath(item)));
117
+ const audit = live || (Boolean(extra.audit) && hasDepDiff);
118
+ return { live, scan: live || hasDepDiff, audit };
167
119
  };
168
120
 
169
- const foldLoaded = (items, top, rev, extra = {}) => {
121
+ const foldLoaded = (items, top, rev, extra) => {
170
122
  const readSides = (origin, rel) => readDepSides(top, rev, origin, rel);
171
- const live = extra.audit === true && !rev;
172
- const hasDepDiff = hasDepFile(items);
123
+ const needs = extrasNeeds(items, rev, extra);
173
124
  const collect = extra.collect !== false;
174
- let usedNames = extra.usedNames ?? null;
175
- let audit = extra.auditMap;
176
- let outdated = extra.outdatedMap;
177
- let exportEntries = extra.exportEntries;
178
- if (collect && usedNames === null && (hasDepDiff || live)) {
179
- usedNames = collectUsedNames(top);
180
- }
181
- if (collect && exportEntries === undefined && (hasDepDiff || live)) {
125
+ const scan = collect && needs.scan;
126
+ const usedNames = extra.usedNames ?? (scan ? collectUsedNames(top) : null);
127
+ let { exportEntries, auditMap: audit, outdatedMap: outdated } = extra;
128
+ if (scan && exportEntries === undefined) {
182
129
  exportEntries = collectExportMeta(top);
183
130
  }
184
- if (collect && live) {
185
- audit = pickNpmMap(extra.auditMap, collectAudit, top);
186
- outdated = pickNpmMap(extra.outdatedMap, collectOutdated, top);
187
- } else if (collect && extra.audit && hasDepDiff) {
188
- audit = pickNpmMap(extra.auditMap, collectAudit, top);
131
+ if (collect && needs.audit && audit === undefined) {
132
+ audit = collectReport(top, 'audit');
133
+ }
134
+ if (collect && needs.live && outdated === undefined) {
135
+ outdated = collectReport(top, 'outdated');
189
136
  }
190
- if (audit === undefined) audit = null;
191
- if (outdated === undefined) outdated = null;
137
+ audit ??= null;
138
+ outdated ??= null;
192
139
  const folded = foldDepItems(items, readSides, usedNames, audit, outdated, {
193
140
  root: top,
194
141
  exportEntries,
195
142
  });
196
- if (!live) return folded;
197
- if (!isPathInScope(MANIFEST, extra.paths ?? [])) return folded;
143
+ if (!needs.live || !isPathInScope(MANIFEST, extra.paths)) return folded;
198
144
  const pkgText = worktreeText(top, MANIFEST);
199
145
  const lockText = worktreeText(top, LOCKFILE);
200
146
  const options = { lockText, usedNames, root: top, exportEntries };
201
147
  return mergeProposedItems(folded, pkgText, outdated, audit, options);
202
148
  };
203
149
 
204
- const needsExtras = (parsed, rev, extra) => {
205
- const live = extra.audit === true && !rev;
206
- if (live) return true;
207
- if (!extra.audit) return false;
208
- return hasDepFile(parsed);
209
- };
210
-
211
150
  const finishLoad = (snapshot, extra) => {
212
- const options = { ...extra, paths: extra.paths ?? [] };
213
- if (extra.deferExtras) options.collect = false;
214
- const parsed = snapshot.parsed;
215
- const top = snapshot.top;
216
- const rev = snapshot.rev;
151
+ const { top, rev, parsed } = snapshot;
152
+ const options = { ...extra, collect: !extra.deferExtras };
217
153
  const items = foldLoaded(parsed, top, rev, options);
218
- const pending = extra.deferExtras && needsExtras(parsed, rev, extra);
154
+ const pending = extra.deferExtras && extrasNeeds(parsed, rev, extra).audit;
219
155
  return { ...snapshot, items, pending };
220
156
  };
221
157
 
222
- const extrasMetaOf = (parsed, rev, extra, top, signal) => {
223
- const live = extra.audit === true && !rev;
224
- const hasDepDiff = hasDepFile(parsed);
225
- const needScan = live || hasDepDiff;
226
- let usedNames = Promise.resolve(null);
227
- if (needScan) {
228
- usedNames = Promise.resolve().then(() => collectUsedNames(top));
229
- }
230
- let exportEntries = Promise.resolve(undefined);
231
- if (extra.exportEntries !== undefined) {
232
- exportEntries = Promise.resolve(extra.exportEntries);
233
- } else if (needScan) {
234
- exportEntries = Promise.resolve().then(() => collectExportMeta(top));
235
- }
236
- let audit = Promise.resolve(null);
237
- if (live || (extra.audit && hasDepDiff)) {
238
- audit = collectAuditAsync(top, extra, signal);
239
- }
240
- let outdated = Promise.resolve(null);
241
- if (live) {
242
- outdated = collectOutdatedAsync(top, extra, signal);
243
- }
244
- return { usedNames, audit, outdated, exportEntries };
158
+ const later = (fn) => Promise.resolve().then(fn);
159
+
160
+ const reportTask = (wanted, known, collect) => {
161
+ if (!wanted) return null;
162
+ return known === undefined ? collect() : known;
245
163
  };
246
164
 
247
165
  const loadExtras = async (snapshot, extra = {}) => {
248
- const top = snapshot.top;
249
- const rev = snapshot.rev;
250
- const parsed = snapshot.parsed;
251
- const signal = extra.signal;
252
- const tasks = extrasMetaOf(parsed, rev, extra, top, signal);
166
+ const { top, rev, parsed } = snapshot;
167
+ const { signal } = extra;
168
+ const needs = extrasNeeds(parsed, rev, extra);
169
+ const known = extra.exportEntries;
253
170
  const [usedNames, audit, outdated, exportEntries] = await Promise.all([
254
- tasks.usedNames,
255
- tasks.audit,
256
- tasks.outdated,
257
- tasks.exportEntries,
171
+ needs.scan ? later(() => collectUsedNames(top)) : null,
172
+ reportTask(needs.audit, extra.auditMap, () =>
173
+ collectReportAsync(top, 'audit', signal),
174
+ ),
175
+ reportTask(needs.live, extra.outdatedMap, () =>
176
+ collectReportAsync(top, 'outdated', signal),
177
+ ),
178
+ known === undefined && needs.scan
179
+ ? later(() => collectExportMeta(top))
180
+ : known,
258
181
  ]);
259
182
  if (signal && signal.aborted) throw abortError();
260
- const folded = foldLoaded(parsed, top, rev, {
183
+ const found = { usedNames, auditMap: audit, outdatedMap: outdated };
184
+ const items = foldLoaded(parsed, top, rev, {
261
185
  ...extra,
186
+ ...found,
262
187
  paths: extra.paths ?? [],
263
- usedNames,
264
- auditMap: audit,
265
- outdatedMap: outdated,
266
188
  exportEntries,
267
189
  collect: false,
268
190
  });
269
- return {
270
- ...snapshot,
271
- items: folded,
272
- pending: false,
273
- usedNames,
274
- auditMap: audit,
275
- outdatedMap: outdated,
276
- exportEntries,
277
- };
191
+ return { ...snapshot, items, pending: false, ...found, exportEntries };
278
192
  };
279
193
 
280
194
  const captureFile = (top, rel) => {
281
- const abs = path.join(top, rel);
282
195
  try {
283
- const text = fs.readFileSync(abs, 'utf8');
196
+ const text = fs.readFileSync(path.join(top, rel), 'utf8');
284
197
  return { rel, existed: true, text };
285
198
  } catch (error) {
286
- if (error.code === 'ENOENT') {
287
- return { rel, existed: false, text: '' };
288
- }
289
- throw error;
199
+ if (error.code !== 'ENOENT') throw error;
200
+ return { rel, existed: false, text: '' };
290
201
  }
291
202
  };
292
203
 
293
204
  const restoreFile = (top, saved) => {
294
205
  const abs = path.join(top, saved.rel);
295
- if (saved.existed) return void fs.writeFileSync(abs, saved.text);
296
- try {
297
- fs.unlinkSync(abs);
298
- } catch {
299
- // missing
300
- }
206
+ if (saved.existed) fs.writeFileSync(abs, saved.text);
207
+ else fs.rmSync(abs, { force: true });
301
208
  };
302
209
 
303
- const captureFiles = (top, rels) => {
304
- const { length } = rels;
305
- const owned = new Array(length);
306
- for (let i = 0; i < length; i++) owned[i] = captureFile(top, rels[i]);
307
- return owned;
308
- };
309
-
310
- const restoreFiles = (top, owned) => {
311
- for (const saved of owned) restoreFile(top, saved);
312
- };
313
-
314
- const mergeDepLive = (top, rel, change, origin, base, side, kind) => {
315
- const sides = readDepSides(top, null, origin, rel);
210
+ const mergeLive = (top, rel, item, base, side) => {
211
+ const sides = readDepSides(top, null, item.origin, rel);
212
+ const { kind } = depFileMeta(rel);
213
+ const change = depChange(item);
316
214
  return mergeDepFile(base, sides.oldText, sides.newText, change, side, kind);
317
215
  };
318
216
 
319
- const applyDepIndex = (top, rel, change, origin, side) => {
320
- const meta = depFileMeta(rel);
321
- if (!meta) return;
217
+ const applyDepIndex = (top, rel, item, side) => {
218
+ if (!depFileMeta(rel)) return;
322
219
  const indexed = gitText(top, `:${rel}`) || gitText(top, `HEAD:${rel}`);
323
- const text = mergeDepLive(top, rel, change, origin, indexed, side, meta.kind);
324
- writeIndex(top, rel, text);
220
+ writeIndex(top, rel, mergeLive(top, rel, item, indexed, side));
325
221
  };
326
222
 
327
- const applyDepWorktree = (top, rel, change, origin) => {
328
- const meta = depFileMeta(rel);
329
- if (!meta) return;
330
- const worktree = worktreeText(top, rel);
331
- const text = mergeDepLive(
332
- top,
333
- rel,
334
- change,
335
- origin,
336
- worktree,
337
- 'old',
338
- meta.kind,
339
- );
223
+ const applyDepWorktree = (top, rel, item, side) => {
224
+ if (!depFileMeta(rel)) return;
225
+ const text = mergeLive(top, rel, item, worktreeText(top, rel), side);
340
226
  writeFile(top, rel, text);
341
227
  };
342
228
 
343
- const ownedRelsOf = (plan) => {
344
- const rels = [];
345
- if (plan.manifest) rels.push(plan.manifest);
346
- if (plan.lockPath && plan.lockPath !== plan.manifest) {
347
- rels.push(plan.lockPath);
348
- }
349
- return rels;
350
- };
351
-
352
- const errorFromResult = (result, fail) => {
353
- if (!result) return new Error(fail);
354
- if (result.error) {
355
- const msg = result.error.message || fail;
356
- return new Error(oneLine(msg, fail));
357
- }
358
- const msg = result.stderr || result.stdout || fail;
359
- return new Error(oneLine(msg, fail));
360
- };
361
-
362
- const applyPreparation = (top, item, plan) => {
363
- if (!plan.prepareManifest || !plan.manifest) return;
364
- const change = depChange(item);
365
- const origin = item.origin;
366
- const worktree = worktreeText(top, plan.manifest);
367
- const text = mergeDepLive(
368
- top,
369
- plan.manifest,
370
- change,
371
- origin,
372
- worktree,
373
- 'new',
374
- 'manifest',
375
- );
376
- writeFile(top, plan.manifest, text);
229
+ const npmError = (result, fail) => {
230
+ const { error } = result;
231
+ const msg = error ? error.message : result.stderr || result.stdout;
232
+ return new Error(oneLine(msg || fail, fail));
377
233
  };
378
234
 
379
235
  const stagePlan = (top, plan) => {
@@ -384,139 +240,121 @@ const stagePlan = (top, plan) => {
384
240
  requireOk(runGit(['add', '--', ...toAdd], top));
385
241
  };
386
242
 
387
- const finishPlan = (top, plan, result, stage) => {
388
- if (result && (result.error || result.status !== 0)) {
389
- throw errorFromResult(result, plan.fail);
390
- }
391
- const stageFn = stage ?? stagePlan;
392
- stageFn(top, plan);
243
+ const startPlan = (top, item) => {
244
+ const plan = proposedNpmPlan(top, item);
245
+ if (!plan) return null;
246
+ const rels = [...new Set([plan.manifest, plan.lockPath].filter(Boolean))];
247
+ const owned = rels.map((rel) => captureFile(top, rel));
248
+ return { plan, owned };
249
+ };
250
+
251
+ const finishPlan = (top, plan, result, stage = stagePlan) => {
252
+ const failed = result && (result.error || result.status !== 0);
253
+ if (failed) throw npmError(result, plan.fail);
254
+ stage(top, plan);
255
+ };
256
+
257
+ const restoreOwned = (top, owned) => {
258
+ for (const saved of owned) restoreFile(top, saved);
393
259
  };
394
260
 
395
261
  const applyProposedUpdate = (top, item, extra = {}) => {
396
- const plan = proposedNpmPlan(top, item);
397
- if (!plan) return;
398
- const owned = captureFiles(top, ownedRelsOf(plan));
262
+ const started = startPlan(top, item);
263
+ if (!started) return;
264
+ const { plan, owned } = started;
399
265
  try {
400
- applyPreparation(top, item, plan);
266
+ if (plan.prepareManifest) applyDepWorktree(top, plan.manifest, item, 'new');
401
267
  const install = extra.run ?? plan.runner ?? runNpm;
402
- const result = install(plan.cwd, plan.npmArgs);
403
- finishPlan(top, plan, result, extra.stage);
268
+ finishPlan(top, plan, install(plan.cwd, plan.npmArgs), extra.stage);
404
269
  } catch (error) {
405
- restoreFiles(top, owned);
270
+ restoreOwned(top, owned);
406
271
  throw error;
407
272
  }
408
273
  };
409
274
 
410
- const applyProposedUpdateAsync = async (top, item, extra = {}) => {
411
- const plan = proposedNpmPlan(top, item);
412
- if (!plan) return;
413
- const owned = captureFiles(top, ownedRelsOf(plan));
275
+ const applyProposedUpdateAsync = async (top, item) => {
276
+ const started = startPlan(top, item);
277
+ if (!started) return;
278
+ const { plan, owned } = started;
414
279
  try {
415
- applyPreparation(top, item, plan);
416
- const run = extra.run ?? plan.runner ?? runNpmAsync;
417
- const result = await run(plan.cwd, plan.npmArgs);
418
- finishPlan(top, plan, result, extra.stage);
280
+ if (plan.prepareManifest) applyDepWorktree(top, plan.manifest, item, 'new');
281
+ const install = plan.runner ?? runNpmAsync;
282
+ finishPlan(top, plan, await install(plan.cwd, plan.npmArgs));
419
283
  } catch (error) {
420
- restoreFiles(top, owned);
284
+ restoreOwned(top, owned);
421
285
  throw error;
422
286
  }
423
287
  };
424
288
 
425
289
  const applyDepChange = (top, item, mode) => {
426
290
  const change = depChange(item);
427
- if (change && change.propose) {
291
+ if (change.propose) {
428
292
  if (mode === 'add') return void applyProposedUpdate(top, item);
429
293
  if (!change.unused) return;
430
294
  }
431
- const origin = item.origin;
432
- const files = depPaths(item);
433
- for (const rel of files) {
434
- if (mode === 'add') {
435
- applyDepIndex(top, rel, change, origin, 'new');
436
- continue;
437
- }
438
- if (mode === 'unstage') {
439
- applyDepIndex(top, rel, change, origin, 'old');
295
+ for (const rel of depPaths(item)) {
296
+ if (mode !== 'revert') {
297
+ applyDepIndex(top, rel, item, mode === 'add' ? 'new' : 'old');
440
298
  continue;
441
299
  }
442
- if (origin === 'staged') applyDepIndex(top, rel, change, origin, 'old');
443
- applyDepWorktree(top, rel, change, origin);
300
+ if (item.origin === 'staged') applyDepIndex(top, rel, item, 'old');
301
+ applyDepWorktree(top, rel, item, 'old');
444
302
  }
445
303
  };
446
304
 
447
305
  const addItem = (top, item) => {
448
- if (item.origin === 'staged' || item.origin === 'commit') return;
306
+ if (!item.dep) return void worktree.addItem(top, item);
307
+ if (isStagedOrCommit(item)) return;
449
308
  if (depChange(item)) return void applyDepChange(top, item, 'add');
450
- const files = depPaths(item);
451
- if (files.length) {
452
- requireOk(runGit(['add', '--', ...files], top));
453
- }
309
+ const rels = depPaths(item);
310
+ if (rels.length) requireOk(runGit(['add', '--', ...rels], top));
454
311
  };
455
312
 
456
313
  const addItemAsync = async (top, item) => {
457
- if (item.origin === 'staged' || item.origin === 'commit') return;
458
- if (hasProposed(item)) {
459
- await applyProposedUpdateAsync(top, item);
460
- return;
314
+ if (isStagedOrCommit(item) || !hasProposed(item)) {
315
+ return void addItem(top, item);
461
316
  }
462
- addItem(top, item);
317
+ await applyProposedUpdateAsync(top, item);
463
318
  };
464
319
 
465
320
  const unstageItem = (top, item) => {
321
+ if (!item.dep) return void worktree.unstageItem(top, item);
466
322
  if (item.origin !== 'staged') return;
467
323
  if (depChange(item)) return void applyDepChange(top, item, 'unstage');
468
- const files = depPaths(item);
469
- if (files.length) {
470
- requireOk(runGit(['restore', '--staged', '--', ...files], top));
471
- }
324
+ const rels = depPaths(item);
325
+ if (!rels.length) return;
326
+ requireOk(runGit(['restore', '--staged', '--', ...rels], top));
472
327
  };
473
328
 
474
- const revertDepItem = (top, item) => {
329
+ const revertItem = (top, item) => {
330
+ if (item.origin === 'commit') return;
331
+ if (!item.dep) return void worktree.revertItem(top, item);
475
332
  if (depChange(item)) return void applyDepChange(top, item, 'revert');
476
- const byPath = new Map();
477
- for (const orig of origDepItems(item)) {
478
- const rel = itemPath(orig);
479
- const list = byPath.get(rel);
480
- if (list) list.push(orig);
481
- else byPath.set(rel, [orig]);
482
- }
483
- for (const rel of byPath.keys()) {
484
- revertOnePath(top, rel, byPath.get(rel));
485
- }
333
+ const byPath = groupByPath(item.dep.items ?? []);
334
+ for (const rel of byPath.keys()) revertOnePath(top, rel, byPath.get(rel));
486
335
  };
487
336
 
488
337
  const revertFile = (top, rel, items) => {
489
- const paths = new Set([rel]);
338
+ const rels = new Set([rel]);
490
339
  for (const item of items) {
491
- for (const file of depPaths(item)) paths.add(file);
340
+ for (const file of depPaths(item)) rels.add(file);
492
341
  }
493
- for (const file of paths) {
342
+ for (const file of rels) {
494
343
  const group = itemsForPath(file, items);
495
- const targets = group.length ? group : items;
496
- revertOnePath(top, file, targets);
344
+ revertOnePath(top, file, group.length ? group : items);
497
345
  }
498
346
  };
499
347
 
500
348
  module.exports = {
501
- hasProposed,
502
- itemsForPath,
503
349
  finishLoad,
504
350
  loadExtras,
505
351
  captureFile,
506
352
  restoreFile,
353
+ proposedNpmPlan,
507
354
  applyProposedUpdate,
508
- applyProposedUpdateAsync,
509
- applyDepChange,
510
355
  addItem,
511
356
  addItemAsync,
512
357
  unstageItem,
513
- revertDepItem,
358
+ revertItem,
514
359
  revertFile,
515
- AUDIT_MS,
516
- INSTALL_MS,
517
- runNpm,
518
- runNpmAsync,
519
- runNpmJson,
520
- runNpmJsonAsync,
521
- proposedNpmPlan,
522
360
  };