reslop 0.2.6 → 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 +27 -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 +61 -66
- package/lib/{files.js → common/files.js} +51 -52
- package/lib/common/find.js +61 -0
- 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 +117 -51
- package/lib/dashboard/npm.js +133 -32
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +41 -31
- package/lib/dashboard/workspace.js +151 -0
- 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 -95
- 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 +240 -71
- package/lib/{editor.js → input/editor.js} +7 -8
- package/lib/{keys.js → input/keys.js} +36 -39
- 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/dashboard/blocks.js +490 -0
- 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 +22 -8
- package/lib/render/footer.js +383 -0
- package/lib/render/frame.js +127 -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 +29 -13
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +89 -0
- package/lib/render/primitives.js +109 -16
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +214 -0
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +19 -61
- package/lib/render/tiles.js +203 -47
- 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 +36 -44
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +122 -56
- package/lib/session/compose.js +197 -146
- package/lib/session/dashboard.js +183 -37
- package/lib/session/dispatch.js +120 -22
- package/lib/session/files-pane.js +60 -24
- package/lib/session/find.js +81 -0
- package/lib/session/import.js +82 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +96 -66
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +8 -3
- package/lib/session/npm/history.js +249 -0
- package/lib/session/npm/npm.js +815 -0
- package/lib/session/npm/packages.js +150 -0
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +216 -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} +166 -59
- package/lib/session/tasks.js +321 -66
- 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 +72 -59
- package/lib/session/workspace.js +416 -0
- 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 -505
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -437
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -628
- package/lib/render/dash-blocks.js +0 -245
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/review.js +0 -744
- 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
package/lib/cli.js
CHANGED
|
@@ -2,50 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
4
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const {
|
|
12
|
-
const
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const {
|
|
18
|
-
const
|
|
19
|
-
const { capabilitiesFor, attachCapabilities } = capabilities;
|
|
20
|
-
const ansi = require('./ansi.js');
|
|
21
|
-
const { setTheme } = ansi;
|
|
22
|
-
const reportRun = require('./report-run.js');
|
|
23
|
-
const { runCapture } = reportRun;
|
|
5
|
+
const { exists } = require('metautil');
|
|
6
|
+
|
|
7
|
+
const { errorMessage } = require('./common/utilities.js');
|
|
8
|
+
const { createGitRepo } = require('./git/git.js');
|
|
9
|
+
const source = require('./source/source.js');
|
|
10
|
+
const { remoteKind, selectChangeSource, createLoadedSource } = source;
|
|
11
|
+
const { Session } = require('./session/session.js');
|
|
12
|
+
const { hasNotes } = require('./review/review.js');
|
|
13
|
+
const {
|
|
14
|
+
capabilitiesFor,
|
|
15
|
+
attachCapabilities,
|
|
16
|
+
} = require('./source/capabilities.js');
|
|
17
|
+
const { runCapture } = require('./report/run.js');
|
|
18
|
+
const { workspaceFromArgs } = require('./dashboard/workspace.js');
|
|
24
19
|
|
|
25
20
|
const HEX_REV = /^[0-9a-f]{4,40}$/i;
|
|
26
21
|
const HEAD_REV = /^HEAD([~^].*)?$/i;
|
|
27
22
|
const RANGE_REV = /[~^]/;
|
|
28
23
|
|
|
29
24
|
const USAGE =
|
|
30
|
-
'Usage: reslop [-
|
|
31
|
-
|
|
32
|
-
const REMOTE_CHANGE = {
|
|
33
|
-
pr: {
|
|
34
|
-
load: 'loadPullRequest',
|
|
35
|
-
fallback: loadPullRequest,
|
|
36
|
-
token: githubToken,
|
|
37
|
-
target: (selected) => selected.pr,
|
|
38
|
-
},
|
|
39
|
-
mr: {
|
|
40
|
-
load: 'loadMergeRequest',
|
|
41
|
-
fallback: loadMergeRequest,
|
|
42
|
-
token: gitlabToken,
|
|
43
|
-
target: (selected) => selected.mr,
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const errorMessage = (reason) =>
|
|
48
|
-
reason instanceof Error ? reason.message : `${reason ?? ''}`;
|
|
25
|
+
'Usage: reslop [-r] [path | commit | pr-url | mr-url | issue-url]';
|
|
49
26
|
|
|
50
27
|
const fail = (stderr, reason) => {
|
|
51
28
|
stderr.write(`reslop: ${errorMessage(reason)}\n`);
|
|
@@ -54,27 +31,27 @@ const fail = (stderr, reason) => {
|
|
|
54
31
|
|
|
55
32
|
const parseArgv = (argv) => {
|
|
56
33
|
const paths = [];
|
|
57
|
-
let newReview = false;
|
|
58
34
|
let readOnly = false;
|
|
59
|
-
let theme = 'dark';
|
|
60
35
|
for (const arg of argv) {
|
|
61
|
-
if (arg === '-
|
|
62
|
-
else if (arg === '-r') readOnly = true;
|
|
63
|
-
else if (arg === '-light') theme = 'light';
|
|
36
|
+
if (arg === '-r') readOnly = true;
|
|
64
37
|
else if (arg.startsWith('-')) throw new Error(`unknown option ${arg}`);
|
|
65
38
|
else paths.push(arg);
|
|
66
39
|
}
|
|
67
|
-
return { paths,
|
|
40
|
+
return { paths, readOnly };
|
|
68
41
|
};
|
|
69
42
|
|
|
70
|
-
const looksLikeRev = (spec) =>
|
|
71
|
-
|
|
43
|
+
const looksLikeRev = (spec) => {
|
|
44
|
+
const hex = HEX_REV.test(spec);
|
|
45
|
+
const head = HEAD_REV.test(spec);
|
|
46
|
+
const range = RANGE_REV.test(spec);
|
|
47
|
+
return hex || head || range;
|
|
48
|
+
};
|
|
72
49
|
|
|
73
50
|
const resolveScope = async (cwd, paths, repo) => {
|
|
74
51
|
if (!paths.length) return { rev: null, paths };
|
|
75
52
|
const first = paths[0];
|
|
76
53
|
if (await exists(path.resolve(cwd, first))) return { rev: null, paths };
|
|
77
|
-
const resolved = repo.resolveRev(cwd, first);
|
|
54
|
+
const resolved = await repo.resolveRev(cwd, first);
|
|
78
55
|
if (resolved) {
|
|
79
56
|
if (paths.length > 1) throw new Error('use a commit or a path, not both');
|
|
80
57
|
return { rev: resolved, paths: [] };
|
|
@@ -85,6 +62,20 @@ const resolveScope = async (cwd, paths, repo) => {
|
|
|
85
62
|
|
|
86
63
|
const localOptions = async (deps, args, selected, cwd) => {
|
|
87
64
|
const rawRepo = deps.repo ?? createGitRepo();
|
|
65
|
+
const found = workspaceFromArgs(cwd, selected.paths);
|
|
66
|
+
if (found) {
|
|
67
|
+
const capabilities = capabilitiesFor('local', { readOnly: args.readOnly });
|
|
68
|
+
return {
|
|
69
|
+
cwd: found.root,
|
|
70
|
+
repo: attachCapabilities(rawRepo, capabilities),
|
|
71
|
+
paths: [],
|
|
72
|
+
rev: null,
|
|
73
|
+
capabilities,
|
|
74
|
+
startPane: 'repos',
|
|
75
|
+
workspace: found,
|
|
76
|
+
repoName: path.basename(found.root),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
88
79
|
const scope = await resolveScope(cwd, selected.paths, rawRepo);
|
|
89
80
|
const kind = scope.rev ? 'commit' : 'local';
|
|
90
81
|
const capabilities = capabilitiesFor(kind, { readOnly: args.readOnly });
|
|
@@ -100,22 +91,21 @@ const localOptions = async (deps, args, selected, cwd) => {
|
|
|
100
91
|
|
|
101
92
|
const remoteRepo = (deps, selected, env, cwd) => {
|
|
102
93
|
if (deps.repo) return deps.repo;
|
|
103
|
-
const remote =
|
|
104
|
-
const loader = deps[remote.
|
|
94
|
+
const remote = remoteKind(selected);
|
|
95
|
+
const loader = deps[remote.option] ?? remote.load;
|
|
105
96
|
const token = remote.token(env);
|
|
106
97
|
const loadRemote = async (_cwd, _paths, options = {}) => {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
});
|
|
98
|
+
const target = remote.target(selected);
|
|
99
|
+
const paths = selected.paths;
|
|
100
|
+
const fetch = deps.fetch;
|
|
101
|
+
const signal = options.signal;
|
|
102
|
+
const request = { cwd, paths, token, fetch, signal };
|
|
103
|
+
const loaded = await loader(target, request);
|
|
114
104
|
const items = loaded.items ?? [];
|
|
115
105
|
return { ...loaded, items: [...items] };
|
|
116
106
|
};
|
|
117
107
|
return createLoadedSource({ items: [] }, selected.kind, {
|
|
118
|
-
|
|
108
|
+
load: loadRemote,
|
|
119
109
|
});
|
|
120
110
|
};
|
|
121
111
|
|
|
@@ -129,7 +119,8 @@ const loadSession = async (proc, deps, args) => {
|
|
|
129
119
|
const env = proc.env ?? {};
|
|
130
120
|
const cwd = deps.cwd ?? proc.cwd();
|
|
131
121
|
const selected = selectChangeSource(args.paths);
|
|
132
|
-
const
|
|
122
|
+
const kind = remoteKind(selected);
|
|
123
|
+
const extra = kind
|
|
133
124
|
? remoteOptions(deps, args, selected, cwd, env)
|
|
134
125
|
: await localOptions(deps, args, selected, cwd);
|
|
135
126
|
const session = new Session({
|
|
@@ -138,12 +129,11 @@ const loadSession = async (proc, deps, args) => {
|
|
|
138
129
|
stdout: proc.stdout,
|
|
139
130
|
color: proc.stdout.isTTY === true && !env.NO_COLOR,
|
|
140
131
|
startPane: 'files',
|
|
141
|
-
newReview: args.newReview,
|
|
142
132
|
readOnly: args.readOnly,
|
|
143
133
|
audit: true,
|
|
144
134
|
...extra,
|
|
145
135
|
});
|
|
146
|
-
session.ensureRepo();
|
|
136
|
+
if (session.pane !== 'repos') await session.ensureRepo();
|
|
147
137
|
return session;
|
|
148
138
|
};
|
|
149
139
|
|
|
@@ -159,29 +149,34 @@ const run = async (proc, deps = {}) => {
|
|
|
159
149
|
if (argv[0] === 't') return runCapture(proc, deps);
|
|
160
150
|
const stdout = proc.stdout;
|
|
161
151
|
const stderr = proc.stderr;
|
|
162
|
-
let args;
|
|
152
|
+
let args = null;
|
|
163
153
|
try {
|
|
164
154
|
args = parseArgv(argv);
|
|
165
155
|
} catch (error) {
|
|
166
156
|
fail(stderr, error);
|
|
167
157
|
stderr.write(`${USAGE}\n`);
|
|
168
|
-
return 1;
|
|
169
158
|
}
|
|
170
|
-
|
|
171
|
-
let session;
|
|
159
|
+
if (!args) return 1;
|
|
160
|
+
let session = null;
|
|
172
161
|
try {
|
|
173
162
|
session = await loadSession(proc, deps, args);
|
|
174
163
|
} catch (error) {
|
|
175
|
-
|
|
164
|
+
fail(stderr, error);
|
|
176
165
|
}
|
|
166
|
+
if (!session) return 1;
|
|
177
167
|
const tty = proc.stdin.isTTY === true && stdout.isTTY === true;
|
|
178
168
|
if (tty) return runUi(session, proc);
|
|
169
|
+
if (session.pane === 'repos') {
|
|
170
|
+
stderr.write('reslop: interactive terminal required\n');
|
|
171
|
+
return 1;
|
|
172
|
+
}
|
|
179
173
|
try {
|
|
180
174
|
await session.loadReady();
|
|
181
175
|
} catch (error) {
|
|
182
176
|
return fail(stderr, error);
|
|
183
177
|
}
|
|
184
|
-
|
|
178
|
+
const noted = session.notes ? hasNotes(session.notes) : false;
|
|
179
|
+
if (!session.items.length && !noted) {
|
|
185
180
|
stdout.write('nothing to review\n');
|
|
186
181
|
return 0;
|
|
187
182
|
}
|
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const { listFilePath } = manifest;
|
|
3
|
+
const path = require('node:path');
|
|
5
4
|
|
|
6
|
-
const REVIEW_DIR = '.
|
|
5
|
+
const REVIEW_DIR = '.plan';
|
|
6
|
+
const LOG_DIR = '.log';
|
|
7
|
+
const ISSUE_FILE = 'issue';
|
|
7
8
|
const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
|
|
8
9
|
const TASKS_FILE = 'TODOs';
|
|
9
|
-
const REPO_TASKS_LABEL =
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
{ id: 'issues', title: 'Issues' },
|
|
14
|
-
{ id: 'bugs', title: 'Bug Reports' },
|
|
15
|
-
{ id: 'features', title: 'Feature Requests' },
|
|
16
|
-
];
|
|
17
|
-
const TASK_KIND_IDS = new Set(TASK_KINDS.map((kind) => kind.id));
|
|
10
|
+
const { label: REPO_TASKS_LABEL, kinds: TASK_KINDS } = require('./tasks.json');
|
|
11
|
+
|
|
12
|
+
const TASK_KIND_IDS = TASK_KINDS.map((kind) => kind.id);
|
|
13
|
+
const TASK_KIND_ALIAS = { backlog: 'debt', issues: 'debt' };
|
|
18
14
|
|
|
19
15
|
const taskKind = (todo) => {
|
|
20
16
|
const kind = todo && todo.kind;
|
|
21
|
-
|
|
17
|
+
if (TASK_KIND_IDS.includes(kind)) return kind;
|
|
18
|
+
return TASK_KIND_ALIAS[kind] ?? 'debt';
|
|
22
19
|
};
|
|
23
20
|
|
|
24
21
|
const taskTitle = (id) => {
|
|
@@ -28,17 +25,9 @@ const taskTitle = (id) => {
|
|
|
28
25
|
return TASK_KINDS[0].title;
|
|
29
26
|
};
|
|
30
27
|
const TOTAL_LABEL = 'total';
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
hour: 'h',
|
|
35
|
-
day: 'd',
|
|
36
|
-
week: 'w',
|
|
37
|
-
month: 'mo',
|
|
38
|
-
year: 'y',
|
|
39
|
-
};
|
|
40
|
-
const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
41
|
-
const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
|
|
28
|
+
const MANIFEST = 'package.json';
|
|
29
|
+
const LOCKFILE = 'package-lock.json';
|
|
30
|
+
const DEP_KIND = { [MANIFEST]: 'manifest', [LOCKFILE]: 'lockfile' };
|
|
42
31
|
|
|
43
32
|
const isPathInScope = (rel, paths) => {
|
|
44
33
|
if (!paths.length) return true;
|
|
@@ -52,6 +41,34 @@ const isPathInScope = (rel, paths) => {
|
|
|
52
41
|
|
|
53
42
|
const itemPath = (item) => item?.file?.newPath || item?.file?.oldPath || '';
|
|
54
43
|
|
|
44
|
+
const posixRel = (rel) => `${rel ?? ''}`.replaceAll('\\', '/');
|
|
45
|
+
|
|
46
|
+
const depFileMeta = (rel) => {
|
|
47
|
+
const normalized = posixRel(rel);
|
|
48
|
+
const kind = DEP_KIND[path.posix.basename(normalized)];
|
|
49
|
+
if (!normalized || !kind) return null;
|
|
50
|
+
return { kind, dir: path.posix.dirname(normalized) };
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const relOf = (dir, name) => (dir === '.' ? name : `${dir}/${name}`);
|
|
54
|
+
|
|
55
|
+
const listFilePath = (rel) => {
|
|
56
|
+
const meta = depFileMeta(rel);
|
|
57
|
+
if (meta && meta.kind === 'lockfile') return relOf(meta.dir, MANIFEST);
|
|
58
|
+
return posixRel(rel);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const groupByPath = (items) => {
|
|
62
|
+
const byPath = new Map();
|
|
63
|
+
for (const item of items) {
|
|
64
|
+
const rel = itemPath(item);
|
|
65
|
+
const list = byPath.get(rel);
|
|
66
|
+
if (list) list.push(item);
|
|
67
|
+
else byPath.set(rel, [item]);
|
|
68
|
+
}
|
|
69
|
+
return byPath;
|
|
70
|
+
};
|
|
71
|
+
|
|
55
72
|
const listPath = (item) => listFilePath(itemPath(item));
|
|
56
73
|
|
|
57
74
|
const isTaskItem = (item) => item?.origin === 'task';
|
|
@@ -97,32 +114,6 @@ const lineDelta = ({ hunk, blockId }) => {
|
|
|
97
114
|
return delta;
|
|
98
115
|
};
|
|
99
116
|
|
|
100
|
-
const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
|
|
101
|
-
|
|
102
|
-
const shortAge = (text) => {
|
|
103
|
-
const raw = `${text ?? ''}`.trim();
|
|
104
|
-
if (/^yesterday$/i.test(raw)) return '1d ago';
|
|
105
|
-
const match = AGE_AGO.exec(raw);
|
|
106
|
-
if (match) return shortUnit(match[1], match[2].toLowerCase());
|
|
107
|
-
const brief = AGE_SHORT.exec(raw);
|
|
108
|
-
if (brief) return `${brief[1]}${brief[2].toLowerCase()} ago`;
|
|
109
|
-
return raw;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const relativeAge = (thenMs, nowMs = Date.now()) => {
|
|
113
|
-
const diff = Math.max(0, Math.round((nowMs - thenMs) / 1000));
|
|
114
|
-
if (diff < 90) return shortUnit(diff, 'second');
|
|
115
|
-
const minutes = Math.round(diff / 60);
|
|
116
|
-
if (minutes < 90) return shortUnit(minutes, 'minute');
|
|
117
|
-
const hours = Math.round(minutes / 60);
|
|
118
|
-
if (hours < 36) return shortUnit(hours, 'hour');
|
|
119
|
-
const days = Math.round(hours / 24);
|
|
120
|
-
if (days < 14) return shortUnit(days, 'day');
|
|
121
|
-
if (days < 70) return shortUnit(Math.round(days / 7), 'week');
|
|
122
|
-
if (days < 365) return shortUnit(Math.round(days / 30), 'month');
|
|
123
|
-
return shortUnit(Math.round(days / 365), 'year');
|
|
124
|
-
};
|
|
125
|
-
|
|
126
117
|
const emptyCounts = () => ({
|
|
127
118
|
remaining: 0,
|
|
128
119
|
added: 0,
|
|
@@ -221,12 +212,21 @@ const fileTotals = (files) => {
|
|
|
221
212
|
|
|
222
213
|
module.exports = {
|
|
223
214
|
REVIEW_DIR,
|
|
215
|
+
LOG_DIR,
|
|
216
|
+
ISSUE_FILE,
|
|
217
|
+
posixRel,
|
|
218
|
+
groupByPath,
|
|
224
219
|
TASKS_FILE,
|
|
225
220
|
REPO_TASKS_LABEL,
|
|
226
221
|
TASK_KINDS,
|
|
227
222
|
taskKind,
|
|
228
223
|
taskTitle,
|
|
229
224
|
TOTAL_LABEL,
|
|
225
|
+
MANIFEST,
|
|
226
|
+
LOCKFILE,
|
|
227
|
+
depFileMeta,
|
|
228
|
+
relOf,
|
|
229
|
+
listFilePath,
|
|
230
230
|
isPathInScope,
|
|
231
231
|
itemPath,
|
|
232
232
|
listPath,
|
|
@@ -236,8 +236,7 @@ module.exports = {
|
|
|
236
236
|
isTotalEntry,
|
|
237
237
|
makeTaskItem,
|
|
238
238
|
fileStatus,
|
|
239
|
+
lineDelta,
|
|
239
240
|
fileEntries,
|
|
240
241
|
fileTotals,
|
|
241
|
-
relativeAge,
|
|
242
|
-
shortAge,
|
|
243
242
|
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const matchesIn = (text, query) => {
|
|
4
|
+
const spans = [];
|
|
5
|
+
if (!query || !text) return spans;
|
|
6
|
+
const hay = text.toLowerCase();
|
|
7
|
+
const needle = query.toLowerCase();
|
|
8
|
+
const step = needle.length;
|
|
9
|
+
let from = 0;
|
|
10
|
+
while (from <= hay.length - step) {
|
|
11
|
+
const at = hay.indexOf(needle, from);
|
|
12
|
+
if (at < 0) break;
|
|
13
|
+
spans.push({ start: at, end: at + step });
|
|
14
|
+
from = at + step;
|
|
15
|
+
}
|
|
16
|
+
return spans;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const fileMatches = (entries, query) => {
|
|
20
|
+
const hits = [];
|
|
21
|
+
if (!query) return hits;
|
|
22
|
+
for (let index = 0; index < entries.length; index++) {
|
|
23
|
+
const entry = entries[index];
|
|
24
|
+
const path = entry && entry.path ? entry.path : '';
|
|
25
|
+
if (!path) continue;
|
|
26
|
+
for (const span of matchesIn(path, query)) {
|
|
27
|
+
hits.push({ index, path, start: span.start, end: span.end });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return hits;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const activeHit = (entries, find) => {
|
|
34
|
+
if (!find || !find.query) return null;
|
|
35
|
+
const hits = fileMatches(entries, find.query);
|
|
36
|
+
if (!hits.length) return null;
|
|
37
|
+
const count = hits.length;
|
|
38
|
+
let at = find.at % count;
|
|
39
|
+
if (at < 0) at += count;
|
|
40
|
+
return hits[at];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const keptPrefix = (shown, full) => {
|
|
44
|
+
const mark = shown.endsWith('…') && shown !== full ? 1 : 0;
|
|
45
|
+
const keep = shown.length - mark;
|
|
46
|
+
if (!full.startsWith(shown.slice(0, keep))) return -1;
|
|
47
|
+
return keep;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const spansFor = (shown, full, query, hit) => {
|
|
51
|
+
const spans = matchesIn(shown, query);
|
|
52
|
+
if (!hit || hit.path !== full) return spans;
|
|
53
|
+
const keep = keptPrefix(shown, full);
|
|
54
|
+
if (keep < 0 || hit.start >= keep) return spans;
|
|
55
|
+
return spans.map((span) => ({
|
|
56
|
+
...span,
|
|
57
|
+
on: span.start === hit.start,
|
|
58
|
+
}));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
module.exports = { fileMatches, activeHit, spansFor };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { bytesToSize } = require('metautil');
|
|
4
|
+
const { trimText, pad2 } = require('./utilities.js');
|
|
5
|
+
|
|
6
|
+
const AGE_UNIT = {
|
|
7
|
+
second: 's',
|
|
8
|
+
minute: 'm',
|
|
9
|
+
hour: 'h',
|
|
10
|
+
day: 'd',
|
|
11
|
+
week: 'w',
|
|
12
|
+
month: 'mo',
|
|
13
|
+
year: 'y',
|
|
14
|
+
};
|
|
15
|
+
const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
16
|
+
const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
|
|
17
|
+
const SECONDS_AGO = /^\d+s ago$/;
|
|
18
|
+
|
|
19
|
+
const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
|
|
20
|
+
|
|
21
|
+
const AGE_SEC = {
|
|
22
|
+
s: 1,
|
|
23
|
+
m: 60,
|
|
24
|
+
h: 3600,
|
|
25
|
+
d: 86400,
|
|
26
|
+
w: 604800,
|
|
27
|
+
mo: 2592000,
|
|
28
|
+
y: 31536000,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const ageParts = (text) => {
|
|
32
|
+
const raw = trimText(text);
|
|
33
|
+
if (/^yesterday$/i.test(raw)) return { count: 1, unit: 'd' };
|
|
34
|
+
const long = AGE_AGO.exec(raw);
|
|
35
|
+
if (long) return { count: long[1], unit: AGE_UNIT[long[2].toLowerCase()] };
|
|
36
|
+
const brief = AGE_SHORT.exec(raw);
|
|
37
|
+
if (brief) return { count: brief[1], unit: brief[2].toLowerCase() };
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const shortAge = (text) => {
|
|
42
|
+
const age = ageParts(text);
|
|
43
|
+
return age ? `${age.count}${age.unit} ago` : trimText(text);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const ageSeconds = (text) => {
|
|
47
|
+
const age = ageParts(text);
|
|
48
|
+
if (!age) return Number.POSITIVE_INFINITY;
|
|
49
|
+
return Number(age.count) * AGE_SEC[age.unit];
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const relativeAge = (thenMs, nowMs = Date.now()) => {
|
|
53
|
+
const diff = Math.max(0, Math.round((nowMs - thenMs) / 1000));
|
|
54
|
+
if (diff < 90) return shortUnit(diff, 'second');
|
|
55
|
+
const minutes = Math.round(diff / 60);
|
|
56
|
+
if (minutes < 90) return shortUnit(minutes, 'minute');
|
|
57
|
+
const hours = Math.round(minutes / 60);
|
|
58
|
+
if (hours < 36) return shortUnit(hours, 'hour');
|
|
59
|
+
const days = Math.round(hours / 24);
|
|
60
|
+
if (days < 14) return shortUnit(days, 'day');
|
|
61
|
+
if (days < 70) return shortUnit(Math.round(days / 7), 'week');
|
|
62
|
+
if (days < 365) return shortUnit(Math.round(days / 30), 'month');
|
|
63
|
+
return shortUnit(Math.round(days / 365), 'year');
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const trimmed = (value) =>
|
|
67
|
+
value >= 10 ? `${Math.round(value)}` : `${Math.round(value * 10) / 10}`;
|
|
68
|
+
|
|
69
|
+
const num = (value) => {
|
|
70
|
+
const n = Math.max(0, Math.round(value));
|
|
71
|
+
if (n >= 1e6) return `${trimmed(n / 1e6)}m`;
|
|
72
|
+
if (n >= 1e4) return `${trimmed(n / 1e3)}k`;
|
|
73
|
+
return `${n}`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const size = (bytes) => bytesToSize(bytes > 0 ? bytes : 0);
|
|
77
|
+
|
|
78
|
+
const ago = (at, now) => {
|
|
79
|
+
if (!at) return '';
|
|
80
|
+
const text = relativeAge(at, now);
|
|
81
|
+
return SECONDS_AGO.test(text) ? '<1m ago' : text;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const duration = (ms) => {
|
|
85
|
+
const value = Math.max(0, ms);
|
|
86
|
+
if (value < 1000) return `${Math.round(value)}ms`;
|
|
87
|
+
if (value < 60000) return `${(value / 1000).toFixed(1)}s`;
|
|
88
|
+
const minutes = Math.floor(value / 60000);
|
|
89
|
+
const seconds = Math.floor((value % 60000) / 1000);
|
|
90
|
+
return `${minutes}m${pad2(seconds)}s`;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
module.exports = {
|
|
94
|
+
shortAge,
|
|
95
|
+
ageSeconds,
|
|
96
|
+
relativeAge,
|
|
97
|
+
num,
|
|
98
|
+
size,
|
|
99
|
+
ago,
|
|
100
|
+
duration,
|
|
101
|
+
};
|
|
@@ -1,48 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const { spawn, spawnSync } = childProcess;
|
|
5
|
-
const metautil = require('metautil');
|
|
6
|
-
const { split } = metautil;
|
|
3
|
+
const { spawn } = require('node:child_process');
|
|
7
4
|
|
|
8
5
|
const IS_WIN = process.platform === 'win32';
|
|
6
|
+
const INSTALL_MS = 120000;
|
|
9
7
|
const DEFAULT_ENCODING = 'utf8';
|
|
10
8
|
const DEFAULT_MAX_BUFFER = 32 * 1024 * 1024;
|
|
11
9
|
|
|
12
|
-
const parseVersion = (text) => {
|
|
13
|
-
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(`${text ?? ''}`);
|
|
14
|
-
if (!match) return null;
|
|
15
|
-
const major = Number(match[1]);
|
|
16
|
-
const minor = Number(match[2]);
|
|
17
|
-
const patch = Number(match[3]);
|
|
18
|
-
return { major, minor, patch };
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const cmpVersion = (left, right) =>
|
|
22
|
-
left.major - right.major ||
|
|
23
|
-
left.minor - right.minor ||
|
|
24
|
-
left.patch - right.patch;
|
|
25
|
-
|
|
26
|
-
const oneLine = (text, fallback = '') =>
|
|
27
|
-
split(`${text ?? ''}`.trim(), '\n')[0] || fallback;
|
|
28
|
-
|
|
29
|
-
const asText = (value) => (typeof value === 'string' ? value : '');
|
|
30
|
-
|
|
31
|
-
const unquote = (value) => {
|
|
32
|
-
const text = `${value ?? ''}`;
|
|
33
|
-
if (text.length < 2) return text;
|
|
34
|
-
const open = text[0];
|
|
35
|
-
const quoted = open === text.at(-1) && (open === '"' || open === '\x27');
|
|
36
|
-
return quoted ? text.slice(1, -1) : text;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const pad2 = (n) => `${n}`.padStart(2, '0');
|
|
40
|
-
|
|
41
|
-
const dateStamp = (date) => {
|
|
42
|
-
const month = pad2(date.getMonth() + 1);
|
|
43
|
-
return `${date.getFullYear()}-${month}-${pad2(date.getDate())}`;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
10
|
const npmBin = () => (IS_WIN ? 'npm.cmd' : 'npm');
|
|
47
11
|
|
|
48
12
|
const spawnBase = (extra = {}) => ({ windowsHide: true, ...extra });
|
|
@@ -50,36 +14,6 @@ const spawnBase = (extra = {}) => ({ windowsHide: true, ...extra });
|
|
|
50
14
|
const npmOpts = (extra = {}, platform = process.platform) =>
|
|
51
15
|
spawnBase({ shell: platform === 'win32', ...extra });
|
|
52
16
|
|
|
53
|
-
const listen = (emitter, event, handler) => {
|
|
54
|
-
if (!emitter || typeof emitter.on !== 'function') return () => {};
|
|
55
|
-
emitter.on(event, handler);
|
|
56
|
-
return () => {
|
|
57
|
-
if (typeof emitter.removeListener === 'function') {
|
|
58
|
-
return void emitter.removeListener(event, handler);
|
|
59
|
-
}
|
|
60
|
-
if (typeof emitter.off === 'function') emitter.off(event, handler);
|
|
61
|
-
};
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
const watchResize = (stdout, proc, onResize, signal) => {
|
|
65
|
-
const stopStdout = listen(stdout, 'resize', onResize);
|
|
66
|
-
let stopProc = () => {};
|
|
67
|
-
try {
|
|
68
|
-
stopProc = listen(proc, 'SIGWINCH', onResize);
|
|
69
|
-
} catch {
|
|
70
|
-
// SIGWINCH is not available on Windows
|
|
71
|
-
}
|
|
72
|
-
const stop = () => {
|
|
73
|
-
stopStdout();
|
|
74
|
-
stopProc();
|
|
75
|
-
};
|
|
76
|
-
if (signal) {
|
|
77
|
-
if (signal.aborted) stop();
|
|
78
|
-
else signal.addEventListener('abort', stop, { once: true });
|
|
79
|
-
}
|
|
80
|
-
return stop;
|
|
81
|
-
};
|
|
82
|
-
|
|
83
17
|
const codeError = (message, code) =>
|
|
84
18
|
Object.assign(new Error(message), { code });
|
|
85
19
|
|
|
@@ -94,19 +28,6 @@ const procResult = (values = {}) => ({
|
|
|
94
28
|
error: values.error ?? null,
|
|
95
29
|
});
|
|
96
30
|
|
|
97
|
-
const runProcSync = (cmd, args, options = {}) => {
|
|
98
|
-
const opts = spawnBase({
|
|
99
|
-
cwd: options.cwd,
|
|
100
|
-
env: options.env,
|
|
101
|
-
encoding: options.encoding ?? DEFAULT_ENCODING,
|
|
102
|
-
input: options.input,
|
|
103
|
-
maxBuffer: options.maxBuffer ?? DEFAULT_MAX_BUFFER,
|
|
104
|
-
timeout: options.timeout,
|
|
105
|
-
});
|
|
106
|
-
if (options.shell) opts.shell = true;
|
|
107
|
-
return procResult(spawnSync(cmd, args, opts));
|
|
108
|
-
};
|
|
109
|
-
|
|
110
31
|
const listenChild = (child, options, resolve) => {
|
|
111
32
|
const encoding = options.encoding ?? DEFAULT_ENCODING;
|
|
112
33
|
const maxBuffer = options.maxBuffer ?? DEFAULT_MAX_BUFFER;
|
|
@@ -156,6 +77,8 @@ const listenChild = (child, options, resolve) => {
|
|
|
156
77
|
finish(procResult({ status, stdout, stderr }));
|
|
157
78
|
});
|
|
158
79
|
if (!child.stdin) return;
|
|
80
|
+
// A child that exits before reading its input reports that by its status
|
|
81
|
+
child.stdin.on('error', () => {});
|
|
159
82
|
try {
|
|
160
83
|
if (options.input) child.stdin.end(options.input);
|
|
161
84
|
else child.stdin.end();
|
|
@@ -173,34 +96,23 @@ const runProc = (cmd, args, options = {}) =>
|
|
|
173
96
|
}
|
|
174
97
|
const spawnOpts = { cwd: options.cwd, env: options.env };
|
|
175
98
|
if (options.shell) spawnOpts.shell = true;
|
|
176
|
-
let child;
|
|
99
|
+
let child = null;
|
|
177
100
|
try {
|
|
178
101
|
child = spawn(cmd, args, spawnBase(spawnOpts));
|
|
179
102
|
} catch (error) {
|
|
180
103
|
resolve(procResult({ error }));
|
|
181
|
-
return;
|
|
182
104
|
}
|
|
105
|
+
if (!child) return;
|
|
183
106
|
listenChild(child, options, resolve);
|
|
184
107
|
});
|
|
185
108
|
|
|
186
109
|
module.exports = {
|
|
187
110
|
IS_WIN,
|
|
188
|
-
|
|
189
|
-
DEFAULT_MAX_BUFFER,
|
|
190
|
-
parseVersion,
|
|
191
|
-
cmpVersion,
|
|
192
|
-
oneLine,
|
|
193
|
-
asText,
|
|
194
|
-
unquote,
|
|
195
|
-
pad2,
|
|
196
|
-
dateStamp,
|
|
111
|
+
INSTALL_MS,
|
|
197
112
|
npmBin,
|
|
198
113
|
spawnBase,
|
|
199
114
|
npmOpts,
|
|
200
|
-
listen,
|
|
201
|
-
watchResize,
|
|
202
115
|
abortError,
|
|
203
116
|
isAbort,
|
|
204
|
-
runProcSync,
|
|
205
117
|
runProc,
|
|
206
118
|
};
|