reslop 0.2.7 → 0.3.1
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 +30 -1
- package/README.md +16 -14
- package/bin/reslop.js +8 -5
- package/lib/agents/agents.js +573 -0
- package/lib/agents/allow.js +71 -0
- package/lib/agents/allow.json +17 -0
- package/lib/agents/models.js +467 -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} +151 -95
- package/lib/git/git.js +71 -0
- package/lib/git/worktree.js +504 -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 +114 -48
- package/lib/{editor.js → input/editor.js} +16 -8
- package/lib/{keys.js → input/keys.js} +24 -45
- package/lib/input/line-edit.js +78 -0
- package/lib/input/menu-pick.js +70 -0
- package/lib/render/agents.js +642 -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 +385 -0
- package/lib/render/frame.js +123 -25
- package/lib/render/header.js +176 -0
- package/lib/render/log.js +320 -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} +48 -19
- 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 +14 -0
- package/lib/review/format.js +420 -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 +976 -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 -40
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +91 -52
- package/lib/session/compose.js +189 -147
- package/lib/session/dashboard.js +23 -22
- package/lib/session/dispatch.js +86 -21
- package/lib/session/files-pane.js +47 -26
- package/lib/session/find.js +16 -29
- package/lib/session/import.js +93 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +78 -72
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +6 -3
- package/lib/session/npm/history.js +250 -0
- package/lib/session/npm/npm.js +836 -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} +158 -63
- 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 +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} +4 -3
- 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
package/lib/cli.js
CHANGED
|
@@ -2,52 +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;
|
|
24
|
-
const workspace = require('./workspace.js');
|
|
25
|
-
const { workspaceFromArgs } = workspace;
|
|
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');
|
|
26
19
|
|
|
27
20
|
const HEX_REV = /^[0-9a-f]{4,40}$/i;
|
|
28
21
|
const HEAD_REV = /^HEAD([~^].*)?$/i;
|
|
29
22
|
const RANGE_REV = /[~^]/;
|
|
30
23
|
|
|
31
24
|
const USAGE =
|
|
32
|
-
'Usage: reslop [-
|
|
33
|
-
|
|
34
|
-
const REMOTE_CHANGE = {
|
|
35
|
-
pr: {
|
|
36
|
-
load: 'loadPullRequest',
|
|
37
|
-
fallback: loadPullRequest,
|
|
38
|
-
token: githubToken,
|
|
39
|
-
target: (selected) => selected.pr,
|
|
40
|
-
},
|
|
41
|
-
mr: {
|
|
42
|
-
load: 'loadMergeRequest',
|
|
43
|
-
fallback: loadMergeRequest,
|
|
44
|
-
token: gitlabToken,
|
|
45
|
-
target: (selected) => selected.mr,
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const errorMessage = (reason) =>
|
|
50
|
-
reason instanceof Error ? reason.message : `${reason ?? ''}`;
|
|
25
|
+
'Usage: reslop [-r] [path | commit | pr-url | mr-url | issue-url]';
|
|
51
26
|
|
|
52
27
|
const fail = (stderr, reason) => {
|
|
53
28
|
stderr.write(`reslop: ${errorMessage(reason)}\n`);
|
|
@@ -56,27 +31,27 @@ const fail = (stderr, reason) => {
|
|
|
56
31
|
|
|
57
32
|
const parseArgv = (argv) => {
|
|
58
33
|
const paths = [];
|
|
59
|
-
let newReview = false;
|
|
60
34
|
let readOnly = false;
|
|
61
|
-
let theme = 'dark';
|
|
62
35
|
for (const arg of argv) {
|
|
63
|
-
if (arg === '-
|
|
64
|
-
else if (arg === '-r') readOnly = true;
|
|
65
|
-
else if (arg === '-light') theme = 'light';
|
|
36
|
+
if (arg === '-r') readOnly = true;
|
|
66
37
|
else if (arg.startsWith('-')) throw new Error(`unknown option ${arg}`);
|
|
67
38
|
else paths.push(arg);
|
|
68
39
|
}
|
|
69
|
-
return { paths,
|
|
40
|
+
return { paths, readOnly };
|
|
70
41
|
};
|
|
71
42
|
|
|
72
|
-
const looksLikeRev = (spec) =>
|
|
73
|
-
|
|
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
|
+
};
|
|
74
49
|
|
|
75
50
|
const resolveScope = async (cwd, paths, repo) => {
|
|
76
51
|
if (!paths.length) return { rev: null, paths };
|
|
77
52
|
const first = paths[0];
|
|
78
53
|
if (await exists(path.resolve(cwd, first))) return { rev: null, paths };
|
|
79
|
-
const resolved = repo.resolveRev(cwd, first);
|
|
54
|
+
const resolved = await repo.resolveRev(cwd, first);
|
|
80
55
|
if (resolved) {
|
|
81
56
|
if (paths.length > 1) throw new Error('use a commit or a path, not both');
|
|
82
57
|
return { rev: resolved, paths: [] };
|
|
@@ -116,22 +91,21 @@ const localOptions = async (deps, args, selected, cwd) => {
|
|
|
116
91
|
|
|
117
92
|
const remoteRepo = (deps, selected, env, cwd) => {
|
|
118
93
|
if (deps.repo) return deps.repo;
|
|
119
|
-
const remote =
|
|
120
|
-
const loader = deps[remote.
|
|
94
|
+
const remote = remoteKind(selected);
|
|
95
|
+
const loader = deps[remote.option] ?? remote.load;
|
|
121
96
|
const token = remote.token(env);
|
|
122
97
|
const loadRemote = async (_cwd, _paths, options = {}) => {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
});
|
|
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);
|
|
130
104
|
const items = loaded.items ?? [];
|
|
131
105
|
return { ...loaded, items: [...items] };
|
|
132
106
|
};
|
|
133
107
|
return createLoadedSource({ items: [] }, selected.kind, {
|
|
134
|
-
|
|
108
|
+
load: loadRemote,
|
|
135
109
|
});
|
|
136
110
|
};
|
|
137
111
|
|
|
@@ -145,7 +119,8 @@ const loadSession = async (proc, deps, args) => {
|
|
|
145
119
|
const env = proc.env ?? {};
|
|
146
120
|
const cwd = deps.cwd ?? proc.cwd();
|
|
147
121
|
const selected = selectChangeSource(args.paths);
|
|
148
|
-
const
|
|
122
|
+
const kind = remoteKind(selected);
|
|
123
|
+
const extra = kind
|
|
149
124
|
? remoteOptions(deps, args, selected, cwd, env)
|
|
150
125
|
: await localOptions(deps, args, selected, cwd);
|
|
151
126
|
const session = new Session({
|
|
@@ -154,12 +129,11 @@ const loadSession = async (proc, deps, args) => {
|
|
|
154
129
|
stdout: proc.stdout,
|
|
155
130
|
color: proc.stdout.isTTY === true && !env.NO_COLOR,
|
|
156
131
|
startPane: 'files',
|
|
157
|
-
newReview: args.newReview,
|
|
158
132
|
readOnly: args.readOnly,
|
|
159
133
|
audit: true,
|
|
160
134
|
...extra,
|
|
161
135
|
});
|
|
162
|
-
if (session.pane !== 'repos') session.ensureRepo();
|
|
136
|
+
if (session.pane !== 'repos') await session.ensureRepo();
|
|
163
137
|
return session;
|
|
164
138
|
};
|
|
165
139
|
|
|
@@ -175,21 +149,21 @@ const run = async (proc, deps = {}) => {
|
|
|
175
149
|
if (argv[0] === 't') return runCapture(proc, deps);
|
|
176
150
|
const stdout = proc.stdout;
|
|
177
151
|
const stderr = proc.stderr;
|
|
178
|
-
let args;
|
|
152
|
+
let args = null;
|
|
179
153
|
try {
|
|
180
154
|
args = parseArgv(argv);
|
|
181
155
|
} catch (error) {
|
|
182
156
|
fail(stderr, error);
|
|
183
157
|
stderr.write(`${USAGE}\n`);
|
|
184
|
-
return 1;
|
|
185
158
|
}
|
|
186
|
-
|
|
187
|
-
let session;
|
|
159
|
+
if (!args) return 1;
|
|
160
|
+
let session = null;
|
|
188
161
|
try {
|
|
189
162
|
session = await loadSession(proc, deps, args);
|
|
190
163
|
} catch (error) {
|
|
191
|
-
|
|
164
|
+
fail(stderr, error);
|
|
192
165
|
}
|
|
166
|
+
if (!session) return 1;
|
|
193
167
|
const tty = proc.stdin.isTTY === true && stdout.isTTY === true;
|
|
194
168
|
if (tty) return runUi(session, proc);
|
|
195
169
|
if (session.pane === 'repos') {
|
|
@@ -201,7 +175,8 @@ const run = async (proc, deps = {}) => {
|
|
|
201
175
|
} catch (error) {
|
|
202
176
|
return fail(stderr, error);
|
|
203
177
|
}
|
|
204
|
-
|
|
178
|
+
const noted = session.notes ? hasNotes(session.notes) : false;
|
|
179
|
+
if (!session.items.length && !noted) {
|
|
205
180
|
stdout.write('nothing to review\n');
|
|
206
181
|
return 0;
|
|
207
182
|
}
|
|
@@ -1,28 +1,20 @@
|
|
|
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
|
-
|
|
12
|
-
].join(' ');
|
|
13
|
-
const TASK_KINDS = [
|
|
14
|
-
{ id: 'features', title: 'Feature requests' },
|
|
15
|
-
{ id: 'bugs', title: 'Bug reports' },
|
|
16
|
-
{ id: 'debt', title: 'Technical debt' },
|
|
17
|
-
{ id: 'research', title: 'Research' },
|
|
18
|
-
{ id: 'security', title: 'Security' },
|
|
19
|
-
];
|
|
20
|
-
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);
|
|
21
13
|
const TASK_KIND_ALIAS = { backlog: 'debt', issues: 'debt' };
|
|
22
14
|
|
|
23
15
|
const taskKind = (todo) => {
|
|
24
16
|
const kind = todo && todo.kind;
|
|
25
|
-
if (TASK_KIND_IDS.
|
|
17
|
+
if (TASK_KIND_IDS.includes(kind)) return kind;
|
|
26
18
|
return TASK_KIND_ALIAS[kind] ?? 'debt';
|
|
27
19
|
};
|
|
28
20
|
|
|
@@ -33,17 +25,9 @@ const taskTitle = (id) => {
|
|
|
33
25
|
return TASK_KINDS[0].title;
|
|
34
26
|
};
|
|
35
27
|
const TOTAL_LABEL = 'total';
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
hour: 'h',
|
|
40
|
-
day: 'd',
|
|
41
|
-
week: 'w',
|
|
42
|
-
month: 'mo',
|
|
43
|
-
year: 'y',
|
|
44
|
-
};
|
|
45
|
-
const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
46
|
-
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' };
|
|
47
31
|
|
|
48
32
|
const isPathInScope = (rel, paths) => {
|
|
49
33
|
if (!paths.length) return true;
|
|
@@ -57,6 +41,34 @@ const isPathInScope = (rel, paths) => {
|
|
|
57
41
|
|
|
58
42
|
const itemPath = (item) => item?.file?.newPath || item?.file?.oldPath || '';
|
|
59
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
|
+
|
|
60
72
|
const listPath = (item) => listFilePath(itemPath(item));
|
|
61
73
|
|
|
62
74
|
const isTaskItem = (item) => item?.origin === 'task';
|
|
@@ -102,32 +114,6 @@ const lineDelta = ({ hunk, blockId }) => {
|
|
|
102
114
|
return delta;
|
|
103
115
|
};
|
|
104
116
|
|
|
105
|
-
const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
|
|
106
|
-
|
|
107
|
-
const shortAge = (text) => {
|
|
108
|
-
const raw = `${text ?? ''}`.trim();
|
|
109
|
-
if (/^yesterday$/i.test(raw)) return '1d ago';
|
|
110
|
-
const match = AGE_AGO.exec(raw);
|
|
111
|
-
if (match) return shortUnit(match[1], match[2].toLowerCase());
|
|
112
|
-
const brief = AGE_SHORT.exec(raw);
|
|
113
|
-
if (brief) return `${brief[1]}${brief[2].toLowerCase()} ago`;
|
|
114
|
-
return raw;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const relativeAge = (thenMs, nowMs = Date.now()) => {
|
|
118
|
-
const diff = Math.max(0, Math.round((nowMs - thenMs) / 1000));
|
|
119
|
-
if (diff < 90) return shortUnit(diff, 'second');
|
|
120
|
-
const minutes = Math.round(diff / 60);
|
|
121
|
-
if (minutes < 90) return shortUnit(minutes, 'minute');
|
|
122
|
-
const hours = Math.round(minutes / 60);
|
|
123
|
-
if (hours < 36) return shortUnit(hours, 'hour');
|
|
124
|
-
const days = Math.round(hours / 24);
|
|
125
|
-
if (days < 14) return shortUnit(days, 'day');
|
|
126
|
-
if (days < 70) return shortUnit(Math.round(days / 7), 'week');
|
|
127
|
-
if (days < 365) return shortUnit(Math.round(days / 30), 'month');
|
|
128
|
-
return shortUnit(Math.round(days / 365), 'year');
|
|
129
|
-
};
|
|
130
|
-
|
|
131
117
|
const emptyCounts = () => ({
|
|
132
118
|
remaining: 0,
|
|
133
119
|
added: 0,
|
|
@@ -226,12 +212,21 @@ const fileTotals = (files) => {
|
|
|
226
212
|
|
|
227
213
|
module.exports = {
|
|
228
214
|
REVIEW_DIR,
|
|
215
|
+
LOG_DIR,
|
|
216
|
+
ISSUE_FILE,
|
|
217
|
+
posixRel,
|
|
218
|
+
groupByPath,
|
|
229
219
|
TASKS_FILE,
|
|
230
220
|
REPO_TASKS_LABEL,
|
|
231
221
|
TASK_KINDS,
|
|
232
222
|
taskKind,
|
|
233
223
|
taskTitle,
|
|
234
224
|
TOTAL_LABEL,
|
|
225
|
+
MANIFEST,
|
|
226
|
+
LOCKFILE,
|
|
227
|
+
depFileMeta,
|
|
228
|
+
relOf,
|
|
229
|
+
listFilePath,
|
|
235
230
|
isPathInScope,
|
|
236
231
|
itemPath,
|
|
237
232
|
listPath,
|
|
@@ -244,6 +239,4 @@ module.exports = {
|
|
|
244
239
|
lineDelta,
|
|
245
240
|
fileEntries,
|
|
246
241
|
fileTotals,
|
|
247
|
-
relativeAge,
|
|
248
|
-
shortAge,
|
|
249
242
|
};
|
|
@@ -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
|
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"label": "Project Tasks: Feature requests, Improvements, Bug reports, Refactoring, Research, Security",
|
|
3
|
+
"kinds": [
|
|
4
|
+
{
|
|
5
|
+
"id": "features",
|
|
6
|
+
"title": "Feature requests and Enhancements",
|
|
7
|
+
"short": "Features"
|
|
8
|
+
},
|
|
9
|
+
{ "id": "improvements", "title": "Improvements", "short": "Improvements" },
|
|
10
|
+
{ "id": "bugs", "title": "Bug Reports and Fixes", "short": "Bug Reports" },
|
|
11
|
+
{
|
|
12
|
+
"id": "debt",
|
|
13
|
+
"title": "Refactoring and Technical debt",
|
|
14
|
+
"short": "Refactoring"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"id": "research",
|
|
18
|
+
"title": "Research and Experiments",
|
|
19
|
+
"short": "Research"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": "security",
|
|
23
|
+
"title": "Security and Compliance",
|
|
24
|
+
"short": "Security"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|