reslop 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -1
- package/README.md +2 -1
- package/bin/reslop.js +2 -2
- package/lib/ansi.js +4 -2
- package/lib/api.js +2 -2
- package/lib/capabilities.js +7 -14
- package/lib/cli.js +28 -47
- package/lib/clipboard.js +1 -1
- package/lib/{deps/changes.js → deps-diff.js} +309 -19
- package/lib/deps.js +498 -29
- package/lib/detect.js +1 -7
- package/lib/diff/align.js +7 -10
- package/lib/diff/diff.js +53 -0
- package/lib/diff/display.js +5 -4
- package/lib/diff/inline.js +26 -21
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +1 -3
- package/lib/diff/unit.js +392 -0
- package/lib/files.js +5 -8
- package/lib/{git/branches.js → git-branches.js} +6 -5
- package/lib/{git/dependencies.js → git-deps.js} +111 -38
- package/lib/git.js +589 -68
- package/lib/github.js +28 -197
- package/lib/gitlab.js +24 -195
- package/lib/highlight/bash.js +1 -3
- package/lib/highlight/css.js +1 -3
- package/lib/highlight/js.js +11 -14
- package/lib/highlight.js +6 -9
- package/lib/keys.js +14 -8
- package/lib/manifest.js +330 -0
- package/lib/remote.js +374 -0
- package/lib/render/branches.js +14 -68
- package/lib/render/chrome.js +24 -9
- package/lib/render/commits.js +6 -59
- package/lib/render/diff.js +16 -16
- package/lib/render/files.js +6 -34
- package/lib/render/frame.js +23 -12
- package/lib/render/notes.js +4 -3
- package/lib/render/primitives.js +45 -0
- package/lib/{render.js → render/render.js} +5 -5
- package/lib/render/unit.js +41 -0
- package/lib/review.js +750 -14
- package/lib/select.js +5 -10
- package/lib/session/accessors.js +9 -1
- package/lib/session/actions.js +32 -16
- package/lib/session/branches.js +116 -128
- package/lib/session/change-actions.js +202 -123
- package/lib/session/commit.js +105 -112
- package/lib/session/compose.js +158 -13
- package/lib/session/dispatch.js +104 -60
- package/lib/session/files-pane.js +142 -148
- package/lib/session/items.js +22 -30
- package/lib/session/lifecycle.js +44 -44
- package/lib/session/load.js +19 -33
- package/lib/session/navigation.js +12 -1
- package/lib/session/ops.js +28 -41
- package/lib/session/pointer.js +54 -57
- package/lib/session/review.js +23 -37
- package/lib/session/terminal.js +6 -12
- package/lib/session/todos.js +8 -12
- package/lib/session/unit.js +208 -0
- package/lib/session/update.js +33 -46
- package/lib/session/view.js +78 -9
- package/lib/session/watch.js +11 -19
- package/lib/session.js +21 -246
- package/lib/source.js +3 -3
- package/lib/update.js +40 -75
- package/lib/{git/proc.js → utilities.js} +75 -11
- package/lib/wrap.js +1 -3
- package/package.json +1 -1
- package/lib/deps/items.js +0 -134
- package/lib/deps/manifest.js +0 -152
- package/lib/deps/merge.js +0 -120
- package/lib/deps/proposals.js +0 -164
- package/lib/deps/reports.js +0 -180
- package/lib/deps/rows.js +0 -176
- package/lib/deps/scan.js +0 -138
- package/lib/deps/usage.js +0 -68
- package/lib/deps/versions.js +0 -119
- package/lib/diff.js +0 -33
- package/lib/git/changes.js +0 -164
- package/lib/git/read.js +0 -253
- package/lib/git/run.js +0 -100
- package/lib/npm/commands.js +0 -100
- package/lib/remote/changes.js +0 -37
- package/lib/remote/headers.js +0 -36
- package/lib/remote/http.js +0 -122
- package/lib/remote/notes.js +0 -94
- package/lib/review/format.js +0 -434
- package/lib/review/storage.js +0 -150
- package/lib/review/store.js +0 -178
- package/lib/review/templates.js +0 -49
- package/lib/session/origins.js +0 -7
- package/lib/sys.js +0 -84
package/lib/deps.js
CHANGED
|
@@ -1,36 +1,504 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { jsonParse, isHashObject } = require('metautil');
|
|
3
6
|
const { itemPath } = require('./files.js');
|
|
7
|
+
const manifest = require('./manifest.js');
|
|
8
|
+
const { MANIFEST, LOCKFILE, SECTION_KEYS, depFileMeta, relOf } = manifest;
|
|
9
|
+
const { listFilePath, readSections, lockEntries, lockPackageCount } = manifest;
|
|
10
|
+
const { mergeDepFile, findDeclared, applyWantedRange } = manifest;
|
|
11
|
+
const { patchedFromRange, proposedLockVersion, proposedWanted } = manifest;
|
|
12
|
+
const depDiff = require('./deps-diff.js');
|
|
13
|
+
const { diffSections, mergeResolved, summarizeGroup, collectGroups } = depDiff;
|
|
14
|
+
const { takeManifestItems, foldedDepNames, stripFoldedDepItem } = depDiff;
|
|
15
|
+
const { makeDepItems, stubFileItem } = depDiff;
|
|
4
16
|
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
17
|
+
const SOURCE_EXT = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'];
|
|
18
|
+
const REQUIRE_SPEC = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
19
|
+
const FROM_SPEC = /\bfrom\s+['"]([^'"]+)['"]/g;
|
|
20
|
+
const IMPORT_FILE = /import\s+['"]([^'"]+)['"]/g;
|
|
21
|
+
const IMPORT_CALL = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
9
22
|
|
|
10
|
-
const
|
|
23
|
+
const toPackageName = (spec) => {
|
|
24
|
+
if (!spec || spec.startsWith('.') || spec.startsWith('/')) return '';
|
|
25
|
+
if (spec.startsWith('node:')) return '';
|
|
26
|
+
if (spec.startsWith('@')) {
|
|
27
|
+
const parts = spec.split('/');
|
|
28
|
+
if (parts.length < 2) return '';
|
|
29
|
+
return `${parts[0]}/${parts[1]}`;
|
|
30
|
+
}
|
|
31
|
+
return spec.split('/')[0];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const addSpec = (used, spec) => {
|
|
35
|
+
const name = toPackageName(spec);
|
|
36
|
+
if (name) used.add(name);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const addMatches = (used, text, pattern) => {
|
|
40
|
+
const matches = text.matchAll(pattern);
|
|
41
|
+
for (const match of matches) addSpec(used, match[1]);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const collectUsedFromText = (used, text) => {
|
|
45
|
+
addMatches(used, text, REQUIRE_SPEC);
|
|
46
|
+
addMatches(used, text, FROM_SPEC);
|
|
47
|
+
addMatches(used, text, IMPORT_FILE);
|
|
48
|
+
addMatches(used, text, IMPORT_CALL);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const collectUsedFromScripts = (used, scripts) => {
|
|
52
|
+
if (!scripts || typeof scripts !== 'object') return;
|
|
53
|
+
for (const script of Object.values(scripts)) {
|
|
54
|
+
const tokens = `${script}`.split(/[\s;&|]+/);
|
|
55
|
+
for (const token of tokens) {
|
|
56
|
+
if (!token || token.startsWith('-')) continue;
|
|
57
|
+
if (token === 'npx' || token === 'npm' || token === 'node') continue;
|
|
58
|
+
addSpec(used, token);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const pkgHasExportEntry = (pkg) => {
|
|
64
|
+
if (!pkg || typeof pkg !== 'object') return false;
|
|
65
|
+
const main = pkg.main;
|
|
66
|
+
if (typeof main === 'string' && main) return true;
|
|
67
|
+
const esm = pkg.module;
|
|
68
|
+
if (typeof esm === 'string' && esm) return true;
|
|
69
|
+
const exported = pkg.exports;
|
|
70
|
+
if (typeof exported === 'string' && exported) return true;
|
|
71
|
+
if (!exported || typeof exported !== 'object') return false;
|
|
72
|
+
if (Array.isArray(exported)) return exported.length > 0;
|
|
73
|
+
return Object.keys(exported).length > 0;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const SKIP_WALK = ['node_modules', '.git', '.review', 'coverage', 'dist'];
|
|
77
|
+
|
|
78
|
+
const collectUsedFromManifest = (used, rel) => {
|
|
79
|
+
let text;
|
|
80
|
+
try {
|
|
81
|
+
text = fs.readFileSync(rel, 'utf8');
|
|
82
|
+
} catch {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const pkg = jsonParse(text);
|
|
86
|
+
if (!pkg) return;
|
|
87
|
+
collectUsedFromScripts(used, pkg.scripts);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const walkUsedNames = (used, dir) => {
|
|
91
|
+
let entries;
|
|
92
|
+
try {
|
|
93
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
94
|
+
} catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
for (const entry of entries) {
|
|
98
|
+
if (entry.isSymbolicLink()) continue;
|
|
99
|
+
const full = path.join(dir, entry.name);
|
|
100
|
+
if (entry.isDirectory()) {
|
|
101
|
+
if (SKIP_WALK.includes(entry.name)) continue;
|
|
102
|
+
if (entry.name.startsWith('.')) continue;
|
|
103
|
+
walkUsedNames(used, full);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (entry.name === MANIFEST) {
|
|
107
|
+
collectUsedFromManifest(used, full);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const ext = path.extname(entry.name);
|
|
111
|
+
if (!SOURCE_EXT.includes(ext)) continue;
|
|
112
|
+
let text;
|
|
113
|
+
try {
|
|
114
|
+
text = fs.readFileSync(full, 'utf8');
|
|
115
|
+
} catch {
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
collectUsedFromText(used, text);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const collectUsedNames = (root) => {
|
|
123
|
+
const used = new Set();
|
|
124
|
+
if (!root) return used;
|
|
125
|
+
walkUsedNames(used, root);
|
|
126
|
+
return used;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const depModuleParts = (name) => {
|
|
130
|
+
if (!name.startsWith('@')) return [name];
|
|
131
|
+
const parts = name.split('/');
|
|
132
|
+
if (parts.length < 2) return [];
|
|
133
|
+
return parts.slice(0, 2);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const readDepPkg = (root, pkgDir, name) => {
|
|
137
|
+
if (!root || !name) return null;
|
|
138
|
+
const parts = depModuleParts(name);
|
|
139
|
+
if (!parts.length) return null;
|
|
140
|
+
const nested = pkgDir && pkgDir !== '.' ? pkgDir : '';
|
|
141
|
+
let dir = nested ? path.join(root, nested) : root;
|
|
142
|
+
while (true) {
|
|
143
|
+
const file = path.join(dir, 'node_modules', ...parts, MANIFEST);
|
|
144
|
+
let text = '';
|
|
145
|
+
try {
|
|
146
|
+
text = fs.readFileSync(file, 'utf8');
|
|
147
|
+
} catch {
|
|
148
|
+
// Missing node_modules copy; try a parent directory.
|
|
149
|
+
}
|
|
150
|
+
if (text) return jsonParse(text);
|
|
151
|
+
const parent = path.dirname(dir);
|
|
152
|
+
if (parent === dir) return null;
|
|
153
|
+
dir = parent;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const depHasExportEntry = (root, pkgDir, name) => {
|
|
158
|
+
const pkg = readDepPkg(root, pkgDir, name);
|
|
159
|
+
if (!pkg) return true;
|
|
160
|
+
return pkgHasExportEntry(pkg);
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const collectExportEntries = (root, pkgDir, names) => {
|
|
164
|
+
const entries = new Map();
|
|
165
|
+
if (!root) return entries;
|
|
166
|
+
for (const name of names) {
|
|
167
|
+
const hasExport = depHasExportEntry(root, pkgDir, name);
|
|
168
|
+
entries.set(name, hasExport);
|
|
169
|
+
}
|
|
170
|
+
return entries;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const declaredDepNames = (root, pkgDir) => {
|
|
174
|
+
const nested = pkgDir && pkgDir !== '.' ? pkgDir : '';
|
|
175
|
+
const file = path.join(root, nested, MANIFEST);
|
|
176
|
+
let text;
|
|
177
|
+
try {
|
|
178
|
+
text = fs.readFileSync(file, 'utf8');
|
|
179
|
+
} catch {
|
|
180
|
+
return [];
|
|
181
|
+
}
|
|
182
|
+
const maps = readSections(jsonParse(text));
|
|
183
|
+
let size = 0;
|
|
184
|
+
for (const key of SECTION_KEYS) size += maps[key].size;
|
|
185
|
+
const names = new Array(size);
|
|
186
|
+
let i = 0;
|
|
187
|
+
for (const key of SECTION_KEYS) {
|
|
188
|
+
for (const name of maps[key].keys()) names[i++] = name;
|
|
189
|
+
}
|
|
190
|
+
return names;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const collectExportMeta = (root, extra = {}) => {
|
|
194
|
+
if (extra.exportEntries !== undefined) return extra.exportEntries;
|
|
195
|
+
const pkgDir = extra.dir ?? '.';
|
|
196
|
+
const names = extra.names ?? declaredDepNames(root, pkgDir);
|
|
197
|
+
return collectExportEntries(root, pkgDir, names);
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const SEVERITY_RANK = { critical: 4, high: 3, moderate: 2, low: 1, info: 0 };
|
|
201
|
+
|
|
202
|
+
const namedTitle = (name, title) => {
|
|
203
|
+
if (!title) return '';
|
|
204
|
+
if (!name) return title;
|
|
205
|
+
const prefix = `${name}: `;
|
|
206
|
+
if (title.startsWith(prefix)) return title;
|
|
207
|
+
return `${prefix}${title}`;
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const viaNotes = (via, pkgName) => {
|
|
211
|
+
if (!Array.isArray(via)) return [];
|
|
212
|
+
const notes = [];
|
|
213
|
+
for (const item of via) {
|
|
214
|
+
if (!item || typeof item !== 'object') continue;
|
|
215
|
+
const title = typeof item.title === 'string' ? item.title : '';
|
|
216
|
+
if (!title) continue;
|
|
217
|
+
const hasName = typeof item.name === 'string' && item.name;
|
|
218
|
+
const named = hasName ? item.name : pkgName;
|
|
219
|
+
const note = namedTitle(named, title);
|
|
220
|
+
if (!notes.includes(note)) notes.push(note);
|
|
221
|
+
}
|
|
222
|
+
return notes;
|
|
223
|
+
};
|
|
11
224
|
|
|
12
|
-
const
|
|
13
|
-
const { diffSections, mergeResolved, summarizeGroup } = changes;
|
|
14
|
-
const { collectGroups, takeManifestItems } = changes;
|
|
15
|
-
const { foldedDepNames, stripFoldedDepItem } = changes;
|
|
225
|
+
const severityRank = (severity) => SEVERITY_RANK[severity] ?? 0;
|
|
16
226
|
|
|
17
|
-
const
|
|
227
|
+
const auditFix = (entry) => {
|
|
228
|
+
const available = entry.fixAvailable;
|
|
229
|
+
if (!available || typeof available !== 'object') return '';
|
|
230
|
+
if (typeof available.version !== 'string') return '';
|
|
231
|
+
return available.version;
|
|
232
|
+
};
|
|
18
233
|
|
|
19
|
-
const
|
|
20
|
-
const
|
|
234
|
+
const auditEntryNames = (key, entry) => {
|
|
235
|
+
const names = [];
|
|
236
|
+
const primary = entry.name || key;
|
|
237
|
+
if (primary) names.push(primary);
|
|
238
|
+
const effects = entry.effects;
|
|
239
|
+
if (!Array.isArray(effects)) return names;
|
|
240
|
+
for (const effect of effects) {
|
|
241
|
+
if (typeof effect === 'string' && effect) names.push(effect);
|
|
242
|
+
}
|
|
243
|
+
return names;
|
|
244
|
+
};
|
|
21
245
|
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
const
|
|
246
|
+
const keepHighestAudit = (audit, name, severity, fix, range, notes) => {
|
|
247
|
+
if (!name || !severity || severity === 'info') return;
|
|
248
|
+
const titles = [];
|
|
249
|
+
for (const note of notes) {
|
|
250
|
+
if (note && !titles.includes(note)) titles.push(note);
|
|
251
|
+
}
|
|
252
|
+
const prev = audit.get(name);
|
|
253
|
+
if (prev && severityRank(prev.severity) >= severityRank(severity)) {
|
|
254
|
+
const merged = [...prev.titles];
|
|
255
|
+
for (const note of titles) {
|
|
256
|
+
if (!merged.includes(note)) merged.push(note);
|
|
257
|
+
}
|
|
258
|
+
const next = {
|
|
259
|
+
...prev,
|
|
260
|
+
titles: merged,
|
|
261
|
+
fix: prev.fix || fix,
|
|
262
|
+
range: prev.range || range,
|
|
263
|
+
};
|
|
264
|
+
audit.set(name, next);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const title = titles[0] || '';
|
|
268
|
+
const next = { name, severity, title, titles, fix, range };
|
|
269
|
+
next.fix ||= '';
|
|
270
|
+
next.range ||= '';
|
|
271
|
+
audit.set(name, next);
|
|
272
|
+
};
|
|
25
273
|
|
|
26
|
-
const
|
|
27
|
-
const
|
|
274
|
+
const parseAuditReport = (text) => {
|
|
275
|
+
const audit = new Map();
|
|
276
|
+
if (!text || !`${text}`.trim()) return audit;
|
|
277
|
+
const data = jsonParse(text);
|
|
278
|
+
if (!isHashObject(data)) return audit;
|
|
279
|
+
const vulns = data.vulnerabilities;
|
|
280
|
+
if (vulns && typeof vulns === 'object') {
|
|
281
|
+
for (const key of Object.keys(vulns)) {
|
|
282
|
+
const entry = vulns[key];
|
|
283
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
284
|
+
const severity = entry.severity;
|
|
285
|
+
const names = auditEntryNames(key, entry);
|
|
286
|
+
const pkgName = entry.name || key;
|
|
287
|
+
const titles = viaNotes(entry.via, pkgName);
|
|
288
|
+
const fix = auditFix(entry);
|
|
289
|
+
const range = typeof entry.range === 'string' ? entry.range : '';
|
|
290
|
+
for (const name of names) {
|
|
291
|
+
keepHighestAudit(audit, name, severity, fix, range, titles);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
const advisories = data.advisories;
|
|
296
|
+
if (advisories && typeof advisories === 'object') {
|
|
297
|
+
for (const entry of Object.values(advisories)) {
|
|
298
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
299
|
+
const name = entry.module_name || entry.name;
|
|
300
|
+
const severity = entry.severity;
|
|
301
|
+
const raw = typeof entry.title === 'string' ? entry.title : '';
|
|
302
|
+
const title = namedTitle(name, raw);
|
|
303
|
+
const titles = title ? [title] : [];
|
|
304
|
+
const fix = auditFix(entry);
|
|
305
|
+
const range = typeof entry.range === 'string' ? entry.range : '';
|
|
306
|
+
keepHighestAudit(audit, name, severity, fix, range, titles);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return audit;
|
|
310
|
+
};
|
|
28
311
|
|
|
29
|
-
const
|
|
30
|
-
const
|
|
31
|
-
|
|
312
|
+
const parseOutdatedReport = (text) => {
|
|
313
|
+
const outdated = new Map();
|
|
314
|
+
if (!text || !`${text}`.trim()) return outdated;
|
|
315
|
+
const data = jsonParse(text);
|
|
316
|
+
if (!isHashObject(data)) return outdated;
|
|
317
|
+
for (const name of Object.keys(data)) {
|
|
318
|
+
const entry = data[name];
|
|
319
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
320
|
+
const current = typeof entry.current === 'string' ? entry.current : '';
|
|
321
|
+
const wanted = typeof entry.wanted === 'string' ? entry.wanted : '';
|
|
322
|
+
const latest = typeof entry.latest === 'string' ? entry.latest : '';
|
|
323
|
+
const type = typeof entry.type === 'string' ? entry.type : '';
|
|
324
|
+
if (!wanted) continue;
|
|
325
|
+
outdated.set(name, { current, wanted, latest, type });
|
|
326
|
+
}
|
|
327
|
+
return outdated;
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const markChanges = (changes, report, kind) => {
|
|
331
|
+
if (!report) return changes;
|
|
332
|
+
return changes.map((change) => {
|
|
333
|
+
if (change.action === 'removed' || change.section === 'field') {
|
|
334
|
+
return change;
|
|
335
|
+
}
|
|
336
|
+
const found = report.get(change.name);
|
|
337
|
+
if (!found) return change;
|
|
338
|
+
if (kind === 'outdated' && found.wanted && found.wanted === found.current) {
|
|
339
|
+
return change;
|
|
340
|
+
}
|
|
341
|
+
return { ...change, [kind]: found };
|
|
342
|
+
});
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const markAuditChanges = (changes, audit) =>
|
|
346
|
+
markChanges(changes, audit, 'audit');
|
|
32
347
|
|
|
33
|
-
const
|
|
348
|
+
const markOutdatedChanges = (changes, outdated) =>
|
|
349
|
+
markChanges(changes, outdated, 'outdated');
|
|
350
|
+
|
|
351
|
+
const depExports = (exportEntries, name) => {
|
|
352
|
+
if (!exportEntries || !exportEntries.has(name)) return true;
|
|
353
|
+
return exportEntries.get(name) === true;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const isAddedDep = (change) => {
|
|
357
|
+
if (change.action !== 'added') return false;
|
|
358
|
+
if (change.section === 'resolved') return false;
|
|
359
|
+
if (change.section === 'field') return false;
|
|
360
|
+
return true;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const unusedChangeNames = (changes, used) => {
|
|
364
|
+
const names = [];
|
|
365
|
+
if (!used || !used.size) return names;
|
|
366
|
+
for (const change of changes) {
|
|
367
|
+
if (!isAddedDep(change)) continue;
|
|
368
|
+
if (used.has(change.name)) continue;
|
|
369
|
+
names.push(change.name);
|
|
370
|
+
}
|
|
371
|
+
return names;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const unusedDeclaredNames = (pkg, used) => {
|
|
375
|
+
const names = [];
|
|
376
|
+
if (!pkgHasExportEntry(pkg)) return names;
|
|
377
|
+
if (!used || !used.size) return names;
|
|
378
|
+
const maps = readSections(pkg);
|
|
379
|
+
for (const section of SECTION_KEYS) {
|
|
380
|
+
if (section === 'peerDependencies') continue;
|
|
381
|
+
const keys = [...maps[section].keys()];
|
|
382
|
+
keys.sort();
|
|
383
|
+
for (const name of keys) {
|
|
384
|
+
if (used.has(name)) continue;
|
|
385
|
+
names.push(name);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return names;
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
const markUnusedChanges = (changes, used, pkg, exportEntries) => {
|
|
392
|
+
if (!pkgHasExportEntry(pkg)) return changes;
|
|
393
|
+
if (!used || !used.size) return changes;
|
|
394
|
+
const { length } = changes;
|
|
395
|
+
const marked = new Array(length);
|
|
396
|
+
for (let i = 0; i < length; i++) {
|
|
397
|
+
const change = changes[i];
|
|
398
|
+
if (!isAddedDep(change) || used.has(change.name)) {
|
|
399
|
+
marked[i] = change;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (!depExports(exportEntries, change.name)) {
|
|
403
|
+
marked[i] = change;
|
|
404
|
+
continue;
|
|
405
|
+
}
|
|
406
|
+
marked[i] = { ...change, unused: true, propose: true };
|
|
407
|
+
}
|
|
408
|
+
return marked;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const unusedDeclaredChanges = (pkg, used, exportEntries) => {
|
|
412
|
+
const changes = [];
|
|
413
|
+
if (!pkgHasExportEntry(pkg)) return changes;
|
|
414
|
+
if (!used || !used.size) return changes;
|
|
415
|
+
const maps = readSections(pkg);
|
|
416
|
+
const taken = new Set();
|
|
417
|
+
for (const section of SECTION_KEYS) {
|
|
418
|
+
if (section === 'peerDependencies') continue;
|
|
419
|
+
const map = maps[section];
|
|
420
|
+
const names = [...map.keys()];
|
|
421
|
+
names.sort();
|
|
422
|
+
for (const name of names) {
|
|
423
|
+
if (used.has(name)) continue;
|
|
424
|
+
if (taken.has(name)) continue;
|
|
425
|
+
if (!depExports(exportEntries, name)) continue;
|
|
426
|
+
taken.add(name);
|
|
427
|
+
const version = map.get(name);
|
|
428
|
+
changes.push({
|
|
429
|
+
name,
|
|
430
|
+
section,
|
|
431
|
+
action: 'removed',
|
|
432
|
+
from: version,
|
|
433
|
+
to: '',
|
|
434
|
+
propose: true,
|
|
435
|
+
unused: true,
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return changes;
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const buildProposedChanges = (pkg, outdated, audit, lock, options = {}) => {
|
|
443
|
+
const used = options.used;
|
|
444
|
+
const exportEntries = options.exportEntries;
|
|
445
|
+
const names = new Set();
|
|
446
|
+
if (outdated) {
|
|
447
|
+
for (const name of outdated.keys()) names.add(name);
|
|
448
|
+
}
|
|
449
|
+
if (audit) {
|
|
450
|
+
for (const name of audit.keys()) names.add(name);
|
|
451
|
+
}
|
|
452
|
+
const listed = [...names];
|
|
453
|
+
listed.sort();
|
|
454
|
+
const resolved = lockEntries(lock);
|
|
455
|
+
const unused = unusedDeclaredChanges(pkg, used, exportEntries);
|
|
456
|
+
const taken = new Set();
|
|
457
|
+
for (const change of unused) taken.add(change.name);
|
|
458
|
+
const changes = [...unused];
|
|
459
|
+
for (const name of listed) {
|
|
460
|
+
if (taken.has(name)) continue;
|
|
461
|
+
const out = outdated ? outdated.get(name) : null;
|
|
462
|
+
const vul = audit ? audit.get(name) : null;
|
|
463
|
+
const type = out ? out.type : '';
|
|
464
|
+
const declared = findDeclared(pkg, name, type);
|
|
465
|
+
if (!declared) {
|
|
466
|
+
if (!vul) continue;
|
|
467
|
+
const version = resolved.get(name) || '';
|
|
468
|
+
if (!version) continue;
|
|
469
|
+
const to = proposedLockVersion(version, vul);
|
|
470
|
+
changes.push({
|
|
471
|
+
name,
|
|
472
|
+
section: 'resolved',
|
|
473
|
+
action: 'vulnerable',
|
|
474
|
+
from: version,
|
|
475
|
+
to,
|
|
476
|
+
propose: true,
|
|
477
|
+
audit: vul,
|
|
478
|
+
});
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
481
|
+
const wanted = proposedWanted(out, vul);
|
|
482
|
+
if (!wanted && !vul) continue;
|
|
483
|
+
let to = declared.version;
|
|
484
|
+
if (wanted) to = applyWantedRange(declared.version, wanted);
|
|
485
|
+
if (to === declared.version && !vul) continue;
|
|
486
|
+
let action = 'changed';
|
|
487
|
+
if (to === declared.version) action = 'vulnerable';
|
|
488
|
+
const change = {
|
|
489
|
+
name,
|
|
490
|
+
section: declared.section,
|
|
491
|
+
action,
|
|
492
|
+
from: declared.version,
|
|
493
|
+
to,
|
|
494
|
+
propose: true,
|
|
495
|
+
};
|
|
496
|
+
if (out) change.outdated = out;
|
|
497
|
+
if (vul) change.audit = vul;
|
|
498
|
+
changes.push(change);
|
|
499
|
+
}
|
|
500
|
+
return changes;
|
|
501
|
+
};
|
|
34
502
|
|
|
35
503
|
const loadExportEntries = (names, extra = {}, pkgDir) => {
|
|
36
504
|
const supplied = extra.exportEntries;
|
|
@@ -48,12 +516,11 @@ const loadExportEntries = (names, extra = {}, pkgDir) => {
|
|
|
48
516
|
return merged;
|
|
49
517
|
};
|
|
50
518
|
|
|
51
|
-
const groupDir = (key) => key.slice(key.indexOf('\0') + 1);
|
|
52
|
-
|
|
53
519
|
const groupedSynthetics = (synthetic) => {
|
|
54
520
|
const byPath = new Map();
|
|
55
521
|
for (const key of synthetic.keys()) {
|
|
56
|
-
const
|
|
522
|
+
const dir = key.slice(key.indexOf('\0') + 1);
|
|
523
|
+
const rel = relOf(dir, MANIFEST);
|
|
57
524
|
const list = byPath.get(rel) ?? [];
|
|
58
525
|
for (const item of synthetic.get(key)) list.push(item);
|
|
59
526
|
byPath.set(rel, list);
|
|
@@ -63,10 +530,11 @@ const groupedSynthetics = (synthetic) => {
|
|
|
63
530
|
|
|
64
531
|
const spliceAtSlots = (out, synthetic, slot) => {
|
|
65
532
|
const byPath = groupedSynthetics(synthetic);
|
|
66
|
-
const inserts =
|
|
533
|
+
const inserts = new Array(byPath.size);
|
|
534
|
+
let i = 0;
|
|
67
535
|
for (const rel of byPath.keys()) {
|
|
68
536
|
const at = slot.has(rel) ? slot.get(rel) : out.length;
|
|
69
|
-
inserts
|
|
537
|
+
inserts[i++] = { at, items: byPath.get(rel) };
|
|
70
538
|
}
|
|
71
539
|
inserts.sort((left, right) => right.at - left.at);
|
|
72
540
|
for (const insert of inserts) {
|
|
@@ -91,7 +559,7 @@ const insertAfterPath = (items, added, rel) => {
|
|
|
91
559
|
|
|
92
560
|
const foldDepItems = (
|
|
93
561
|
items,
|
|
94
|
-
readSides =
|
|
562
|
+
readSides = () => ({ oldText: '', newText: '' }),
|
|
95
563
|
usedNames = null,
|
|
96
564
|
audit = null,
|
|
97
565
|
outdated = null,
|
|
@@ -155,9 +623,9 @@ const foldDepItems = (
|
|
|
155
623
|
};
|
|
156
624
|
|
|
157
625
|
const proposeDepItems = (pkgText, outdated, audit, extra = {}) => {
|
|
158
|
-
const pkg =
|
|
626
|
+
const pkg = jsonParse(pkgText ?? '');
|
|
159
627
|
if (!pkg) return [];
|
|
160
|
-
const lock =
|
|
628
|
+
const lock = jsonParse(extra.lockText ?? '');
|
|
161
629
|
const used = extra.usedNames;
|
|
162
630
|
const names = unusedDeclaredNames(pkg, used);
|
|
163
631
|
const exportEntries = loadExportEntries(names, extra, extra.dir);
|
|
@@ -202,6 +670,7 @@ module.exports = {
|
|
|
202
670
|
mergeDepFile,
|
|
203
671
|
mergeResolved,
|
|
204
672
|
collectUsedNames,
|
|
673
|
+
collectExportMeta,
|
|
205
674
|
parseAuditReport,
|
|
206
675
|
parseOutdatedReport,
|
|
207
676
|
applyWantedRange,
|
package/lib/detect.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
|
+
const { fileExt } = require('metautil');
|
|
4
5
|
|
|
5
6
|
const { mapLang } = require('./highlight.js');
|
|
6
7
|
|
|
@@ -13,13 +14,6 @@ const PROSE_BASENAMES = [
|
|
|
13
14
|
'copyright',
|
|
14
15
|
];
|
|
15
16
|
|
|
16
|
-
const fileExt = (filePath) => {
|
|
17
|
-
const base = path.basename(filePath || '');
|
|
18
|
-
const dot = base.lastIndexOf('.');
|
|
19
|
-
if (dot <= 0) return '';
|
|
20
|
-
return base.slice(dot + 1).toLowerCase();
|
|
21
|
-
};
|
|
22
|
-
|
|
23
17
|
const isProseBasename = (base) => {
|
|
24
18
|
const name = (base || '').toLowerCase();
|
|
25
19
|
if (PROSE_BASENAMES.includes(name)) return true;
|
package/lib/diff/align.js
CHANGED
|
@@ -189,9 +189,6 @@ const mapHunkBlocks = (lines, onCtx, onChange) => {
|
|
|
189
189
|
return out;
|
|
190
190
|
};
|
|
191
191
|
|
|
192
|
-
const alignPairs = (lines) =>
|
|
193
|
-
mapHunkBlocks(lines, (line) => line, alignChangeBlock);
|
|
194
|
-
|
|
195
192
|
const alignSplitBlock = (dels, adds, delToAdd) => {
|
|
196
193
|
const out = [];
|
|
197
194
|
walkAligned(dels, adds, delToAdd, {
|
|
@@ -208,14 +205,14 @@ const alignSplitBlock = (dels, adds, delToAdd) => {
|
|
|
208
205
|
return out;
|
|
209
206
|
};
|
|
210
207
|
|
|
211
|
-
const splitCtxRow = (line) => ({ left: line, right: line });
|
|
212
|
-
|
|
213
|
-
const alignSplit = (lines) =>
|
|
214
|
-
mapHunkBlocks(lines, splitCtxRow, alignSplitBlock);
|
|
215
|
-
|
|
216
208
|
const LAYOUT_ALIGN = {
|
|
217
|
-
mixed:
|
|
218
|
-
side:
|
|
209
|
+
mixed: (lines) => mapHunkBlocks(lines, (line) => line, alignChangeBlock),
|
|
210
|
+
side: (lines) =>
|
|
211
|
+
mapHunkBlocks(
|
|
212
|
+
lines,
|
|
213
|
+
(line) => ({ left: line, right: line }),
|
|
214
|
+
alignSplitBlock,
|
|
215
|
+
),
|
|
219
216
|
};
|
|
220
217
|
|
|
221
218
|
module.exports = {
|
package/lib/diff/diff.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { diffChars } = require('./inline.js');
|
|
4
|
+
const { isCtxType, pairIndices, attachInline } = require('./align.js');
|
|
5
|
+
const { parseDiff } = require('./parse.js');
|
|
6
|
+
const patch = require('./patch.js');
|
|
7
|
+
const { splitHunk, flattenBlock, countSides, formatPatch } = patch;
|
|
8
|
+
const display = require('./display.js');
|
|
9
|
+
const { DISPLAY_CONTEXT, displayLines, blockAddText } = display;
|
|
10
|
+
const edit = require('./edit.js');
|
|
11
|
+
const { addBlockRange, replaceBlockAdds } = edit;
|
|
12
|
+
const items = require('./items.js');
|
|
13
|
+
const { synthesizeNewFile, itemsFromFiles, refreshIndexPatches } = items;
|
|
14
|
+
const unit = require('./unit.js');
|
|
15
|
+
const { unitLines, itemsForPath, blockLineRange, blockLineIndex } = unit;
|
|
16
|
+
const { hunkLineRange, hunkKey, sameHunk, splitEditor } = unit;
|
|
17
|
+
const { fileOffset, fileLines, sameBlock, splitFile } = unit;
|
|
18
|
+
const { fileLineIndex, attachEditSpans, mergeEditRows } = unit;
|
|
19
|
+
|
|
20
|
+
module.exports = {
|
|
21
|
+
DISPLAY_CONTEXT,
|
|
22
|
+
isCtxType,
|
|
23
|
+
diffChars,
|
|
24
|
+
pairIndices,
|
|
25
|
+
attachInline,
|
|
26
|
+
parseDiff,
|
|
27
|
+
splitHunk,
|
|
28
|
+
flattenBlock,
|
|
29
|
+
countSides,
|
|
30
|
+
formatPatch,
|
|
31
|
+
displayLines,
|
|
32
|
+
blockAddText,
|
|
33
|
+
addBlockRange,
|
|
34
|
+
replaceBlockAdds,
|
|
35
|
+
synthesizeNewFile,
|
|
36
|
+
itemsFromFiles,
|
|
37
|
+
refreshIndexPatches,
|
|
38
|
+
splitFile,
|
|
39
|
+
splitEditor,
|
|
40
|
+
itemsForPath,
|
|
41
|
+
unitLines,
|
|
42
|
+
sameBlock,
|
|
43
|
+
sameHunk,
|
|
44
|
+
hunkKey,
|
|
45
|
+
blockLineRange,
|
|
46
|
+
hunkLineRange,
|
|
47
|
+
blockLineIndex,
|
|
48
|
+
fileOffset,
|
|
49
|
+
fileLines,
|
|
50
|
+
fileLineIndex,
|
|
51
|
+
attachEditSpans,
|
|
52
|
+
mergeEditRows,
|
|
53
|
+
};
|