reslop 0.1.7 → 0.1.8
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 +19 -1
- package/README.md +1 -1
- package/bin/reslop.js +3 -1
- package/lib/capabilities.js +106 -0
- package/lib/cli.js +18 -14
- package/lib/deps/changes.js +331 -0
- package/lib/deps/items.js +131 -0
- package/lib/deps/manifest.js +145 -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 +177 -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 +65 -1348
- 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 +88 -3
- package/lib/git/branches.js +291 -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 +82 -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 +59 -167
- 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 +218 -0
- package/lib/render/chrome.js +575 -0
- package/lib/render/diff.js +307 -0
- package/lib/render/files.js +166 -0
- package/lib/render/frame.js +197 -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 -1687
- 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 +275 -0
- package/lib/session/branches.js +304 -0
- package/lib/session/change-actions.js +320 -0
- package/lib/session/commit.js +124 -0
- package/lib/session/compose.js +540 -0
- package/lib/session/dispatch.js +105 -0
- package/lib/session/files-pane.js +234 -0
- package/lib/session/items.js +177 -0
- package/lib/session/lifecycle.js +476 -0
- package/lib/session/load.js +85 -0
- package/lib/session/navigation.js +93 -0
- package/lib/session/ops.js +110 -0
- package/lib/session/origins.js +7 -0
- package/lib/session/pointer.js +154 -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 +129 -0
- package/lib/session/watch.js +219 -0
- package/lib/session.js +574 -2354
- package/lib/source.js +11 -11
- package/lib/sys.js +25 -4
- package/lib/wrap.js +4 -2
- package/package.json +2 -2
package/lib/deps/rows.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
if (!change && !countLine) rows.push(ctxLine('lockfile updated'));
|
|
157
|
+
return rows;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const toHunkLine = (row, blockId) => {
|
|
161
|
+
const type = row.type;
|
|
162
|
+
const text = row.text;
|
|
163
|
+
const noNl = false;
|
|
164
|
+
const id = isCtxType(type) ? null : blockId;
|
|
165
|
+
return { type, text, noNl, blockId: id };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const makeHunk = (rows, blockId) => {
|
|
169
|
+
const lines = rows.map((row) => toHunkLine(row, blockId));
|
|
170
|
+
const counts = countSides(lines);
|
|
171
|
+
const oldCount = counts.oldCount;
|
|
172
|
+
const newCount = counts.newCount;
|
|
173
|
+
const header = `@@ -1,${oldCount} +1,${newCount} @@`;
|
|
174
|
+
return { oldStart: 1, oldCount, newStart: 1, newCount, header, lines };
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
module.exports = { DEP_CAPTION, buildRows, makeHunk };
|
package/lib/deps/scan.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const manifest = require('./manifest.js');
|
|
7
|
+
const { MANIFEST, SECTION_KEYS, parseJson, readSections } = manifest;
|
|
8
|
+
|
|
9
|
+
const usage = require('./usage.js');
|
|
10
|
+
const { SOURCE_EXT, collectUsedFromText, collectUsedFromScripts } = usage;
|
|
11
|
+
const { pkgHasExportEntry } = usage;
|
|
12
|
+
|
|
13
|
+
const SKIP_WALK = ['node_modules', '.git', '.review', 'coverage', 'dist'];
|
|
14
|
+
|
|
15
|
+
const collectUsedFromManifest = (used, rel) => {
|
|
16
|
+
let text;
|
|
17
|
+
try {
|
|
18
|
+
text = fs.readFileSync(rel, 'utf8');
|
|
19
|
+
} catch {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const pkg = parseJson(text);
|
|
23
|
+
if (!pkg) return;
|
|
24
|
+
collectUsedFromScripts(used, pkg.scripts);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const walkUsedNames = (used, dir) => {
|
|
28
|
+
let entries;
|
|
29
|
+
try {
|
|
30
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
31
|
+
} catch {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
for (const entry of entries) {
|
|
35
|
+
if (entry.isSymbolicLink()) continue;
|
|
36
|
+
const full = path.join(dir, entry.name);
|
|
37
|
+
if (entry.isDirectory()) {
|
|
38
|
+
if (SKIP_WALK.includes(entry.name)) continue;
|
|
39
|
+
if (entry.name.startsWith('.')) continue;
|
|
40
|
+
walkUsedNames(used, full);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (entry.name === MANIFEST) {
|
|
44
|
+
collectUsedFromManifest(used, full);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const ext = path.extname(entry.name);
|
|
48
|
+
if (!SOURCE_EXT.includes(ext)) continue;
|
|
49
|
+
let text;
|
|
50
|
+
try {
|
|
51
|
+
text = fs.readFileSync(full, 'utf8');
|
|
52
|
+
} catch {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
collectUsedFromText(used, text);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const collectUsedNames = (root) => {
|
|
60
|
+
const used = new Set();
|
|
61
|
+
if (!root) return used;
|
|
62
|
+
walkUsedNames(used, root);
|
|
63
|
+
return used;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const depModuleParts = (name) => {
|
|
67
|
+
if (!name.startsWith('@')) return [name];
|
|
68
|
+
const parts = name.split('/');
|
|
69
|
+
if (parts.length < 2) return [];
|
|
70
|
+
return parts.slice(0, 2);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const readDepPkg = (root, pkgDir, name) => {
|
|
74
|
+
if (!root || !name) return null;
|
|
75
|
+
const parts = depModuleParts(name);
|
|
76
|
+
if (!parts.length) return null;
|
|
77
|
+
const nested = pkgDir && pkgDir !== '.' ? pkgDir : '';
|
|
78
|
+
let dir = nested ? path.join(root, nested) : root;
|
|
79
|
+
while (true) {
|
|
80
|
+
const file = path.join(dir, 'node_modules', ...parts, MANIFEST);
|
|
81
|
+
let text = '';
|
|
82
|
+
try {
|
|
83
|
+
text = fs.readFileSync(file, 'utf8');
|
|
84
|
+
} catch {
|
|
85
|
+
// Missing node_modules copy; try a parent directory.
|
|
86
|
+
}
|
|
87
|
+
if (text) return parseJson(text);
|
|
88
|
+
const parent = path.dirname(dir);
|
|
89
|
+
if (parent === dir) return null;
|
|
90
|
+
dir = parent;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const depHasExportEntry = (root, pkgDir, name) => {
|
|
95
|
+
const pkg = readDepPkg(root, pkgDir, name);
|
|
96
|
+
if (!pkg) return true;
|
|
97
|
+
return pkgHasExportEntry(pkg);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const collectExportEntries = (root, pkgDir, names) => {
|
|
101
|
+
const entries = new Map();
|
|
102
|
+
if (!root) return entries;
|
|
103
|
+
for (const name of names) {
|
|
104
|
+
const hasExport = depHasExportEntry(root, pkgDir, name);
|
|
105
|
+
entries.set(name, hasExport);
|
|
106
|
+
}
|
|
107
|
+
return entries;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const declaredDepNames = (root, pkgDir) => {
|
|
111
|
+
const nested = pkgDir && pkgDir !== '.' ? pkgDir : '';
|
|
112
|
+
const file = path.join(root, nested, MANIFEST);
|
|
113
|
+
let text;
|
|
114
|
+
try {
|
|
115
|
+
text = fs.readFileSync(file, 'utf8');
|
|
116
|
+
} catch {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
const maps = readSections(parseJson(text));
|
|
120
|
+
const names = [];
|
|
121
|
+
for (const key of SECTION_KEYS) {
|
|
122
|
+
for (const name of maps[key].keys()) names.push(name);
|
|
123
|
+
}
|
|
124
|
+
return names;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const collectExportMeta = (root, extra = {}) => {
|
|
128
|
+
if (extra.exportEntries !== undefined) return extra.exportEntries;
|
|
129
|
+
const pkgDir = extra.dir ?? '.';
|
|
130
|
+
const names = extra.names ?? declaredDepNames(root, pkgDir);
|
|
131
|
+
return collectExportEntries(root, pkgDir, names);
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
collectUsedNames,
|
|
136
|
+
collectExportEntries,
|
|
137
|
+
collectExportMeta,
|
|
138
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const SOURCE_EXT = ['.js', '.mjs', '.cjs', '.ts', '.mts', '.cts'];
|
|
4
|
+
const REQUIRE_SPEC = /require\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
5
|
+
const FROM_SPEC = /\bfrom\s+['"]([^'"]+)['"]/g;
|
|
6
|
+
const IMPORT_FILE = /import\s+['"]([^'"]+)['"]/g;
|
|
7
|
+
const IMPORT_CALL = /import\(\s*['"]([^'"]+)['"]\s*\)/g;
|
|
8
|
+
|
|
9
|
+
const toPackageName = (spec) => {
|
|
10
|
+
if (!spec || spec.startsWith('.') || spec.startsWith('/')) return '';
|
|
11
|
+
if (spec.startsWith('node:')) return '';
|
|
12
|
+
if (spec.startsWith('@')) {
|
|
13
|
+
const parts = spec.split('/');
|
|
14
|
+
if (parts.length < 2) return '';
|
|
15
|
+
return `${parts[0]}/${parts[1]}`;
|
|
16
|
+
}
|
|
17
|
+
return spec.split('/')[0];
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const addSpec = (used, spec) => {
|
|
21
|
+
const name = toPackageName(spec);
|
|
22
|
+
if (name) used.add(name);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const addMatches = (used, text, pattern) => {
|
|
26
|
+
const matches = text.matchAll(pattern);
|
|
27
|
+
for (const match of matches) addSpec(used, match[1]);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const collectUsedFromText = (used, text) => {
|
|
31
|
+
addMatches(used, text, REQUIRE_SPEC);
|
|
32
|
+
addMatches(used, text, FROM_SPEC);
|
|
33
|
+
addMatches(used, text, IMPORT_FILE);
|
|
34
|
+
addMatches(used, text, IMPORT_CALL);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const collectUsedFromScripts = (used, scripts) => {
|
|
38
|
+
if (!scripts || typeof scripts !== 'object') return;
|
|
39
|
+
for (const script of Object.values(scripts)) {
|
|
40
|
+
const tokens = `${script}`.split(/[\s;&|]+/);
|
|
41
|
+
for (const token of tokens) {
|
|
42
|
+
if (!token || token.startsWith('-')) continue;
|
|
43
|
+
if (token === 'npx' || token === 'npm' || token === 'node') continue;
|
|
44
|
+
addSpec(used, token);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const pkgHasExportEntry = (pkg) => {
|
|
50
|
+
if (!pkg || typeof pkg !== 'object') return false;
|
|
51
|
+
const main = pkg.main;
|
|
52
|
+
if (typeof main === 'string' && main) return true;
|
|
53
|
+
const esm = pkg.module;
|
|
54
|
+
if (typeof esm === 'string' && esm) return true;
|
|
55
|
+
const exported = pkg.exports;
|
|
56
|
+
if (typeof exported === 'string' && exported) return true;
|
|
57
|
+
if (!exported || typeof exported !== 'object') return false;
|
|
58
|
+
if (Array.isArray(exported)) return exported.length > 0;
|
|
59
|
+
return Object.keys(exported).length > 0;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
SOURCE_EXT,
|
|
64
|
+
toPackageName,
|
|
65
|
+
collectUsedFromText,
|
|
66
|
+
collectUsedFromScripts,
|
|
67
|
+
pkgHasExportEntry,
|
|
68
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { SECTION_KEYS } = require('./manifest.js');
|
|
4
|
+
|
|
5
|
+
const applyWantedRange = (declared, wanted) => {
|
|
6
|
+
if (!wanted) return declared ?? '';
|
|
7
|
+
if (!declared) return wanted;
|
|
8
|
+
if (/^(file:|git[+@:]|workspace:|https?:|npm:)/.test(declared)) {
|
|
9
|
+
return declared;
|
|
10
|
+
}
|
|
11
|
+
if (declared.includes(' ') || declared.includes('|')) return declared;
|
|
12
|
+
if (declared.includes('<') || declared.includes('>')) return declared;
|
|
13
|
+
const prefix = declared.startsWith('^') || declared.startsWith('~');
|
|
14
|
+
const mark = prefix ? declared[0] : '';
|
|
15
|
+
return `${mark}${wanted}`;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const findDeclared = (pkg, name, type) => {
|
|
19
|
+
if (type && SECTION_KEYS.includes(type)) {
|
|
20
|
+
const map = pkg[type];
|
|
21
|
+
if (map && typeof map[name] === 'string') {
|
|
22
|
+
return { section: type, version: map[name] };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const section of SECTION_KEYS) {
|
|
26
|
+
const map = pkg[section];
|
|
27
|
+
if (map && typeof map[name] === 'string') {
|
|
28
|
+
return { section, version: map[name] };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const VER_CORE = /^(\d+)\.(\d+)\.(\d+)/;
|
|
35
|
+
|
|
36
|
+
const parseVer = (text) => {
|
|
37
|
+
const match = `${text}`.match(VER_CORE);
|
|
38
|
+
if (!match) return null;
|
|
39
|
+
const major = Number(match[1]);
|
|
40
|
+
const minor = Number(match[2]);
|
|
41
|
+
const patch = Number(match[3]);
|
|
42
|
+
return [major, minor, patch];
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const fmtVer = (parts) => `${parts[0]}.${parts[1]}.${parts[2]}`;
|
|
46
|
+
|
|
47
|
+
const cmpVer = (left, right) => {
|
|
48
|
+
if (left[0] !== right[0]) return left[0] - right[0];
|
|
49
|
+
if (left[1] !== right[1]) return left[1] - right[1];
|
|
50
|
+
return left[2] - right[2];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const bumpPatch = (parts) => {
|
|
54
|
+
const major = parts[0];
|
|
55
|
+
const minor = parts[1];
|
|
56
|
+
const patch = parts[2] + 1;
|
|
57
|
+
return [major, minor, patch];
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const clauseUpper = (clause) => {
|
|
61
|
+
const pattern = /(<=?)\s*(\d+\.\d+\.\d+)/g;
|
|
62
|
+
const matches = clause.matchAll(pattern);
|
|
63
|
+
let best = null;
|
|
64
|
+
for (const match of matches) {
|
|
65
|
+
const op = match[1];
|
|
66
|
+
const parsed = parseVer(match[2]);
|
|
67
|
+
if (!parsed) continue;
|
|
68
|
+
if (best && cmpVer(parsed, best.ver) >= 0) continue;
|
|
69
|
+
best = { op, ver: parsed };
|
|
70
|
+
}
|
|
71
|
+
return best;
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const patchedFromRange = (current, range) => {
|
|
75
|
+
if (!current || !range) return '';
|
|
76
|
+
const now = parseVer(current);
|
|
77
|
+
if (!now) return '';
|
|
78
|
+
const clauses = `${range}`.split('||');
|
|
79
|
+
for (const clause of clauses) {
|
|
80
|
+
const upper = clauseUpper(clause);
|
|
81
|
+
if (!upper) continue;
|
|
82
|
+
if (now[0] !== upper.ver[0]) continue;
|
|
83
|
+
if (cmpVer(now, upper.ver) > 0) continue;
|
|
84
|
+
if (upper.op === '<') return fmtVer(upper.ver);
|
|
85
|
+
return fmtVer(bumpPatch(upper.ver));
|
|
86
|
+
}
|
|
87
|
+
return '';
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const sameMajor = (from, to) => {
|
|
91
|
+
const left = parseVer(from);
|
|
92
|
+
const right = parseVer(to);
|
|
93
|
+
if (!left || !right) return false;
|
|
94
|
+
return left[0] === right[0];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const proposedLockVersion = (current, vul) => {
|
|
98
|
+
if (!current) return '';
|
|
99
|
+
if (!vul) return current;
|
|
100
|
+
const fix = vul.fix;
|
|
101
|
+
if (fix && sameMajor(current, fix) && fix !== current) return fix;
|
|
102
|
+
const patched = patchedFromRange(current, vul.range);
|
|
103
|
+
if (patched && patched !== current) return patched;
|
|
104
|
+
return current;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const proposedWanted = (out, vul) => {
|
|
108
|
+
if (out && out.wanted && out.wanted !== out.current) return out.wanted;
|
|
109
|
+
if (vul && vul.fix) return vul.fix;
|
|
110
|
+
return '';
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
applyWantedRange,
|
|
115
|
+
findDeclared,
|
|
116
|
+
patchedFromRange,
|
|
117
|
+
proposedLockVersion,
|
|
118
|
+
proposedWanted,
|
|
119
|
+
};
|