reslop 0.2.7 → 0.3.0
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 +17 -1
- package/README.md +16 -14
- package/bin/reslop.js +8 -5
- package/lib/agents/agents.js +504 -0
- package/lib/agents/allow.js +71 -0
- package/lib/agents/allow.json +16 -0
- package/lib/agents/models.js +460 -0
- package/lib/api.js +15 -12
- package/lib/cli.js +43 -68
- package/lib/{files.js → common/files.js} +48 -55
- package/lib/{find.js → common/find.js} +1 -1
- package/lib/common/format.js +101 -0
- package/lib/{utilities.js → common/process.js} +7 -95
- package/lib/common/tasks.json +27 -0
- package/lib/common/utilities.js +112 -0
- package/lib/dashboard/git.js +9 -16
- package/lib/dashboard/model.js +90 -49
- package/lib/dashboard/npm.js +17 -13
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +36 -31
- package/lib/{workspace.js → dashboard/workspace.js} +23 -38
- package/lib/{deps.js → deps/deps.js} +31 -45
- package/lib/{deps-diff.js → deps/diff.js} +35 -25
- package/lib/{manifest.js → deps/manifest.js} +47 -30
- package/lib/diff/align.js +1 -2
- package/lib/diff/diff.js +2 -2
- package/lib/diff/display.js +29 -5
- package/lib/diff/inline.js +10 -14
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +39 -3
- package/lib/diff/unit.js +45 -16
- package/lib/{git-branches.js → git/branches.js} +53 -48
- package/lib/{git-deps.js → git/deps.js} +123 -96
- package/lib/git/git.js +71 -0
- package/lib/git/worktree.js +475 -0
- package/lib/highlight/bash.js +1 -2
- package/lib/highlight/csv.js +1 -2
- package/lib/highlight/dot.js +1 -2
- package/lib/{highlight.js → highlight/highlight.js} +42 -11
- package/lib/highlight/html.js +20 -18
- package/lib/highlight/json.js +1 -2
- package/lib/highlight/log.js +1 -2
- package/lib/highlight/txt.js +1 -2
- package/lib/{session → input}/actions.js +111 -48
- package/lib/{editor.js → input/editor.js} +7 -8
- package/lib/{keys.js → input/keys.js} +15 -43
- package/lib/input/line-edit.js +77 -0
- package/lib/input/menu-pick.js +70 -0
- package/lib/render/agents.js +640 -0
- package/lib/render/branches.js +4 -5
- package/lib/render/commits.js +54 -64
- package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
- package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
- package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
- package/lib/render/diff.js +166 -45
- package/lib/render/field.js +131 -0
- package/lib/render/files.js +9 -14
- package/lib/render/footer.js +383 -0
- package/lib/render/frame.js +123 -25
- package/lib/render/header.js +176 -0
- package/lib/render/log.js +319 -0
- package/lib/render/menu.js +60 -0
- package/lib/render/notes.js +16 -11
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +9 -13
- package/lib/render/primitives.js +101 -15
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +13 -25
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +13 -60
- package/lib/render/tiles.js +109 -48
- package/lib/render/unit.js +32 -13
- package/lib/{report-model.js → report/model.js} +4 -6
- package/lib/{report-parse.js → report/parse.js} +9 -14
- package/lib/{report-render.js → report/render.js} +46 -14
- package/lib/{report-run.js → report/run.js} +42 -30
- package/lib/{report-test.js → report/test.js} +56 -55
- package/lib/{report-tools.js → report/tools.js} +24 -33
- package/lib/review/agent-instructions.md +9 -0
- package/lib/review/format.js +426 -0
- package/lib/{review-merge.js → review/merge.js} +9 -8
- package/lib/review/review.js +190 -0
- package/lib/review/store.js +281 -0
- package/lib/review/templates.js +83 -0
- package/lib/runs/commands.js +14 -0
- package/lib/runs/logs.js +299 -0
- package/lib/runs/output.js +174 -0
- package/lib/runs/process.js +113 -0
- package/lib/{runs.js → runs/runs.js} +84 -36
- package/lib/runs/scripts.js +150 -0
- package/lib/session/agents/agents.js +958 -0
- package/lib/session/agents/job.js +320 -0
- package/lib/session/agents/jobs.js +355 -0
- package/lib/session/agents/options.js +153 -0
- package/lib/session/agents/plans.js +83 -0
- package/lib/session/branches.js +33 -39
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +91 -52
- package/lib/session/compose.js +181 -145
- package/lib/session/dashboard.js +23 -22
- package/lib/session/dispatch.js +85 -21
- package/lib/session/files-pane.js +47 -26
- package/lib/session/find.js +16 -29
- package/lib/session/import.js +82 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +75 -72
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +6 -3
- package/lib/session/npm/history.js +249 -0
- package/lib/session/npm/npm.js +815 -0
- package/lib/session/{packages.js → npm/packages.js} +21 -14
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +202 -10
- package/lib/session/prefs.js +112 -0
- package/lib/session/reads.js +109 -0
- package/lib/session/review.js +28 -10
- package/lib/session/run-panel.js +40 -0
- package/lib/{session.js → session/session.js} +156 -63
- package/lib/session/tasks.js +320 -65
- package/lib/session/templates.js +1 -1
- package/lib/session/terminal.js +94 -12
- package/lib/session/unit.js +35 -30
- package/lib/session/update.js +194 -6
- package/lib/session/view.js +65 -65
- package/lib/session/workspace.js +39 -78
- package/lib/{capabilities.js → source/capabilities.js} +3 -12
- package/lib/{github.js → source/github.js} +102 -32
- package/lib/{gitlab.js → source/gitlab.js} +87 -10
- package/lib/{remote.js → source/remote.js} +144 -40
- package/lib/source/review-threads.graphql +20 -0
- package/lib/source/source.js +74 -0
- package/lib/term/ansi.js +396 -0
- package/lib/{clipboard.js → term/clipboard.js} +3 -6
- package/lib/{select.js → term/select.js} +2 -2
- package/lib/term/themes/dark.json +122 -0
- package/lib/term/themes/light.json +122 -0
- package/lib/term/wide.txt +21 -0
- package/lib/{wrap.js → term/wrap.js} +4 -4
- package/package.json +2 -2
- package/lib/ansi.js +0 -545
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -455
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -806
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/render/search.js +0 -25
- package/lib/review.js +0 -745
- package/lib/session/accessors.js +0 -26
- package/lib/session/edit-click.js +0 -57
- package/lib/session/npm.js +0 -452
- package/lib/session/progress.js +0 -41
- package/lib/source.js +0 -36
- package/lib/update.js +0 -192
- /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
|
@@ -3,14 +3,15 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
|
-
const runs = require('../runs.js');
|
|
6
|
+
const runs = require('../runs/runs.js');
|
|
7
7
|
const { isRunsRel } = runs;
|
|
8
|
+
const { REVIEW_DIR, posixRel } = require('../common/files.js');
|
|
8
9
|
|
|
9
10
|
const DEBOUNCE_MS = 200;
|
|
10
11
|
const UNKNOWN_PATH = '.';
|
|
11
12
|
const MODULES_STAMP = 'node_modules/.package-lock.json';
|
|
12
13
|
|
|
13
|
-
const SKIP_DIRS = ['node_modules',
|
|
14
|
+
const SKIP_DIRS = ['node_modules', REVIEW_DIR, 'coverage', 'dist', '.cache'];
|
|
14
15
|
|
|
15
16
|
const GIT_WATCH_NAMES = [
|
|
16
17
|
'HEAD',
|
|
@@ -23,32 +24,31 @@ const GIT_WATCH_NAMES = [
|
|
|
23
24
|
'rebase-apply',
|
|
24
25
|
];
|
|
25
26
|
|
|
26
|
-
const canWatchRecursive = (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
const [major, minor] = `${version}`.split('.').map(Number);
|
|
27
|
+
const canWatchRecursive = (platform, version) => {
|
|
28
|
+
const host = platform ?? process.platform;
|
|
29
|
+
const node = version ?? process.versions.node;
|
|
30
|
+
if (host === 'win32' || host === 'darwin') return true;
|
|
31
|
+
if (host !== 'linux') return false;
|
|
32
|
+
const [major, minor] = `${node}`.split('.').map(Number);
|
|
33
33
|
return major > 19 || (major === 19 && minor >= 1);
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
const IGNORED_SUFFIXES = ['.lock', '~', '.swp', '.swo'];
|
|
37
37
|
|
|
38
|
-
const ignoredName = (name) =>
|
|
39
|
-
IGNORED_SUFFIXES.some((
|
|
40
|
-
name.startsWith('.#')
|
|
41
|
-
name === '.DS_Store';
|
|
38
|
+
const ignoredName = (name) => {
|
|
39
|
+
const suffix = IGNORED_SUFFIXES.some((item) => name.endsWith(item));
|
|
40
|
+
const backup = name.startsWith('.#');
|
|
41
|
+
const store = name === '.DS_Store';
|
|
42
|
+
return suffix || backup || store;
|
|
43
|
+
};
|
|
42
44
|
|
|
43
45
|
const ignoredGitRel = ([, name = '', sub]) => {
|
|
44
46
|
if (name === 'logs' && sub === 'HEAD') return false;
|
|
45
47
|
return name !== '' && !GIT_WATCH_NAMES.includes(name);
|
|
46
48
|
};
|
|
47
49
|
|
|
48
|
-
const slashRel = (rel) => `${rel}`.replaceAll('\\', '/');
|
|
49
|
-
|
|
50
50
|
const ignoredRel = (rel) => {
|
|
51
|
-
const norm =
|
|
51
|
+
const norm = posixRel(rel);
|
|
52
52
|
if (!norm || norm === '.') return false;
|
|
53
53
|
if (norm === MODULES_STAMP) return false;
|
|
54
54
|
const parts = norm.split('/');
|
|
@@ -60,15 +60,16 @@ const ignoredRel = (rel) => {
|
|
|
60
60
|
const skipDirName = (name) => name === '.git' || SKIP_DIRS.includes(name);
|
|
61
61
|
|
|
62
62
|
const reviewMarkdown = (rel) => {
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
const prefix = `${REVIEW_DIR}/`;
|
|
64
|
+
if (!rel.startsWith(prefix)) return false;
|
|
65
|
+
const rest = rel.slice(prefix.length);
|
|
65
66
|
if (!rest || rest.includes('/')) return false;
|
|
66
67
|
return rest.endsWith('.md');
|
|
67
68
|
};
|
|
68
69
|
|
|
69
70
|
const isReviewDir = (root, dir) => {
|
|
70
|
-
const rel =
|
|
71
|
-
return rel ===
|
|
71
|
+
const rel = posixRel(path.relative(root, dir));
|
|
72
|
+
return rel === REVIEW_DIR || rel.startsWith(`${REVIEW_DIR}/`);
|
|
72
73
|
};
|
|
73
74
|
|
|
74
75
|
const hasFile = (full) => {
|
|
@@ -84,13 +85,13 @@ const isBareReviewName = (root, dir, rel) => {
|
|
|
84
85
|
if (!rel || rel.includes('/')) return false;
|
|
85
86
|
const base = path.basename(rel);
|
|
86
87
|
if (hasFile(path.join(dir, base))) return false;
|
|
87
|
-
return hasFile(path.join(root,
|
|
88
|
+
return hasFile(path.join(root, REVIEW_DIR, base));
|
|
88
89
|
};
|
|
89
90
|
|
|
90
91
|
const isReviewEvent = (root, dir, name) => {
|
|
91
92
|
if (isReviewDir(root, dir)) return true;
|
|
92
93
|
if (!name) return false;
|
|
93
|
-
const rel =
|
|
94
|
+
const rel = posixRel(path.relative(root, path.join(dir, name)));
|
|
94
95
|
if (reviewMarkdown(rel)) return true;
|
|
95
96
|
return isBareReviewName(root, dir, rel);
|
|
96
97
|
};
|
|
@@ -98,7 +99,7 @@ const isReviewEvent = (root, dir, name) => {
|
|
|
98
99
|
const isOwnDirEvent = (dir, name) => {
|
|
99
100
|
if (!name) return false;
|
|
100
101
|
const base = path.basename(dir);
|
|
101
|
-
const slash =
|
|
102
|
+
const slash = posixRel(name);
|
|
102
103
|
const bare = slash.startsWith('./') ? slash.slice(2) : slash;
|
|
103
104
|
const trimmed = bare.endsWith('/') ? bare.slice(0, -1) : bare;
|
|
104
105
|
if (trimmed === base) {
|
|
@@ -153,8 +154,9 @@ class DiskWatcher {
|
|
|
153
154
|
this.watchOne(gitDir);
|
|
154
155
|
this.watchOne(path.join(gitDir, 'refs'));
|
|
155
156
|
this.watchOne(path.join(gitDir, 'logs'));
|
|
157
|
+
this.watchOne(path.resolve(this.root, runs.logDir('.')));
|
|
156
158
|
this.watchOne(path.resolve(this.root, runs.runsDir('.')));
|
|
157
|
-
this.watchOne(path.resolve(this.root,
|
|
159
|
+
this.watchOne(path.resolve(this.root, REVIEW_DIR));
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
schedule(key, fire) {
|
|
@@ -211,9 +213,11 @@ class DiskWatcher {
|
|
|
211
213
|
onEvent(dir, eventType, filename) {
|
|
212
214
|
if (this.closed) return;
|
|
213
215
|
const name = filename ? `${filename}` : '';
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
216
|
+
let rel = '';
|
|
217
|
+
if (name) {
|
|
218
|
+
const full = path.join(dir, name);
|
|
219
|
+
rel = posixRel(path.relative(this.root, full));
|
|
220
|
+
}
|
|
217
221
|
const wanted = this.files.has(rel);
|
|
218
222
|
if (isRunsRel(rel)) {
|
|
219
223
|
this.pending.add(rel);
|
|
@@ -260,12 +264,13 @@ class DiskWatcher {
|
|
|
260
264
|
watchTree(dir) {
|
|
261
265
|
this.watchOne(dir, this.recursive);
|
|
262
266
|
if (this.recursive) return;
|
|
263
|
-
let entries;
|
|
267
|
+
let entries = null;
|
|
264
268
|
try {
|
|
265
269
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
266
270
|
} catch {
|
|
267
|
-
|
|
271
|
+
// unreadable directory
|
|
268
272
|
}
|
|
273
|
+
if (!entries) return;
|
|
269
274
|
for (const entry of entries) {
|
|
270
275
|
if (!entry.isDirectory() || skipDirName(entry.name)) continue;
|
|
271
276
|
this.watchTree(path.join(dir, entry.name));
|
|
@@ -280,7 +285,7 @@ class DiskWatcher {
|
|
|
280
285
|
return;
|
|
281
286
|
}
|
|
282
287
|
const name = path.basename(full);
|
|
283
|
-
if (name ===
|
|
288
|
+
if (name === REVIEW_DIR) this.watchOne(full);
|
|
284
289
|
else if (!skipDirName(name)) this.watchTree(full);
|
|
285
290
|
}
|
|
286
291
|
|
|
@@ -299,7 +304,7 @@ class DiskWatcher {
|
|
|
299
304
|
for (const rel of rels) {
|
|
300
305
|
if (!rel) continue;
|
|
301
306
|
const full = path.resolve(this.root, rel);
|
|
302
|
-
this.files.add(
|
|
307
|
+
this.files.add(posixRel(path.relative(this.root, full)));
|
|
303
308
|
this.watchOne(path.dirname(full));
|
|
304
309
|
}
|
|
305
310
|
}
|
|
@@ -3,16 +3,13 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const review = require('./review.js');
|
|
6
|
+
const { REVIEW_DIR } = require('../common/files.js');
|
|
7
|
+
const review = require('../review/review.js');
|
|
9
8
|
const { listReviewNames, latestReviewName, loadReview, noteCounts } = review;
|
|
10
|
-
const
|
|
11
|
-
const {
|
|
12
|
-
const
|
|
13
|
-
const {
|
|
14
|
-
const runs = require('./runs.js');
|
|
15
|
-
const { readRuns } = runs;
|
|
9
|
+
const { countUntrackedAdded } = require('../git/worktree.js');
|
|
10
|
+
const { gitText, countOf } = require('./git.js');
|
|
11
|
+
const { runName } = require('./model.js');
|
|
12
|
+
const { readRuns } = require('../runs/runs.js');
|
|
16
13
|
|
|
17
14
|
const emptyDiff = () => ({
|
|
18
15
|
stagedAdded: 0,
|
|
@@ -31,12 +28,13 @@ const isRepoDir = (dir) => {
|
|
|
31
28
|
};
|
|
32
29
|
|
|
33
30
|
const listChildRepos = (dir) => {
|
|
34
|
-
let entries;
|
|
31
|
+
let entries = null;
|
|
35
32
|
try {
|
|
36
33
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
37
34
|
} catch {
|
|
38
|
-
|
|
35
|
+
// unreadable directory
|
|
39
36
|
}
|
|
37
|
+
if (!entries) return [];
|
|
40
38
|
const repos = [];
|
|
41
39
|
for (const entry of entries) {
|
|
42
40
|
if (!entry.isDirectory() || entry.name.startsWith('.')) continue;
|
|
@@ -49,12 +47,13 @@ const listChildRepos = (dir) => {
|
|
|
49
47
|
};
|
|
50
48
|
|
|
51
49
|
const workspaceAt = (dir) => {
|
|
52
|
-
let stat;
|
|
50
|
+
let stat = null;
|
|
53
51
|
try {
|
|
54
52
|
stat = fs.statSync(dir);
|
|
55
53
|
} catch {
|
|
56
|
-
|
|
54
|
+
// missing path
|
|
57
55
|
}
|
|
56
|
+
if (!stat) return null;
|
|
58
57
|
if (!stat.isDirectory() || isRepoDir(dir)) return null;
|
|
59
58
|
const repos = listChildRepos(dir);
|
|
60
59
|
if (!repos.length) return null;
|
|
@@ -89,22 +88,18 @@ const sumNumstat = (text) => {
|
|
|
89
88
|
};
|
|
90
89
|
|
|
91
90
|
const readNumstat = async (dir, cached) => {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const result = await runGitAsync(args, dir);
|
|
97
|
-
if (result.status !== 0) return { added: 0, removed: 0 };
|
|
98
|
-
return sumNumstat(result.stdout);
|
|
99
|
-
} catch {
|
|
100
|
-
return { added: 0, removed: 0 };
|
|
101
|
-
}
|
|
91
|
+
const stagedArgs = ['diff', '--cached', '--numstat'];
|
|
92
|
+
const openArgs = ['diff', '--numstat'];
|
|
93
|
+
const args = cached ? stagedArgs : openArgs;
|
|
94
|
+
return sumNumstat(await gitText(dir, args));
|
|
102
95
|
};
|
|
103
96
|
|
|
104
97
|
const readDiffStat = async (dir) => {
|
|
105
|
-
const staged = await
|
|
106
|
-
|
|
107
|
-
|
|
98
|
+
const [staged, open, untracked] = await Promise.all([
|
|
99
|
+
readNumstat(dir, true),
|
|
100
|
+
readNumstat(dir, false),
|
|
101
|
+
countUntrackedAdded(dir),
|
|
102
|
+
]);
|
|
108
103
|
return {
|
|
109
104
|
stagedAdded: staged.added,
|
|
110
105
|
unstagedAdded: open.added + untracked,
|
|
@@ -113,16 +108,8 @@ const readDiffStat = async (dir) => {
|
|
|
113
108
|
};
|
|
114
109
|
};
|
|
115
110
|
|
|
116
|
-
const countCommits = async (dir) =>
|
|
117
|
-
|
|
118
|
-
const result = await runGitAsync(['rev-list', '--count', 'HEAD'], dir);
|
|
119
|
-
if (result.status !== 0) return 0;
|
|
120
|
-
const value = parseInt(result.stdout.trim(), 10);
|
|
121
|
-
return Number.isFinite(value) ? value : 0;
|
|
122
|
-
} catch {
|
|
123
|
-
return 0;
|
|
124
|
-
}
|
|
125
|
-
};
|
|
111
|
+
const countCommits = async (dir) =>
|
|
112
|
+
countOf(await gitText(dir, ['rev-list', '--count', 'HEAD']));
|
|
126
113
|
|
|
127
114
|
const taskStats = (dir) => {
|
|
128
115
|
const latest = latestReviewName(listReviewNames(dir));
|
|
@@ -154,8 +141,6 @@ const pickRun = (dir) => {
|
|
|
154
141
|
|
|
155
142
|
module.exports = {
|
|
156
143
|
emptyDiff,
|
|
157
|
-
isRepoDir,
|
|
158
|
-
listChildRepos,
|
|
159
144
|
workspaceAt,
|
|
160
145
|
workspaceFromArgs,
|
|
161
146
|
sumNumstat,
|
|
@@ -2,18 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
7
|
-
const
|
|
8
|
-
const { itemPath } = files;
|
|
9
|
-
const utilities = require('./utilities.js');
|
|
10
|
-
const { asText } = utilities;
|
|
5
|
+
const { jsonParse, isHashObject } = require('metautil');
|
|
6
|
+
const { itemPath, REVIEW_DIR } = require('../common/files.js');
|
|
7
|
+
const { asText, readText, readJson } = require('../common/utilities.js');
|
|
11
8
|
const manifest = require('./manifest.js');
|
|
12
9
|
const { MANIFEST, LOCKFILE, SECTION_KEYS, isObject } = manifest;
|
|
13
10
|
const { depFileMeta, relOf, listFilePath, readSections } = manifest;
|
|
14
11
|
const { lockEntries, findDeclared, applyWantedRange } = manifest;
|
|
15
12
|
const { proposedLockVersion, proposedWanted } = manifest;
|
|
16
|
-
const depDiff = require('./
|
|
13
|
+
const depDiff = require('./diff.js');
|
|
17
14
|
const { summarizeGroup, collectGroups, takeManifestItems } = depDiff;
|
|
18
15
|
const { foldedDepNames, stripFoldedDepItem } = depDiff;
|
|
19
16
|
const { makeDepItems, stubFileItem } = depDiff;
|
|
@@ -26,17 +23,9 @@ const SPEC_PATTERNS = [
|
|
|
26
23
|
/import\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
27
24
|
];
|
|
28
25
|
const SCRIPT_RUNNERS = ['npx', 'npm', 'node'];
|
|
29
|
-
const SKIP_WALK = ['node_modules', '.git',
|
|
26
|
+
const SKIP_WALK = ['node_modules', '.git', REVIEW_DIR, 'coverage', 'dist'];
|
|
30
27
|
const SEVERITY_RANK = { critical: 4, high: 3, moderate: 2, low: 1, info: 0 };
|
|
31
28
|
|
|
32
|
-
const readText = (file) => {
|
|
33
|
-
try {
|
|
34
|
-
return fs.readFileSync(file, 'utf8');
|
|
35
|
-
} catch {
|
|
36
|
-
return '';
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
|
|
40
29
|
const toPackageName = (spec) => {
|
|
41
30
|
if (!spec || spec.startsWith('.') || spec.startsWith('/')) return '';
|
|
42
31
|
if (spec.startsWith('node:')) return '';
|
|
@@ -102,12 +91,13 @@ const pkgExportsLibrary = (pkg) => {
|
|
|
102
91
|
};
|
|
103
92
|
|
|
104
93
|
const walkUsedNames = (used, dir) => {
|
|
105
|
-
let entries;
|
|
94
|
+
let entries = null;
|
|
106
95
|
try {
|
|
107
96
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
108
97
|
} catch {
|
|
109
|
-
|
|
98
|
+
// unreadable directory
|
|
110
99
|
}
|
|
100
|
+
if (!entries) return;
|
|
111
101
|
for (const entry of entries) {
|
|
112
102
|
if (entry.isSymbolicLink()) continue;
|
|
113
103
|
const full = path.join(dir, entry.name);
|
|
@@ -115,7 +105,7 @@ const walkUsedNames = (used, dir) => {
|
|
|
115
105
|
const skip = SKIP_WALK.includes(entry.name) || entry.name.startsWith('.');
|
|
116
106
|
if (!skip) walkUsedNames(used, full);
|
|
117
107
|
} else if (entry.name === MANIFEST) {
|
|
118
|
-
collectUsedFromScripts(used,
|
|
108
|
+
collectUsedFromScripts(used, readJson(full)?.scripts);
|
|
119
109
|
} else if (SOURCE_EXT.includes(path.extname(entry.name))) {
|
|
120
110
|
collectUsedFromText(used, readText(full));
|
|
121
111
|
}
|
|
@@ -160,7 +150,7 @@ const collectExportEntries = (root, pkgDir, names) => {
|
|
|
160
150
|
};
|
|
161
151
|
|
|
162
152
|
const collectExportMeta = (root) => {
|
|
163
|
-
const pkg =
|
|
153
|
+
const pkg = readJson(path.join(root, MANIFEST));
|
|
164
154
|
const maps = readSections(pkg);
|
|
165
155
|
const names = SECTION_KEYS.flatMap((key) => [...maps[key].keys()]);
|
|
166
156
|
return collectExportEntries(root, '.', names);
|
|
@@ -288,8 +278,11 @@ const markChanges = (changes, report, kind) => {
|
|
|
288
278
|
|
|
289
279
|
const depExports = (exportEntries, name) => exportEntries?.get(name) === true;
|
|
290
280
|
|
|
291
|
-
const isAddedDep = (change) =>
|
|
292
|
-
|
|
281
|
+
const isAddedDep = (change) => {
|
|
282
|
+
const added = change.action === 'added';
|
|
283
|
+
const declared = change.section !== 'resolved';
|
|
284
|
+
return added && declared;
|
|
285
|
+
};
|
|
293
286
|
|
|
294
287
|
const unusedChangeNames = (changes, used) => {
|
|
295
288
|
if (!used || !used.size) return [];
|
|
@@ -330,15 +323,10 @@ const unusedDeclaredChanges = (pkg, used, exportEntries) => {
|
|
|
330
323
|
taken.add(name);
|
|
331
324
|
const action = 'removed';
|
|
332
325
|
const to = '';
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
from,
|
|
338
|
-
to,
|
|
339
|
-
propose: true,
|
|
340
|
-
unused: true,
|
|
341
|
-
});
|
|
326
|
+
const propose = true;
|
|
327
|
+
const unused = true;
|
|
328
|
+
const change = { name, section, action, from, to, propose, unused };
|
|
329
|
+
changes.push(change);
|
|
342
330
|
}
|
|
343
331
|
return changes;
|
|
344
332
|
};
|
|
@@ -438,28 +426,26 @@ const placeFolded = (items, { folded, synthetic, leftoverNames }) => {
|
|
|
438
426
|
return spliceAtSlots(out, synthetic, slot);
|
|
439
427
|
};
|
|
440
428
|
|
|
441
|
-
const foldDepItems = (
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
extra = {},
|
|
448
|
-
) => {
|
|
429
|
+
const foldDepItems = (items, readSides, usedNames, audit, outdated, extra) => {
|
|
430
|
+
const read = readSides ?? (() => ({ oldText: '', newText: '' }));
|
|
431
|
+
const used = usedNames ?? null;
|
|
432
|
+
const auditMap = audit ?? null;
|
|
433
|
+
const outdatedMap = outdated ?? null;
|
|
434
|
+
const options = extra ?? {};
|
|
449
435
|
const groups = collectGroups(items);
|
|
450
436
|
if (!groups.size) return items;
|
|
451
437
|
const folded = new Set();
|
|
452
438
|
const synthetic = new Map();
|
|
453
439
|
const leftoverNames = new Map();
|
|
454
440
|
for (const [key, group] of groups) {
|
|
455
|
-
const summary = summarizeGroup(group,
|
|
456
|
-
const unusedNames = unusedChangeNames(summary.changes,
|
|
457
|
-
const exportEntries = loadExportEntries(unusedNames,
|
|
441
|
+
const summary = summarizeGroup(group, read);
|
|
442
|
+
const unusedNames = unusedChangeNames(summary.changes, used);
|
|
443
|
+
const exportEntries = loadExportEntries(unusedNames, options, group.dir);
|
|
458
444
|
const { pkg } = summary;
|
|
459
445
|
let changes = summary.changes;
|
|
460
|
-
changes = markUnusedChanges(changes,
|
|
461
|
-
changes = markChanges(changes,
|
|
462
|
-
changes = markChanges(changes,
|
|
446
|
+
changes = markUnusedChanges(changes, used, pkg, exportEntries);
|
|
447
|
+
changes = markChanges(changes, auditMap, 'audit');
|
|
448
|
+
changes = markChanges(changes, outdatedMap, 'outdated');
|
|
463
449
|
const next = { ...summary, changes };
|
|
464
450
|
leftoverNames.set(key, foldedDepNames(changes));
|
|
465
451
|
const taken = takeManifestItems(next);
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const { itemPath } = files;
|
|
7
|
-
const patch = require('./diff/patch.js');
|
|
3
|
+
const { jsonParse } = require('metautil');
|
|
4
|
+
const { itemPath } = require('../common/files.js');
|
|
5
|
+
const patch = require('../diff/patch.js');
|
|
8
6
|
const { countSides, formatHunkHeader, formatPatch } = patch;
|
|
9
|
-
const
|
|
10
|
-
const { isCtxType } = diff;
|
|
7
|
+
const { isCtxType } = require('../diff/diff.js');
|
|
11
8
|
const manifest = require('./manifest.js');
|
|
12
9
|
const { MANIFEST, LOCKFILE, SECTION_KEYS, depFileMeta, relOf } = manifest;
|
|
13
10
|
const { listFilePath, readSections, emptySectionMaps } = manifest;
|
|
@@ -66,8 +63,10 @@ const lineEntryName = (text) => {
|
|
|
66
63
|
return entry ? entry[1] : '';
|
|
67
64
|
};
|
|
68
65
|
|
|
69
|
-
const lineInBlock = (item, line) =>
|
|
70
|
-
typeof item.blockId !== 'number'
|
|
66
|
+
const lineInBlock = (item, line) => {
|
|
67
|
+
const open = typeof item.blockId !== 'number';
|
|
68
|
+
return open || line.blockId === item.blockId;
|
|
69
|
+
};
|
|
71
70
|
|
|
72
71
|
const hunkOnlyFoldedDeps = (item, names) => {
|
|
73
72
|
if (!item.hunk || !names.size) return false;
|
|
@@ -86,8 +85,10 @@ const hunkOnlyFoldedDeps = (item, names) => {
|
|
|
86
85
|
|
|
87
86
|
const stripFoldedDepItem = (item, names) => {
|
|
88
87
|
if (!item.hunk || !names.size) return item;
|
|
89
|
-
const isFolded = (line) =>
|
|
90
|
-
line.type !== 'ctx'
|
|
88
|
+
const isFolded = (line) => {
|
|
89
|
+
const changed = line.type !== 'ctx';
|
|
90
|
+
return changed && names.has(lineEntryName(line.text));
|
|
91
|
+
};
|
|
91
92
|
const lines = item.hunk.lines.filter((line) => !isFolded(line));
|
|
92
93
|
if (lines.length === item.hunk.lines.length) return item;
|
|
93
94
|
const isEdit = (line) => line.type !== 'ctx' && lineInBlock(item, line);
|
|
@@ -164,8 +165,15 @@ const mergeResolved = (changes, oldResolved, newResolved, oldMaps, newMaps) => {
|
|
|
164
165
|
const action = changeAction(from, to);
|
|
165
166
|
const resolvedFrom = from;
|
|
166
167
|
const resolvedTo = to;
|
|
167
|
-
|
|
168
|
-
|
|
168
|
+
merged.push({
|
|
169
|
+
name,
|
|
170
|
+
section: 'resolved',
|
|
171
|
+
action,
|
|
172
|
+
from,
|
|
173
|
+
to,
|
|
174
|
+
resolvedFrom,
|
|
175
|
+
resolvedTo,
|
|
176
|
+
});
|
|
169
177
|
}
|
|
170
178
|
return merged;
|
|
171
179
|
};
|
|
@@ -194,29 +202,31 @@ const readJsonSides = (readSides, origin, rel) => {
|
|
|
194
202
|
|
|
195
203
|
const sectionMapsOf = (pkg, lock, manifestItems) => {
|
|
196
204
|
if (pkg.before || pkg.after) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
};
|
|
205
|
+
const oldMaps = readSections(pkg.before);
|
|
206
|
+
const newMaps = readSections(pkg.after);
|
|
207
|
+
return { oldMaps, newMaps };
|
|
201
208
|
}
|
|
202
209
|
if (manifestItems.length) return mapsFromHunks(manifestItems);
|
|
203
210
|
const oldMaps = lockRootSections(lock.before);
|
|
204
|
-
|
|
211
|
+
const newMaps = lockRootSections(lock.after);
|
|
212
|
+
return { oldMaps, newMaps };
|
|
205
213
|
};
|
|
206
214
|
|
|
207
215
|
const manifestPolicy = (manifestItems, pkg, hasChanges) => {
|
|
208
216
|
if (!manifestItems.length) {
|
|
209
|
-
|
|
217
|
+
const foldManifest = false;
|
|
218
|
+
const takeAllManifest = false;
|
|
219
|
+
return { foldManifest, takeAllManifest };
|
|
210
220
|
}
|
|
211
221
|
if (pkg.before || pkg.after) {
|
|
212
222
|
const onlyDeps = !hasFieldChanges(pkg.before, pkg.after);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
};
|
|
223
|
+
const foldManifest = hasChanges;
|
|
224
|
+
const takeAllManifest = hasChanges && onlyDeps;
|
|
225
|
+
return { foldManifest, takeAllManifest };
|
|
217
226
|
}
|
|
218
|
-
const
|
|
219
|
-
|
|
227
|
+
const foldManifest = hasChanges && manifestItems.every(isDepOnlyItem);
|
|
228
|
+
const takeAllManifest = foldManifest;
|
|
229
|
+
return { foldManifest, takeAllManifest };
|
|
220
230
|
};
|
|
221
231
|
|
|
222
232
|
const summarizeGroup = (group, readSides) => {
|
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const MANIFEST = 'package.json';
|
|
10
|
-
const LOCKFILE = 'package-lock.json';
|
|
11
|
-
const DEP_KIND = { [MANIFEST]: 'manifest', [LOCKFILE]: 'lockfile' };
|
|
3
|
+
const { isHashObject, jsonParse } = require('metautil');
|
|
4
|
+
const { parseVersion, cmpVersion } = require('../common/utilities.js');
|
|
5
|
+
const files = require('../common/files.js');
|
|
6
|
+
const { MANIFEST, LOCKFILE, depFileMeta, relOf, listFilePath } = files;
|
|
7
|
+
|
|
12
8
|
const SECTION_KEYS = [
|
|
13
9
|
'dependencies',
|
|
14
10
|
'devDependencies',
|
|
@@ -19,23 +15,6 @@ const LOCK_PREFIX = 'node_modules/';
|
|
|
19
15
|
|
|
20
16
|
const isObject = (value) => !!value && typeof value === 'object';
|
|
21
17
|
|
|
22
|
-
const posixRel = (rel) => `${rel ?? ''}`.replaceAll('\\', '/');
|
|
23
|
-
|
|
24
|
-
const depFileMeta = (rel) => {
|
|
25
|
-
const normalized = posixRel(rel);
|
|
26
|
-
const kind = DEP_KIND[path.posix.basename(normalized)];
|
|
27
|
-
if (!normalized || !kind) return null;
|
|
28
|
-
return { kind, dir: path.posix.dirname(normalized) };
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const relOf = (dir, name) => (dir === '.' ? name : `${dir}/${name}`);
|
|
32
|
-
|
|
33
|
-
const listFilePath = (rel) => {
|
|
34
|
-
const meta = depFileMeta(rel);
|
|
35
|
-
if (meta && meta.kind === 'lockfile') return relOf(meta.dir, MANIFEST);
|
|
36
|
-
return posixRel(rel);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
18
|
const readSectionMap = (pkg, key) => {
|
|
40
19
|
const map = new Map();
|
|
41
20
|
const raw = pkg ? pkg[key] : null;
|
|
@@ -155,14 +134,17 @@ const seedManifest = (oldObj, newObj) => {
|
|
|
155
134
|
return base;
|
|
156
135
|
};
|
|
157
136
|
|
|
137
|
+
const mergedBase = (parsedBase, oldObj, newObj, kind) => {
|
|
138
|
+
if (parsedBase) return cloneJson(parsedBase);
|
|
139
|
+
if (kind === 'manifest') return seedManifest(oldObj, newObj);
|
|
140
|
+
return cloneJson(oldObj) || {};
|
|
141
|
+
};
|
|
142
|
+
|
|
158
143
|
const mergeDepFile = (baseText, oldText, newText, change, side, kind) => {
|
|
159
144
|
const oldObj = jsonParse(oldText ?? '');
|
|
160
145
|
const newObj = jsonParse(newText ?? '');
|
|
161
146
|
const parsedBase = jsonParse(baseText ?? '');
|
|
162
|
-
|
|
163
|
-
if (parsedBase) base = cloneJson(parsedBase);
|
|
164
|
-
else if (kind === 'manifest') base = seedManifest(oldObj, newObj);
|
|
165
|
-
else base = cloneJson(oldObj) || {};
|
|
147
|
+
const base = mergedBase(parsedBase, oldObj, newObj, kind);
|
|
166
148
|
if (kind === 'lockfile') applyLockChange(base, change, side, oldObj, newObj);
|
|
167
149
|
else applyManifestChange(base, change, side);
|
|
168
150
|
return stringifyJson(base, baseText || newText || oldText || '');
|
|
@@ -247,7 +229,42 @@ const proposedWanted = (out, vul) => {
|
|
|
247
229
|
return '';
|
|
248
230
|
};
|
|
249
231
|
|
|
232
|
+
const cmpText = (left, right) => {
|
|
233
|
+
const a = parseVersion(left);
|
|
234
|
+
const b = parseVersion(right);
|
|
235
|
+
if (!a || !b) return null;
|
|
236
|
+
return cmpVersion(a, b);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const boundHolds = (cmp, op) => {
|
|
240
|
+
if (op === '<') return cmp < 0;
|
|
241
|
+
if (op === '<=') return cmp <= 0;
|
|
242
|
+
if (op === '>') return cmp > 0;
|
|
243
|
+
return cmp >= 0;
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const inClause = (version, clause) => {
|
|
247
|
+
const ver = parseVersion(version);
|
|
248
|
+
if (!ver) return false;
|
|
249
|
+
const bounds = [...clause.matchAll(/(>=|<=|>|<)\s*(\d+\.\d+\.\d+)/g)];
|
|
250
|
+
if (!bounds.length) return false;
|
|
251
|
+
for (const match of bounds) {
|
|
252
|
+
const bound = parseVersion(match[2]);
|
|
253
|
+
if (!bound) return false;
|
|
254
|
+
if (!boundHolds(cmpVersion(ver, bound), match[1])) return false;
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
const inRange = (version, range) =>
|
|
260
|
+
`${range ?? ''}`.split('||').some((clause) => inClause(version, clause));
|
|
261
|
+
|
|
262
|
+
const hasRange = (range) => /[<>]=?\s*\d+\.\d+\.\d+/.test(`${range ?? ''}`);
|
|
263
|
+
|
|
250
264
|
module.exports = {
|
|
265
|
+
cmpText,
|
|
266
|
+
inRange,
|
|
267
|
+
hasRange,
|
|
251
268
|
MANIFEST,
|
|
252
269
|
LOCKFILE,
|
|
253
270
|
SECTION_KEYS,
|
package/lib/diff/align.js
CHANGED
package/lib/diff/diff.js
CHANGED
|
@@ -5,7 +5,6 @@ const align = require('./align.js');
|
|
|
5
5
|
const parse = require('./parse.js');
|
|
6
6
|
const patch = require('./patch.js');
|
|
7
7
|
const display = require('./display.js');
|
|
8
|
-
const edit = require('./edit.js');
|
|
9
8
|
const items = require('./items.js');
|
|
10
9
|
const unit = require('./unit.js');
|
|
11
10
|
|
|
@@ -19,7 +18,8 @@ module.exports = {
|
|
|
19
18
|
flattenBlock: patch.flattenBlock,
|
|
20
19
|
formatPatch: patch.formatPatch,
|
|
21
20
|
...display,
|
|
22
|
-
|
|
21
|
+
addBlockRange: patch.addBlockRange,
|
|
22
|
+
replaceBlockAdds: patch.replaceBlockAdds,
|
|
23
23
|
...items,
|
|
24
24
|
...unit,
|
|
25
25
|
};
|