reslop 0.1.7 → 0.1.9
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 +32 -1
- package/README.md +26 -38
- package/bin/reslop.js +3 -1
- package/lib/ansi.js +2 -0
- package/lib/capabilities.js +109 -0
- package/lib/cli.js +18 -14
- package/lib/deps/changes.js +422 -0
- package/lib/deps/items.js +134 -0
- package/lib/deps/manifest.js +152 -0
- package/lib/deps/merge.js +120 -0
- package/lib/deps/proposals.js +164 -0
- package/lib/deps/reports.js +180 -0
- package/lib/deps/rows.js +176 -0
- package/lib/deps/scan.js +138 -0
- package/lib/deps/usage.js +68 -0
- package/lib/deps/versions.js +119 -0
- package/lib/deps.js +117 -1345
- package/lib/diff/align.js +226 -0
- package/lib/diff/display.js +101 -0
- package/lib/diff/edit.js +47 -0
- package/lib/diff/inline.js +300 -0
- package/lib/diff/items.js +101 -0
- package/lib/diff/parse.js +156 -0
- package/lib/diff/patch.js +103 -0
- package/lib/diff.js +13 -932
- package/lib/editor.js +37 -0
- package/lib/files.js +118 -4
- package/lib/git/branches.js +383 -0
- package/lib/git/changes.js +164 -0
- package/lib/git/dependencies.js +489 -0
- package/lib/git/proc.js +162 -0
- package/lib/git/read.js +253 -0
- package/lib/git/run.js +100 -0
- package/lib/git.js +88 -1193
- package/lib/github.js +92 -222
- package/lib/gitlab.js +72 -216
- package/lib/highlight/bash.js +5 -9
- package/lib/highlight/core.js +1 -1
- package/lib/highlight/css.js +1 -3
- package/lib/highlight/csv.js +1 -3
- package/lib/highlight/dart.js +6 -20
- package/lib/highlight/dot.js +13 -13
- package/lib/highlight/html.js +1 -5
- package/lib/highlight/js.js +151 -115
- package/lib/highlight/json.js +1 -3
- package/lib/highlight/log.js +1 -3
- package/lib/highlight/txt.js +5 -10
- package/lib/highlight.js +4 -4
- package/lib/keys.js +67 -168
- package/lib/npm/commands.js +100 -0
- package/lib/remote/changes.js +37 -0
- package/lib/remote/headers.js +36 -0
- package/lib/remote/http.js +122 -0
- package/lib/remote/notes.js +94 -0
- package/lib/render/branches.js +240 -0
- package/lib/render/chrome.js +595 -0
- package/lib/render/commits.js +138 -0
- package/lib/render/diff.js +307 -0
- package/lib/render/files.js +186 -0
- package/lib/render/frame.js +202 -0
- package/lib/render/notes.js +201 -0
- package/lib/render/present.js +25 -0
- package/lib/render/primitives.js +200 -0
- package/lib/render.js +13 -1688
- package/lib/review/format.js +434 -0
- package/lib/review/storage.js +150 -0
- package/lib/review/store.js +178 -0
- package/lib/review/templates.js +49 -0
- package/lib/review.js +14 -747
- package/lib/select.js +3 -5
- package/lib/session/accessors.js +25 -0
- package/lib/session/actions.js +316 -0
- package/lib/session/branches.js +302 -0
- package/lib/session/change-actions.js +323 -0
- package/lib/session/commit.js +286 -0
- package/lib/session/compose.js +540 -0
- package/lib/session/dispatch.js +115 -0
- package/lib/session/files-pane.js +243 -0
- package/lib/session/items.js +282 -0
- package/lib/session/lifecycle.js +516 -0
- package/lib/session/load.js +85 -0
- package/lib/session/navigation.js +94 -0
- package/lib/session/ops.js +110 -0
- package/lib/session/origins.js +7 -0
- package/lib/session/pointer.js +161 -0
- package/lib/session/progress.js +29 -0
- package/lib/session/review.js +88 -0
- package/lib/session/templates.js +64 -0
- package/lib/session/terminal.js +169 -0
- package/lib/session/todos.js +269 -0
- package/lib/session/update.js +125 -0
- package/lib/session/view.js +131 -0
- package/lib/session/watch.js +219 -0
- package/lib/session.js +598 -2350
- package/lib/source.js +11 -11
- package/lib/sys.js +25 -4
- package/lib/wrap.js +4 -2
- package/package.json +2 -2
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { itemPath } = require('../files.js');
|
|
4
|
+
const {
|
|
5
|
+
countSides,
|
|
6
|
+
formatHunkHeader,
|
|
7
|
+
formatPatch,
|
|
8
|
+
} = require('../diff/patch.js');
|
|
9
|
+
|
|
10
|
+
const manifest = require('./manifest.js');
|
|
11
|
+
const { MANIFEST, LOCKFILE, SECTION_KEYS, depFileMeta, relOf } = manifest;
|
|
12
|
+
const { parseJson, readSections, emptySectionMaps } = manifest;
|
|
13
|
+
const { lockRootSections, lockEntries, lockPackageCount } = manifest;
|
|
14
|
+
|
|
15
|
+
const SECTION_ORDER = [...SECTION_KEYS, 'resolved', 'field'];
|
|
16
|
+
const ACTION_ORDER = ['added', 'removed', 'changed', 'vulnerable'];
|
|
17
|
+
const SECTION_NAMES = SECTION_KEYS.join('|');
|
|
18
|
+
const SECTION_OPEN = new RegExp(`^\\s*"(${SECTION_NAMES})"\\s*:\\s*\\{\\s*$`);
|
|
19
|
+
const SECTION_END = /^\s*\},?\s*$/;
|
|
20
|
+
const DEP_ENTRY = /^\s*"([^"]+)"\s*:\s*"([^"]*)"\s*,?\s*$/;
|
|
21
|
+
|
|
22
|
+
const diffMaps = (oldMap, newMap, section) => {
|
|
23
|
+
const keys = [...oldMap.keys(), ...newMap.keys()];
|
|
24
|
+
const names = [...new Set(keys)];
|
|
25
|
+
names.sort();
|
|
26
|
+
const changes = [];
|
|
27
|
+
for (const name of names) {
|
|
28
|
+
const from = oldMap.has(name) ? oldMap.get(name) : '';
|
|
29
|
+
const to = newMap.has(name) ? newMap.get(name) : '';
|
|
30
|
+
if (from === to) continue;
|
|
31
|
+
let action = 'changed';
|
|
32
|
+
if (!oldMap.has(name)) action = 'added';
|
|
33
|
+
else if (!newMap.has(name)) action = 'removed';
|
|
34
|
+
changes.push({ name, section, action, from, to });
|
|
35
|
+
}
|
|
36
|
+
return changes;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const diffSections = (oldMaps, newMaps) => {
|
|
40
|
+
const changes = [];
|
|
41
|
+
for (const key of SECTION_KEYS) {
|
|
42
|
+
const found = diffMaps(oldMaps[key], newMaps[key], key);
|
|
43
|
+
for (const change of found) changes.push(change);
|
|
44
|
+
}
|
|
45
|
+
return changes;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const fieldText = (value) => {
|
|
49
|
+
if (value === undefined) return '';
|
|
50
|
+
if (typeof value === 'string') return value;
|
|
51
|
+
return JSON.stringify(value);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const sameJson = (left, right) =>
|
|
55
|
+
JSON.stringify(left) === JSON.stringify(right);
|
|
56
|
+
|
|
57
|
+
const diffOtherFields = (oldPkg, newPkg) => {
|
|
58
|
+
if (!oldPkg && !newPkg) return [];
|
|
59
|
+
const oldObj = oldPkg ?? {};
|
|
60
|
+
const newObj = newPkg ?? {};
|
|
61
|
+
const keys = [...Object.keys(oldObj), ...Object.keys(newObj)];
|
|
62
|
+
const names = [...new Set(keys)];
|
|
63
|
+
names.sort();
|
|
64
|
+
const changes = [];
|
|
65
|
+
for (const name of names) {
|
|
66
|
+
if (SECTION_KEYS.includes(name)) continue;
|
|
67
|
+
const prev = oldObj[name];
|
|
68
|
+
const next = newObj[name];
|
|
69
|
+
if (sameJson(prev, next)) continue;
|
|
70
|
+
let action = 'changed';
|
|
71
|
+
if (!Object.hasOwn(oldObj, name)) action = 'added';
|
|
72
|
+
else if (!Object.hasOwn(newObj, name)) action = 'removed';
|
|
73
|
+
const from = fieldText(prev);
|
|
74
|
+
const to = fieldText(next);
|
|
75
|
+
const section = 'field';
|
|
76
|
+
changes.push({ name, section, action, from, to });
|
|
77
|
+
}
|
|
78
|
+
return changes;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const foldedDepNames = (changes) => {
|
|
82
|
+
const names = new Set();
|
|
83
|
+
for (const change of changes) {
|
|
84
|
+
if (change.section === 'field') continue;
|
|
85
|
+
names.add(change.name);
|
|
86
|
+
}
|
|
87
|
+
return names;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const lineEntryName = (text) => {
|
|
91
|
+
const entry = DEP_ENTRY.exec(text);
|
|
92
|
+
return entry ? entry[1] : '';
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const lineInBlock = (item, line) => {
|
|
96
|
+
const blockId = item.blockId;
|
|
97
|
+
if (typeof blockId !== 'number') return true;
|
|
98
|
+
return line.blockId === blockId;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const isSectionFence = (text) =>
|
|
102
|
+
SECTION_OPEN.test(text) || SECTION_END.test(text);
|
|
103
|
+
|
|
104
|
+
const hunkOnlyFoldedDeps = (item, names) => {
|
|
105
|
+
if (!item.hunk || !names.size) return false;
|
|
106
|
+
let folded = 0;
|
|
107
|
+
for (const line of item.hunk.lines) {
|
|
108
|
+
if (line.type === 'ctx') continue;
|
|
109
|
+
if (!lineInBlock(item, line)) continue;
|
|
110
|
+
const name = lineEntryName(line.text);
|
|
111
|
+
if (name && names.has(name)) {
|
|
112
|
+
folded += 1;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (isSectionFence(line.text)) continue;
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return folded > 0;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const stripFoldedDepItem = (item, names) => {
|
|
122
|
+
if (!item.hunk || !names.size) return item;
|
|
123
|
+
const lines = [];
|
|
124
|
+
let removed = false;
|
|
125
|
+
for (const line of item.hunk.lines) {
|
|
126
|
+
if (line.type !== 'ctx') {
|
|
127
|
+
const name = lineEntryName(line.text);
|
|
128
|
+
if (name && names.has(name)) {
|
|
129
|
+
removed = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
lines.push(line);
|
|
134
|
+
}
|
|
135
|
+
if (!removed) return item;
|
|
136
|
+
let hasEdit = false;
|
|
137
|
+
for (const line of lines) {
|
|
138
|
+
if (line.type === 'ctx') continue;
|
|
139
|
+
if (!lineInBlock(item, line)) continue;
|
|
140
|
+
hasEdit = true;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
if (!hasEdit) return null;
|
|
144
|
+
const counts = countSides(lines);
|
|
145
|
+
const hunk = {
|
|
146
|
+
...item.hunk,
|
|
147
|
+
oldCount: counts.oldCount,
|
|
148
|
+
newCount: counts.newCount,
|
|
149
|
+
header: formatHunkHeader(
|
|
150
|
+
item.hunk.oldStart,
|
|
151
|
+
counts.oldCount,
|
|
152
|
+
item.hunk.newStart,
|
|
153
|
+
counts.newCount,
|
|
154
|
+
),
|
|
155
|
+
lines,
|
|
156
|
+
};
|
|
157
|
+
const patchAdd = formatPatch(item.file, hunk, item.blockId, 'old');
|
|
158
|
+
const patchRevert = formatPatch(item.file, hunk, item.blockId, 'new');
|
|
159
|
+
return { ...item, hunk, patchAdd, patchRevert };
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const onlyDepEdits = (items) => {
|
|
163
|
+
if (!items.length) return false;
|
|
164
|
+
for (const item of items) {
|
|
165
|
+
if (!item.hunk) return false;
|
|
166
|
+
const blockId = item.blockId;
|
|
167
|
+
const scoped = typeof blockId === 'number';
|
|
168
|
+
let section = '';
|
|
169
|
+
for (const line of item.hunk.lines) {
|
|
170
|
+
const open = SECTION_OPEN.exec(line.text);
|
|
171
|
+
let nextSection = section;
|
|
172
|
+
if (open) nextSection = open[1];
|
|
173
|
+
else if (section && SECTION_END.test(line.text)) nextSection = '';
|
|
174
|
+
const inBlock = !scoped || line.blockId === blockId;
|
|
175
|
+
if (inBlock && line.type !== 'ctx') {
|
|
176
|
+
const inDep = !!(open || section);
|
|
177
|
+
if (!inDep) return false;
|
|
178
|
+
const isEntry = DEP_ENTRY.test(line.text);
|
|
179
|
+
const isClose = SECTION_END.test(line.text);
|
|
180
|
+
if (!open && !isEntry && !isClose) return false;
|
|
181
|
+
}
|
|
182
|
+
section = nextSection;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const mapsFromHunks = (items) => {
|
|
189
|
+
const oldMaps = emptySectionMaps();
|
|
190
|
+
const newMaps = emptySectionMaps();
|
|
191
|
+
let section = '';
|
|
192
|
+
for (const item of items) {
|
|
193
|
+
if (!item.hunk) continue;
|
|
194
|
+
for (const line of item.hunk.lines) {
|
|
195
|
+
const open = SECTION_OPEN.exec(line.text);
|
|
196
|
+
if (open) {
|
|
197
|
+
section = open[1];
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (section && SECTION_END.test(line.text)) {
|
|
201
|
+
section = '';
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (!section) continue;
|
|
205
|
+
const entry = DEP_ENTRY.exec(line.text);
|
|
206
|
+
if (!entry) continue;
|
|
207
|
+
const name = entry[1];
|
|
208
|
+
const version = entry[2];
|
|
209
|
+
if (line.type === 'del' || line.type === 'ctx') {
|
|
210
|
+
oldMaps[section].set(name, version);
|
|
211
|
+
}
|
|
212
|
+
if (line.type === 'add' || line.type === 'ctx') {
|
|
213
|
+
newMaps[section].set(name, version);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { oldMaps, newMaps };
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const directNames = (oldMaps, newMaps) => {
|
|
221
|
+
const names = new Set();
|
|
222
|
+
for (const key of SECTION_KEYS) {
|
|
223
|
+
for (const name of oldMaps[key].keys()) names.add(name);
|
|
224
|
+
for (const name of newMaps[key].keys()) names.add(name);
|
|
225
|
+
}
|
|
226
|
+
return names;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const mergeResolved = (changes, oldResolved, newResolved, oldMaps, newMaps) => {
|
|
230
|
+
const seen = new Set();
|
|
231
|
+
const merged = [];
|
|
232
|
+
for (const change of changes) {
|
|
233
|
+
seen.add(change.name);
|
|
234
|
+
const resolvedFrom = oldResolved.get(change.name) ?? '';
|
|
235
|
+
const resolvedTo = newResolved.get(change.name) ?? '';
|
|
236
|
+
merged.push({ ...change, resolvedFrom, resolvedTo });
|
|
237
|
+
}
|
|
238
|
+
const names = [...directNames(oldMaps, newMaps)];
|
|
239
|
+
names.sort();
|
|
240
|
+
for (const name of names) {
|
|
241
|
+
if (seen.has(name)) continue;
|
|
242
|
+
const from = oldResolved.get(name) ?? '';
|
|
243
|
+
const to = newResolved.get(name) ?? '';
|
|
244
|
+
if (from === to) continue;
|
|
245
|
+
if (!from && !to) continue;
|
|
246
|
+
let action = 'changed';
|
|
247
|
+
if (!from) action = 'added';
|
|
248
|
+
else if (!to) action = 'removed';
|
|
249
|
+
const section = 'resolved';
|
|
250
|
+
const resolvedFrom = from;
|
|
251
|
+
const resolvedTo = to;
|
|
252
|
+
merged.push({ name, section, action, from, to, resolvedFrom, resolvedTo });
|
|
253
|
+
}
|
|
254
|
+
return merged;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const compareChangeOrder = (left, right) => {
|
|
258
|
+
const section = SECTION_ORDER.indexOf(left.section);
|
|
259
|
+
const other = SECTION_ORDER.indexOf(right.section);
|
|
260
|
+
if (section !== other) return section - other;
|
|
261
|
+
const leftAction = ACTION_ORDER.indexOf(left.action);
|
|
262
|
+
const rightAction = ACTION_ORDER.indexOf(right.action);
|
|
263
|
+
if (leftAction !== rightAction) return leftAction - rightAction;
|
|
264
|
+
return left.name.localeCompare(right.name);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const orderedChanges = (changes) => [...changes].sort(compareChangeOrder);
|
|
268
|
+
|
|
269
|
+
const kindItems = (items, kind) => {
|
|
270
|
+
const matched = [];
|
|
271
|
+
for (const item of items) {
|
|
272
|
+
const rel = itemPath(item);
|
|
273
|
+
const meta = depFileMeta(rel);
|
|
274
|
+
if (meta && meta.kind === kind) matched.push(item);
|
|
275
|
+
}
|
|
276
|
+
return matched;
|
|
277
|
+
};
|
|
278
|
+
|
|
279
|
+
const readGroupSnapshot = (group, readSides) => {
|
|
280
|
+
const origin = group.origin;
|
|
281
|
+
const dir = group.dir;
|
|
282
|
+
const groupItems = group.items;
|
|
283
|
+
const manifestItems = kindItems(groupItems, 'manifest');
|
|
284
|
+
const lockItems = kindItems(groupItems, 'lockfile');
|
|
285
|
+
const manifestRel = relOf(dir, MANIFEST);
|
|
286
|
+
const lockRel = relOf(dir, LOCKFILE);
|
|
287
|
+
const manifestSides = readSides(origin, manifestRel);
|
|
288
|
+
const lockSides = readSides(origin, lockRel);
|
|
289
|
+
const oldPkg = parseJson(manifestSides.oldText);
|
|
290
|
+
const newPkg = parseJson(manifestSides.newText);
|
|
291
|
+
const oldLock = parseJson(lockSides.oldText);
|
|
292
|
+
const newLock = parseJson(lockSides.newText);
|
|
293
|
+
const parsedManifest = !!(oldPkg || newPkg);
|
|
294
|
+
return {
|
|
295
|
+
origin,
|
|
296
|
+
dir,
|
|
297
|
+
groupItems,
|
|
298
|
+
manifestItems,
|
|
299
|
+
lockItems,
|
|
300
|
+
oldPkg,
|
|
301
|
+
newPkg,
|
|
302
|
+
oldLock,
|
|
303
|
+
newLock,
|
|
304
|
+
parsedManifest,
|
|
305
|
+
};
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const sectionMapsOf = (snapshot) => {
|
|
309
|
+
if (snapshot.parsedManifest) {
|
|
310
|
+
const oldMaps = readSections(snapshot.oldPkg);
|
|
311
|
+
const newMaps = readSections(snapshot.newPkg);
|
|
312
|
+
return { oldMaps, newMaps };
|
|
313
|
+
}
|
|
314
|
+
if (snapshot.manifestItems.length) {
|
|
315
|
+
return mapsFromHunks(snapshot.manifestItems);
|
|
316
|
+
}
|
|
317
|
+
const oldMaps = lockRootSections(snapshot.oldLock);
|
|
318
|
+
const newMaps = lockRootSections(snapshot.newLock);
|
|
319
|
+
return { oldMaps, newMaps };
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const comparedChanges = (snapshot) => {
|
|
323
|
+
const maps = sectionMapsOf(snapshot);
|
|
324
|
+
const oldMaps = maps.oldMaps;
|
|
325
|
+
const newMaps = maps.newMaps;
|
|
326
|
+
const manifestChanges = diffSections(oldMaps, newMaps);
|
|
327
|
+
const parsed = snapshot.parsedManifest;
|
|
328
|
+
let otherChanges = [];
|
|
329
|
+
if (parsed) {
|
|
330
|
+
otherChanges = diffOtherFields(snapshot.oldPkg, snapshot.newPkg);
|
|
331
|
+
}
|
|
332
|
+
const oldResolved = lockEntries(snapshot.oldLock);
|
|
333
|
+
const newResolved = lockEntries(snapshot.newLock);
|
|
334
|
+
const changes = mergeResolved(
|
|
335
|
+
manifestChanges,
|
|
336
|
+
oldResolved,
|
|
337
|
+
newResolved,
|
|
338
|
+
oldMaps,
|
|
339
|
+
newMaps,
|
|
340
|
+
);
|
|
341
|
+
const oldCount = lockPackageCount(snapshot.oldLock);
|
|
342
|
+
const newCount = lockPackageCount(snapshot.newLock);
|
|
343
|
+
return { changes, otherChanges, oldCount, newCount };
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
const foldingPolicy = (snapshot, compared) => {
|
|
347
|
+
const foldLock = snapshot.lockItems.length > 0;
|
|
348
|
+
const hasDepChanges = compared.changes.length > 0;
|
|
349
|
+
if (!snapshot.manifestItems.length) {
|
|
350
|
+
const foldManifest = false;
|
|
351
|
+
const takeAllManifest = false;
|
|
352
|
+
return { foldLock, foldManifest, takeAllManifest };
|
|
353
|
+
}
|
|
354
|
+
if (snapshot.parsedManifest) {
|
|
355
|
+
const foldManifest = hasDepChanges;
|
|
356
|
+
const takeAllManifest = hasDepChanges && compared.otherChanges.length === 0;
|
|
357
|
+
return { foldLock, foldManifest, takeAllManifest };
|
|
358
|
+
}
|
|
359
|
+
const foldManifest = hasDepChanges && onlyDepEdits(snapshot.manifestItems);
|
|
360
|
+
const takeAllManifest = foldManifest;
|
|
361
|
+
return { foldLock, foldManifest, takeAllManifest };
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
const summarizeGroup = (group, readSides) => {
|
|
365
|
+
const snapshot = readGroupSnapshot(group, readSides);
|
|
366
|
+
const compared = comparedChanges(snapshot);
|
|
367
|
+
const policy = foldingPolicy(snapshot, compared);
|
|
368
|
+
return {
|
|
369
|
+
changes: compared.changes,
|
|
370
|
+
oldCount: compared.oldCount,
|
|
371
|
+
newCount: compared.newCount,
|
|
372
|
+
foldLock: policy.foldLock,
|
|
373
|
+
foldManifest: policy.foldManifest,
|
|
374
|
+
takeAllManifest: policy.takeAllManifest,
|
|
375
|
+
manifestItems: snapshot.manifestItems,
|
|
376
|
+
lockItems: snapshot.lockItems,
|
|
377
|
+
pkg: snapshot.newPkg || snapshot.oldPkg,
|
|
378
|
+
};
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
const collectGroups = (items) => {
|
|
382
|
+
const groups = new Map();
|
|
383
|
+
for (const item of items) {
|
|
384
|
+
const rel = itemPath(item);
|
|
385
|
+
const meta = depFileMeta(rel);
|
|
386
|
+
if (!meta) continue;
|
|
387
|
+
const key = `${item.origin}\0${meta.dir}`;
|
|
388
|
+
let group = groups.get(key);
|
|
389
|
+
if (!group) {
|
|
390
|
+
const dir = meta.dir;
|
|
391
|
+
const origin = item.origin;
|
|
392
|
+
group = { dir, origin, items: [] };
|
|
393
|
+
groups.set(key, group);
|
|
394
|
+
}
|
|
395
|
+
group.items.push(item);
|
|
396
|
+
}
|
|
397
|
+
return groups;
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const takeManifestItems = (summary) => {
|
|
401
|
+
if (!summary.foldManifest) return [];
|
|
402
|
+
if (summary.takeAllManifest) return [...summary.manifestItems];
|
|
403
|
+
const names = foldedDepNames(summary.changes);
|
|
404
|
+
const taken = [];
|
|
405
|
+
for (const item of summary.manifestItems) {
|
|
406
|
+
if (onlyDepEdits([item]) || hunkOnlyFoldedDeps(item, names)) {
|
|
407
|
+
taken.push(item);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return taken;
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
module.exports = {
|
|
414
|
+
diffSections,
|
|
415
|
+
mergeResolved,
|
|
416
|
+
orderedChanges,
|
|
417
|
+
summarizeGroup,
|
|
418
|
+
collectGroups,
|
|
419
|
+
takeManifestItems,
|
|
420
|
+
foldedDepNames,
|
|
421
|
+
stripFoldedDepItem,
|
|
422
|
+
};
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { itemPath } = require('../files.js');
|
|
4
|
+
|
|
5
|
+
const { depFileMeta, listFilePath } = require('./manifest.js');
|
|
6
|
+
const { orderedChanges } = require('./changes.js');
|
|
7
|
+
const { buildRows, makeHunk } = require('./rows.js');
|
|
8
|
+
|
|
9
|
+
const uniquePaths = (items) => {
|
|
10
|
+
const files = [];
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
for (const item of items) {
|
|
13
|
+
const rel = itemPath(item);
|
|
14
|
+
if (!rel || seen.has(rel)) continue;
|
|
15
|
+
seen.add(rel);
|
|
16
|
+
files.push(rel);
|
|
17
|
+
}
|
|
18
|
+
return files;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const filesForChange = (change, files) => {
|
|
22
|
+
const manifest = [];
|
|
23
|
+
const lockfile = [];
|
|
24
|
+
for (const rel of files) {
|
|
25
|
+
const meta = depFileMeta(rel);
|
|
26
|
+
if (!meta) continue;
|
|
27
|
+
if (meta.kind === 'manifest') manifest.push(rel);
|
|
28
|
+
else lockfile.push(rel);
|
|
29
|
+
}
|
|
30
|
+
if (change.section === 'field') {
|
|
31
|
+
if (manifest.length) return manifest;
|
|
32
|
+
return files;
|
|
33
|
+
}
|
|
34
|
+
if (change.section === 'resolved') {
|
|
35
|
+
if (lockfile.length) return lockfile;
|
|
36
|
+
return files;
|
|
37
|
+
}
|
|
38
|
+
if (change.propose) {
|
|
39
|
+
if (manifest.length) return manifest;
|
|
40
|
+
return files;
|
|
41
|
+
}
|
|
42
|
+
const both = [...manifest, ...lockfile];
|
|
43
|
+
if (both.length) return both;
|
|
44
|
+
return files;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const pickPrimary = (items) => {
|
|
48
|
+
for (const item of items) {
|
|
49
|
+
const rel = itemPath(item);
|
|
50
|
+
const meta = depFileMeta(rel);
|
|
51
|
+
if (meta && meta.kind === 'manifest') return item;
|
|
52
|
+
}
|
|
53
|
+
return items[0];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const makeDepItem = (origin, fileItems, hidden, change, extra, blockId) => {
|
|
57
|
+
const allFiles = uniquePaths(fileItems);
|
|
58
|
+
const files = change ? filesForChange(change, allFiles) : allFiles;
|
|
59
|
+
const primary = pickPrimary(fileItems);
|
|
60
|
+
const oldPath = listFilePath(itemPath(primary));
|
|
61
|
+
const newPath = oldPath;
|
|
62
|
+
const isNew = fileItems.some((item) => item.file && item.file.isNew);
|
|
63
|
+
const isDeleted = fileItems.every((item) => item.file && item.file.isDeleted);
|
|
64
|
+
let oldCount = 0;
|
|
65
|
+
let newCount = 0;
|
|
66
|
+
if (!change) {
|
|
67
|
+
oldCount = extra.oldCount ?? 0;
|
|
68
|
+
newCount = extra.newCount ?? 0;
|
|
69
|
+
}
|
|
70
|
+
const rows = buildRows(change, oldCount, newCount);
|
|
71
|
+
const hunk = makeHunk(rows, blockId);
|
|
72
|
+
const isBinary = false;
|
|
73
|
+
const preamble = [];
|
|
74
|
+
const hunks = [hunk];
|
|
75
|
+
const file = {
|
|
76
|
+
oldPath,
|
|
77
|
+
newPath,
|
|
78
|
+
isNew,
|
|
79
|
+
isDeleted,
|
|
80
|
+
isBinary,
|
|
81
|
+
preamble,
|
|
82
|
+
hunks,
|
|
83
|
+
};
|
|
84
|
+
const patchAdd = '';
|
|
85
|
+
const patchRevert = '';
|
|
86
|
+
const items = hidden;
|
|
87
|
+
const dep = { files, items, change };
|
|
88
|
+
const reload = !!(change && change.propose);
|
|
89
|
+
return { origin, file, hunk, blockId, patchAdd, patchRevert, dep, reload };
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const makeDepItems = (origin, fileItems, hidden, summary) => {
|
|
93
|
+
const ordered = orderedChanges(summary.changes);
|
|
94
|
+
if (!ordered.length) {
|
|
95
|
+
const oldCount = summary.oldCount ?? 0;
|
|
96
|
+
const newCount = summary.newCount ?? 0;
|
|
97
|
+
if (oldCount === newCount) return [];
|
|
98
|
+
return [makeDepItem(origin, fileItems, hidden, null, summary, 0)];
|
|
99
|
+
}
|
|
100
|
+
const items = [];
|
|
101
|
+
let blockId = 0;
|
|
102
|
+
for (const change of ordered) {
|
|
103
|
+
const made = makeDepItem(
|
|
104
|
+
origin,
|
|
105
|
+
fileItems,
|
|
106
|
+
hidden,
|
|
107
|
+
change,
|
|
108
|
+
summary,
|
|
109
|
+
blockId,
|
|
110
|
+
);
|
|
111
|
+
items.push(made);
|
|
112
|
+
blockId += 1;
|
|
113
|
+
}
|
|
114
|
+
return items;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const stubFileItem = (rel, origin) => {
|
|
118
|
+
const file = {
|
|
119
|
+
oldPath: rel,
|
|
120
|
+
newPath: rel,
|
|
121
|
+
isNew: false,
|
|
122
|
+
isDeleted: false,
|
|
123
|
+
isBinary: false,
|
|
124
|
+
preamble: [],
|
|
125
|
+
hunks: [],
|
|
126
|
+
};
|
|
127
|
+
const hunk = null;
|
|
128
|
+
const blockId = 0;
|
|
129
|
+
const patchAdd = '';
|
|
130
|
+
const patchRevert = '';
|
|
131
|
+
return { origin, file, hunk, blockId, patchAdd, patchRevert };
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
module.exports = { makeDepItems, stubFileItem };
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
|
|
5
|
+
const MANIFEST = 'package.json';
|
|
6
|
+
const LOCKFILE = 'package-lock.json';
|
|
7
|
+
const DEP_KIND = { [MANIFEST]: 'manifest', [LOCKFILE]: 'lockfile' };
|
|
8
|
+
const SECTION_KEYS = [
|
|
9
|
+
'dependencies',
|
|
10
|
+
'devDependencies',
|
|
11
|
+
'optionalDependencies',
|
|
12
|
+
'peerDependencies',
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const posixRel = (rel) => {
|
|
16
|
+
const raw = `${rel ?? ''}`;
|
|
17
|
+
return raw.replaceAll('\\', '/');
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const depFileMeta = (rel) => {
|
|
21
|
+
const normalized = posixRel(rel);
|
|
22
|
+
if (!normalized) return null;
|
|
23
|
+
const base = path.posix.basename(normalized);
|
|
24
|
+
const kind = DEP_KIND[base];
|
|
25
|
+
if (!kind) return null;
|
|
26
|
+
const dir = path.posix.dirname(normalized);
|
|
27
|
+
return { kind, dir, base, rel: normalized };
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const relOf = (dir, name) => (dir === '.' ? name : `${dir}/${name}`);
|
|
31
|
+
|
|
32
|
+
const listFilePath = (rel) => {
|
|
33
|
+
const meta = depFileMeta(rel);
|
|
34
|
+
if (meta && meta.kind === 'lockfile') return relOf(meta.dir, MANIFEST);
|
|
35
|
+
return posixRel(rel);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const parseJson = (text) => {
|
|
39
|
+
if (!text || !`${text}`.trim()) return null;
|
|
40
|
+
try {
|
|
41
|
+
const data = JSON.parse(text);
|
|
42
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) return null;
|
|
43
|
+
return data;
|
|
44
|
+
} catch {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const emptySectionMaps = () => {
|
|
50
|
+
const maps = {};
|
|
51
|
+
for (const key of SECTION_KEYS) maps[key] = new Map();
|
|
52
|
+
return maps;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const readSectionMap = (pkg, key) => {
|
|
56
|
+
const map = new Map();
|
|
57
|
+
const raw = pkg ? pkg[key] : null;
|
|
58
|
+
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return map;
|
|
59
|
+
for (const name of Object.keys(raw)) {
|
|
60
|
+
const version = raw[name];
|
|
61
|
+
if (typeof version === 'string') map.set(name, version);
|
|
62
|
+
}
|
|
63
|
+
return map;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const readSections = (pkg) => {
|
|
67
|
+
const maps = emptySectionMaps();
|
|
68
|
+
if (!pkg) return maps;
|
|
69
|
+
for (const key of SECTION_KEYS) maps[key] = readSectionMap(pkg, key);
|
|
70
|
+
return maps;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const lockRootSections = (lock) => {
|
|
74
|
+
if (!lock) return emptySectionMaps();
|
|
75
|
+
const packages = lock.packages;
|
|
76
|
+
const root = packages && typeof packages === 'object' ? packages[''] : null;
|
|
77
|
+
const src = root && typeof root === 'object' ? root : lock;
|
|
78
|
+
return readSections(src);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const lockEntries = (lock) => {
|
|
82
|
+
const versions = new Map();
|
|
83
|
+
if (!lock) return versions;
|
|
84
|
+
const packages = lock.packages;
|
|
85
|
+
if (packages && typeof packages === 'object') {
|
|
86
|
+
for (const key of Object.keys(packages)) {
|
|
87
|
+
const entry = packages[key];
|
|
88
|
+
if (!key.startsWith('node_modules/')) continue;
|
|
89
|
+
if (key.includes('/node_modules/')) continue;
|
|
90
|
+
if (!entry || typeof entry.version !== 'string') continue;
|
|
91
|
+
const name = key.slice('node_modules/'.length);
|
|
92
|
+
versions.set(name, entry.version);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const deps = lock.dependencies;
|
|
96
|
+
if (!deps || typeof deps !== 'object') return versions;
|
|
97
|
+
for (const name of Object.keys(deps)) {
|
|
98
|
+
const entry = deps[name];
|
|
99
|
+
if (versions.has(name)) continue;
|
|
100
|
+
if (!entry || typeof entry.version !== 'string') continue;
|
|
101
|
+
versions.set(name, entry.version);
|
|
102
|
+
}
|
|
103
|
+
return versions;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const lockPackageCount = (lock) => {
|
|
107
|
+
if (!lock) return 0;
|
|
108
|
+
const packages = lock.packages;
|
|
109
|
+
if (packages && typeof packages === 'object') {
|
|
110
|
+
let count = 0;
|
|
111
|
+
for (const key of Object.keys(packages)) {
|
|
112
|
+
if (key !== '') count += 1;
|
|
113
|
+
}
|
|
114
|
+
return count;
|
|
115
|
+
}
|
|
116
|
+
const deps = lock.dependencies;
|
|
117
|
+
if (!deps || typeof deps !== 'object') return 0;
|
|
118
|
+
return Object.keys(deps).length;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const cloneJson = (value) => JSON.parse(JSON.stringify(value ?? null));
|
|
122
|
+
|
|
123
|
+
const detectIndent = (text) => {
|
|
124
|
+
const match = /\n([ \t]+)"/.exec(text);
|
|
125
|
+
if (!match) return 2;
|
|
126
|
+
return match[1];
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const stringifyJson = (value, original) => {
|
|
130
|
+
const indent = detectIndent(original || '');
|
|
131
|
+
const body = JSON.stringify(value, null, indent);
|
|
132
|
+
return `${body}\n`;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
module.exports = {
|
|
136
|
+
MANIFEST,
|
|
137
|
+
LOCKFILE,
|
|
138
|
+
DEP_KIND,
|
|
139
|
+
SECTION_KEYS,
|
|
140
|
+
posixRel,
|
|
141
|
+
depFileMeta,
|
|
142
|
+
relOf,
|
|
143
|
+
listFilePath,
|
|
144
|
+
parseJson,
|
|
145
|
+
emptySectionMaps,
|
|
146
|
+
readSections,
|
|
147
|
+
lockRootSections,
|
|
148
|
+
lockEntries,
|
|
149
|
+
lockPackageCount,
|
|
150
|
+
cloneJson,
|
|
151
|
+
stringifyJson,
|
|
152
|
+
};
|