reslop 0.2.5 → 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 +12 -1
- package/LICENSE +1 -1
- package/README.md +11 -11
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -8
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +39 -47
- 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 +197 -402
- package/lib/deps.js +113 -240
- 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 +76 -144
- 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 +79 -178
- package/lib/editor.js +52 -69
- package/lib/files.js +71 -89
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +147 -272
- 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 +155 -141
- 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 +14 -14
- 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 +19 -25
- package/lib/highlight/txt.js +7 -10
- package/lib/highlight.js +17 -46
- package/lib/keys.js +49 -82
- package/lib/manifest.js +67 -126
- package/lib/npm-commands.js +120 -169
- package/lib/remote.js +140 -190
- package/lib/render/branches.js +63 -61
- package/lib/render/chrome.js +220 -333
- package/lib/render/commits.js +160 -361
- 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 -258
- package/lib/render/files.js +40 -85
- package/lib/render/frame.js +78 -117
- package/lib/render/notes.js +139 -210
- package/lib/render/npm.js +213 -177
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +72 -174
- 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 -31
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -37
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -77
- package/lib/report-test.js +221 -99
- package/lib/report-tools.js +96 -154
- package/lib/review-merge.js +33 -41
- package/lib/review.js +68 -129
- package/lib/runs.js +225 -0
- package/lib/select.js +53 -91
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +61 -43
- package/lib/session/branches.js +166 -222
- package/lib/session/change-actions.js +49 -119
- package/lib/session/commit.js +274 -355
- package/lib/session/compose.js +556 -714
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +162 -170
- package/lib/session/files-pane.js +76 -44
- package/lib/session/items.js +6 -17
- package/lib/session/lifecycle.js +393 -466
- package/lib/session/load.js +1 -14
- package/lib/session/navigation.js +2 -2
- package/lib/session/npm.js +403 -420
- package/lib/session/ops.js +5 -13
- package/lib/session/pointer.js +16 -14
- package/lib/session/progress.js +39 -30
- package/lib/session/review.js +1 -3
- package/lib/session/tasks.js +276 -235
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -132
- package/lib/session/view.js +47 -81
- package/lib/session/watch.js +196 -212
- package/lib/session.js +81 -161
- package/lib/update.js +44 -111
- package/lib/utilities.js +46 -88
- package/lib/wrap.js +29 -55
- package/package.json +1 -1
package/lib/deps-diff.js
CHANGED
|
@@ -13,25 +13,29 @@ const { MANIFEST, LOCKFILE, SECTION_KEYS, depFileMeta, relOf } = manifest;
|
|
|
13
13
|
const { listFilePath, readSections, emptySectionMaps } = manifest;
|
|
14
14
|
const { lockRootSections, lockEntries, lockPackageCount } = manifest;
|
|
15
15
|
|
|
16
|
-
const SECTION_ORDER = [...SECTION_KEYS, 'resolved'
|
|
16
|
+
const SECTION_ORDER = [...SECTION_KEYS, 'resolved'];
|
|
17
17
|
const ACTION_ORDER = ['added', 'removed', 'changed', 'vulnerable'];
|
|
18
18
|
const SECTION_NAMES = SECTION_KEYS.join('|');
|
|
19
19
|
const SECTION_OPEN = new RegExp(`^\\s*"(${SECTION_NAMES})"\\s*:\\s*\\{\\s*$`);
|
|
20
20
|
const SECTION_END = /^\s*\},?\s*$/;
|
|
21
21
|
const DEP_ENTRY = /^\s*"([^"]+)"\s*:\s*"([^"]*)"\s*,?\s*$/;
|
|
22
|
+
const DEP_CAPTION = 'Dependencies in package.json & package-lock.json';
|
|
23
|
+
|
|
24
|
+
const changeAction = (hadOld, hasNew) => {
|
|
25
|
+
if (!hadOld) return 'added';
|
|
26
|
+
if (!hasNew) return 'removed';
|
|
27
|
+
return 'changed';
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const sortedUnion = (left, right) => [...new Set([...left, ...right])].sort();
|
|
22
31
|
|
|
23
32
|
const diffMaps = (oldMap, newMap, section) => {
|
|
24
|
-
const keys = [...oldMap.keys(), ...newMap.keys()];
|
|
25
|
-
const names = [...new Set(keys)];
|
|
26
|
-
names.sort();
|
|
27
33
|
const changes = [];
|
|
28
|
-
for (const name of
|
|
29
|
-
const from = oldMap.
|
|
30
|
-
const to = newMap.
|
|
34
|
+
for (const name of sortedUnion(oldMap.keys(), newMap.keys())) {
|
|
35
|
+
const from = oldMap.get(name) ?? '';
|
|
36
|
+
const to = newMap.get(name) ?? '';
|
|
31
37
|
if (from === to) continue;
|
|
32
|
-
|
|
33
|
-
if (!oldMap.has(name)) action = 'added';
|
|
34
|
-
else if (!newMap.has(name)) action = 'removed';
|
|
38
|
+
const action = changeAction(oldMap.has(name), newMap.has(name));
|
|
35
39
|
changes.push({ name, section, action, from, to });
|
|
36
40
|
}
|
|
37
41
|
return changes;
|
|
@@ -40,64 +44,37 @@ const diffMaps = (oldMap, newMap, section) => {
|
|
|
40
44
|
const diffSections = (oldMaps, newMaps) =>
|
|
41
45
|
SECTION_KEYS.flatMap((key) => diffMaps(oldMaps[key], newMaps[key], key));
|
|
42
46
|
|
|
43
|
-
const
|
|
44
|
-
if (value === undefined) return '';
|
|
45
|
-
if (typeof value === 'string') return value;
|
|
46
|
-
return JSON.stringify(value);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const diffOtherFields = (oldPkg, newPkg) => {
|
|
50
|
-
if (!oldPkg && !newPkg) return [];
|
|
47
|
+
const hasFieldChanges = (oldPkg, newPkg) => {
|
|
51
48
|
const oldObj = oldPkg ?? {};
|
|
52
49
|
const newObj = newPkg ?? {};
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
for (const name of names) {
|
|
50
|
+
for (const name of new Set([
|
|
51
|
+
...Object.keys(oldObj),
|
|
52
|
+
...Object.keys(newObj),
|
|
53
|
+
])) {
|
|
58
54
|
if (SECTION_KEYS.includes(name)) continue;
|
|
59
|
-
const prev = oldObj[name];
|
|
60
|
-
|
|
61
|
-
if (JSON.stringify(prev) === JSON.stringify(next)) continue;
|
|
62
|
-
let action = 'changed';
|
|
63
|
-
if (!Object.hasOwn(oldObj, name)) action = 'added';
|
|
64
|
-
else if (!Object.hasOwn(newObj, name)) action = 'removed';
|
|
65
|
-
const from = fieldText(prev);
|
|
66
|
-
const to = fieldText(next);
|
|
67
|
-
const section = 'field';
|
|
68
|
-
changes.push({ name, section, action, from, to });
|
|
55
|
+
const prev = JSON.stringify(oldObj[name]);
|
|
56
|
+
if (prev !== JSON.stringify(newObj[name])) return true;
|
|
69
57
|
}
|
|
70
|
-
return
|
|
58
|
+
return false;
|
|
71
59
|
};
|
|
72
60
|
|
|
73
|
-
const foldedDepNames = (changes) =>
|
|
74
|
-
|
|
75
|
-
for (const change of changes) {
|
|
76
|
-
if (change.section === 'field') continue;
|
|
77
|
-
names.add(change.name);
|
|
78
|
-
}
|
|
79
|
-
return names;
|
|
80
|
-
};
|
|
61
|
+
const foldedDepNames = (changes) =>
|
|
62
|
+
new Set(changes.map((change) => change.name));
|
|
81
63
|
|
|
82
64
|
const lineEntryName = (text) => {
|
|
83
65
|
const entry = DEP_ENTRY.exec(text);
|
|
84
66
|
return entry ? entry[1] : '';
|
|
85
67
|
};
|
|
86
68
|
|
|
87
|
-
const lineInBlock = (item, line) =>
|
|
88
|
-
|
|
89
|
-
if (typeof blockId !== 'number') return true;
|
|
90
|
-
return line.blockId === blockId;
|
|
91
|
-
};
|
|
69
|
+
const lineInBlock = (item, line) =>
|
|
70
|
+
typeof item.blockId !== 'number' || line.blockId === item.blockId;
|
|
92
71
|
|
|
93
72
|
const hunkOnlyFoldedDeps = (item, names) => {
|
|
94
73
|
if (!item.hunk || !names.size) return false;
|
|
95
74
|
let folded = 0;
|
|
96
75
|
for (const line of item.hunk.lines) {
|
|
97
|
-
if (line.type === 'ctx') continue;
|
|
98
|
-
if (
|
|
99
|
-
const name = lineEntryName(line.text);
|
|
100
|
-
if (name && names.has(name)) {
|
|
76
|
+
if (line.type === 'ctx' || !lineInBlock(item, line)) continue;
|
|
77
|
+
if (names.has(lineEntryName(line.text))) {
|
|
101
78
|
folded += 1;
|
|
102
79
|
continue;
|
|
103
80
|
}
|
|
@@ -109,67 +86,33 @@ const hunkOnlyFoldedDeps = (item, names) => {
|
|
|
109
86
|
|
|
110
87
|
const stripFoldedDepItem = (item, names) => {
|
|
111
88
|
if (!item.hunk || !names.size) return item;
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
lines.push(line);
|
|
123
|
-
}
|
|
124
|
-
if (!removed) return item;
|
|
125
|
-
let hasEdit = false;
|
|
126
|
-
for (const line of lines) {
|
|
127
|
-
if (line.type === 'ctx') continue;
|
|
128
|
-
if (!lineInBlock(item, line)) continue;
|
|
129
|
-
hasEdit = true;
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
if (!hasEdit) return null;
|
|
133
|
-
const counts = countSides(lines);
|
|
134
|
-
const hunk = {
|
|
135
|
-
...item.hunk,
|
|
136
|
-
oldCount: counts.oldCount,
|
|
137
|
-
newCount: counts.newCount,
|
|
138
|
-
header: formatHunkHeader(
|
|
139
|
-
item.hunk.oldStart,
|
|
140
|
-
counts.oldCount,
|
|
141
|
-
item.hunk.newStart,
|
|
142
|
-
counts.newCount,
|
|
143
|
-
),
|
|
144
|
-
lines,
|
|
145
|
-
};
|
|
89
|
+
const isFolded = (line) =>
|
|
90
|
+
line.type !== 'ctx' && names.has(lineEntryName(line.text));
|
|
91
|
+
const lines = item.hunk.lines.filter((line) => !isFolded(line));
|
|
92
|
+
if (lines.length === item.hunk.lines.length) return item;
|
|
93
|
+
const isEdit = (line) => line.type !== 'ctx' && lineInBlock(item, line);
|
|
94
|
+
if (!lines.some(isEdit)) return null;
|
|
95
|
+
const { oldStart, newStart } = item.hunk;
|
|
96
|
+
const { oldCount, newCount } = countSides(lines);
|
|
97
|
+
const header = formatHunkHeader(oldStart, oldCount, newStart, newCount);
|
|
98
|
+
const hunk = { ...item.hunk, oldCount, newCount, header, lines };
|
|
146
99
|
const patchAdd = formatPatch(item.file, hunk, item.blockId, 'old');
|
|
147
100
|
const patchRevert = formatPatch(item.file, hunk, item.blockId, 'new');
|
|
148
101
|
return { ...item, hunk, patchAdd, patchRevert };
|
|
149
102
|
};
|
|
150
103
|
|
|
151
|
-
const
|
|
152
|
-
if (!
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
let nextSection = section;
|
|
161
|
-
if (open) nextSection = open[1];
|
|
162
|
-
else if (section && SECTION_END.test(line.text)) nextSection = '';
|
|
163
|
-
const inBlock = !scoped || line.blockId === blockId;
|
|
164
|
-
if (inBlock && line.type !== 'ctx') {
|
|
165
|
-
const inDep = !!(open || section);
|
|
166
|
-
if (!inDep) return false;
|
|
167
|
-
const isEntry = DEP_ENTRY.test(line.text);
|
|
168
|
-
const isClose = SECTION_END.test(line.text);
|
|
169
|
-
if (!open && !isEntry && !isClose) return false;
|
|
170
|
-
}
|
|
171
|
-
section = nextSection;
|
|
104
|
+
const isDepOnlyItem = (item) => {
|
|
105
|
+
if (!item.hunk) return false;
|
|
106
|
+
let section = '';
|
|
107
|
+
for (const line of item.hunk.lines) {
|
|
108
|
+
const open = SECTION_OPEN.exec(line.text);
|
|
109
|
+
const close = SECTION_END.test(line.text);
|
|
110
|
+
if (line.type !== 'ctx' && lineInBlock(item, line) && !open) {
|
|
111
|
+
if (!section) return false;
|
|
112
|
+
if (!close && !DEP_ENTRY.test(line.text)) return false;
|
|
172
113
|
}
|
|
114
|
+
if (open) section = open[1];
|
|
115
|
+
else if (close) section = '';
|
|
173
116
|
}
|
|
174
117
|
return true;
|
|
175
118
|
};
|
|
@@ -190,56 +133,38 @@ const mapsFromHunks = (items) => {
|
|
|
190
133
|
section = '';
|
|
191
134
|
continue;
|
|
192
135
|
}
|
|
193
|
-
|
|
194
|
-
const entry = DEP_ENTRY.exec(line.text);
|
|
136
|
+
const entry = section ? DEP_ENTRY.exec(line.text) : null;
|
|
195
137
|
if (!entry) continue;
|
|
196
138
|
const name = entry[1];
|
|
197
139
|
const version = entry[2];
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (line.type === 'add' || line.type === 'ctx') {
|
|
202
|
-
newMaps[section].set(name, version);
|
|
203
|
-
}
|
|
140
|
+
const isCtx = line.type === 'ctx';
|
|
141
|
+
if (isCtx || line.type === 'del') oldMaps[section].set(name, version);
|
|
142
|
+
if (isCtx || line.type === 'add') newMaps[section].set(name, version);
|
|
204
143
|
}
|
|
205
144
|
}
|
|
206
145
|
return { oldMaps, newMaps };
|
|
207
146
|
};
|
|
208
147
|
|
|
209
|
-
const directNames = (oldMaps, newMaps) => {
|
|
210
|
-
const names = new Set();
|
|
211
|
-
for (const key of SECTION_KEYS) {
|
|
212
|
-
for (const name of oldMaps[key].keys()) names.add(name);
|
|
213
|
-
for (const name of newMaps[key].keys()) names.add(name);
|
|
214
|
-
}
|
|
215
|
-
return names;
|
|
216
|
-
};
|
|
217
|
-
|
|
218
148
|
const mergeResolved = (changes, oldResolved, newResolved, oldMaps, newMaps) => {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
names.sort();
|
|
231
|
-
for (const name of names) {
|
|
149
|
+
const merged = changes.map((change) => ({
|
|
150
|
+
...change,
|
|
151
|
+
resolvedFrom: oldResolved.get(change.name) ?? '',
|
|
152
|
+
resolvedTo: newResolved.get(change.name) ?? '',
|
|
153
|
+
}));
|
|
154
|
+
const seen = new Set(changes.map((change) => change.name));
|
|
155
|
+
const direct = SECTION_KEYS.flatMap((key) => [
|
|
156
|
+
...oldMaps[key].keys(),
|
|
157
|
+
...newMaps[key].keys(),
|
|
158
|
+
]);
|
|
159
|
+
for (const name of [...new Set(direct)].sort()) {
|
|
232
160
|
if (seen.has(name)) continue;
|
|
233
161
|
const from = oldResolved.get(name) ?? '';
|
|
234
162
|
const to = newResolved.get(name) ?? '';
|
|
235
163
|
if (from === to) continue;
|
|
236
|
-
|
|
237
|
-
let action = 'changed';
|
|
238
|
-
if (!from) action = 'added';
|
|
239
|
-
else if (!to) action = 'removed';
|
|
240
|
-
const section = 'resolved';
|
|
164
|
+
const action = changeAction(from, to);
|
|
241
165
|
const resolvedFrom = from;
|
|
242
166
|
const resolvedTo = to;
|
|
167
|
+
const section = 'resolved';
|
|
243
168
|
merged.push({ name, section, action, from, to, resolvedFrom, resolvedTo });
|
|
244
169
|
}
|
|
245
170
|
return merged;
|
|
@@ -255,128 +180,81 @@ const compareChangeOrder = (left, right) => {
|
|
|
255
180
|
return left.name.localeCompare(right.name);
|
|
256
181
|
};
|
|
257
182
|
|
|
258
|
-
const
|
|
183
|
+
const kindOf = (rel) => depFileMeta(rel)?.kind;
|
|
259
184
|
|
|
260
185
|
const kindItems = (items, kind) =>
|
|
261
|
-
items.filter((item) =>
|
|
262
|
-
|
|
263
|
-
const readGroupSnapshot = (group, readSides) => {
|
|
264
|
-
const origin = group.origin;
|
|
265
|
-
const dir = group.dir;
|
|
266
|
-
const groupItems = group.items;
|
|
267
|
-
const manifestItems = kindItems(groupItems, 'manifest');
|
|
268
|
-
const lockItems = kindItems(groupItems, 'lockfile');
|
|
269
|
-
const manifestRel = relOf(dir, MANIFEST);
|
|
270
|
-
const lockRel = relOf(dir, LOCKFILE);
|
|
271
|
-
const manifestSides = readSides(origin, manifestRel);
|
|
272
|
-
const lockSides = readSides(origin, lockRel);
|
|
273
|
-
const oldPkg = jsonParse(manifestSides.oldText ?? '');
|
|
274
|
-
const newPkg = jsonParse(manifestSides.newText ?? '');
|
|
275
|
-
const oldLock = jsonParse(lockSides.oldText ?? '');
|
|
276
|
-
const newLock = jsonParse(lockSides.newText ?? '');
|
|
277
|
-
const parsedManifest = !!(oldPkg || newPkg);
|
|
278
|
-
return {
|
|
279
|
-
origin,
|
|
280
|
-
dir,
|
|
281
|
-
groupItems,
|
|
282
|
-
manifestItems,
|
|
283
|
-
lockItems,
|
|
284
|
-
oldPkg,
|
|
285
|
-
newPkg,
|
|
286
|
-
oldLock,
|
|
287
|
-
newLock,
|
|
288
|
-
parsedManifest,
|
|
289
|
-
};
|
|
290
|
-
};
|
|
186
|
+
items.filter((item) => kindOf(itemPath(item)) === kind);
|
|
291
187
|
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
if (snapshot.manifestItems.length) {
|
|
299
|
-
return mapsFromHunks(snapshot.manifestItems);
|
|
300
|
-
}
|
|
301
|
-
const oldMaps = lockRootSections(snapshot.oldLock);
|
|
302
|
-
const newMaps = lockRootSections(snapshot.newLock);
|
|
303
|
-
return { oldMaps, newMaps };
|
|
188
|
+
const readJsonSides = (readSides, origin, rel) => {
|
|
189
|
+
const sides = readSides(origin, rel);
|
|
190
|
+
const before = jsonParse(sides.oldText ?? '');
|
|
191
|
+
const after = jsonParse(sides.newText ?? '');
|
|
192
|
+
return { before, after };
|
|
304
193
|
};
|
|
305
194
|
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
let otherChanges = [];
|
|
313
|
-
if (parsed) {
|
|
314
|
-
otherChanges = diffOtherFields(snapshot.oldPkg, snapshot.newPkg);
|
|
195
|
+
const sectionMapsOf = (pkg, lock, manifestItems) => {
|
|
196
|
+
if (pkg.before || pkg.after) {
|
|
197
|
+
return {
|
|
198
|
+
oldMaps: readSections(pkg.before),
|
|
199
|
+
newMaps: readSections(pkg.after),
|
|
200
|
+
};
|
|
315
201
|
}
|
|
316
|
-
|
|
317
|
-
const
|
|
318
|
-
|
|
319
|
-
manifestChanges,
|
|
320
|
-
oldResolved,
|
|
321
|
-
newResolved,
|
|
322
|
-
oldMaps,
|
|
323
|
-
newMaps,
|
|
324
|
-
);
|
|
325
|
-
const oldCount = lockPackageCount(snapshot.oldLock);
|
|
326
|
-
const newCount = lockPackageCount(snapshot.newLock);
|
|
327
|
-
return { changes, otherChanges, oldCount, newCount };
|
|
202
|
+
if (manifestItems.length) return mapsFromHunks(manifestItems);
|
|
203
|
+
const oldMaps = lockRootSections(lock.before);
|
|
204
|
+
return { oldMaps, newMaps: lockRootSections(lock.after) };
|
|
328
205
|
};
|
|
329
206
|
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
if (!snapshot.manifestItems.length) {
|
|
334
|
-
const foldManifest = false;
|
|
335
|
-
const takeAllManifest = false;
|
|
336
|
-
return { foldLock, foldManifest, takeAllManifest };
|
|
207
|
+
const manifestPolicy = (manifestItems, pkg, hasChanges) => {
|
|
208
|
+
if (!manifestItems.length) {
|
|
209
|
+
return { foldManifest: false, takeAllManifest: false };
|
|
337
210
|
}
|
|
338
|
-
if (
|
|
339
|
-
const
|
|
340
|
-
|
|
341
|
-
|
|
211
|
+
if (pkg.before || pkg.after) {
|
|
212
|
+
const onlyDeps = !hasFieldChanges(pkg.before, pkg.after);
|
|
213
|
+
return {
|
|
214
|
+
foldManifest: hasChanges,
|
|
215
|
+
takeAllManifest: hasChanges && onlyDeps,
|
|
216
|
+
};
|
|
342
217
|
}
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
return { foldLock, foldManifest, takeAllManifest };
|
|
218
|
+
const fold = hasChanges && manifestItems.every(isDepOnlyItem);
|
|
219
|
+
return { foldManifest: fold, takeAllManifest: fold };
|
|
346
220
|
};
|
|
347
221
|
|
|
348
222
|
const summarizeGroup = (group, readSides) => {
|
|
349
|
-
const
|
|
350
|
-
const
|
|
351
|
-
const
|
|
223
|
+
const { origin, dir } = group;
|
|
224
|
+
const manifestItems = kindItems(group.items, 'manifest');
|
|
225
|
+
const lockItems = kindItems(group.items, 'lockfile');
|
|
226
|
+
const pkg = readJsonSides(readSides, origin, relOf(dir, MANIFEST));
|
|
227
|
+
const lock = readJsonSides(readSides, origin, relOf(dir, LOCKFILE));
|
|
228
|
+
const { oldMaps, newMaps } = sectionMapsOf(pkg, lock, manifestItems);
|
|
229
|
+
const changes = mergeResolved(
|
|
230
|
+
diffSections(oldMaps, newMaps),
|
|
231
|
+
lockEntries(lock.before),
|
|
232
|
+
lockEntries(lock.after),
|
|
233
|
+
oldMaps,
|
|
234
|
+
newMaps,
|
|
235
|
+
);
|
|
352
236
|
return {
|
|
353
|
-
changes
|
|
354
|
-
oldCount:
|
|
355
|
-
newCount:
|
|
356
|
-
foldLock:
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
pkg: snapshot.newPkg || snapshot.oldPkg,
|
|
237
|
+
changes,
|
|
238
|
+
oldCount: lockPackageCount(lock.before),
|
|
239
|
+
newCount: lockPackageCount(lock.after),
|
|
240
|
+
foldLock: lockItems.length > 0,
|
|
241
|
+
...manifestPolicy(manifestItems, pkg, changes.length > 0),
|
|
242
|
+
manifestItems,
|
|
243
|
+
lockItems,
|
|
244
|
+
pkg: pkg.after || pkg.before,
|
|
362
245
|
};
|
|
363
246
|
};
|
|
364
247
|
|
|
365
248
|
const collectGroups = (items) => {
|
|
366
249
|
const groups = new Map();
|
|
367
250
|
for (const item of items) {
|
|
368
|
-
const
|
|
369
|
-
const meta = depFileMeta(rel);
|
|
251
|
+
const meta = depFileMeta(itemPath(item));
|
|
370
252
|
if (!meta) continue;
|
|
371
253
|
const key = `${item.origin}\0${meta.dir}`;
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
const dir = meta.dir;
|
|
375
|
-
const origin = item.origin;
|
|
376
|
-
group = { dir, origin, items: [] };
|
|
377
|
-
groups.set(key, group);
|
|
254
|
+
if (!groups.has(key)) {
|
|
255
|
+
groups.set(key, { dir: meta.dir, origin: item.origin, items: [] });
|
|
378
256
|
}
|
|
379
|
-
|
|
257
|
+
groups.get(key).items.push(item);
|
|
380
258
|
}
|
|
381
259
|
return groups;
|
|
382
260
|
};
|
|
@@ -385,17 +263,11 @@ const takeManifestItems = (summary) => {
|
|
|
385
263
|
if (!summary.foldManifest) return [];
|
|
386
264
|
if (summary.takeAllManifest) return [...summary.manifestItems];
|
|
387
265
|
const names = foldedDepNames(summary.changes);
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
taken.push(item);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
return taken;
|
|
266
|
+
return summary.manifestItems.filter(
|
|
267
|
+
(item) => isDepOnlyItem(item) || hunkOnlyFoldedDeps(item, names),
|
|
268
|
+
);
|
|
395
269
|
};
|
|
396
270
|
|
|
397
|
-
const DEP_CAPTION = 'Dependencies in package.json & package-lock.json';
|
|
398
|
-
|
|
399
271
|
const changeMarks = (change) => {
|
|
400
272
|
const marks = [];
|
|
401
273
|
if (change.unused) marks.push('unused');
|
|
@@ -405,191 +277,113 @@ const changeMarks = (change) => {
|
|
|
405
277
|
};
|
|
406
278
|
|
|
407
279
|
const changeSources = (change) => {
|
|
280
|
+
if (change.unused) return ['npm uninstall'];
|
|
408
281
|
const sources = [];
|
|
409
|
-
if (change.unused) {
|
|
410
|
-
sources.push('npm uninstall');
|
|
411
|
-
return sources;
|
|
412
|
-
}
|
|
413
282
|
if (change.propose && change.outdated) sources.push('npm outdated');
|
|
414
283
|
if (change.audit) sources.push('npm audit');
|
|
415
284
|
return sources;
|
|
416
285
|
};
|
|
417
286
|
|
|
418
|
-
const sectionTitle = (
|
|
287
|
+
const sectionTitle = (change, marks, sources) => {
|
|
288
|
+
const { section, action } = change;
|
|
419
289
|
const title = action === 'changed' ? 'version changed' : action;
|
|
420
290
|
const kind = section.replace(/ies$/, 'y');
|
|
421
291
|
let label = `${kind} ${title}`;
|
|
422
|
-
if (section === 'field') label = `package.json ${title}`;
|
|
423
292
|
if (section === 'resolved' && action === 'vulnerable') {
|
|
424
293
|
label = 'lockfile vulnerable';
|
|
425
294
|
} else if (section === 'resolved') {
|
|
426
295
|
label = `lockfile resolved ${title}`;
|
|
427
296
|
}
|
|
428
|
-
if (marks.includes('unused')) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
for (const mark of marks) {
|
|
432
|
-
if (mark !== 'unused') extra.push(mark);
|
|
433
|
-
}
|
|
434
|
-
if (extra.length) label = `${label}, ${extra.join(', ')}`;
|
|
435
|
-
} else if (marks.length) {
|
|
436
|
-
label = `${label}, ${marks.join(', ')}`;
|
|
437
|
-
}
|
|
297
|
+
if (marks.includes('unused')) label = `${kind} unused`;
|
|
298
|
+
const extra = marks.filter((mark) => mark !== 'unused');
|
|
299
|
+
if (extra.length) label = `${label}, ${extra.join(', ')}`;
|
|
438
300
|
if (!sources.length) return label;
|
|
439
301
|
return `${sources.join(', ')}: ${label}`;
|
|
440
302
|
};
|
|
441
303
|
|
|
442
|
-
const
|
|
443
|
-
const version = side === 'old' ? change.from : change.to;
|
|
444
|
-
return `"${change.name}": "${version}"`;
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
const ctxLine = (text) => ({ type: 'ctx', text });
|
|
448
|
-
const warnLine = (text) => ({ type: 'warn', text });
|
|
449
|
-
const noteLine = (text) => ({ type: 'note', text });
|
|
450
|
-
const noteSepLine = () => ({ type: 'noteSep', text: '' });
|
|
304
|
+
const row = (type, text) => ({ type, text });
|
|
451
305
|
|
|
452
|
-
const
|
|
453
|
-
const
|
|
454
|
-
return
|
|
306
|
+
const entryRow = (type, change, side) => {
|
|
307
|
+
const version = side === 'old' ? change.from : change.to;
|
|
308
|
+
return row(type, `"${change.name}": "${version}"`);
|
|
455
309
|
};
|
|
456
310
|
|
|
457
|
-
const
|
|
311
|
+
const changeLines = (change) => {
|
|
458
312
|
if (change.unused) {
|
|
459
313
|
const side = change.action === 'added' ? 'new' : 'old';
|
|
460
|
-
|
|
461
|
-
return;
|
|
462
|
-
}
|
|
463
|
-
if (change.action === 'added') {
|
|
464
|
-
rows.push(changeLine('add', change, 'new'));
|
|
465
|
-
return;
|
|
466
|
-
}
|
|
467
|
-
if (change.action === 'removed') {
|
|
468
|
-
rows.push(changeLine('del', change, 'old'));
|
|
469
|
-
return;
|
|
314
|
+
return [entryRow('del', change, side)];
|
|
470
315
|
}
|
|
471
|
-
if (change.
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
rows.push(changeLine('del', change, 'old'));
|
|
476
|
-
rows.push(changeLine('add', change, 'new'));
|
|
316
|
+
if (change.action === 'added') return [entryRow('add', change, 'new')];
|
|
317
|
+
if (change.action === 'removed') return [entryRow('del', change, 'old')];
|
|
318
|
+
if (change.from === change.to) return [entryRow('ctx', change, 'old')];
|
|
319
|
+
return [entryRow('del', change, 'old'), entryRow('add', change, 'new')];
|
|
477
320
|
};
|
|
478
321
|
|
|
479
|
-
const
|
|
480
|
-
if (oldCount === newCount) return '';
|
|
481
|
-
return `lockfile packages ${oldCount} → ${newCount}`;
|
|
482
|
-
};
|
|
483
|
-
|
|
484
|
-
const auditNoteTexts = (audit) => {
|
|
322
|
+
const auditRows = (audit) => {
|
|
485
323
|
if (!audit) return [];
|
|
486
|
-
const
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
324
|
+
const titles = Array.isArray(audit.titles) ? audit.titles : [audit.title];
|
|
325
|
+
const notes = [...new Set(titles.filter(Boolean))];
|
|
326
|
+
const prefix = `npm audit ${audit.severity}`;
|
|
327
|
+
if (!notes.length) return [row('ctx', ''), row('note', prefix)];
|
|
328
|
+
const rows = [row('ctx', '')];
|
|
329
|
+
for (const note of notes) {
|
|
330
|
+
if (rows.length > 1) rows.push(row('noteSep', ''));
|
|
331
|
+
rows.push(row('note', `${prefix} ${note}`));
|
|
493
332
|
}
|
|
494
|
-
|
|
495
|
-
if (!notes.length) return [`npm audit ${severity}`];
|
|
496
|
-
return notes.map((note) => `npm audit ${severity} ${note}`);
|
|
333
|
+
return rows;
|
|
497
334
|
};
|
|
498
335
|
|
|
499
|
-
const
|
|
500
|
-
const
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
336
|
+
const changeRows = (change) => {
|
|
337
|
+
const sources = changeSources(change);
|
|
338
|
+
const title = sectionTitle(change, changeMarks(change), sources);
|
|
339
|
+
const audited = sources.includes('npm audit');
|
|
340
|
+
return [
|
|
341
|
+
row('ctx', DEP_CAPTION),
|
|
342
|
+
row('ctx', ''),
|
|
343
|
+
row(audited ? 'warn' : 'ctx', title),
|
|
344
|
+
row('ctx', ''),
|
|
345
|
+
...changeLines(change),
|
|
346
|
+
...auditRows(change.audit),
|
|
347
|
+
];
|
|
509
348
|
};
|
|
510
349
|
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
const title = sectionTitle(change.section, change.action, marks, sources);
|
|
517
|
-
const audited = sources.includes('npm audit');
|
|
518
|
-
rows.push(audited ? warnLine(title) : ctxLine(title));
|
|
519
|
-
rows.push(ctxLine(''));
|
|
520
|
-
pushChangeLines(rows, change);
|
|
521
|
-
pushAuditNotes(rows, change.audit);
|
|
522
|
-
}
|
|
523
|
-
const countLine = lockCountText(oldCount, newCount);
|
|
524
|
-
if (countLine) {
|
|
525
|
-
if (rows.at(-1).text !== '') rows.push(ctxLine(''));
|
|
526
|
-
rows.push(ctxLine(countLine));
|
|
527
|
-
}
|
|
528
|
-
return rows;
|
|
529
|
-
};
|
|
530
|
-
|
|
531
|
-
const toHunkLine = (row, blockId) => ({
|
|
532
|
-
type: row.type,
|
|
533
|
-
text: row.text,
|
|
534
|
-
noNl: false,
|
|
535
|
-
blockId: isCtxType(row.type) ? null : blockId,
|
|
536
|
-
});
|
|
350
|
+
const countRows = (oldCount, newCount) => [
|
|
351
|
+
row('ctx', DEP_CAPTION),
|
|
352
|
+
row('ctx', ''),
|
|
353
|
+
row('ctx', `lockfile packages ${oldCount} → ${newCount}`),
|
|
354
|
+
];
|
|
537
355
|
|
|
538
356
|
const makeHunk = (rows, blockId) => {
|
|
539
|
-
const lines = rows.map((
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
357
|
+
const lines = rows.map((entry) => ({
|
|
358
|
+
type: entry.type,
|
|
359
|
+
text: entry.text,
|
|
360
|
+
noNl: false,
|
|
361
|
+
blockId: isCtxType(entry.type) ? null : blockId,
|
|
362
|
+
}));
|
|
363
|
+
const { oldCount, newCount } = countSides(lines);
|
|
543
364
|
const header = `@@ -1,${oldCount} +1,${newCount} @@`;
|
|
544
365
|
return { oldStart: 1, oldCount, newStart: 1, newCount, header, lines };
|
|
545
366
|
};
|
|
546
367
|
|
|
547
|
-
const
|
|
548
|
-
const
|
|
549
|
-
const
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
if (!rel || seen.has(rel)) continue;
|
|
553
|
-
seen.add(rel);
|
|
554
|
-
files.push(rel);
|
|
555
|
-
}
|
|
556
|
-
return files;
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
const filesForChange = (change, files) => {
|
|
560
|
-
const manifest = [];
|
|
561
|
-
const lockfile = [];
|
|
562
|
-
for (const rel of files) {
|
|
563
|
-
const meta = depFileMeta(rel);
|
|
564
|
-
if (!meta) continue;
|
|
565
|
-
if (meta.kind === 'manifest') manifest.push(rel);
|
|
566
|
-
else lockfile.push(rel);
|
|
567
|
-
}
|
|
568
|
-
if (change.section === 'resolved') return lockfile.length ? lockfile : files;
|
|
569
|
-
if (change.section === 'field' || change.propose) {
|
|
570
|
-
return manifest.length ? manifest : files;
|
|
368
|
+
const filesForChange = (change, rels) => {
|
|
369
|
+
const manifests = rels.filter((rel) => kindOf(rel) === 'manifest');
|
|
370
|
+
const lockfiles = rels.filter((rel) => kindOf(rel) === 'lockfile');
|
|
371
|
+
if (change.section === 'resolved') {
|
|
372
|
+
return lockfiles.length ? lockfiles : rels;
|
|
571
373
|
}
|
|
572
|
-
return
|
|
573
|
-
|
|
574
|
-
|
|
374
|
+
if (change.propose) return manifests.length ? manifests : rels;
|
|
375
|
+
const both = [...manifests, ...lockfiles];
|
|
376
|
+
return both.length ? both : rels;
|
|
575
377
|
};
|
|
576
378
|
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
};
|
|
585
|
-
|
|
586
|
-
const makeDepItem = (origin, fileItems, hidden, change, extra, blockId) => {
|
|
587
|
-
const allFiles = uniquePaths(fileItems);
|
|
588
|
-
const files = change ? filesForChange(change, allFiles) : allFiles;
|
|
589
|
-
const primary = pickPrimary(fileItems);
|
|
379
|
+
const makeDepItem = (origin, fileItems, hidden, change, rows, blockId) => {
|
|
380
|
+
const paths = new Set(fileItems.map((item) => itemPath(item)));
|
|
381
|
+
const rels = [...paths].filter(Boolean);
|
|
382
|
+
const files = change ? filesForChange(change, rels) : rels;
|
|
383
|
+
const primary =
|
|
384
|
+
fileItems.find((item) => kindOf(itemPath(item)) === 'manifest') ??
|
|
385
|
+
fileItems[0];
|
|
590
386
|
const rel = listFilePath(itemPath(primary));
|
|
591
|
-
const counts = change ? {} : extra;
|
|
592
|
-
const rows = buildRows(change, counts.oldCount ?? 0, counts.newCount ?? 0);
|
|
593
387
|
const hunk = makeHunk(rows, blockId);
|
|
594
388
|
return {
|
|
595
389
|
origin,
|
|
@@ -612,16 +406,17 @@ const makeDepItem = (origin, fileItems, hidden, change, extra, blockId) => {
|
|
|
612
406
|
};
|
|
613
407
|
|
|
614
408
|
const makeDepItems = (origin, fileItems, hidden, summary) => {
|
|
615
|
-
const ordered =
|
|
616
|
-
if (
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
);
|
|
409
|
+
const ordered = [...summary.changes].sort(compareChangeOrder);
|
|
410
|
+
if (ordered.length) {
|
|
411
|
+
return ordered.map((change, index) =>
|
|
412
|
+
makeDepItem(origin, fileItems, hidden, change, changeRows(change), index),
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const oldCount = summary.oldCount ?? 0;
|
|
416
|
+
const newCount = summary.newCount ?? 0;
|
|
417
|
+
if (oldCount === newCount) return [];
|
|
418
|
+
const rows = countRows(oldCount, newCount);
|
|
419
|
+
return [makeDepItem(origin, fileItems, hidden, null, rows, 0)];
|
|
625
420
|
};
|
|
626
421
|
|
|
627
422
|
const stubFileItem = (rel, origin) => ({
|