reslop 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/LICENSE +1 -1
- package/README.md +12 -13
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -12
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +48 -18
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +332 -0
- package/lib/dashboard/npm.js +193 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +196 -404
- package/lib/deps.js +240 -399
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +138 -141
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +90 -206
- package/lib/editor.js +98 -55
- package/lib/files.js +86 -103
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +182 -344
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +141 -212
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +157 -132
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +16 -26
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +22 -35
- package/lib/highlight/txt.js +9 -22
- package/lib/highlight.js +17 -46
- package/lib/keys.js +62 -85
- package/lib/manifest.js +67 -130
- package/lib/npm-commands.js +119 -189
- package/lib/remote.js +141 -197
- package/lib/render/branches.js +66 -73
- package/lib/render/chrome.js +264 -323
- package/lib/render/commits.js +187 -500
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +245 -0
- package/lib/render/dash-table.js +290 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -228
- package/lib/render/files.js +45 -99
- package/lib/render/frame.js +129 -183
- package/lib/render/notes.js +144 -160
- package/lib/render/npm.js +234 -112
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +131 -170
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/tiles.js +260 -0
- package/lib/render/unit.js +15 -28
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -38
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -78
- package/lib/report-test.js +256 -141
- package/lib/report-tools.js +96 -161
- package/lib/review-merge.js +114 -152
- package/lib/review.js +115 -189
- package/lib/runs.js +225 -0
- package/lib/select.js +89 -75
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +82 -63
- package/lib/session/branches.js +193 -247
- package/lib/session/change-actions.js +171 -214
- package/lib/session/commit.js +280 -342
- package/lib/session/compose.js +559 -674
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +164 -157
- package/lib/session/edit-click.js +57 -0
- package/lib/session/files-pane.js +85 -53
- package/lib/session/items.js +73 -104
- package/lib/session/lifecycle.js +403 -541
- package/lib/session/load.js +48 -68
- package/lib/session/navigation.js +96 -87
- package/lib/session/npm.js +409 -345
- package/lib/session/ops.js +196 -63
- package/lib/session/pointer.js +23 -15
- package/lib/session/progress.js +33 -21
- package/lib/session/review.js +33 -54
- package/lib/session/tasks.js +333 -0
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -136
- package/lib/session/unit.js +3 -4
- package/lib/session/update.js +76 -97
- package/lib/session/view.js +52 -83
- package/lib/session/watch.js +208 -204
- package/lib/session.js +112 -237
- package/lib/update.js +45 -116
- package/lib/utilities.js +67 -89
- package/lib/wrap.js +33 -44
- package/package.json +1 -1
- package/lib/session/todos.js +0 -293
package/lib/deps.js
CHANGED
|
@@ -6,31 +6,43 @@ const metautil = require('metautil');
|
|
|
6
6
|
const { jsonParse, isHashObject } = metautil;
|
|
7
7
|
const files = require('./files.js');
|
|
8
8
|
const { itemPath } = files;
|
|
9
|
+
const utilities = require('./utilities.js');
|
|
10
|
+
const { asText } = utilities;
|
|
9
11
|
const manifest = require('./manifest.js');
|
|
10
|
-
const { MANIFEST, LOCKFILE, SECTION_KEYS,
|
|
11
|
-
const {
|
|
12
|
-
const {
|
|
13
|
-
const {
|
|
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;
|
|
14
16
|
const depDiff = require('./deps-diff.js');
|
|
15
|
-
const {
|
|
16
|
-
const {
|
|
17
|
+
const { summarizeGroup, collectGroups, takeManifestItems } = depDiff;
|
|
18
|
+
const { foldedDepNames, stripFoldedDepItem } = depDiff;
|
|
17
19
|
const { makeDepItems, stubFileItem } = depDiff;
|
|
18
20
|
|
|
19
21
|
const SOURCE_EXT = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'];
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
+
};
|
|
24
39
|
|
|
25
40
|
const toPackageName = (spec) => {
|
|
26
41
|
if (!spec || spec.startsWith('.') || spec.startsWith('/')) return '';
|
|
27
42
|
if (spec.startsWith('node:')) return '';
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return `${parts[0]}/${parts[1]}`;
|
|
32
|
-
}
|
|
33
|
-
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]}`;
|
|
34
46
|
};
|
|
35
47
|
|
|
36
48
|
const addSpec = (used, spec) => {
|
|
@@ -38,54 +50,39 @@ const addSpec = (used, spec) => {
|
|
|
38
50
|
if (name) used.add(name);
|
|
39
51
|
};
|
|
40
52
|
|
|
41
|
-
const addMatches = (used, text, pattern) => {
|
|
42
|
-
const matches = text.matchAll(pattern);
|
|
43
|
-
for (const match of matches) addSpec(used, match[1]);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
53
|
const collectUsedFromText = (used, text) => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
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
|
+
}
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
const collectUsedFromScripts = (used, scripts) => {
|
|
54
|
-
if (!scripts
|
|
60
|
+
if (!isObject(scripts)) return;
|
|
55
61
|
for (const script of Object.values(scripts)) {
|
|
56
|
-
const
|
|
57
|
-
for (const token of tokens) {
|
|
62
|
+
for (const token of `${script}`.split(/[\s;&|]+/)) {
|
|
58
63
|
if (!token || token.startsWith('-')) continue;
|
|
59
|
-
if (token
|
|
60
|
-
addSpec(used, token);
|
|
64
|
+
if (!SCRIPT_RUNNERS.includes(token)) addSpec(used, token);
|
|
61
65
|
}
|
|
62
66
|
}
|
|
63
67
|
};
|
|
64
68
|
|
|
69
|
+
const hasText = (value) => typeof value === 'string' && value !== '';
|
|
70
|
+
|
|
65
71
|
const pkgHasExportEntry = (pkg) => {
|
|
66
|
-
if (!pkg
|
|
67
|
-
|
|
68
|
-
if (typeof main === 'string' && main) return true;
|
|
69
|
-
const esm = pkg.module;
|
|
70
|
-
if (typeof esm === 'string' && esm) return true;
|
|
72
|
+
if (!isObject(pkg)) return false;
|
|
73
|
+
if (hasText(pkg.main) || hasText(pkg.module)) return true;
|
|
71
74
|
const exported = pkg.exports;
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
if (Array.isArray(exported)) return exported.length > 0;
|
|
75
|
-
return Object.keys(exported).length > 0;
|
|
75
|
+
if (hasText(exported)) return true;
|
|
76
|
+
return isObject(exported) && Object.keys(exported).length > 0;
|
|
76
77
|
};
|
|
77
78
|
|
|
78
|
-
const isRuntimeFile = (value) =>
|
|
79
|
-
if (typeof value !== 'string' || !value) return false;
|
|
80
|
-
return !value.endsWith('.d.ts');
|
|
81
|
-
};
|
|
79
|
+
const isRuntimeFile = (value) => hasText(value) && !value.endsWith('.d.ts');
|
|
82
80
|
|
|
83
81
|
const hasRuntimeExport = (value) => {
|
|
84
82
|
if (isRuntimeFile(value)) return true;
|
|
85
83
|
if (Array.isArray(value)) return value.some(hasRuntimeExport);
|
|
86
|
-
if (!value
|
|
87
|
-
const
|
|
88
|
-
for (const key of keys) {
|
|
84
|
+
if (!isObject(value)) return false;
|
|
85
|
+
for (const key of Object.keys(value)) {
|
|
89
86
|
if (key === 'types' || key === 'typings') continue;
|
|
90
87
|
if (hasRuntimeExport(value[key])) return true;
|
|
91
88
|
}
|
|
@@ -93,35 +90,17 @@ const hasRuntimeExport = (value) => {
|
|
|
93
90
|
};
|
|
94
91
|
|
|
95
92
|
const pkgHasBin = (pkg) => {
|
|
96
|
-
if (!pkg || typeof pkg !== 'object') return false;
|
|
97
93
|
const bin = pkg.bin;
|
|
98
94
|
if (typeof bin === 'string') return !!bin;
|
|
99
|
-
|
|
100
|
-
return Object.keys(bin).length > 0;
|
|
95
|
+
return isHashObject(bin) && Object.keys(bin).length > 0;
|
|
101
96
|
};
|
|
102
97
|
|
|
103
98
|
const pkgExportsLibrary = (pkg) => {
|
|
104
|
-
if (!pkg ||
|
|
105
|
-
if (
|
|
106
|
-
if (isRuntimeFile(pkg.main)) return true;
|
|
107
|
-
if (isRuntimeFile(pkg.module)) return true;
|
|
99
|
+
if (!isObject(pkg) || pkgHasBin(pkg)) return false;
|
|
100
|
+
if (isRuntimeFile(pkg.main) || isRuntimeFile(pkg.module)) return true;
|
|
108
101
|
return hasRuntimeExport(pkg.exports);
|
|
109
102
|
};
|
|
110
103
|
|
|
111
|
-
const SKIP_WALK = ['node_modules', '.git', '.review', 'coverage', 'dist'];
|
|
112
|
-
|
|
113
|
-
const collectUsedFromManifest = (used, rel) => {
|
|
114
|
-
let text;
|
|
115
|
-
try {
|
|
116
|
-
text = fs.readFileSync(rel, 'utf8');
|
|
117
|
-
} catch {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
const pkg = jsonParse(text);
|
|
121
|
-
if (!pkg) return;
|
|
122
|
-
collectUsedFromScripts(used, pkg.scripts);
|
|
123
|
-
};
|
|
124
|
-
|
|
125
104
|
const walkUsedNames = (used, dir) => {
|
|
126
105
|
let entries;
|
|
127
106
|
try {
|
|
@@ -133,55 +112,36 @@ const walkUsedNames = (used, dir) => {
|
|
|
133
112
|
if (entry.isSymbolicLink()) continue;
|
|
134
113
|
const full = path.join(dir, entry.name);
|
|
135
114
|
if (entry.isDirectory()) {
|
|
136
|
-
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
collectUsedFromManifest(used, full);
|
|
143
|
-
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));
|
|
144
121
|
}
|
|
145
|
-
const ext = path.extname(entry.name);
|
|
146
|
-
if (!SOURCE_EXT.includes(ext)) continue;
|
|
147
|
-
let text;
|
|
148
|
-
try {
|
|
149
|
-
text = fs.readFileSync(full, 'utf8');
|
|
150
|
-
} catch {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
153
|
-
collectUsedFromText(used, text);
|
|
154
122
|
}
|
|
155
123
|
};
|
|
156
124
|
|
|
157
125
|
const collectUsedNames = (root) => {
|
|
158
126
|
const used = new Set();
|
|
159
|
-
if (
|
|
160
|
-
walkUsedNames(used, root);
|
|
127
|
+
if (root) walkUsedNames(used, root);
|
|
161
128
|
return used;
|
|
162
129
|
};
|
|
163
130
|
|
|
164
131
|
const depModuleParts = (name) => {
|
|
165
132
|
if (!name.startsWith('@')) return [name];
|
|
166
133
|
const parts = name.split('/');
|
|
167
|
-
|
|
168
|
-
return parts.slice(0, 2);
|
|
134
|
+
return parts.length < 2 ? [] : parts.slice(0, 2);
|
|
169
135
|
};
|
|
170
136
|
|
|
171
137
|
const readDepPkg = (root, pkgDir, name) => {
|
|
172
|
-
if (!
|
|
138
|
+
if (!name) return null;
|
|
173
139
|
const parts = depModuleParts(name);
|
|
174
140
|
if (!parts.length) return null;
|
|
175
|
-
|
|
176
|
-
let dir = nested ? path.join(root, nested) : root;
|
|
141
|
+
let dir = path.join(root, pkgDir || '.');
|
|
177
142
|
while (true) {
|
|
178
143
|
const file = path.join(dir, 'node_modules', ...parts, MANIFEST);
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
text = fs.readFileSync(file, 'utf8');
|
|
182
|
-
} catch {
|
|
183
|
-
// Missing node_modules copy; try a parent directory.
|
|
184
|
-
}
|
|
144
|
+
const text = readText(file);
|
|
185
145
|
if (text) return jsonParse(text);
|
|
186
146
|
const parent = path.dirname(dir);
|
|
187
147
|
if (parent === dir) return null;
|
|
@@ -189,61 +149,37 @@ const readDepPkg = (root, pkgDir, name) => {
|
|
|
189
149
|
}
|
|
190
150
|
};
|
|
191
151
|
|
|
192
|
-
const depHasExportEntry = (root, pkgDir, name) => {
|
|
193
|
-
const pkg = readDepPkg(root, pkgDir, name);
|
|
194
|
-
if (!pkg) return false;
|
|
195
|
-
return pkgExportsLibrary(pkg);
|
|
196
|
-
};
|
|
197
|
-
|
|
198
152
|
const collectExportEntries = (root, pkgDir, names) => {
|
|
199
153
|
const entries = new Map();
|
|
200
154
|
if (!root) return entries;
|
|
201
155
|
for (const name of names) {
|
|
202
|
-
const
|
|
203
|
-
entries.set(name,
|
|
156
|
+
const pkg = readDepPkg(root, pkgDir, name);
|
|
157
|
+
entries.set(name, !!pkg && pkgExportsLibrary(pkg));
|
|
204
158
|
}
|
|
205
159
|
return entries;
|
|
206
160
|
};
|
|
207
161
|
|
|
208
|
-
const
|
|
209
|
-
const
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
text = fs.readFileSync(file, 'utf8');
|
|
214
|
-
} catch {
|
|
215
|
-
return [];
|
|
216
|
-
}
|
|
217
|
-
const maps = readSections(jsonParse(text));
|
|
218
|
-
return SECTION_KEYS.flatMap((key) => [...maps[key].keys()]);
|
|
219
|
-
};
|
|
220
|
-
|
|
221
|
-
const collectExportMeta = (root, extra = {}) => {
|
|
222
|
-
if (extra.exportEntries !== undefined) return extra.exportEntries;
|
|
223
|
-
const pkgDir = extra.dir ?? '.';
|
|
224
|
-
const names = extra.names ?? declaredDepNames(root, pkgDir);
|
|
225
|
-
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);
|
|
226
167
|
};
|
|
227
168
|
|
|
228
|
-
const SEVERITY_RANK = { critical: 4, high: 3, moderate: 2, low: 1, info: 0 };
|
|
229
|
-
|
|
230
169
|
const namedTitle = (name, title) => {
|
|
231
170
|
if (!title) return '';
|
|
232
171
|
if (!name) return title;
|
|
233
172
|
const prefix = `${name}: `;
|
|
234
|
-
|
|
235
|
-
return `${prefix}${title}`;
|
|
173
|
+
return title.startsWith(prefix) ? title : `${prefix}${title}`;
|
|
236
174
|
};
|
|
237
175
|
|
|
238
176
|
const viaNotes = (via, pkgName) => {
|
|
239
177
|
if (!Array.isArray(via)) return [];
|
|
240
178
|
const notes = [];
|
|
241
179
|
for (const item of via) {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const hasName = typeof item.name === 'string' && item.name;
|
|
246
|
-
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;
|
|
247
183
|
const note = namedTitle(named, title);
|
|
248
184
|
if (!notes.includes(note)) notes.push(note);
|
|
249
185
|
}
|
|
@@ -252,104 +188,87 @@ const viaNotes = (via, pkgName) => {
|
|
|
252
188
|
|
|
253
189
|
const severityRank = (severity) => SEVERITY_RANK[severity] ?? 0;
|
|
254
190
|
|
|
255
|
-
const auditFix = (entry) =>
|
|
256
|
-
const available = entry.fixAvailable;
|
|
257
|
-
if (!available || typeof available !== 'object') return '';
|
|
258
|
-
if (typeof available.version !== 'string') return '';
|
|
259
|
-
return available.version;
|
|
260
|
-
};
|
|
191
|
+
const auditFix = (entry) => asText(entry.fixAvailable?.version);
|
|
261
192
|
|
|
262
193
|
const auditEntryNames = (key, entry) => {
|
|
263
|
-
const
|
|
194
|
+
const effects = Array.isArray(entry.effects) ? entry.effects : [];
|
|
195
|
+
const names = effects.filter(hasText);
|
|
264
196
|
const primary = entry.name || key;
|
|
265
|
-
|
|
266
|
-
const effects = entry.effects;
|
|
267
|
-
if (!Array.isArray(effects)) return names;
|
|
268
|
-
for (const effect of effects) {
|
|
269
|
-
if (typeof effect === 'string' && effect) names.push(effect);
|
|
270
|
-
}
|
|
271
|
-
return names;
|
|
197
|
+
return primary ? [primary, ...names] : names;
|
|
272
198
|
};
|
|
273
199
|
|
|
274
|
-
const
|
|
200
|
+
const uniqueTexts = (list) => [...new Set(list.filter(Boolean))];
|
|
201
|
+
|
|
202
|
+
const keepHighestAudit = (audit, found) => {
|
|
203
|
+
const { name, severity } = found;
|
|
275
204
|
if (!name || !severity || severity === 'info') return;
|
|
276
|
-
const titles =
|
|
277
|
-
for (const note of notes) {
|
|
278
|
-
if (note && !titles.includes(note)) titles.push(note);
|
|
279
|
-
}
|
|
205
|
+
const titles = uniqueTexts(found.titles);
|
|
280
206
|
const prev = audit.get(name);
|
|
281
207
|
if (prev && severityRank(prev.severity) >= severityRank(severity)) {
|
|
282
|
-
|
|
283
|
-
for (const note of titles) {
|
|
284
|
-
if (!merged.includes(note)) merged.push(note);
|
|
285
|
-
}
|
|
286
|
-
const next = {
|
|
208
|
+
audit.set(name, {
|
|
287
209
|
...prev,
|
|
288
|
-
titles:
|
|
289
|
-
fix: prev.fix || fix,
|
|
290
|
-
range: prev.range || range,
|
|
291
|
-
};
|
|
292
|
-
audit.set(name, next);
|
|
210
|
+
titles: uniqueTexts([...prev.titles, ...titles]),
|
|
211
|
+
fix: prev.fix || found.fix,
|
|
212
|
+
range: prev.range || found.range,
|
|
213
|
+
});
|
|
293
214
|
return;
|
|
294
215
|
}
|
|
295
216
|
const title = titles[0] || '';
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
217
|
+
const fix = found.fix || '';
|
|
218
|
+
const range = found.range || '';
|
|
219
|
+
audit.set(name, { name, severity, title, titles, fix, range });
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const vulnFindings = (vulns) => {
|
|
223
|
+
const found = [];
|
|
224
|
+
for (const key of Object.keys(vulns)) {
|
|
225
|
+
const entry = vulns[key];
|
|
226
|
+
if (!isObject(entry)) continue;
|
|
227
|
+
const { severity } = entry;
|
|
228
|
+
const titles = viaNotes(entry.via, entry.name || key);
|
|
229
|
+
const fix = auditFix(entry);
|
|
230
|
+
const range = asText(entry.range);
|
|
231
|
+
for (const name of auditEntryNames(key, entry)) {
|
|
232
|
+
found.push({ name, severity, fix, range, titles });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return found;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const advisoryFinding = (entry) => {
|
|
239
|
+
const name = entry.module_name || entry.name;
|
|
240
|
+
const title = namedTitle(name, asText(entry.title));
|
|
241
|
+
const titles = title ? [title] : [];
|
|
242
|
+
const fix = auditFix(entry);
|
|
243
|
+
const range = asText(entry.range);
|
|
244
|
+
return { name, severity: entry.severity, fix, range, titles };
|
|
300
245
|
};
|
|
301
246
|
|
|
302
247
|
const parseAuditReport = (text) => {
|
|
303
248
|
const audit = new Map();
|
|
304
|
-
|
|
305
|
-
const data = jsonParse(text);
|
|
249
|
+
const data = text ? jsonParse(text) : null;
|
|
306
250
|
if (!isHashObject(data)) return audit;
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
const severity = entry.severity;
|
|
313
|
-
const names = auditEntryNames(key, entry);
|
|
314
|
-
const pkgName = entry.name || key;
|
|
315
|
-
const titles = viaNotes(entry.via, pkgName);
|
|
316
|
-
const fix = auditFix(entry);
|
|
317
|
-
const range = typeof entry.range === 'string' ? entry.range : '';
|
|
318
|
-
for (const name of names) {
|
|
319
|
-
keepHighestAudit(audit, name, severity, fix, range, titles);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
const advisories = data.advisories;
|
|
324
|
-
if (advisories && typeof advisories === 'object') {
|
|
325
|
-
for (const entry of Object.values(advisories)) {
|
|
326
|
-
if (!entry || typeof entry !== 'object') continue;
|
|
327
|
-
const name = entry.module_name || entry.name;
|
|
328
|
-
const severity = entry.severity;
|
|
329
|
-
const raw = typeof entry.title === 'string' ? entry.title : '';
|
|
330
|
-
const title = namedTitle(name, raw);
|
|
331
|
-
const titles = title ? [title] : [];
|
|
332
|
-
const fix = auditFix(entry);
|
|
333
|
-
const range = typeof entry.range === 'string' ? entry.range : '';
|
|
334
|
-
keepHighestAudit(audit, name, severity, fix, range, titles);
|
|
335
|
-
}
|
|
251
|
+
const { vulnerabilities, advisories } = data;
|
|
252
|
+
const found = isObject(vulnerabilities) ? vulnFindings(vulnerabilities) : [];
|
|
253
|
+
if (isObject(advisories)) {
|
|
254
|
+
const listed = Object.values(advisories).filter(isObject);
|
|
255
|
+
found.push(...listed.map(advisoryFinding));
|
|
336
256
|
}
|
|
257
|
+
for (const entry of found) keepHighestAudit(audit, entry);
|
|
337
258
|
return audit;
|
|
338
259
|
};
|
|
339
260
|
|
|
340
261
|
const parseOutdatedReport = (text) => {
|
|
341
262
|
const outdated = new Map();
|
|
342
|
-
|
|
343
|
-
const data = jsonParse(text);
|
|
263
|
+
const data = text ? jsonParse(text) : null;
|
|
344
264
|
if (!isHashObject(data)) return outdated;
|
|
345
265
|
for (const name of Object.keys(data)) {
|
|
346
266
|
const entry = data[name];
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
const
|
|
350
|
-
const latest =
|
|
351
|
-
const type =
|
|
352
|
-
if (!wanted) continue;
|
|
267
|
+
const wanted = asText(entry?.wanted);
|
|
268
|
+
if (!isObject(entry) || !wanted) continue;
|
|
269
|
+
const current = asText(entry.current);
|
|
270
|
+
const latest = asText(entry.latest);
|
|
271
|
+
const type = asText(entry.type);
|
|
353
272
|
outdated.set(name, { current, wanted, latest, type });
|
|
354
273
|
}
|
|
355
274
|
return outdated;
|
|
@@ -358,53 +277,40 @@ const parseOutdatedReport = (text) => {
|
|
|
358
277
|
const markChanges = (changes, report, kind) => {
|
|
359
278
|
if (!report) return changes;
|
|
360
279
|
return changes.map((change) => {
|
|
361
|
-
if (change.action === 'removed'
|
|
362
|
-
return change;
|
|
363
|
-
}
|
|
280
|
+
if (change.action === 'removed') return change;
|
|
364
281
|
const found = report.get(change.name);
|
|
365
282
|
if (!found) return change;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
283
|
+
const isCurrent = found.wanted && found.wanted === found.current;
|
|
284
|
+
if (kind === 'outdated' && isCurrent) return change;
|
|
369
285
|
return { ...change, [kind]: found };
|
|
370
286
|
});
|
|
371
287
|
};
|
|
372
288
|
|
|
373
289
|
const depExports = (exportEntries, name) => exportEntries?.get(name) === true;
|
|
374
290
|
|
|
375
|
-
const isAddedDep = (change) =>
|
|
376
|
-
|
|
377
|
-
if (change.section === 'resolved') return false;
|
|
378
|
-
if (change.section === 'field') return false;
|
|
379
|
-
return true;
|
|
380
|
-
};
|
|
291
|
+
const isAddedDep = (change) =>
|
|
292
|
+
change.action === 'added' && change.section !== 'resolved';
|
|
381
293
|
|
|
382
294
|
const unusedChangeNames = (changes, used) => {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
names.push(change.name);
|
|
389
|
-
}
|
|
390
|
-
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);
|
|
391
300
|
};
|
|
392
301
|
|
|
393
|
-
const
|
|
394
|
-
const
|
|
395
|
-
if (!pkgHasExportEntry(pkg)) return
|
|
396
|
-
if (!used || !used.size) return names;
|
|
302
|
+
const unusedDeclared = (pkg, used) => {
|
|
303
|
+
const found = [];
|
|
304
|
+
if (!pkgHasExportEntry(pkg) || !used?.size) return found;
|
|
397
305
|
const maps = readSections(pkg);
|
|
398
306
|
for (const section of SECTION_KEYS) {
|
|
399
307
|
if (section === 'peerDependencies') continue;
|
|
400
|
-
const
|
|
401
|
-
keys.sort()
|
|
402
|
-
|
|
403
|
-
if (used.has(name)) continue;
|
|
404
|
-
names.push(name);
|
|
308
|
+
const map = maps[section];
|
|
309
|
+
for (const name of [...map.keys()].sort()) {
|
|
310
|
+
if (!used.has(name)) found.push({ name, section, from: map.get(name) });
|
|
405
311
|
}
|
|
406
312
|
}
|
|
407
|
-
return
|
|
313
|
+
return found;
|
|
408
314
|
};
|
|
409
315
|
|
|
410
316
|
const markUnusedChanges = (changes, used, pkg, exportEntries) => {
|
|
@@ -418,147 +324,118 @@ const markUnusedChanges = (changes, used, pkg, exportEntries) => {
|
|
|
418
324
|
|
|
419
325
|
const unusedDeclaredChanges = (pkg, used, exportEntries) => {
|
|
420
326
|
const changes = [];
|
|
421
|
-
if (!pkgHasExportEntry(pkg)) return changes;
|
|
422
|
-
if (!used || !used.size) return changes;
|
|
423
|
-
const maps = readSections(pkg);
|
|
424
327
|
const taken = new Set();
|
|
425
|
-
for (const section of
|
|
426
|
-
if (
|
|
427
|
-
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
action: 'removed',
|
|
440
|
-
from: version,
|
|
441
|
-
to: '',
|
|
442
|
-
propose: true,
|
|
443
|
-
unused: true,
|
|
444
|
-
});
|
|
445
|
-
}
|
|
328
|
+
for (const { name, section, from } of unusedDeclared(pkg, used)) {
|
|
329
|
+
if (taken.has(name) || !depExports(exportEntries, name)) continue;
|
|
330
|
+
taken.add(name);
|
|
331
|
+
const action = 'removed';
|
|
332
|
+
const to = '';
|
|
333
|
+
changes.push({
|
|
334
|
+
name,
|
|
335
|
+
section,
|
|
336
|
+
action,
|
|
337
|
+
from,
|
|
338
|
+
to,
|
|
339
|
+
propose: true,
|
|
340
|
+
unused: true,
|
|
341
|
+
});
|
|
446
342
|
}
|
|
447
343
|
return changes;
|
|
448
344
|
};
|
|
449
345
|
|
|
450
|
-
const
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
const
|
|
346
|
+
const resolvedChange = (name, vul, resolved) => {
|
|
347
|
+
const from = resolved.get(name) || '';
|
|
348
|
+
if (!vul || !from) return null;
|
|
349
|
+
const to = proposedLockVersion(from, vul);
|
|
350
|
+
const section = 'resolved';
|
|
351
|
+
const action = 'vulnerable';
|
|
352
|
+
return { name, section, action, from, to, propose: true, audit: vul };
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const declaredChange = (name, declared, out, vul) => {
|
|
356
|
+
const wanted = proposedWanted(out, vul);
|
|
357
|
+
if (!wanted && !vul) return null;
|
|
358
|
+
const from = declared.version;
|
|
359
|
+
const to = wanted ? applyWantedRange(from, wanted) : from;
|
|
360
|
+
if (to === from && !vul) return null;
|
|
361
|
+
const action = to === from ? 'vulnerable' : 'changed';
|
|
362
|
+
const { section } = declared;
|
|
363
|
+
const change = { name, section, action, from, to, propose: true };
|
|
364
|
+
if (out) change.outdated = out;
|
|
365
|
+
if (vul) change.audit = vul;
|
|
366
|
+
return change;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const mapKeys = (map) => (map ? [...map.keys()] : []);
|
|
370
|
+
|
|
371
|
+
const buildProposedChanges = (pkg, outdated, audit, lock, options) => {
|
|
372
|
+
const listed = [...new Set([...mapKeys(outdated), ...mapKeys(audit)])];
|
|
461
373
|
listed.sort();
|
|
462
374
|
const resolved = lockEntries(lock);
|
|
463
|
-
const
|
|
464
|
-
const
|
|
465
|
-
|
|
466
|
-
const changes = [...unused];
|
|
375
|
+
const { used, exportEntries } = options;
|
|
376
|
+
const changes = unusedDeclaredChanges(pkg, used, exportEntries);
|
|
377
|
+
const taken = new Set(changes.map((change) => change.name));
|
|
467
378
|
for (const name of listed) {
|
|
468
379
|
if (taken.has(name)) continue;
|
|
469
380
|
const out = outdated ? outdated.get(name) : null;
|
|
470
381
|
const vul = audit ? audit.get(name) : null;
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
if (!version) continue;
|
|
477
|
-
const to = proposedLockVersion(version, vul);
|
|
478
|
-
changes.push({
|
|
479
|
-
name,
|
|
480
|
-
section: 'resolved',
|
|
481
|
-
action: 'vulnerable',
|
|
482
|
-
from: version,
|
|
483
|
-
to,
|
|
484
|
-
propose: true,
|
|
485
|
-
audit: vul,
|
|
486
|
-
});
|
|
487
|
-
continue;
|
|
488
|
-
}
|
|
489
|
-
const wanted = proposedWanted(out, vul);
|
|
490
|
-
if (!wanted && !vul) continue;
|
|
491
|
-
let to = declared.version;
|
|
492
|
-
if (wanted) to = applyWantedRange(declared.version, wanted);
|
|
493
|
-
if (to === declared.version && !vul) continue;
|
|
494
|
-
let action = 'changed';
|
|
495
|
-
if (to === declared.version) action = 'vulnerable';
|
|
496
|
-
const change = {
|
|
497
|
-
name,
|
|
498
|
-
section: declared.section,
|
|
499
|
-
action,
|
|
500
|
-
from: declared.version,
|
|
501
|
-
to,
|
|
502
|
-
propose: true,
|
|
503
|
-
};
|
|
504
|
-
if (out) change.outdated = out;
|
|
505
|
-
if (vul) change.audit = vul;
|
|
506
|
-
changes.push(change);
|
|
382
|
+
const declared = findDeclared(pkg, name, out ? out.type : '');
|
|
383
|
+
const change = declared
|
|
384
|
+
? declaredChange(name, declared, out, vul)
|
|
385
|
+
: resolvedChange(name, vul, resolved);
|
|
386
|
+
if (change) changes.push(change);
|
|
507
387
|
}
|
|
508
388
|
return changes;
|
|
509
389
|
};
|
|
510
390
|
|
|
511
|
-
const loadExportEntries = (names, extra
|
|
391
|
+
const loadExportEntries = (names, extra, dir) => {
|
|
512
392
|
const supplied = extra.exportEntries;
|
|
513
393
|
if (supplied === null) return new Map();
|
|
514
|
-
const
|
|
515
|
-
const missing = [];
|
|
516
|
-
for (const name of names) {
|
|
517
|
-
if (!supplied || !supplied.has(name)) missing.push(name);
|
|
518
|
-
}
|
|
394
|
+
const missing = names.filter((name) => !supplied || !supplied.has(name));
|
|
519
395
|
if (!missing.length) return supplied ?? new Map();
|
|
520
396
|
const found = collectExportEntries(extra.root, dir, missing);
|
|
521
|
-
|
|
522
|
-
return new Map([...supplied, ...found]);
|
|
397
|
+
return supplied ? new Map([...supplied, ...found]) : found;
|
|
523
398
|
};
|
|
524
399
|
|
|
525
|
-
const
|
|
400
|
+
const spliceAtSlots = (out, synthetic, slot) => {
|
|
526
401
|
const byPath = new Map();
|
|
527
|
-
for (const
|
|
528
|
-
const
|
|
529
|
-
const rel = relOf(dir, MANIFEST);
|
|
402
|
+
for (const key of synthetic.keys()) {
|
|
403
|
+
const rel = relOf(key.slice(key.indexOf('\0') + 1), MANIFEST);
|
|
530
404
|
const list = byPath.get(rel) ?? [];
|
|
531
|
-
|
|
405
|
+
list.push(...synthetic.get(key));
|
|
532
406
|
byPath.set(rel, list);
|
|
533
407
|
}
|
|
534
|
-
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
const spliceAtSlots = (out, synthetic, slot) => {
|
|
538
|
-
const byPath = groupedSynthetics(synthetic);
|
|
539
|
-
const inserts = [...byPath].map(([rel, items]) => ({
|
|
408
|
+
const inserts = [...byPath.keys()].map((rel) => ({
|
|
540
409
|
at: slot.get(rel) ?? out.length,
|
|
541
|
-
items,
|
|
410
|
+
items: byPath.get(rel),
|
|
542
411
|
}));
|
|
543
412
|
inserts.sort((left, right) => right.at - left.at);
|
|
544
|
-
for (const insert of inserts)
|
|
545
|
-
out.splice(insert.at, 0, ...insert.items);
|
|
546
|
-
}
|
|
413
|
+
for (const insert of inserts) out.splice(insert.at, 0, ...insert.items);
|
|
547
414
|
return out;
|
|
548
415
|
};
|
|
549
416
|
|
|
550
|
-
const
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
417
|
+
const placeFolded = (items, { folded, synthetic, leftoverNames }) => {
|
|
418
|
+
const out = [];
|
|
419
|
+
const slot = new Map();
|
|
420
|
+
for (const item of items) {
|
|
421
|
+
const meta = depFileMeta(itemPath(item));
|
|
422
|
+
const pkgRel = meta ? relOf(meta.dir, MANIFEST) : '';
|
|
423
|
+
const key = meta ? `${item.origin}\0${meta.dir}` : '';
|
|
424
|
+
if (meta && synthetic.has(key) && !slot.has(pkgRel)) {
|
|
425
|
+
slot.set(pkgRel, out.length);
|
|
426
|
+
}
|
|
427
|
+
if (folded.has(item)) continue;
|
|
428
|
+
if (!meta || meta.kind !== 'manifest') {
|
|
429
|
+
out.push(item);
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
const names = leftoverNames.get(key);
|
|
433
|
+
const kept = names ? stripFoldedDepItem(item, names) : item;
|
|
434
|
+
if (!kept) continue;
|
|
435
|
+
out.push(kept);
|
|
436
|
+
slot.set(pkgRel, out.length);
|
|
554
437
|
}
|
|
555
|
-
return
|
|
556
|
-
};
|
|
557
|
-
|
|
558
|
-
const insertAfterPath = (items, added, rel) => {
|
|
559
|
-
const at = lastPathIndex(items, rel);
|
|
560
|
-
if (at < 0) return [...items, ...added];
|
|
561
|
-
return [...items.slice(0, at + 1), ...added, ...items.slice(at + 1)];
|
|
438
|
+
return spliceAtSlots(out, synthetic, slot);
|
|
562
439
|
};
|
|
563
440
|
|
|
564
441
|
const foldDepItems = (
|
|
@@ -578,51 +455,22 @@ const foldDepItems = (
|
|
|
578
455
|
const summary = summarizeGroup(group, readSides);
|
|
579
456
|
const unusedNames = unusedChangeNames(summary.changes, usedNames);
|
|
580
457
|
const exportEntries = loadExportEntries(unusedNames, extra, group.dir);
|
|
581
|
-
const
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
);
|
|
587
|
-
const audited = markChanges(unused, audit, 'audit');
|
|
588
|
-
const changes = markChanges(audited, outdated, 'outdated');
|
|
458
|
+
const { pkg } = summary;
|
|
459
|
+
let changes = summary.changes;
|
|
460
|
+
changes = markUnusedChanges(changes, usedNames, pkg, exportEntries);
|
|
461
|
+
changes = markChanges(changes, audit, 'audit');
|
|
462
|
+
changes = markChanges(changes, outdated, 'outdated');
|
|
589
463
|
const next = { ...summary, changes };
|
|
590
|
-
leftoverNames.set(key, foldedDepNames(
|
|
464
|
+
leftoverNames.set(key, foldedDepNames(changes));
|
|
591
465
|
const taken = takeManifestItems(next);
|
|
592
|
-
if (next.foldLock)
|
|
593
|
-
|
|
594
|
-
}
|
|
595
|
-
if (!taken.length && !next.changes.length) continue;
|
|
466
|
+
if (next.foldLock) taken.push(...next.lockItems);
|
|
467
|
+
if (!taken.length && !changes.length) continue;
|
|
596
468
|
const made = makeDepItems(group.origin, group.items, taken, next);
|
|
597
469
|
if (made.length) synthetic.set(key, made);
|
|
598
470
|
for (const item of taken) folded.add(item);
|
|
599
471
|
}
|
|
600
472
|
if (!synthetic.size && !folded.size) return items;
|
|
601
|
-
|
|
602
|
-
const slot = new Map();
|
|
603
|
-
for (const item of items) {
|
|
604
|
-
const rel = itemPath(item);
|
|
605
|
-
const meta = depFileMeta(rel);
|
|
606
|
-
if (meta) {
|
|
607
|
-
const pkgRel = relOf(meta.dir, MANIFEST);
|
|
608
|
-
const key = `${item.origin}\0${meta.dir}`;
|
|
609
|
-
if (synthetic.has(key) && !slot.has(pkgRel)) {
|
|
610
|
-
slot.set(pkgRel, out.length);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
if (folded.has(item)) continue;
|
|
614
|
-
if (meta && meta.kind === 'manifest') {
|
|
615
|
-
const names = leftoverNames.get(`${item.origin}\0${meta.dir}`);
|
|
616
|
-
const kept = names ? stripFoldedDepItem(item, names) : item;
|
|
617
|
-
if (kept) {
|
|
618
|
-
out.push(kept);
|
|
619
|
-
slot.set(relOf(meta.dir, MANIFEST), out.length);
|
|
620
|
-
}
|
|
621
|
-
continue;
|
|
622
|
-
}
|
|
623
|
-
out.push(item);
|
|
624
|
-
}
|
|
625
|
-
return spliceAtSlots(out, synthetic, slot);
|
|
473
|
+
return placeFolded(items, { folded, synthetic, leftoverNames });
|
|
626
474
|
};
|
|
627
475
|
|
|
628
476
|
const proposeDepItems = (pkgText, outdated, audit, extra = {}) => {
|
|
@@ -630,7 +478,7 @@ const proposeDepItems = (pkgText, outdated, audit, extra = {}) => {
|
|
|
630
478
|
if (!pkg) return [];
|
|
631
479
|
const lock = jsonParse(extra.lockText ?? '');
|
|
632
480
|
const used = extra.usedNames;
|
|
633
|
-
const names =
|
|
481
|
+
const names = unusedDeclared(pkg, used).map((dep) => dep.name);
|
|
634
482
|
const exportEntries = loadExportEntries(names, extra, extra.dir);
|
|
635
483
|
const options = { used, exportEntries };
|
|
636
484
|
const changes = buildProposedChanges(pkg, outdated, audit, lock, options);
|
|
@@ -645,39 +493,32 @@ const proposeDepItems = (pkgText, outdated, audit, extra = {}) => {
|
|
|
645
493
|
|
|
646
494
|
const mergeProposedItems = (items, pkgText, outdated, audit, extra = {}) => {
|
|
647
495
|
const proposed = proposeDepItems(pkgText, outdated, audit, extra);
|
|
648
|
-
if (!proposed.length) return items;
|
|
649
496
|
const taken = new Set();
|
|
650
497
|
for (const item of items) {
|
|
651
|
-
const change = item.dep
|
|
498
|
+
const change = item.dep?.change;
|
|
652
499
|
if (change) taken.add(change.name);
|
|
653
500
|
}
|
|
654
|
-
const added =
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
added.push(item);
|
|
659
|
-
}
|
|
501
|
+
const added = proposed.filter((item) => {
|
|
502
|
+
const change = item.dep?.change;
|
|
503
|
+
return change && !taken.has(change.name);
|
|
504
|
+
});
|
|
660
505
|
if (!added.length) return items;
|
|
661
506
|
const rel = relOf(extra.dir ?? '.', MANIFEST);
|
|
662
|
-
|
|
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)];
|
|
663
512
|
};
|
|
664
513
|
|
|
665
514
|
module.exports = {
|
|
666
515
|
MANIFEST,
|
|
667
516
|
LOCKFILE,
|
|
668
517
|
depFileMeta,
|
|
669
|
-
readSections,
|
|
670
|
-
lockEntries,
|
|
671
|
-
lockPackageCount,
|
|
672
|
-
diffSections,
|
|
673
|
-
mergeDepFile,
|
|
674
|
-
mergeResolved,
|
|
675
518
|
collectUsedNames,
|
|
676
519
|
collectExportMeta,
|
|
677
520
|
parseAuditReport,
|
|
678
521
|
parseOutdatedReport,
|
|
679
|
-
applyWantedRange,
|
|
680
|
-
patchedFromRange,
|
|
681
522
|
proposeDepItems,
|
|
682
523
|
foldDepItems,
|
|
683
524
|
mergeProposedItems,
|