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,120 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const manifest = require('./manifest.js');
|
|
4
|
+
const { SECTION_KEYS, parseJson, cloneJson, stringifyJson } = manifest;
|
|
5
|
+
|
|
6
|
+
const parseFieldValue = (text) => {
|
|
7
|
+
try {
|
|
8
|
+
return JSON.parse(text);
|
|
9
|
+
} catch {
|
|
10
|
+
return text;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const setSectionEntry = (pkg, section, name, version) => {
|
|
15
|
+
if (!version) {
|
|
16
|
+
const map = pkg[section];
|
|
17
|
+
if (!map || typeof map !== 'object') return;
|
|
18
|
+
delete map[name];
|
|
19
|
+
if (!Object.keys(map).length) delete pkg[section];
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const map = pkg[section];
|
|
23
|
+
if (!map || typeof map !== 'object') pkg[section] = {};
|
|
24
|
+
pkg[section][name] = version;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const dropsEntry = (change, side) => {
|
|
28
|
+
if (side === 'new') return change.action === 'removed';
|
|
29
|
+
return change.action === 'added';
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const applyManifestChange = (pkg, change, side) => {
|
|
33
|
+
if (change.section === 'resolved') return pkg;
|
|
34
|
+
const value = side === 'new' ? change.to : change.from;
|
|
35
|
+
if (change.section === 'field') {
|
|
36
|
+
if (value === '') {
|
|
37
|
+
delete pkg[change.name];
|
|
38
|
+
return pkg;
|
|
39
|
+
}
|
|
40
|
+
pkg[change.name] = parseFieldValue(value);
|
|
41
|
+
return pkg;
|
|
42
|
+
}
|
|
43
|
+
const version = dropsEntry(change, side) ? '' : value;
|
|
44
|
+
setSectionEntry(pkg, change.section, change.name, version);
|
|
45
|
+
return pkg;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const ensureLockRoot = (lock) => {
|
|
49
|
+
if (!lock.packages || typeof lock.packages !== 'object') {
|
|
50
|
+
lock.packages = {};
|
|
51
|
+
}
|
|
52
|
+
const root = lock.packages[''];
|
|
53
|
+
if (!root || typeof root !== 'object') lock.packages[''] = {};
|
|
54
|
+
return lock.packages[''];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const lockNodeKey = (name) => `node_modules/${name}`;
|
|
58
|
+
|
|
59
|
+
const applyLegacyLockDep = (lock, change, side, src) => {
|
|
60
|
+
const deps = lock.dependencies;
|
|
61
|
+
if (!deps || typeof deps !== 'object') return;
|
|
62
|
+
if (dropsEntry(change, side)) {
|
|
63
|
+
delete deps[change.name];
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const srcDeps = src && src.dependencies;
|
|
67
|
+
if (!srcDeps || typeof srcDeps !== 'object') return;
|
|
68
|
+
const entry = srcDeps[change.name];
|
|
69
|
+
if (entry) deps[change.name] = cloneJson(entry);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const applyLockChange = (lock, change, side, oldLock, newLock) => {
|
|
73
|
+
if (change.section === 'field') return lock;
|
|
74
|
+
const src = side === 'new' ? newLock : oldLock;
|
|
75
|
+
if (change.section !== 'resolved') {
|
|
76
|
+
const root = ensureLockRoot(lock);
|
|
77
|
+
applyManifestChange(root, change, side);
|
|
78
|
+
applyLegacyLockDep(lock, change, side, src);
|
|
79
|
+
}
|
|
80
|
+
const packages = lock.packages;
|
|
81
|
+
if (!packages || typeof packages !== 'object') return lock;
|
|
82
|
+
const key = lockNodeKey(change.name);
|
|
83
|
+
if (dropsEntry(change, side)) {
|
|
84
|
+
delete packages[key];
|
|
85
|
+
return lock;
|
|
86
|
+
}
|
|
87
|
+
const srcPkgs = src && src.packages;
|
|
88
|
+
if (!srcPkgs || typeof srcPkgs !== 'object') return lock;
|
|
89
|
+
const entry = srcPkgs[key];
|
|
90
|
+
if (entry) packages[key] = cloneJson(entry);
|
|
91
|
+
return lock;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const seedManifest = (oldObj, newObj) => {
|
|
95
|
+
const base = cloneJson(oldObj) || {};
|
|
96
|
+
if (!newObj) return base;
|
|
97
|
+
for (const key of Object.keys(newObj)) {
|
|
98
|
+
const value = newObj[key];
|
|
99
|
+
if (SECTION_KEYS.includes(key)) continue;
|
|
100
|
+
if (Object.hasOwn(base, key)) continue;
|
|
101
|
+
base[key] = cloneJson(value);
|
|
102
|
+
}
|
|
103
|
+
return base;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const mergeDepFile = (baseText, oldText, newText, change, side, kind) => {
|
|
107
|
+
const oldObj = parseJson(oldText);
|
|
108
|
+
const newObj = parseJson(newText);
|
|
109
|
+
const parsedBase = parseJson(baseText);
|
|
110
|
+
let base;
|
|
111
|
+
if (parsedBase) base = cloneJson(parsedBase);
|
|
112
|
+
else if (kind === 'manifest') base = seedManifest(oldObj, newObj);
|
|
113
|
+
else base = cloneJson(oldObj) || {};
|
|
114
|
+
if (kind === 'lockfile') applyLockChange(base, change, side, oldObj, newObj);
|
|
115
|
+
else applyManifestChange(base, change, side);
|
|
116
|
+
const original = baseText || newText || oldText || '';
|
|
117
|
+
return stringifyJson(base, original);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
module.exports = { mergeDepFile };
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SECTION_KEYS, readSections, lockEntries } = require('./manifest.js');
|
|
4
|
+
const { pkgHasExportEntry } = require('./usage.js');
|
|
5
|
+
const versions = require('./versions.js');
|
|
6
|
+
const { findDeclared, applyWantedRange } = versions;
|
|
7
|
+
const { proposedLockVersion, proposedWanted } = versions;
|
|
8
|
+
|
|
9
|
+
const depExports = (exportEntries, name) => {
|
|
10
|
+
if (!exportEntries || !exportEntries.has(name)) return true;
|
|
11
|
+
return exportEntries.get(name) === true;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const isAddedDep = (change) => {
|
|
15
|
+
if (change.action !== 'added') return false;
|
|
16
|
+
if (change.section === 'resolved') return false;
|
|
17
|
+
if (change.section === 'field') return false;
|
|
18
|
+
return true;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const unusedChangeNames = (changes, used) => {
|
|
22
|
+
const names = [];
|
|
23
|
+
if (!used || !used.size) return names;
|
|
24
|
+
for (const change of changes) {
|
|
25
|
+
if (!isAddedDep(change)) continue;
|
|
26
|
+
if (used.has(change.name)) continue;
|
|
27
|
+
names.push(change.name);
|
|
28
|
+
}
|
|
29
|
+
return names;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const unusedDeclaredNames = (pkg, used) => {
|
|
33
|
+
const names = [];
|
|
34
|
+
if (!pkgHasExportEntry(pkg)) return names;
|
|
35
|
+
if (!used || !used.size) return names;
|
|
36
|
+
const maps = readSections(pkg);
|
|
37
|
+
for (const section of SECTION_KEYS) {
|
|
38
|
+
if (section === 'peerDependencies') continue;
|
|
39
|
+
const keys = [...maps[section].keys()];
|
|
40
|
+
keys.sort();
|
|
41
|
+
for (const name of keys) {
|
|
42
|
+
if (used.has(name)) continue;
|
|
43
|
+
names.push(name);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return names;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const markUnusedChanges = (changes, used, pkg, exportEntries) => {
|
|
50
|
+
if (!pkgHasExportEntry(pkg)) return changes;
|
|
51
|
+
if (!used || !used.size) return changes;
|
|
52
|
+
const marked = [];
|
|
53
|
+
for (const change of changes) {
|
|
54
|
+
if (!isAddedDep(change) || used.has(change.name)) {
|
|
55
|
+
marked.push(change);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!depExports(exportEntries, change.name)) {
|
|
59
|
+
marked.push(change);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
marked.push({ ...change, unused: true, propose: true });
|
|
63
|
+
}
|
|
64
|
+
return marked;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const unusedDeclaredChanges = (pkg, used, exportEntries) => {
|
|
68
|
+
const changes = [];
|
|
69
|
+
if (!pkgHasExportEntry(pkg)) return changes;
|
|
70
|
+
if (!used || !used.size) return changes;
|
|
71
|
+
const maps = readSections(pkg);
|
|
72
|
+
const taken = new Set();
|
|
73
|
+
for (const section of SECTION_KEYS) {
|
|
74
|
+
if (section === 'peerDependencies') continue;
|
|
75
|
+
const map = maps[section];
|
|
76
|
+
const names = [...map.keys()];
|
|
77
|
+
names.sort();
|
|
78
|
+
for (const name of names) {
|
|
79
|
+
if (used.has(name)) continue;
|
|
80
|
+
if (taken.has(name)) continue;
|
|
81
|
+
if (!depExports(exportEntries, name)) continue;
|
|
82
|
+
taken.add(name);
|
|
83
|
+
const version = map.get(name);
|
|
84
|
+
changes.push({
|
|
85
|
+
name,
|
|
86
|
+
section,
|
|
87
|
+
action: 'removed',
|
|
88
|
+
from: version,
|
|
89
|
+
to: '',
|
|
90
|
+
propose: true,
|
|
91
|
+
unused: true,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return changes;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const buildProposedChanges = (pkg, outdated, audit, lock, options = {}) => {
|
|
99
|
+
const used = options.used;
|
|
100
|
+
const exportEntries = options.exportEntries;
|
|
101
|
+
const names = new Set();
|
|
102
|
+
if (outdated) {
|
|
103
|
+
for (const name of outdated.keys()) names.add(name);
|
|
104
|
+
}
|
|
105
|
+
if (audit) {
|
|
106
|
+
for (const name of audit.keys()) names.add(name);
|
|
107
|
+
}
|
|
108
|
+
const listed = [...names];
|
|
109
|
+
listed.sort();
|
|
110
|
+
const resolved = lockEntries(lock);
|
|
111
|
+
const unused = unusedDeclaredChanges(pkg, used, exportEntries);
|
|
112
|
+
const taken = new Set();
|
|
113
|
+
for (const change of unused) taken.add(change.name);
|
|
114
|
+
const changes = [...unused];
|
|
115
|
+
for (const name of listed) {
|
|
116
|
+
if (taken.has(name)) continue;
|
|
117
|
+
const out = outdated ? outdated.get(name) : null;
|
|
118
|
+
const vul = audit ? audit.get(name) : null;
|
|
119
|
+
const type = out ? out.type : '';
|
|
120
|
+
const declared = findDeclared(pkg, name, type);
|
|
121
|
+
if (!declared) {
|
|
122
|
+
if (!vul) continue;
|
|
123
|
+
const version = resolved.get(name) || '';
|
|
124
|
+
if (!version) continue;
|
|
125
|
+
const to = proposedLockVersion(version, vul);
|
|
126
|
+
changes.push({
|
|
127
|
+
name,
|
|
128
|
+
section: 'resolved',
|
|
129
|
+
action: 'vulnerable',
|
|
130
|
+
from: version,
|
|
131
|
+
to,
|
|
132
|
+
propose: true,
|
|
133
|
+
audit: vul,
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const wanted = proposedWanted(out, vul);
|
|
138
|
+
if (!wanted && !vul) continue;
|
|
139
|
+
let to = declared.version;
|
|
140
|
+
if (wanted) to = applyWantedRange(declared.version, wanted);
|
|
141
|
+
if (to === declared.version && !vul) continue;
|
|
142
|
+
let action = 'changed';
|
|
143
|
+
if (to === declared.version) action = 'vulnerable';
|
|
144
|
+
const change = {
|
|
145
|
+
name,
|
|
146
|
+
section: declared.section,
|
|
147
|
+
action,
|
|
148
|
+
from: declared.version,
|
|
149
|
+
to,
|
|
150
|
+
propose: true,
|
|
151
|
+
};
|
|
152
|
+
if (out) change.outdated = out;
|
|
153
|
+
if (vul) change.audit = vul;
|
|
154
|
+
changes.push(change);
|
|
155
|
+
}
|
|
156
|
+
return changes;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
module.exports = {
|
|
160
|
+
unusedChangeNames,
|
|
161
|
+
unusedDeclaredNames,
|
|
162
|
+
markUnusedChanges,
|
|
163
|
+
buildProposedChanges,
|
|
164
|
+
};
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { jsonParse, isHashObject } = require('metautil');
|
|
4
|
+
|
|
5
|
+
const SEVERITY_RANK = { critical: 4, high: 3, moderate: 2, low: 1, info: 0 };
|
|
6
|
+
|
|
7
|
+
const namedTitle = (name, title) => {
|
|
8
|
+
if (!title) return '';
|
|
9
|
+
if (!name) return title;
|
|
10
|
+
const prefix = `${name}: `;
|
|
11
|
+
if (title.startsWith(prefix)) return title;
|
|
12
|
+
return `${prefix}${title}`;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const viaNotes = (via, pkgName) => {
|
|
16
|
+
if (!Array.isArray(via)) return [];
|
|
17
|
+
const notes = [];
|
|
18
|
+
for (const item of via) {
|
|
19
|
+
if (!item || typeof item !== 'object') continue;
|
|
20
|
+
const title = typeof item.title === 'string' ? item.title : '';
|
|
21
|
+
if (!title) continue;
|
|
22
|
+
const hasName = typeof item.name === 'string' && item.name;
|
|
23
|
+
const named = hasName ? item.name : pkgName;
|
|
24
|
+
const note = namedTitle(named, title);
|
|
25
|
+
if (!notes.includes(note)) notes.push(note);
|
|
26
|
+
}
|
|
27
|
+
return notes;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const severityRank = (severity) => SEVERITY_RANK[severity] ?? 0;
|
|
31
|
+
|
|
32
|
+
const auditFix = (entry) => {
|
|
33
|
+
const available = entry.fixAvailable;
|
|
34
|
+
if (!available || typeof available !== 'object') return '';
|
|
35
|
+
if (typeof available.version !== 'string') return '';
|
|
36
|
+
return available.version;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const auditEntryNames = (key, entry) => {
|
|
40
|
+
const names = [];
|
|
41
|
+
const primary = entry.name || key;
|
|
42
|
+
if (primary) names.push(primary);
|
|
43
|
+
const effects = entry.effects;
|
|
44
|
+
if (!Array.isArray(effects)) return names;
|
|
45
|
+
for (const effect of effects) {
|
|
46
|
+
if (typeof effect === 'string' && effect) names.push(effect);
|
|
47
|
+
}
|
|
48
|
+
return names;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const keepHighestAudit = (audit, name, severity, fix, range, notes) => {
|
|
52
|
+
if (!name || !severity || severity === 'info') return;
|
|
53
|
+
const titles = [];
|
|
54
|
+
for (const note of notes) {
|
|
55
|
+
if (note && !titles.includes(note)) titles.push(note);
|
|
56
|
+
}
|
|
57
|
+
const prev = audit.get(name);
|
|
58
|
+
if (prev && severityRank(prev.severity) >= severityRank(severity)) {
|
|
59
|
+
const merged = [...prev.titles];
|
|
60
|
+
for (const note of titles) {
|
|
61
|
+
if (!merged.includes(note)) merged.push(note);
|
|
62
|
+
}
|
|
63
|
+
const next = {
|
|
64
|
+
...prev,
|
|
65
|
+
titles: merged,
|
|
66
|
+
fix: prev.fix || fix,
|
|
67
|
+
range: prev.range || range,
|
|
68
|
+
};
|
|
69
|
+
audit.set(name, next);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const title = titles[0] || '';
|
|
73
|
+
const next = { name, severity, title, titles, fix, range };
|
|
74
|
+
next.fix ||= '';
|
|
75
|
+
next.range ||= '';
|
|
76
|
+
audit.set(name, next);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const parseAuditReport = (text) => {
|
|
80
|
+
const audit = new Map();
|
|
81
|
+
if (!text || !`${text}`.trim()) return audit;
|
|
82
|
+
const data = jsonParse(text);
|
|
83
|
+
if (!isHashObject(data)) return audit;
|
|
84
|
+
const vulns = data.vulnerabilities;
|
|
85
|
+
if (vulns && typeof vulns === 'object') {
|
|
86
|
+
for (const key of Object.keys(vulns)) {
|
|
87
|
+
const entry = vulns[key];
|
|
88
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
89
|
+
const severity = entry.severity;
|
|
90
|
+
const names = auditEntryNames(key, entry);
|
|
91
|
+
const pkgName = entry.name || key;
|
|
92
|
+
const titles = viaNotes(entry.via, pkgName);
|
|
93
|
+
const fix = auditFix(entry);
|
|
94
|
+
const range = typeof entry.range === 'string' ? entry.range : '';
|
|
95
|
+
for (const name of names) {
|
|
96
|
+
keepHighestAudit(audit, name, severity, fix, range, titles);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const advisories = data.advisories;
|
|
101
|
+
if (advisories && typeof advisories === 'object') {
|
|
102
|
+
for (const entry of Object.values(advisories)) {
|
|
103
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
104
|
+
const name = entry.module_name || entry.name;
|
|
105
|
+
const severity = entry.severity;
|
|
106
|
+
const raw = typeof entry.title === 'string' ? entry.title : '';
|
|
107
|
+
const title = namedTitle(name, raw);
|
|
108
|
+
const titles = title ? [title] : [];
|
|
109
|
+
const fix = auditFix(entry);
|
|
110
|
+
const range = typeof entry.range === 'string' ? entry.range : '';
|
|
111
|
+
keepHighestAudit(audit, name, severity, fix, range, titles);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return audit;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const parseOutdatedReport = (text) => {
|
|
118
|
+
const outdated = new Map();
|
|
119
|
+
if (!text || !`${text}`.trim()) return outdated;
|
|
120
|
+
const data = jsonParse(text);
|
|
121
|
+
if (!isHashObject(data)) return outdated;
|
|
122
|
+
for (const name of Object.keys(data)) {
|
|
123
|
+
const entry = data[name];
|
|
124
|
+
if (!entry || typeof entry !== 'object') continue;
|
|
125
|
+
const current = typeof entry.current === 'string' ? entry.current : '';
|
|
126
|
+
const wanted = typeof entry.wanted === 'string' ? entry.wanted : '';
|
|
127
|
+
const latest = typeof entry.latest === 'string' ? entry.latest : '';
|
|
128
|
+
const type = typeof entry.type === 'string' ? entry.type : '';
|
|
129
|
+
if (!wanted) continue;
|
|
130
|
+
outdated.set(name, { current, wanted, latest, type });
|
|
131
|
+
}
|
|
132
|
+
return outdated;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const markAuditChanges = (changes, audit) => {
|
|
136
|
+
if (!audit) return changes;
|
|
137
|
+
const marked = [];
|
|
138
|
+
for (const change of changes) {
|
|
139
|
+
if (change.action === 'removed' || change.section === 'field') {
|
|
140
|
+
marked.push(change);
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const found = audit.get(change.name);
|
|
144
|
+
if (!found) {
|
|
145
|
+
marked.push(change);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
marked.push({ ...change, audit: found });
|
|
149
|
+
}
|
|
150
|
+
return marked;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const markOutdatedChanges = (changes, outdated) => {
|
|
154
|
+
if (!outdated) return changes;
|
|
155
|
+
const marked = [];
|
|
156
|
+
for (const change of changes) {
|
|
157
|
+
if (change.action === 'removed' || change.section === 'field') {
|
|
158
|
+
marked.push(change);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const found = outdated.get(change.name);
|
|
162
|
+
if (!found) {
|
|
163
|
+
marked.push(change);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (found.wanted && found.wanted === found.current) {
|
|
167
|
+
marked.push(change);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
marked.push({ ...change, outdated: found });
|
|
171
|
+
}
|
|
172
|
+
return marked;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
parseAuditReport,
|
|
177
|
+
parseOutdatedReport,
|
|
178
|
+
markAuditChanges,
|
|
179
|
+
markOutdatedChanges,
|
|
180
|
+
};
|
package/lib/deps/rows.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { countSides, isCtxType } = require('../diff.js');
|
|
4
|
+
|
|
5
|
+
const DEP_CAPTION = 'Dependencies in package.json & package-lock.json';
|
|
6
|
+
|
|
7
|
+
const changeMarks = (change) => {
|
|
8
|
+
const marks = [];
|
|
9
|
+
if (change.unused) marks.push('unused');
|
|
10
|
+
if (change.outdated && !change.propose) marks.push('outdated');
|
|
11
|
+
if (change.audit) marks.push(change.audit.severity);
|
|
12
|
+
return marks;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const changeSources = (change) => {
|
|
16
|
+
const sources = [];
|
|
17
|
+
if (change.unused) {
|
|
18
|
+
sources.push('npm uninstall');
|
|
19
|
+
return sources;
|
|
20
|
+
}
|
|
21
|
+
if (change.propose && change.outdated) sources.push('npm outdated');
|
|
22
|
+
if (change.audit) sources.push('npm audit');
|
|
23
|
+
return sources;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const sectionTitle = (section, action, marks, sources) => {
|
|
27
|
+
const title = action === 'changed' ? 'version changed' : action;
|
|
28
|
+
const kind = section.replace(/ies$/, 'y');
|
|
29
|
+
let label = `${kind} ${title}`;
|
|
30
|
+
if (section === 'field') label = `package.json ${title}`;
|
|
31
|
+
if (section === 'resolved' && action === 'vulnerable') {
|
|
32
|
+
label = 'lockfile vulnerable';
|
|
33
|
+
} else if (section === 'resolved') {
|
|
34
|
+
label = `lockfile resolved ${title}`;
|
|
35
|
+
}
|
|
36
|
+
if (marks.includes('unused')) {
|
|
37
|
+
label = `${kind} unused`;
|
|
38
|
+
const extra = [];
|
|
39
|
+
for (const mark of marks) {
|
|
40
|
+
if (mark !== 'unused') extra.push(mark);
|
|
41
|
+
}
|
|
42
|
+
if (extra.length) label = `${label}, ${extra.join(', ')}`;
|
|
43
|
+
} else if (marks.length) {
|
|
44
|
+
label = `${label}, ${marks.join(', ')}`;
|
|
45
|
+
}
|
|
46
|
+
if (!sources.length) return label;
|
|
47
|
+
return `${sources.join(', ')}: ${label}`;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const entryText = (change, side) => {
|
|
51
|
+
const version = side === 'old' ? change.from : change.to;
|
|
52
|
+
return `"${change.name}": "${version}"`;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const ctxLine = (text) => {
|
|
56
|
+
const type = 'ctx';
|
|
57
|
+
return { type, text };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const warnLine = (text) => {
|
|
61
|
+
const type = 'warn';
|
|
62
|
+
return { type, text };
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const noteLine = (text) => {
|
|
66
|
+
const type = 'note';
|
|
67
|
+
return { type, text };
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const noteSepLine = () => {
|
|
71
|
+
const type = 'noteSep';
|
|
72
|
+
const text = '';
|
|
73
|
+
return { type, text };
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const changeLine = (type, change, side) => {
|
|
77
|
+
const text = entryText(change, side);
|
|
78
|
+
return { type, text };
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const pushChangeLines = (rows, change) => {
|
|
82
|
+
if (change.unused) {
|
|
83
|
+
const side = change.action === 'added' ? 'new' : 'old';
|
|
84
|
+
rows.push(changeLine('del', change, side));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (change.action === 'added') {
|
|
88
|
+
rows.push(changeLine('add', change, 'new'));
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (change.action === 'removed') {
|
|
92
|
+
rows.push(changeLine('del', change, 'old'));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (change.from === change.to) {
|
|
96
|
+
rows.push(changeLine('ctx', change, 'old'));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
rows.push(changeLine('del', change, 'old'));
|
|
100
|
+
rows.push(changeLine('add', change, 'new'));
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const lockCountText = (oldCount, newCount) => {
|
|
104
|
+
if (oldCount === newCount) return '';
|
|
105
|
+
return `lockfile packages ${oldCount} → ${newCount}`;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const auditNoteTexts = (audit) => {
|
|
109
|
+
if (!audit) return [];
|
|
110
|
+
const notes = [];
|
|
111
|
+
if (Array.isArray(audit.titles)) {
|
|
112
|
+
for (const note of audit.titles) {
|
|
113
|
+
if (note && !notes.includes(note)) notes.push(note);
|
|
114
|
+
}
|
|
115
|
+
} else if (audit.title) {
|
|
116
|
+
notes.push(audit.title);
|
|
117
|
+
}
|
|
118
|
+
const severity = audit.severity;
|
|
119
|
+
if (!notes.length) return [`npm audit ${severity}`];
|
|
120
|
+
const texts = [];
|
|
121
|
+
for (const note of notes) {
|
|
122
|
+
texts.push(`npm audit ${severity} ${note}`);
|
|
123
|
+
}
|
|
124
|
+
return texts;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const pushAuditNotes = (rows, audit) => {
|
|
128
|
+
const texts = auditNoteTexts(audit);
|
|
129
|
+
if (!texts.length) return;
|
|
130
|
+
rows.push(ctxLine(''));
|
|
131
|
+
let first = true;
|
|
132
|
+
for (const text of texts) {
|
|
133
|
+
if (!first) rows.push(noteSepLine());
|
|
134
|
+
first = false;
|
|
135
|
+
rows.push(noteLine(text));
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const buildRows = (change, oldCount, newCount) => {
|
|
140
|
+
const rows = [ctxLine(DEP_CAPTION), ctxLine('')];
|
|
141
|
+
if (change) {
|
|
142
|
+
const marks = changeMarks(change);
|
|
143
|
+
const sources = changeSources(change);
|
|
144
|
+
const title = sectionTitle(change.section, change.action, marks, sources);
|
|
145
|
+
const audited = sources.includes('npm audit');
|
|
146
|
+
rows.push(audited ? warnLine(title) : ctxLine(title));
|
|
147
|
+
rows.push(ctxLine(''));
|
|
148
|
+
pushChangeLines(rows, change);
|
|
149
|
+
pushAuditNotes(rows, change.audit);
|
|
150
|
+
}
|
|
151
|
+
const countLine = lockCountText(oldCount, newCount);
|
|
152
|
+
if (countLine) {
|
|
153
|
+
if (rows.at(-1).text !== '') rows.push(ctxLine(''));
|
|
154
|
+
rows.push(ctxLine(countLine));
|
|
155
|
+
}
|
|
156
|
+
return rows;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const toHunkLine = (row, blockId) => {
|
|
160
|
+
const type = row.type;
|
|
161
|
+
const text = row.text;
|
|
162
|
+
const noNl = false;
|
|
163
|
+
const id = isCtxType(type) ? null : blockId;
|
|
164
|
+
return { type, text, noNl, blockId: id };
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const makeHunk = (rows, blockId) => {
|
|
168
|
+
const lines = rows.map((row) => toHunkLine(row, blockId));
|
|
169
|
+
const counts = countSides(lines);
|
|
170
|
+
const oldCount = counts.oldCount;
|
|
171
|
+
const newCount = counts.newCount;
|
|
172
|
+
const header = `@@ -1,${oldCount} +1,${newCount} @@`;
|
|
173
|
+
return { oldStart: 1, oldCount, newStart: 1, newCount, header, lines };
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
module.exports = { DEP_CAPTION, buildRows, makeHunk };
|