reslop 0.2.7 → 0.3.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 (158) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +573 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +17 -0
  7. package/lib/agents/models.js +467 -0
  8. package/lib/api.js +15 -12
  9. package/lib/cli.js +43 -68
  10. package/lib/{files.js → common/files.js} +48 -55
  11. package/lib/{find.js → common/find.js} +1 -1
  12. package/lib/common/format.js +101 -0
  13. package/lib/{utilities.js → common/process.js} +7 -95
  14. package/lib/common/tasks.json +27 -0
  15. package/lib/common/utilities.js +112 -0
  16. package/lib/dashboard/git.js +9 -16
  17. package/lib/dashboard/model.js +90 -49
  18. package/lib/dashboard/npm.js +17 -13
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +36 -31
  21. package/lib/{workspace.js → dashboard/workspace.js} +23 -38
  22. package/lib/{deps.js → deps/deps.js} +31 -45
  23. package/lib/{deps-diff.js → deps/diff.js} +35 -25
  24. package/lib/{manifest.js → deps/manifest.js} +47 -30
  25. package/lib/diff/align.js +1 -2
  26. package/lib/diff/diff.js +2 -2
  27. package/lib/diff/display.js +29 -5
  28. package/lib/diff/inline.js +10 -14
  29. package/lib/diff/items.js +6 -4
  30. package/lib/diff/patch.js +39 -3
  31. package/lib/diff/unit.js +45 -16
  32. package/lib/{git-branches.js → git/branches.js} +53 -48
  33. package/lib/{git-deps.js → git/deps.js} +151 -95
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +504 -0
  36. package/lib/highlight/bash.js +1 -2
  37. package/lib/highlight/csv.js +1 -2
  38. package/lib/highlight/dot.js +1 -2
  39. package/lib/{highlight.js → highlight/highlight.js} +42 -11
  40. package/lib/highlight/html.js +20 -18
  41. package/lib/highlight/json.js +1 -2
  42. package/lib/highlight/log.js +1 -2
  43. package/lib/highlight/txt.js +1 -2
  44. package/lib/{session → input}/actions.js +114 -48
  45. package/lib/{editor.js → input/editor.js} +16 -8
  46. package/lib/{keys.js → input/keys.js} +24 -45
  47. package/lib/input/line-edit.js +78 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +642 -0
  50. package/lib/render/branches.js +4 -5
  51. package/lib/render/commits.js +54 -64
  52. package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
  53. package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
  54. package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
  55. package/lib/render/diff.js +166 -45
  56. package/lib/render/field.js +131 -0
  57. package/lib/render/files.js +9 -14
  58. package/lib/render/footer.js +385 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +320 -0
  62. package/lib/render/menu.js +60 -0
  63. package/lib/render/notes.js +16 -11
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +9 -13
  66. package/lib/render/primitives.js +101 -15
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +13 -25
  69. package/lib/render/status.js +370 -0
  70. package/lib/render/status.json +85 -0
  71. package/lib/render/{dash-table.js → table.js} +13 -60
  72. package/lib/render/tiles.js +109 -48
  73. package/lib/render/unit.js +32 -13
  74. package/lib/{report-model.js → report/model.js} +4 -6
  75. package/lib/{report-parse.js → report/parse.js} +9 -14
  76. package/lib/{report-render.js → report/render.js} +48 -19
  77. package/lib/{report-run.js → report/run.js} +42 -30
  78. package/lib/{report-test.js → report/test.js} +56 -55
  79. package/lib/{report-tools.js → report/tools.js} +24 -33
  80. package/lib/review/agent-instructions.md +14 -0
  81. package/lib/review/format.js +420 -0
  82. package/lib/{review-merge.js → review/merge.js} +9 -8
  83. package/lib/review/review.js +190 -0
  84. package/lib/review/store.js +281 -0
  85. package/lib/review/templates.js +83 -0
  86. package/lib/runs/commands.js +14 -0
  87. package/lib/runs/logs.js +299 -0
  88. package/lib/runs/output.js +174 -0
  89. package/lib/runs/process.js +113 -0
  90. package/lib/{runs.js → runs/runs.js} +84 -36
  91. package/lib/runs/scripts.js +150 -0
  92. package/lib/session/agents/agents.js +976 -0
  93. package/lib/session/agents/job.js +320 -0
  94. package/lib/session/agents/jobs.js +355 -0
  95. package/lib/session/agents/options.js +153 -0
  96. package/lib/session/agents/plans.js +83 -0
  97. package/lib/session/branches.js +33 -40
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +189 -147
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +86 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +93 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +78 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +250 -0
  111. package/lib/session/npm/npm.js +836 -0
  112. package/lib/session/{packages.js → npm/packages.js} +21 -14
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +202 -10
  115. package/lib/session/prefs.js +112 -0
  116. package/lib/session/reads.js +109 -0
  117. package/lib/session/review.js +28 -10
  118. package/lib/session/run-panel.js +40 -0
  119. package/lib/{session.js → session/session.js} +158 -63
  120. package/lib/session/tasks.js +321 -66
  121. package/lib/session/templates.js +1 -1
  122. package/lib/session/terminal.js +94 -12
  123. package/lib/session/unit.js +35 -30
  124. package/lib/session/update.js +194 -6
  125. package/lib/session/view.js +65 -65
  126. package/lib/session/workspace.js +39 -78
  127. package/lib/{capabilities.js → source/capabilities.js} +3 -12
  128. package/lib/{github.js → source/github.js} +102 -32
  129. package/lib/{gitlab.js → source/gitlab.js} +87 -10
  130. package/lib/{remote.js → source/remote.js} +144 -40
  131. package/lib/source/review-threads.graphql +20 -0
  132. package/lib/source/source.js +74 -0
  133. package/lib/term/ansi.js +396 -0
  134. package/lib/{clipboard.js → term/clipboard.js} +3 -6
  135. package/lib/{select.js → term/select.js} +4 -3
  136. package/lib/term/themes/dark.json +122 -0
  137. package/lib/term/themes/light.json +122 -0
  138. package/lib/term/wide.txt +21 -0
  139. package/lib/{wrap.js → term/wrap.js} +4 -4
  140. package/package.json +2 -2
  141. package/lib/ansi.js +0 -545
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -455
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -806
  148. package/lib/render/present.js +0 -19
  149. package/lib/render/report.js +0 -43
  150. package/lib/render/search.js +0 -25
  151. package/lib/review.js +0 -745
  152. package/lib/session/accessors.js +0 -26
  153. package/lib/session/edit-click.js +0 -57
  154. package/lib/session/npm.js +0 -452
  155. package/lib/session/progress.js +0 -41
  156. package/lib/source.js +0 -36
  157. package/lib/update.js +0 -192
  158. /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
@@ -3,14 +3,15 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
- const runs = require('../runs.js');
6
+ const runs = require('../runs/runs.js');
7
7
  const { isRunsRel } = runs;
8
+ const { REVIEW_DIR, posixRel } = require('../common/files.js');
8
9
 
9
10
  const DEBOUNCE_MS = 200;
10
11
  const UNKNOWN_PATH = '.';
11
12
  const MODULES_STAMP = 'node_modules/.package-lock.json';
12
13
 
13
- const SKIP_DIRS = ['node_modules', '.review', 'coverage', 'dist', '.cache'];
14
+ const SKIP_DIRS = ['node_modules', REVIEW_DIR, 'coverage', 'dist', '.cache'];
14
15
 
15
16
  const GIT_WATCH_NAMES = [
16
17
  'HEAD',
@@ -23,32 +24,31 @@ const GIT_WATCH_NAMES = [
23
24
  'rebase-apply',
24
25
  ];
25
26
 
26
- const canWatchRecursive = (
27
- platform = process.platform,
28
- version = process.versions.node,
29
- ) => {
30
- if (platform === 'win32' || platform === 'darwin') return true;
31
- if (platform !== 'linux') return false;
32
- const [major, minor] = `${version}`.split('.').map(Number);
27
+ const canWatchRecursive = (platform, version) => {
28
+ const host = platform ?? process.platform;
29
+ const node = version ?? process.versions.node;
30
+ if (host === 'win32' || host === 'darwin') return true;
31
+ if (host !== 'linux') return false;
32
+ const [major, minor] = `${node}`.split('.').map(Number);
33
33
  return major > 19 || (major === 19 && minor >= 1);
34
34
  };
35
35
 
36
36
  const IGNORED_SUFFIXES = ['.lock', '~', '.swp', '.swo'];
37
37
 
38
- const ignoredName = (name) =>
39
- IGNORED_SUFFIXES.some((suffix) => name.endsWith(suffix)) ||
40
- name.startsWith('.#') ||
41
- name === '.DS_Store';
38
+ const ignoredName = (name) => {
39
+ const suffix = IGNORED_SUFFIXES.some((item) => name.endsWith(item));
40
+ const backup = name.startsWith('.#');
41
+ const store = name === '.DS_Store';
42
+ return suffix || backup || store;
43
+ };
42
44
 
43
45
  const ignoredGitRel = ([, name = '', sub]) => {
44
46
  if (name === 'logs' && sub === 'HEAD') return false;
45
47
  return name !== '' && !GIT_WATCH_NAMES.includes(name);
46
48
  };
47
49
 
48
- const slashRel = (rel) => `${rel}`.replaceAll('\\', '/');
49
-
50
50
  const ignoredRel = (rel) => {
51
- const norm = slashRel(rel);
51
+ const norm = posixRel(rel);
52
52
  if (!norm || norm === '.') return false;
53
53
  if (norm === MODULES_STAMP) return false;
54
54
  const parts = norm.split('/');
@@ -60,15 +60,16 @@ const ignoredRel = (rel) => {
60
60
  const skipDirName = (name) => name === '.git' || SKIP_DIRS.includes(name);
61
61
 
62
62
  const reviewMarkdown = (rel) => {
63
- if (!rel.startsWith('.review/')) return false;
64
- const rest = rel.slice('.review/'.length);
63
+ const prefix = `${REVIEW_DIR}/`;
64
+ if (!rel.startsWith(prefix)) return false;
65
+ const rest = rel.slice(prefix.length);
65
66
  if (!rest || rest.includes('/')) return false;
66
67
  return rest.endsWith('.md');
67
68
  };
68
69
 
69
70
  const isReviewDir = (root, dir) => {
70
- const rel = slashRel(path.relative(root, dir));
71
- return rel === '.review' || rel.startsWith('.review/');
71
+ const rel = posixRel(path.relative(root, dir));
72
+ return rel === REVIEW_DIR || rel.startsWith(`${REVIEW_DIR}/`);
72
73
  };
73
74
 
74
75
  const hasFile = (full) => {
@@ -84,13 +85,13 @@ const isBareReviewName = (root, dir, rel) => {
84
85
  if (!rel || rel.includes('/')) return false;
85
86
  const base = path.basename(rel);
86
87
  if (hasFile(path.join(dir, base))) return false;
87
- return hasFile(path.join(root, '.review', base));
88
+ return hasFile(path.join(root, REVIEW_DIR, base));
88
89
  };
89
90
 
90
91
  const isReviewEvent = (root, dir, name) => {
91
92
  if (isReviewDir(root, dir)) return true;
92
93
  if (!name) return false;
93
- const rel = slashRel(path.relative(root, path.join(dir, name)));
94
+ const rel = posixRel(path.relative(root, path.join(dir, name)));
94
95
  if (reviewMarkdown(rel)) return true;
95
96
  return isBareReviewName(root, dir, rel);
96
97
  };
@@ -98,7 +99,7 @@ const isReviewEvent = (root, dir, name) => {
98
99
  const isOwnDirEvent = (dir, name) => {
99
100
  if (!name) return false;
100
101
  const base = path.basename(dir);
101
- const slash = slashRel(name);
102
+ const slash = posixRel(name);
102
103
  const bare = slash.startsWith('./') ? slash.slice(2) : slash;
103
104
  const trimmed = bare.endsWith('/') ? bare.slice(0, -1) : bare;
104
105
  if (trimmed === base) {
@@ -153,8 +154,9 @@ class DiskWatcher {
153
154
  this.watchOne(gitDir);
154
155
  this.watchOne(path.join(gitDir, 'refs'));
155
156
  this.watchOne(path.join(gitDir, 'logs'));
157
+ this.watchOne(path.resolve(this.root, runs.logDir('.')));
156
158
  this.watchOne(path.resolve(this.root, runs.runsDir('.')));
157
- this.watchOne(path.resolve(this.root, '.review'));
159
+ this.watchOne(path.resolve(this.root, REVIEW_DIR));
158
160
  }
159
161
 
160
162
  schedule(key, fire) {
@@ -211,9 +213,11 @@ class DiskWatcher {
211
213
  onEvent(dir, eventType, filename) {
212
214
  if (this.closed) return;
213
215
  const name = filename ? `${filename}` : '';
214
- const rel = name
215
- ? slashRel(path.relative(this.root, path.join(dir, name)))
216
- : '';
216
+ let rel = '';
217
+ if (name) {
218
+ const full = path.join(dir, name);
219
+ rel = posixRel(path.relative(this.root, full));
220
+ }
217
221
  const wanted = this.files.has(rel);
218
222
  if (isRunsRel(rel)) {
219
223
  this.pending.add(rel);
@@ -260,12 +264,13 @@ class DiskWatcher {
260
264
  watchTree(dir) {
261
265
  this.watchOne(dir, this.recursive);
262
266
  if (this.recursive) return;
263
- let entries;
267
+ let entries = null;
264
268
  try {
265
269
  entries = fs.readdirSync(dir, { withFileTypes: true });
266
270
  } catch {
267
- return;
271
+ // unreadable directory
268
272
  }
273
+ if (!entries) return;
269
274
  for (const entry of entries) {
270
275
  if (!entry.isDirectory() || skipDirName(entry.name)) continue;
271
276
  this.watchTree(path.join(dir, entry.name));
@@ -280,7 +285,7 @@ class DiskWatcher {
280
285
  return;
281
286
  }
282
287
  const name = path.basename(full);
283
- if (name === '.review') this.watchOne(full);
288
+ if (name === REVIEW_DIR) this.watchOne(full);
284
289
  else if (!skipDirName(name)) this.watchTree(full);
285
290
  }
286
291
 
@@ -299,7 +304,7 @@ class DiskWatcher {
299
304
  for (const rel of rels) {
300
305
  if (!rel) continue;
301
306
  const full = path.resolve(this.root, rel);
302
- this.files.add(slashRel(path.relative(this.root, full)));
307
+ this.files.add(posixRel(path.relative(this.root, full)));
303
308
  this.watchOne(path.dirname(full));
304
309
  }
305
310
  }
@@ -3,16 +3,13 @@
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
5
 
6
- const files = require('./files.js');
7
- const { REVIEW_DIR } = files;
8
- const review = require('./review.js');
6
+ const { REVIEW_DIR } = require('../common/files.js');
7
+ const review = require('../review/review.js');
9
8
  const { listReviewNames, latestReviewName, loadReview, noteCounts } = review;
10
- const worktree = require('./git-worktree.js');
11
- const { runGitAsync, countUntrackedAdded } = worktree;
12
- const model = require('./dashboard/model.js');
13
- const { runName } = model;
14
- const runs = require('./runs.js');
15
- const { readRuns } = runs;
9
+ const { countUntrackedAdded } = require('../git/worktree.js');
10
+ const { gitText, countOf } = require('./git.js');
11
+ const { runName } = require('./model.js');
12
+ const { readRuns } = require('../runs/runs.js');
16
13
 
17
14
  const emptyDiff = () => ({
18
15
  stagedAdded: 0,
@@ -31,12 +28,13 @@ const isRepoDir = (dir) => {
31
28
  };
32
29
 
33
30
  const listChildRepos = (dir) => {
34
- let entries;
31
+ let entries = null;
35
32
  try {
36
33
  entries = fs.readdirSync(dir, { withFileTypes: true });
37
34
  } catch {
38
- return [];
35
+ // unreadable directory
39
36
  }
37
+ if (!entries) return [];
40
38
  const repos = [];
41
39
  for (const entry of entries) {
42
40
  if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
@@ -49,12 +47,13 @@ const listChildRepos = (dir) => {
49
47
  };
50
48
 
51
49
  const workspaceAt = (dir) => {
52
- let stat;
50
+ let stat = null;
53
51
  try {
54
52
  stat = fs.statSync(dir);
55
53
  } catch {
56
- return null;
54
+ // missing path
57
55
  }
56
+ if (!stat) return null;
58
57
  if (!stat.isDirectory() || isRepoDir(dir)) return null;
59
58
  const repos = listChildRepos(dir);
60
59
  if (!repos.length) return null;
@@ -89,22 +88,18 @@ const sumNumstat = (text) => {
89
88
  };
90
89
 
91
90
  const readNumstat = async (dir, cached) => {
92
- const args = cached
93
- ? ['diff', '--cached', '--numstat']
94
- : ['diff', '--numstat'];
95
- try {
96
- const result = await runGitAsync(args, dir);
97
- if (result.status !== 0) return { added: 0, removed: 0 };
98
- return sumNumstat(result.stdout);
99
- } catch {
100
- return { added: 0, removed: 0 };
101
- }
91
+ const stagedArgs = ['diff', '--cached', '--numstat'];
92
+ const openArgs = ['diff', '--numstat'];
93
+ const args = cached ? stagedArgs : openArgs;
94
+ return sumNumstat(await gitText(dir, args));
102
95
  };
103
96
 
104
97
  const readDiffStat = async (dir) => {
105
- const staged = await readNumstat(dir, true);
106
- const open = await readNumstat(dir, false);
107
- const untracked = await countUntrackedAdded(dir);
98
+ const [staged, open, untracked] = await Promise.all([
99
+ readNumstat(dir, true),
100
+ readNumstat(dir, false),
101
+ countUntrackedAdded(dir),
102
+ ]);
108
103
  return {
109
104
  stagedAdded: staged.added,
110
105
  unstagedAdded: open.added + untracked,
@@ -113,16 +108,8 @@ const readDiffStat = async (dir) => {
113
108
  };
114
109
  };
115
110
 
116
- const countCommits = async (dir) => {
117
- try {
118
- const result = await runGitAsync(['rev-list', '--count', 'HEAD'], dir);
119
- if (result.status !== 0) return 0;
120
- const value = parseInt(result.stdout.trim(), 10);
121
- return Number.isFinite(value) ? value : 0;
122
- } catch {
123
- return 0;
124
- }
125
- };
111
+ const countCommits = async (dir) =>
112
+ countOf(await gitText(dir, ['rev-list', '--count', 'HEAD']));
126
113
 
127
114
  const taskStats = (dir) => {
128
115
  const latest = latestReviewName(listReviewNames(dir));
@@ -154,8 +141,6 @@ const pickRun = (dir) => {
154
141
 
155
142
  module.exports = {
156
143
  emptyDiff,
157
- isRepoDir,
158
- listChildRepos,
159
144
  workspaceAt,
160
145
  workspaceFromArgs,
161
146
  sumNumstat,
@@ -2,18 +2,15 @@
2
2
 
3
3
  const fs = require('node:fs');
4
4
  const path = require('node:path');
5
- const metautil = require('metautil');
6
- const { jsonParse, isHashObject } = metautil;
7
- const files = require('./files.js');
8
- const { itemPath } = files;
9
- const utilities = require('./utilities.js');
10
- const { asText } = utilities;
5
+ const { jsonParse, isHashObject } = require('metautil');
6
+ const { itemPath, REVIEW_DIR } = require('../common/files.js');
7
+ const { asText, readText, readJson } = require('../common/utilities.js');
11
8
  const manifest = require('./manifest.js');
12
9
  const { MANIFEST, LOCKFILE, SECTION_KEYS, isObject } = manifest;
13
10
  const { depFileMeta, relOf, listFilePath, readSections } = manifest;
14
11
  const { lockEntries, findDeclared, applyWantedRange } = manifest;
15
12
  const { proposedLockVersion, proposedWanted } = manifest;
16
- const depDiff = require('./deps-diff.js');
13
+ const depDiff = require('./diff.js');
17
14
  const { summarizeGroup, collectGroups, takeManifestItems } = depDiff;
18
15
  const { foldedDepNames, stripFoldedDepItem } = depDiff;
19
16
  const { makeDepItems, stubFileItem } = depDiff;
@@ -26,17 +23,9 @@ const SPEC_PATTERNS = [
26
23
  /import\(\s*['"]([^'"]+)['"]\s*\)/g,
27
24
  ];
28
25
  const SCRIPT_RUNNERS = ['npx', 'npm', 'node'];
29
- const SKIP_WALK = ['node_modules', '.git', '.review', 'coverage', 'dist'];
26
+ const SKIP_WALK = ['node_modules', '.git', REVIEW_DIR, 'coverage', 'dist'];
30
27
  const SEVERITY_RANK = { critical: 4, high: 3, moderate: 2, low: 1, info: 0 };
31
28
 
32
- const readText = (file) => {
33
- try {
34
- return fs.readFileSync(file, 'utf8');
35
- } catch {
36
- return '';
37
- }
38
- };
39
-
40
29
  const toPackageName = (spec) => {
41
30
  if (!spec || spec.startsWith('.') || spec.startsWith('/')) return '';
42
31
  if (spec.startsWith('node:')) return '';
@@ -102,12 +91,13 @@ const pkgExportsLibrary = (pkg) => {
102
91
  };
103
92
 
104
93
  const walkUsedNames = (used, dir) => {
105
- let entries;
94
+ let entries = null;
106
95
  try {
107
96
  entries = fs.readdirSync(dir, { withFileTypes: true });
108
97
  } catch {
109
- return;
98
+ // unreadable directory
110
99
  }
100
+ if (!entries) return;
111
101
  for (const entry of entries) {
112
102
  if (entry.isSymbolicLink()) continue;
113
103
  const full = path.join(dir, entry.name);
@@ -115,7 +105,7 @@ const walkUsedNames = (used, dir) => {
115
105
  const skip = SKIP_WALK.includes(entry.name) || entry.name.startsWith('.');
116
106
  if (!skip) walkUsedNames(used, full);
117
107
  } else if (entry.name === MANIFEST) {
118
- collectUsedFromScripts(used, jsonParse(readText(full))?.scripts);
108
+ collectUsedFromScripts(used, readJson(full)?.scripts);
119
109
  } else if (SOURCE_EXT.includes(path.extname(entry.name))) {
120
110
  collectUsedFromText(used, readText(full));
121
111
  }
@@ -160,7 +150,7 @@ const collectExportEntries = (root, pkgDir, names) => {
160
150
  };
161
151
 
162
152
  const collectExportMeta = (root) => {
163
- const pkg = jsonParse(readText(path.join(root, MANIFEST)));
153
+ const pkg = readJson(path.join(root, MANIFEST));
164
154
  const maps = readSections(pkg);
165
155
  const names = SECTION_KEYS.flatMap((key) => [...maps[key].keys()]);
166
156
  return collectExportEntries(root, '.', names);
@@ -288,8 +278,11 @@ const markChanges = (changes, report, kind) => {
288
278
 
289
279
  const depExports = (exportEntries, name) => exportEntries?.get(name) === true;
290
280
 
291
- const isAddedDep = (change) =>
292
- change.action === 'added' && change.section !== 'resolved';
281
+ const isAddedDep = (change) => {
282
+ const added = change.action === 'added';
283
+ const declared = change.section !== 'resolved';
284
+ return added && declared;
285
+ };
293
286
 
294
287
  const unusedChangeNames = (changes, used) => {
295
288
  if (!used || !used.size) return [];
@@ -330,15 +323,10 @@ const unusedDeclaredChanges = (pkg, used, exportEntries) => {
330
323
  taken.add(name);
331
324
  const action = 'removed';
332
325
  const to = '';
333
- changes.push({
334
- name,
335
- section,
336
- action,
337
- from,
338
- to,
339
- propose: true,
340
- unused: true,
341
- });
326
+ const propose = true;
327
+ const unused = true;
328
+ const change = { name, section, action, from, to, propose, unused };
329
+ changes.push(change);
342
330
  }
343
331
  return changes;
344
332
  };
@@ -438,28 +426,26 @@ const placeFolded = (items, { folded, synthetic, leftoverNames }) => {
438
426
  return spliceAtSlots(out, synthetic, slot);
439
427
  };
440
428
 
441
- const foldDepItems = (
442
- items,
443
- readSides = () => ({ oldText: '', newText: '' }),
444
- usedNames = null,
445
- audit = null,
446
- outdated = null,
447
- extra = {},
448
- ) => {
429
+ const foldDepItems = (items, readSides, usedNames, audit, outdated, extra) => {
430
+ const read = readSides ?? (() => ({ oldText: '', newText: '' }));
431
+ const used = usedNames ?? null;
432
+ const auditMap = audit ?? null;
433
+ const outdatedMap = outdated ?? null;
434
+ const options = extra ?? {};
449
435
  const groups = collectGroups(items);
450
436
  if (!groups.size) return items;
451
437
  const folded = new Set();
452
438
  const synthetic = new Map();
453
439
  const leftoverNames = new Map();
454
440
  for (const [key, group] of groups) {
455
- const summary = summarizeGroup(group, readSides);
456
- const unusedNames = unusedChangeNames(summary.changes, usedNames);
457
- const exportEntries = loadExportEntries(unusedNames, extra, group.dir);
441
+ const summary = summarizeGroup(group, read);
442
+ const unusedNames = unusedChangeNames(summary.changes, used);
443
+ const exportEntries = loadExportEntries(unusedNames, options, group.dir);
458
444
  const { pkg } = summary;
459
445
  let changes = summary.changes;
460
- changes = markUnusedChanges(changes, usedNames, pkg, exportEntries);
461
- changes = markChanges(changes, audit, 'audit');
462
- changes = markChanges(changes, outdated, 'outdated');
446
+ changes = markUnusedChanges(changes, used, pkg, exportEntries);
447
+ changes = markChanges(changes, auditMap, 'audit');
448
+ changes = markChanges(changes, outdatedMap, 'outdated');
463
449
  const next = { ...summary, changes };
464
450
  leftoverNames.set(key, foldedDepNames(changes));
465
451
  const taken = takeManifestItems(next);
@@ -1,13 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const metautil = require('metautil');
4
- const { jsonParse } = metautil;
5
- const files = require('./files.js');
6
- const { itemPath } = files;
7
- const patch = require('./diff/patch.js');
3
+ const { jsonParse } = require('metautil');
4
+ const { itemPath } = require('../common/files.js');
5
+ const patch = require('../diff/patch.js');
8
6
  const { countSides, formatHunkHeader, formatPatch } = patch;
9
- const diff = require('./diff/diff.js');
10
- const { isCtxType } = diff;
7
+ const { isCtxType } = require('../diff/diff.js');
11
8
  const manifest = require('./manifest.js');
12
9
  const { MANIFEST, LOCKFILE, SECTION_KEYS, depFileMeta, relOf } = manifest;
13
10
  const { listFilePath, readSections, emptySectionMaps } = manifest;
@@ -66,8 +63,10 @@ const lineEntryName = (text) => {
66
63
  return entry ? entry[1] : '';
67
64
  };
68
65
 
69
- const lineInBlock = (item, line) =>
70
- typeof item.blockId !== 'number' || line.blockId === item.blockId;
66
+ const lineInBlock = (item, line) => {
67
+ const open = typeof item.blockId !== 'number';
68
+ return open || line.blockId === item.blockId;
69
+ };
71
70
 
72
71
  const hunkOnlyFoldedDeps = (item, names) => {
73
72
  if (!item.hunk || !names.size) return false;
@@ -86,8 +85,10 @@ const hunkOnlyFoldedDeps = (item, names) => {
86
85
 
87
86
  const stripFoldedDepItem = (item, names) => {
88
87
  if (!item.hunk || !names.size) return item;
89
- const isFolded = (line) =>
90
- line.type !== 'ctx' && names.has(lineEntryName(line.text));
88
+ const isFolded = (line) => {
89
+ const changed = line.type !== 'ctx';
90
+ return changed && names.has(lineEntryName(line.text));
91
+ };
91
92
  const lines = item.hunk.lines.filter((line) => !isFolded(line));
92
93
  if (lines.length === item.hunk.lines.length) return item;
93
94
  const isEdit = (line) => line.type !== 'ctx' && lineInBlock(item, line);
@@ -164,8 +165,15 @@ const mergeResolved = (changes, oldResolved, newResolved, oldMaps, newMaps) => {
164
165
  const action = changeAction(from, to);
165
166
  const resolvedFrom = from;
166
167
  const resolvedTo = to;
167
- const section = 'resolved';
168
- merged.push({ name, section, action, from, to, resolvedFrom, resolvedTo });
168
+ merged.push({
169
+ name,
170
+ section: 'resolved',
171
+ action,
172
+ from,
173
+ to,
174
+ resolvedFrom,
175
+ resolvedTo,
176
+ });
169
177
  }
170
178
  return merged;
171
179
  };
@@ -194,29 +202,31 @@ const readJsonSides = (readSides, origin, rel) => {
194
202
 
195
203
  const sectionMapsOf = (pkg, lock, manifestItems) => {
196
204
  if (pkg.before || pkg.after) {
197
- return {
198
- oldMaps: readSections(pkg.before),
199
- newMaps: readSections(pkg.after),
200
- };
205
+ const oldMaps = readSections(pkg.before);
206
+ const newMaps = readSections(pkg.after);
207
+ return { oldMaps, newMaps };
201
208
  }
202
209
  if (manifestItems.length) return mapsFromHunks(manifestItems);
203
210
  const oldMaps = lockRootSections(lock.before);
204
- return { oldMaps, newMaps: lockRootSections(lock.after) };
211
+ const newMaps = lockRootSections(lock.after);
212
+ return { oldMaps, newMaps };
205
213
  };
206
214
 
207
215
  const manifestPolicy = (manifestItems, pkg, hasChanges) => {
208
216
  if (!manifestItems.length) {
209
- return { foldManifest: false, takeAllManifest: false };
217
+ const foldManifest = false;
218
+ const takeAllManifest = false;
219
+ return { foldManifest, takeAllManifest };
210
220
  }
211
221
  if (pkg.before || pkg.after) {
212
222
  const onlyDeps = !hasFieldChanges(pkg.before, pkg.after);
213
- return {
214
- foldManifest: hasChanges,
215
- takeAllManifest: hasChanges && onlyDeps,
216
- };
223
+ const foldManifest = hasChanges;
224
+ const takeAllManifest = hasChanges && onlyDeps;
225
+ return { foldManifest, takeAllManifest };
217
226
  }
218
- const fold = hasChanges && manifestItems.every(isDepOnlyItem);
219
- return { foldManifest: fold, takeAllManifest: fold };
227
+ const foldManifest = hasChanges && manifestItems.every(isDepOnlyItem);
228
+ const takeAllManifest = foldManifest;
229
+ return { foldManifest, takeAllManifest };
220
230
  };
221
231
 
222
232
  const summarizeGroup = (group, readSides) => {
@@ -1,14 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const path = require('node:path');
4
- const metautil = require('metautil');
5
- const { isHashObject, jsonParse } = metautil;
6
- const utilities = require('./utilities.js');
7
- const { parseVersion, cmpVersion } = utilities;
8
-
9
- const MANIFEST = 'package.json';
10
- const LOCKFILE = 'package-lock.json';
11
- const DEP_KIND = { [MANIFEST]: 'manifest', [LOCKFILE]: 'lockfile' };
3
+ const { isHashObject, jsonParse } = require('metautil');
4
+ const { parseVersion, cmpVersion } = require('../common/utilities.js');
5
+ const files = require('../common/files.js');
6
+ const { MANIFEST, LOCKFILE, depFileMeta, relOf, listFilePath } = files;
7
+
12
8
  const SECTION_KEYS = [
13
9
  'dependencies',
14
10
  'devDependencies',
@@ -19,23 +15,6 @@ const LOCK_PREFIX = 'node_modules/';
19
15
 
20
16
  const isObject = (value) => !!value && typeof value === 'object';
21
17
 
22
- const posixRel = (rel) => `${rel ?? ''}`.replaceAll('\\', '/');
23
-
24
- const depFileMeta = (rel) => {
25
- const normalized = posixRel(rel);
26
- const kind = DEP_KIND[path.posix.basename(normalized)];
27
- if (!normalized || !kind) return null;
28
- return { kind, dir: path.posix.dirname(normalized) };
29
- };
30
-
31
- const relOf = (dir, name) => (dir === '.' ? name : `${dir}/${name}`);
32
-
33
- const listFilePath = (rel) => {
34
- const meta = depFileMeta(rel);
35
- if (meta && meta.kind === 'lockfile') return relOf(meta.dir, MANIFEST);
36
- return posixRel(rel);
37
- };
38
-
39
18
  const readSectionMap = (pkg, key) => {
40
19
  const map = new Map();
41
20
  const raw = pkg ? pkg[key] : null;
@@ -155,14 +134,17 @@ const seedManifest = (oldObj, newObj) => {
155
134
  return base;
156
135
  };
157
136
 
137
+ const mergedBase = (parsedBase, oldObj, newObj, kind) => {
138
+ if (parsedBase) return cloneJson(parsedBase);
139
+ if (kind === 'manifest') return seedManifest(oldObj, newObj);
140
+ return cloneJson(oldObj) || {};
141
+ };
142
+
158
143
  const mergeDepFile = (baseText, oldText, newText, change, side, kind) => {
159
144
  const oldObj = jsonParse(oldText ?? '');
160
145
  const newObj = jsonParse(newText ?? '');
161
146
  const parsedBase = jsonParse(baseText ?? '');
162
- let base;
163
- if (parsedBase) base = cloneJson(parsedBase);
164
- else if (kind === 'manifest') base = seedManifest(oldObj, newObj);
165
- else base = cloneJson(oldObj) || {};
147
+ const base = mergedBase(parsedBase, oldObj, newObj, kind);
166
148
  if (kind === 'lockfile') applyLockChange(base, change, side, oldObj, newObj);
167
149
  else applyManifestChange(base, change, side);
168
150
  return stringifyJson(base, baseText || newText || oldText || '');
@@ -247,7 +229,42 @@ const proposedWanted = (out, vul) => {
247
229
  return '';
248
230
  };
249
231
 
232
+ const cmpText = (left, right) => {
233
+ const a = parseVersion(left);
234
+ const b = parseVersion(right);
235
+ if (!a || !b) return null;
236
+ return cmpVersion(a, b);
237
+ };
238
+
239
+ const boundHolds = (cmp, op) => {
240
+ if (op === '<') return cmp < 0;
241
+ if (op === '<=') return cmp <= 0;
242
+ if (op === '>') return cmp > 0;
243
+ return cmp >= 0;
244
+ };
245
+
246
+ const inClause = (version, clause) => {
247
+ const ver = parseVersion(version);
248
+ if (!ver) return false;
249
+ const bounds = [...clause.matchAll(/(>=|<=|>|<)\s*(\d+\.\d+\.\d+)/g)];
250
+ if (!bounds.length) return false;
251
+ for (const match of bounds) {
252
+ const bound = parseVersion(match[2]);
253
+ if (!bound) return false;
254
+ if (!boundHolds(cmpVersion(ver, bound), match[1])) return false;
255
+ }
256
+ return true;
257
+ };
258
+
259
+ const inRange = (version, range) =>
260
+ `${range ?? ''}`.split('||').some((clause) => inClause(version, clause));
261
+
262
+ const hasRange = (range) => /[<>]=?\s*\d+\.\d+\.\d+/.test(`${range ?? ''}`);
263
+
250
264
  module.exports = {
265
+ cmpText,
266
+ inRange,
267
+ hasRange,
251
268
  MANIFEST,
252
269
  LOCKFILE,
253
270
  SECTION_KEYS,
package/lib/diff/align.js CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const inline = require('./inline.js');
4
- const { diffChars, tokenSimilarity } = inline;
3
+ const { diffChars, tokenSimilarity } = require('./inline.js');
5
4
 
6
5
  const PAIR_MIN_SIM = 0.3;
7
6
  const PAIR_HEAD_WEIGHT = 0.4;
package/lib/diff/diff.js CHANGED
@@ -5,7 +5,6 @@ const align = require('./align.js');
5
5
  const parse = require('./parse.js');
6
6
  const patch = require('./patch.js');
7
7
  const display = require('./display.js');
8
- const edit = require('./edit.js');
9
8
  const items = require('./items.js');
10
9
  const unit = require('./unit.js');
11
10
 
@@ -19,7 +18,8 @@ module.exports = {
19
18
  flattenBlock: patch.flattenBlock,
20
19
  formatPatch: patch.formatPatch,
21
20
  ...display,
22
- ...edit,
21
+ addBlockRange: patch.addBlockRange,
22
+ replaceBlockAdds: patch.replaceBlockAdds,
23
23
  ...items,
24
24
  ...unit,
25
25
  };