reslop 0.2.5 → 0.2.7

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 (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/LICENSE +1 -1
  3. package/README.md +11 -11
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +72 -19
  6. package/lib/api.js +1 -1
  7. package/lib/capabilities.js +8 -8
  8. package/lib/cli.js +61 -75
  9. package/lib/clipboard.js +39 -47
  10. package/lib/dashboard/git.js +128 -0
  11. package/lib/dashboard/model.js +357 -0
  12. package/lib/dashboard/npm.js +290 -0
  13. package/lib/dashboard/tree.js +278 -0
  14. package/lib/deps-diff.js +197 -402
  15. package/lib/deps.js +113 -240
  16. package/lib/detect.js +8 -18
  17. package/lib/diff/align.js +41 -113
  18. package/lib/diff/diff.js +12 -43
  19. package/lib/diff/display.js +25 -36
  20. package/lib/diff/edit.js +6 -20
  21. package/lib/diff/inline.js +76 -144
  22. package/lib/diff/items.js +33 -47
  23. package/lib/diff/parse.js +28 -56
  24. package/lib/diff/patch.js +12 -33
  25. package/lib/diff/unit.js +79 -178
  26. package/lib/editor.js +52 -69
  27. package/lib/files.js +79 -91
  28. package/lib/find.js +61 -0
  29. package/lib/git-branches.js +254 -416
  30. package/lib/git-deps.js +148 -272
  31. package/lib/git-reword-todo.js +5 -13
  32. package/lib/git-worktree.js +161 -214
  33. package/lib/git.js +21 -49
  34. package/lib/github.js +67 -181
  35. package/lib/gitlab.js +40 -90
  36. package/lib/highlight/bash.js +55 -177
  37. package/lib/highlight/core.js +155 -141
  38. package/lib/highlight/css.js +25 -58
  39. package/lib/highlight/csv.js +19 -37
  40. package/lib/highlight/dart.js +72 -213
  41. package/lib/highlight/dot.js +14 -14
  42. package/lib/highlight/html.js +42 -63
  43. package/lib/highlight/js.js +177 -506
  44. package/lib/highlight/json.js +17 -37
  45. package/lib/highlight/log.js +19 -25
  46. package/lib/highlight/txt.js +7 -10
  47. package/lib/highlight.js +17 -46
  48. package/lib/keys.js +75 -83
  49. package/lib/manifest.js +67 -126
  50. package/lib/npm-commands.js +120 -169
  51. package/lib/remote.js +140 -190
  52. package/lib/render/branches.js +63 -61
  53. package/lib/render/chrome.js +412 -347
  54. package/lib/render/commits.js +160 -361
  55. package/lib/render/dash-activity.js +212 -0
  56. package/lib/render/dash-blocks.js +496 -0
  57. package/lib/render/dash-table.js +295 -0
  58. package/lib/render/dashboard.js +62 -0
  59. package/lib/render/diff.js +123 -258
  60. package/lib/render/files.js +60 -86
  61. package/lib/render/frame.js +82 -117
  62. package/lib/render/notes.js +150 -210
  63. package/lib/render/npm.js +213 -177
  64. package/lib/render/packages.js +93 -0
  65. package/lib/render/present.js +4 -11
  66. package/lib/render/primitives.js +77 -172
  67. package/lib/render/render.js +5 -14
  68. package/lib/render/report.js +43 -0
  69. package/lib/render/repos.js +226 -0
  70. package/lib/render/search.js +25 -0
  71. package/lib/render/tiles.js +355 -0
  72. package/lib/render/unit.js +15 -31
  73. package/lib/report-model.js +110 -33
  74. package/lib/report-parse.js +56 -37
  75. package/lib/report-render.js +102 -59
  76. package/lib/report-run.js +87 -77
  77. package/lib/report-test.js +221 -99
  78. package/lib/report-tools.js +96 -154
  79. package/lib/review-merge.js +33 -41
  80. package/lib/review.js +69 -129
  81. package/lib/runs.js +225 -0
  82. package/lib/select.js +53 -91
  83. package/lib/session/accessors.js +14 -21
  84. package/lib/session/actions.js +189 -65
  85. package/lib/session/branches.js +164 -222
  86. package/lib/session/change-actions.js +49 -119
  87. package/lib/session/commit.js +298 -352
  88. package/lib/session/compose.js +572 -715
  89. package/lib/session/dashboard.js +530 -0
  90. package/lib/session/dispatch.js +197 -171
  91. package/lib/session/files-pane.js +92 -45
  92. package/lib/session/find.js +94 -0
  93. package/lib/session/items.js +6 -17
  94. package/lib/session/lifecycle.js +420 -466
  95. package/lib/session/load.js +1 -14
  96. package/lib/session/navigation.js +4 -2
  97. package/lib/session/npm.js +403 -420
  98. package/lib/session/ops.js +5 -13
  99. package/lib/session/packages.js +143 -0
  100. package/lib/session/pointer.js +30 -14
  101. package/lib/session/progress.js +39 -30
  102. package/lib/session/review.js +1 -3
  103. package/lib/session/tasks.js +277 -236
  104. package/lib/session/templates.js +51 -59
  105. package/lib/session/terminal.js +85 -132
  106. package/lib/session/view.js +59 -80
  107. package/lib/session/watch.js +201 -212
  108. package/lib/session/workspace.js +455 -0
  109. package/lib/session.js +95 -161
  110. package/lib/update.js +44 -111
  111. package/lib/utilities.js +46 -88
  112. package/lib/workspace.js +166 -0
  113. package/lib/wrap.js +29 -55
  114. package/package.json +1 -1
package/lib/deps.js CHANGED
@@ -9,30 +9,40 @@ const { itemPath } = files;
9
9
  const utilities = require('./utilities.js');
10
10
  const { asText } = utilities;
11
11
  const manifest = require('./manifest.js');
12
- const { MANIFEST, LOCKFILE, SECTION_KEYS, depFileMeta, relOf } = manifest;
13
- const { listFilePath, readSections, lockEntries, lockPackageCount } = manifest;
14
- const { mergeDepFile, findDeclared, applyWantedRange } = manifest;
15
- const { patchedFromRange, proposedLockVersion, proposedWanted } = manifest;
12
+ const { MANIFEST, LOCKFILE, SECTION_KEYS, isObject } = manifest;
13
+ const { depFileMeta, relOf, listFilePath, readSections } = manifest;
14
+ const { lockEntries, findDeclared, applyWantedRange } = manifest;
15
+ const { proposedLockVersion, proposedWanted } = manifest;
16
16
  const depDiff = require('./deps-diff.js');
17
- const { diffSections, mergeResolved, summarizeGroup, collectGroups } = depDiff;
18
- const { takeManifestItems, foldedDepNames, stripFoldedDepItem } = depDiff;
17
+ const { summarizeGroup, collectGroups, takeManifestItems } = depDiff;
18
+ const { foldedDepNames, stripFoldedDepItem } = depDiff;
19
19
  const { makeDepItems, stubFileItem } = depDiff;
20
20
 
21
21
  const SOURCE_EXT = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'];
22
- const REQUIRE_SPEC = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
23
- const FROM_SPEC = /\bfrom\s+['"]([^'"]+)['"]/g;
24
- const IMPORT_FILE = /import\s+['"]([^'"]+)['"]/g;
25
- const IMPORT_CALL = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
22
+ const SPEC_PATTERNS = [
23
+ /require\(\s*['"]([^'"]+)['"]\s*\)/g,
24
+ /\bfrom\s+['"]([^'"]+)['"]/g,
25
+ /import\s+['"]([^'"]+)['"]/g,
26
+ /import\(\s*['"]([^'"]+)['"]\s*\)/g,
27
+ ];
28
+ const SCRIPT_RUNNERS = ['npx', 'npm', 'node'];
29
+ const SKIP_WALK = ['node_modules', '.git', '.review', 'coverage', 'dist'];
30
+ const SEVERITY_RANK = { critical: 4, high: 3, moderate: 2, low: 1, info: 0 };
31
+
32
+ const readText = (file) => {
33
+ try {
34
+ return fs.readFileSync(file, 'utf8');
35
+ } catch {
36
+ return '';
37
+ }
38
+ };
26
39
 
27
40
  const toPackageName = (spec) => {
28
41
  if (!spec || spec.startsWith('.') || spec.startsWith('/')) return '';
29
42
  if (spec.startsWith('node:')) return '';
30
- if (spec.startsWith('@')) {
31
- const parts = spec.split('/');
32
- if (parts.length < 2) return '';
33
- return `${parts[0]}/${parts[1]}`;
34
- }
35
- return spec.split('/')[0];
43
+ const parts = spec.split('/');
44
+ if (!spec.startsWith('@')) return parts[0];
45
+ return parts.length < 2 ? '' : `${parts[0]}/${parts[1]}`;
36
46
  };
37
47
 
38
48
  const addSpec = (used, spec) => {
@@ -40,54 +50,39 @@ const addSpec = (used, spec) => {
40
50
  if (name) used.add(name);
41
51
  };
42
52
 
43
- const addMatches = (used, text, pattern) => {
44
- const matches = text.matchAll(pattern);
45
- for (const match of matches) addSpec(used, match[1]);
46
- };
47
-
48
53
  const collectUsedFromText = (used, text) => {
49
- addMatches(used, text, REQUIRE_SPEC);
50
- addMatches(used, text, FROM_SPEC);
51
- addMatches(used, text, IMPORT_FILE);
52
- addMatches(used, text, IMPORT_CALL);
54
+ for (const pattern of SPEC_PATTERNS) {
55
+ for (const match of text.matchAll(pattern)) addSpec(used, match[1]);
56
+ }
53
57
  };
54
58
 
55
59
  const collectUsedFromScripts = (used, scripts) => {
56
- if (!scripts || typeof scripts !== 'object') return;
60
+ if (!isObject(scripts)) return;
57
61
  for (const script of Object.values(scripts)) {
58
- const tokens = `${script}`.split(/[\s;&|]+/);
59
- for (const token of tokens) {
62
+ for (const token of `${script}`.split(/[\s;&|]+/)) {
60
63
  if (!token || token.startsWith('-')) continue;
61
- if (token === 'npx' || token === 'npm' || token === 'node') continue;
62
- addSpec(used, token);
64
+ if (!SCRIPT_RUNNERS.includes(token)) addSpec(used, token);
63
65
  }
64
66
  }
65
67
  };
66
68
 
69
+ const hasText = (value) => typeof value === 'string' && value !== '';
70
+
67
71
  const pkgHasExportEntry = (pkg) => {
68
- if (!pkg || typeof pkg !== 'object') return false;
69
- const main = pkg.main;
70
- if (typeof main === 'string' && main) return true;
71
- const esm = pkg.module;
72
- if (typeof esm === 'string' && esm) return true;
72
+ if (!isObject(pkg)) return false;
73
+ if (hasText(pkg.main) || hasText(pkg.module)) return true;
73
74
  const exported = pkg.exports;
74
- if (typeof exported === 'string' && exported) return true;
75
- if (!exported || typeof exported !== 'object') return false;
76
- if (Array.isArray(exported)) return exported.length > 0;
77
- return Object.keys(exported).length > 0;
75
+ if (hasText(exported)) return true;
76
+ return isObject(exported) && Object.keys(exported).length > 0;
78
77
  };
79
78
 
80
- const isRuntimeFile = (value) => {
81
- if (typeof value !== 'string' || !value) return false;
82
- return !value.endsWith('.d.ts');
83
- };
79
+ const isRuntimeFile = (value) => hasText(value) && !value.endsWith('.d.ts');
84
80
 
85
81
  const hasRuntimeExport = (value) => {
86
82
  if (isRuntimeFile(value)) return true;
87
83
  if (Array.isArray(value)) return value.some(hasRuntimeExport);
88
- if (!value || typeof value !== 'object') return false;
89
- const keys = Object.keys(value);
90
- for (const key of keys) {
84
+ if (!isObject(value)) return false;
85
+ for (const key of Object.keys(value)) {
91
86
  if (key === 'types' || key === 'typings') continue;
92
87
  if (hasRuntimeExport(value[key])) return true;
93
88
  }
@@ -95,35 +90,17 @@ const hasRuntimeExport = (value) => {
95
90
  };
96
91
 
97
92
  const pkgHasBin = (pkg) => {
98
- if (!pkg || typeof pkg !== 'object') return false;
99
93
  const bin = pkg.bin;
100
94
  if (typeof bin === 'string') return !!bin;
101
- if (!bin || typeof bin !== 'object' || Array.isArray(bin)) return false;
102
- return Object.keys(bin).length > 0;
95
+ return isHashObject(bin) && Object.keys(bin).length > 0;
103
96
  };
104
97
 
105
98
  const pkgExportsLibrary = (pkg) => {
106
- if (!pkg || typeof pkg !== 'object') return false;
107
- if (pkgHasBin(pkg)) return false;
108
- if (isRuntimeFile(pkg.main)) return true;
109
- if (isRuntimeFile(pkg.module)) return true;
99
+ if (!isObject(pkg) || pkgHasBin(pkg)) return false;
100
+ if (isRuntimeFile(pkg.main) || isRuntimeFile(pkg.module)) return true;
110
101
  return hasRuntimeExport(pkg.exports);
111
102
  };
112
103
 
113
- const SKIP_WALK = ['node_modules', '.git', '.review', 'coverage', 'dist'];
114
-
115
- const collectUsedFromManifest = (used, rel) => {
116
- let text;
117
- try {
118
- text = fs.readFileSync(rel, 'utf8');
119
- } catch {
120
- return;
121
- }
122
- const pkg = jsonParse(text);
123
- if (!pkg) return;
124
- collectUsedFromScripts(used, pkg.scripts);
125
- };
126
-
127
104
  const walkUsedNames = (used, dir) => {
128
105
  let entries;
129
106
  try {
@@ -135,55 +112,36 @@ const walkUsedNames = (used, dir) => {
135
112
  if (entry.isSymbolicLink()) continue;
136
113
  const full = path.join(dir, entry.name);
137
114
  if (entry.isDirectory()) {
138
- if (SKIP_WALK.includes(entry.name)) continue;
139
- if (entry.name.startsWith('.')) continue;
140
- walkUsedNames(used, full);
141
- continue;
142
- }
143
- if (entry.name === MANIFEST) {
144
- collectUsedFromManifest(used, full);
145
- continue;
146
- }
147
- const ext = path.extname(entry.name);
148
- if (!SOURCE_EXT.includes(ext)) continue;
149
- let text;
150
- try {
151
- text = fs.readFileSync(full, 'utf8');
152
- } catch {
153
- continue;
115
+ const skip = SKIP_WALK.includes(entry.name) || entry.name.startsWith('.');
116
+ if (!skip) walkUsedNames(used, full);
117
+ } else if (entry.name === MANIFEST) {
118
+ collectUsedFromScripts(used, jsonParse(readText(full))?.scripts);
119
+ } else if (SOURCE_EXT.includes(path.extname(entry.name))) {
120
+ collectUsedFromText(used, readText(full));
154
121
  }
155
- collectUsedFromText(used, text);
156
122
  }
157
123
  };
158
124
 
159
125
  const collectUsedNames = (root) => {
160
126
  const used = new Set();
161
- if (!root) return used;
162
- walkUsedNames(used, root);
127
+ if (root) walkUsedNames(used, root);
163
128
  return used;
164
129
  };
165
130
 
166
131
  const depModuleParts = (name) => {
167
132
  if (!name.startsWith('@')) return [name];
168
133
  const parts = name.split('/');
169
- if (parts.length < 2) return [];
170
- return parts.slice(0, 2);
134
+ return parts.length < 2 ? [] : parts.slice(0, 2);
171
135
  };
172
136
 
173
137
  const readDepPkg = (root, pkgDir, name) => {
174
- if (!root || !name) return null;
138
+ if (!name) return null;
175
139
  const parts = depModuleParts(name);
176
140
  if (!parts.length) return null;
177
- const nested = pkgDir && pkgDir !== '.' ? pkgDir : '';
178
- let dir = nested ? path.join(root, nested) : root;
141
+ let dir = path.join(root, pkgDir || '.');
179
142
  while (true) {
180
143
  const file = path.join(dir, 'node_modules', ...parts, MANIFEST);
181
- let text = '';
182
- try {
183
- text = fs.readFileSync(file, 'utf8');
184
- } catch {
185
- // Missing node_modules copy; try a parent directory.
186
- }
144
+ const text = readText(file);
187
145
  if (text) return jsonParse(text);
188
146
  const parent = path.dirname(dir);
189
147
  if (parent === dir) return null;
@@ -191,61 +149,37 @@ const readDepPkg = (root, pkgDir, name) => {
191
149
  }
192
150
  };
193
151
 
194
- const depHasExportEntry = (root, pkgDir, name) => {
195
- const pkg = readDepPkg(root, pkgDir, name);
196
- if (!pkg) return false;
197
- return pkgExportsLibrary(pkg);
198
- };
199
-
200
152
  const collectExportEntries = (root, pkgDir, names) => {
201
153
  const entries = new Map();
202
154
  if (!root) return entries;
203
155
  for (const name of names) {
204
- const hasExport = depHasExportEntry(root, pkgDir, name);
205
- entries.set(name, hasExport);
156
+ const pkg = readDepPkg(root, pkgDir, name);
157
+ entries.set(name, !!pkg && pkgExportsLibrary(pkg));
206
158
  }
207
159
  return entries;
208
160
  };
209
161
 
210
- const declaredDepNames = (root, pkgDir) => {
211
- const nested = pkgDir && pkgDir !== '.' ? pkgDir : '';
212
- const file = path.join(root, nested, MANIFEST);
213
- let text;
214
- try {
215
- text = fs.readFileSync(file, 'utf8');
216
- } catch {
217
- return [];
218
- }
219
- const maps = readSections(jsonParse(text));
220
- return SECTION_KEYS.flatMap((key) => [...maps[key].keys()]);
221
- };
222
-
223
- const collectExportMeta = (root, extra = {}) => {
224
- if (extra.exportEntries !== undefined) return extra.exportEntries;
225
- const pkgDir = extra.dir ?? '.';
226
- const names = extra.names ?? declaredDepNames(root, pkgDir);
227
- return collectExportEntries(root, pkgDir, names);
162
+ const collectExportMeta = (root) => {
163
+ const pkg = jsonParse(readText(path.join(root, MANIFEST)));
164
+ const maps = readSections(pkg);
165
+ const names = SECTION_KEYS.flatMap((key) => [...maps[key].keys()]);
166
+ return collectExportEntries(root, '.', names);
228
167
  };
229
168
 
230
- const SEVERITY_RANK = { critical: 4, high: 3, moderate: 2, low: 1, info: 0 };
231
-
232
169
  const namedTitle = (name, title) => {
233
170
  if (!title) return '';
234
171
  if (!name) return title;
235
172
  const prefix = `${name}: `;
236
- if (title.startsWith(prefix)) return title;
237
- return `${prefix}${title}`;
173
+ return title.startsWith(prefix) ? title : `${prefix}${title}`;
238
174
  };
239
175
 
240
176
  const viaNotes = (via, pkgName) => {
241
177
  if (!Array.isArray(via)) return [];
242
178
  const notes = [];
243
179
  for (const item of via) {
244
- if (!item || typeof item !== 'object') continue;
245
- const title = asText(item.title);
246
- if (!title) continue;
247
- const hasName = typeof item.name === 'string' && item.name;
248
- const named = hasName ? item.name : pkgName;
180
+ const title = asText(item?.title);
181
+ if (!isObject(item) || !title) continue;
182
+ const named = hasText(item.name) ? item.name : pkgName;
249
183
  const note = namedTitle(named, title);
250
184
  if (!notes.includes(note)) notes.push(note);
251
185
  }
@@ -254,23 +188,13 @@ const viaNotes = (via, pkgName) => {
254
188
 
255
189
  const severityRank = (severity) => SEVERITY_RANK[severity] ?? 0;
256
190
 
257
- const auditFix = (entry) => {
258
- const available = entry.fixAvailable;
259
- if (!available || typeof available !== 'object') return '';
260
- if (typeof available.version !== 'string') return '';
261
- return available.version;
262
- };
191
+ const auditFix = (entry) => asText(entry.fixAvailable?.version);
263
192
 
264
193
  const auditEntryNames = (key, entry) => {
265
- const names = [];
194
+ const effects = Array.isArray(entry.effects) ? entry.effects : [];
195
+ const names = effects.filter(hasText);
266
196
  const primary = entry.name || key;
267
- if (primary) names.push(primary);
268
- const effects = entry.effects;
269
- if (!Array.isArray(effects)) return names;
270
- for (const effect of effects) {
271
- if (typeof effect === 'string' && effect) names.push(effect);
272
- }
273
- return names;
197
+ return primary ? [primary, ...names] : names;
274
198
  };
275
199
 
276
200
  const uniqueTexts = (list) => [...new Set(list.filter(Boolean))];
@@ -295,12 +219,11 @@ const keepHighestAudit = (audit, found) => {
295
219
  audit.set(name, { name, severity, title, titles, fix, range });
296
220
  };
297
221
 
298
- const isEntry = (value) => Boolean(value) && typeof value === 'object';
299
-
300
222
  const vulnFindings = (vulns) => {
301
223
  const found = [];
302
- for (const [key, entry] of Object.entries(vulns)) {
303
- if (!isEntry(entry)) continue;
224
+ for (const key of Object.keys(vulns)) {
225
+ const entry = vulns[key];
226
+ if (!isObject(entry)) continue;
304
227
  const { severity } = entry;
305
228
  const titles = viaNotes(entry.via, entry.name || key);
306
229
  const fix = auditFix(entry);
@@ -323,13 +246,12 @@ const advisoryFinding = (entry) => {
323
246
 
324
247
  const parseAuditReport = (text) => {
325
248
  const audit = new Map();
326
- if (!text || !`${text}`.trim()) return audit;
327
- const data = jsonParse(text);
249
+ const data = text ? jsonParse(text) : null;
328
250
  if (!isHashObject(data)) return audit;
329
251
  const { vulnerabilities, advisories } = data;
330
- const found = isEntry(vulnerabilities) ? vulnFindings(vulnerabilities) : [];
331
- if (isEntry(advisories)) {
332
- const listed = Object.values(advisories).filter(isEntry);
252
+ const found = isObject(vulnerabilities) ? vulnFindings(vulnerabilities) : [];
253
+ if (isObject(advisories)) {
254
+ const listed = Object.values(advisories).filter(isObject);
333
255
  found.push(...listed.map(advisoryFinding));
334
256
  }
335
257
  for (const entry of found) keepHighestAudit(audit, entry);
@@ -338,14 +260,12 @@ const parseAuditReport = (text) => {
338
260
 
339
261
  const parseOutdatedReport = (text) => {
340
262
  const outdated = new Map();
341
- if (!text || !`${text}`.trim()) return outdated;
342
- const data = jsonParse(text);
263
+ const data = text ? jsonParse(text) : null;
343
264
  if (!isHashObject(data)) return outdated;
344
265
  for (const name of Object.keys(data)) {
345
266
  const entry = data[name];
346
- if (!entry || typeof entry !== 'object') continue;
347
- const wanted = asText(entry.wanted);
348
- if (!wanted) continue;
267
+ const wanted = asText(entry?.wanted);
268
+ if (!isObject(entry) || !wanted) continue;
349
269
  const current = asText(entry.current);
350
270
  const latest = asText(entry.latest);
351
271
  const type = asText(entry.type);
@@ -357,36 +277,26 @@ const parseOutdatedReport = (text) => {
357
277
  const markChanges = (changes, report, kind) => {
358
278
  if (!report) return changes;
359
279
  return changes.map((change) => {
360
- if (change.action === 'removed' || change.section === 'field') {
361
- return change;
362
- }
280
+ if (change.action === 'removed') return change;
363
281
  const found = report.get(change.name);
364
282
  if (!found) return change;
365
- if (kind === 'outdated' && found.wanted && found.wanted === found.current) {
366
- return change;
367
- }
283
+ const isCurrent = found.wanted && found.wanted === found.current;
284
+ if (kind === 'outdated' && isCurrent) return change;
368
285
  return { ...change, [kind]: found };
369
286
  });
370
287
  };
371
288
 
372
289
  const depExports = (exportEntries, name) => exportEntries?.get(name) === true;
373
290
 
374
- const isAddedDep = (change) => {
375
- if (change.action !== 'added') return false;
376
- if (change.section === 'resolved') return false;
377
- if (change.section === 'field') return false;
378
- return true;
379
- };
291
+ const isAddedDep = (change) =>
292
+ change.action === 'added' && change.section !== 'resolved';
380
293
 
381
294
  const unusedChangeNames = (changes, used) => {
382
- const names = [];
383
- if (!used || !used.size) return names;
384
- for (const change of changes) {
385
- if (!isAddedDep(change)) continue;
386
- if (used.has(change.name)) continue;
387
- names.push(change.name);
388
- }
389
- return names;
295
+ if (!used || !used.size) return [];
296
+ const unused = changes.filter(
297
+ (change) => isAddedDep(change) && !used.has(change.name),
298
+ );
299
+ return unused.map((change) => change.name);
390
300
  };
391
301
 
392
302
  const unusedDeclared = (pkg, used) => {
@@ -458,15 +368,12 @@ const declaredChange = (name, declared, out, vul) => {
458
368
 
459
369
  const mapKeys = (map) => (map ? [...map.keys()] : []);
460
370
 
461
- const buildProposedChanges = (pkg, outdated, audit, lock, options = {}) => {
371
+ const buildProposedChanges = (pkg, outdated, audit, lock, options) => {
462
372
  const listed = [...new Set([...mapKeys(outdated), ...mapKeys(audit)])];
463
373
  listed.sort();
464
374
  const resolved = lockEntries(lock);
465
- const changes = unusedDeclaredChanges(
466
- pkg,
467
- options.used,
468
- options.exportEntries,
469
- );
375
+ const { used, exportEntries } = options;
376
+ const changes = unusedDeclaredChanges(pkg, used, exportEntries);
470
377
  const taken = new Set(changes.map((change) => change.name));
471
378
  for (const name of listed) {
472
379
  if (taken.has(name)) continue;
@@ -481,59 +388,32 @@ const buildProposedChanges = (pkg, outdated, audit, lock, options = {}) => {
481
388
  return changes;
482
389
  };
483
390
 
484
- const loadExportEntries = (names, extra = {}, pkgDir) => {
391
+ const loadExportEntries = (names, extra, dir) => {
485
392
  const supplied = extra.exportEntries;
486
393
  if (supplied === null) return new Map();
487
- const dir = pkgDir ?? extra.dir;
488
- const missing = [];
489
- for (const name of names) {
490
- if (!supplied || !supplied.has(name)) missing.push(name);
491
- }
394
+ const missing = names.filter((name) => !supplied || !supplied.has(name));
492
395
  if (!missing.length) return supplied ?? new Map();
493
396
  const found = collectExportEntries(extra.root, dir, missing);
494
- if (!supplied) return found;
495
- return new Map([...supplied, ...found]);
397
+ return supplied ? new Map([...supplied, ...found]) : found;
496
398
  };
497
399
 
498
- const groupedSynthetics = (synthetic) => {
400
+ const spliceAtSlots = (out, synthetic, slot) => {
499
401
  const byPath = new Map();
500
- for (const [key, items] of synthetic) {
501
- const dir = key.slice(key.indexOf('\0') + 1);
502
- const rel = relOf(dir, MANIFEST);
402
+ for (const key of synthetic.keys()) {
403
+ const rel = relOf(key.slice(key.indexOf('\0') + 1), MANIFEST);
503
404
  const list = byPath.get(rel) ?? [];
504
- for (const item of items) list.push(item);
405
+ list.push(...synthetic.get(key));
505
406
  byPath.set(rel, list);
506
407
  }
507
- return byPath;
508
- };
509
-
510
- const spliceAtSlots = (out, synthetic, slot) => {
511
- const byPath = groupedSynthetics(synthetic);
512
- const inserts = [...byPath].map(([rel, items]) => ({
408
+ const inserts = [...byPath.keys()].map((rel) => ({
513
409
  at: slot.get(rel) ?? out.length,
514
- items,
410
+ items: byPath.get(rel),
515
411
  }));
516
412
  inserts.sort((left, right) => right.at - left.at);
517
- for (const insert of inserts) {
518
- out.splice(insert.at, 0, ...insert.items);
519
- }
413
+ for (const insert of inserts) out.splice(insert.at, 0, ...insert.items);
520
414
  return out;
521
415
  };
522
416
 
523
- const lastPathIndex = (items, rel) => {
524
- let at = -1;
525
- for (let i = 0; i < items.length; i++) {
526
- if (listFilePath(itemPath(items[i])) === rel) at = i;
527
- }
528
- return at;
529
- };
530
-
531
- const insertAfterPath = (items, added, rel) => {
532
- const at = lastPathIndex(items, rel);
533
- if (at < 0) return [...items, ...added];
534
- return [...items.slice(0, at + 1), ...added, ...items.slice(at + 1)];
535
- };
536
-
537
417
  const placeFolded = (items, { folded, synthetic, leftoverNames }) => {
538
418
  const out = [];
539
419
  const slot = new Map();
@@ -613,39 +493,32 @@ const proposeDepItems = (pkgText, outdated, audit, extra = {}) => {
613
493
 
614
494
  const mergeProposedItems = (items, pkgText, outdated, audit, extra = {}) => {
615
495
  const proposed = proposeDepItems(pkgText, outdated, audit, extra);
616
- if (!proposed.length) return items;
617
496
  const taken = new Set();
618
497
  for (const item of items) {
619
- const change = item.dep && item.dep.change;
498
+ const change = item.dep?.change;
620
499
  if (change) taken.add(change.name);
621
500
  }
622
- const added = [];
623
- for (const item of proposed) {
624
- const change = item.dep && item.dep.change;
625
- if (!change || taken.has(change.name)) continue;
626
- added.push(item);
627
- }
501
+ const added = proposed.filter((item) => {
502
+ const change = item.dep?.change;
503
+ return change && !taken.has(change.name);
504
+ });
628
505
  if (!added.length) return items;
629
506
  const rel = relOf(extra.dir ?? '.', MANIFEST);
630
- return insertAfterPath(items, added, rel);
507
+ const at = items.findLastIndex(
508
+ (item) => listFilePath(itemPath(item)) === rel,
509
+ );
510
+ if (at < 0) return [...items, ...added];
511
+ return [...items.slice(0, at + 1), ...added, ...items.slice(at + 1)];
631
512
  };
632
513
 
633
514
  module.exports = {
634
515
  MANIFEST,
635
516
  LOCKFILE,
636
517
  depFileMeta,
637
- readSections,
638
- lockEntries,
639
- lockPackageCount,
640
- diffSections,
641
- mergeDepFile,
642
- mergeResolved,
643
518
  collectUsedNames,
644
519
  collectExportMeta,
645
520
  parseAuditReport,
646
521
  parseOutdatedReport,
647
- applyWantedRange,
648
- patchedFromRange,
649
522
  proposeDepItems,
650
523
  foldDepItems,
651
524
  mergeProposedItems,
package/lib/detect.js CHANGED
@@ -16,29 +16,19 @@ const PROSE_BASENAMES = [
16
16
  'copyright',
17
17
  ];
18
18
 
19
- const isProseBasename = (base) => {
20
- const name = (base || '').toLowerCase();
21
- if (PROSE_BASENAMES.includes(name)) return true;
22
- const stem = name.replace(/\.(txt|text|md|markdown)$/i, '');
19
+ const isProse = (base) => {
20
+ const stem = base.replace(/\.(txt|text|md|markdown)$/, '');
23
21
  return PROSE_BASENAMES.includes(stem);
24
22
  };
25
23
 
26
- const LANG_RULES = [
27
- { test: (base) => base.endsWith('.d.ts'), lang: 'dts' },
28
- { test: isProseBasename, lang: 'md' },
29
- { test: (base) => base.endsWith('.log'), lang: 'log' },
30
- { test: (base) => base.startsWith('.'), lang: 'dot' },
31
- { test: (base) => !base.includes('.'), lang: 'dot' },
32
- ];
33
-
34
24
  const detectLang = (filePath) => {
35
25
  const base = path.basename(filePath || '').toLowerCase();
36
- for (const rule of LANG_RULES) {
37
- if (rule.test(base)) return rule.lang;
38
- }
26
+ if (base.endsWith('.d.ts')) return 'dts';
27
+ if (isProse(base)) return 'md';
28
+ if (base.endsWith('.log')) return 'log';
29
+ if (base.startsWith('.') || !base.includes('.')) return 'dot';
39
30
  const ext = fileExt(base);
40
- if (ext) return mapLang(ext);
41
- return 'dot';
31
+ return ext ? mapLang(ext) : 'dot';
42
32
  };
43
33
 
44
- module.exports = { mapLang, detectLang };
34
+ module.exports = { detectLang };