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
package/lib/session/workspace.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const path = require('node:path');
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
7
|
-
const
|
|
8
|
-
const { UNKNOWN_PATH } = watch;
|
|
9
|
-
const workspace = require('../workspace.js');
|
|
4
|
+
const { REVIEW_DIR } = require('../common/files.js');
|
|
5
|
+
const { FileIndex, HEAT_MS } = require('../dashboard/tree.js');
|
|
6
|
+
const { UNKNOWN_PATH } = require('../dashboard/watch.js');
|
|
7
|
+
const workspace = require('../dashboard/workspace.js');
|
|
10
8
|
const { emptyDiff, countCommits, readDiffStat, taskStats, pickRun } = workspace;
|
|
11
|
-
const
|
|
12
|
-
const {
|
|
13
|
-
const gitDeps = require('../git-deps.js');
|
|
14
|
-
const { collectReportAsync } = gitDeps;
|
|
9
|
+
const { readNpmSummary } = require('../dashboard/npm.js');
|
|
10
|
+
const { collectReport } = require('../git/deps.js');
|
|
15
11
|
|
|
16
12
|
const TICK_ID = 'repos';
|
|
17
13
|
const HEAT_TIMER = 'repos-heat';
|
|
@@ -104,7 +100,7 @@ const readRuns = (row) => {
|
|
|
104
100
|
};
|
|
105
101
|
|
|
106
102
|
const reportCount = async (dir, kind) => {
|
|
107
|
-
const report = await
|
|
103
|
+
const report = await collectReport(dir, kind);
|
|
108
104
|
return report ? report.size : 0;
|
|
109
105
|
};
|
|
110
106
|
|
|
@@ -138,13 +134,11 @@ const toTile = (row) => ({
|
|
|
138
134
|
npmReady: row.npmReady,
|
|
139
135
|
});
|
|
140
136
|
|
|
141
|
-
const rowBusy = (row) =>
|
|
142
|
-
!row.filesReady ||
|
|
143
|
-
!row.
|
|
144
|
-
!row.
|
|
145
|
-
|
|
146
|
-
!row.runsReady ||
|
|
147
|
-
!row.npmReady;
|
|
137
|
+
const rowBusy = (row) => {
|
|
138
|
+
if (!row.filesReady || !row.commitsReady) return true;
|
|
139
|
+
if (!row.diffReady || !row.tasksReady) return true;
|
|
140
|
+
return !row.runsReady || !row.npmReady;
|
|
141
|
+
};
|
|
148
142
|
|
|
149
143
|
const classifyRest = (rest) => {
|
|
150
144
|
const bag = freshBag();
|
|
@@ -172,7 +166,7 @@ const classifyRest = (rest) => {
|
|
|
172
166
|
bag.runs = true;
|
|
173
167
|
return bag;
|
|
174
168
|
}
|
|
175
|
-
if (rest ===
|
|
169
|
+
if (rest === REVIEW_DIR || rest.startsWith(`${REVIEW_DIR}/`)) {
|
|
176
170
|
bag.tasks = true;
|
|
177
171
|
return bag;
|
|
178
172
|
}
|
|
@@ -193,13 +187,7 @@ class Workspace {
|
|
|
193
187
|
this.rows = [];
|
|
194
188
|
this.byName = new Map();
|
|
195
189
|
this.cursor = 0;
|
|
196
|
-
this.
|
|
197
|
-
now: 0,
|
|
198
|
-
frame: 0,
|
|
199
|
-
cursor: 0,
|
|
200
|
-
cols: 1,
|
|
201
|
-
tiles: [],
|
|
202
|
-
};
|
|
190
|
+
this.cols = 1;
|
|
203
191
|
}
|
|
204
192
|
|
|
205
193
|
seed() {
|
|
@@ -349,61 +337,50 @@ class Workspace {
|
|
|
349
337
|
}
|
|
350
338
|
|
|
351
339
|
view() {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
340
|
+
return {
|
|
341
|
+
now: Date.now(),
|
|
342
|
+
frame: this.ui.progressFrame,
|
|
343
|
+
cursor: this.cursor,
|
|
344
|
+
tiles: this.rows.map(toTile),
|
|
345
|
+
};
|
|
358
346
|
}
|
|
359
347
|
|
|
360
348
|
move(dx, dy) {
|
|
361
349
|
const count = this.rows.length;
|
|
362
350
|
if (!count) return;
|
|
363
|
-
const cols = Math.max(1, this.
|
|
351
|
+
const cols = Math.max(1, this.cols || 1);
|
|
364
352
|
const next = this.cursor + dx + dy * cols;
|
|
365
353
|
if (next < 0 || next >= count) return;
|
|
366
354
|
this.cursor = next;
|
|
367
355
|
if (this.ui.uiOpen) this.ui.paint();
|
|
368
356
|
}
|
|
369
357
|
|
|
370
|
-
|
|
371
|
-
const row = this.byName.get(name);
|
|
358
|
+
async switchTo(row) {
|
|
372
359
|
const ui = this.ui;
|
|
373
|
-
if (!row || ui.mode !== 'review') return Promise.resolve();
|
|
374
360
|
ui.flushReview();
|
|
375
361
|
this.stop();
|
|
376
|
-
ui.
|
|
377
|
-
ui.dashboard.stop();
|
|
378
|
-
ui.loader.reset();
|
|
379
|
-
ui.cwd = row.dir;
|
|
380
|
-
ui.paths = [];
|
|
381
|
-
ui.repoName = row.name;
|
|
382
|
-
ui.branch = '';
|
|
383
|
-
ui.rev = null;
|
|
384
|
-
ui.revShort = '';
|
|
385
|
-
ui.change = null;
|
|
386
|
-
ui.dashboardHome = true;
|
|
387
|
-
ui.collection.reset();
|
|
388
|
-
ui.review.reset();
|
|
389
|
-
ui.nav.pane = 'dashboard';
|
|
390
|
-
ui.nav.tasksOpen = false;
|
|
391
|
-
ui.nav.scroll = 0;
|
|
392
|
-
ui.nav.clearSelection();
|
|
393
|
-
ui.status = '';
|
|
362
|
+
ui.retarget({ cwd: row.dir, repoName: row.name, pane: 'dashboard' });
|
|
394
363
|
try {
|
|
395
|
-
ui.ensureRepo();
|
|
364
|
+
await ui.ensureRepo();
|
|
396
365
|
} catch (error) {
|
|
397
366
|
ui.status = error instanceof Error ? error.message : `${error}`;
|
|
398
|
-
return this.show();
|
|
367
|
+
return { loading: this.show() };
|
|
399
368
|
}
|
|
400
|
-
const
|
|
369
|
+
const loading = ui.openLoad();
|
|
401
370
|
if (ui.uiOpen) {
|
|
402
371
|
ui.lifecycle.startWatch();
|
|
403
372
|
ui.dashboard.start();
|
|
404
373
|
ui.paint();
|
|
405
374
|
}
|
|
406
|
-
return
|
|
375
|
+
return { loading };
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
async enter(name) {
|
|
379
|
+
const row = this.byName.get(name);
|
|
380
|
+
const ui = this.ui;
|
|
381
|
+
if (!row || ui.mode !== 'review') return;
|
|
382
|
+
const opened = await ui.ops.hold(this.switchTo(row));
|
|
383
|
+
if (opened) await opened.loading;
|
|
407
384
|
}
|
|
408
385
|
|
|
409
386
|
openSelected() {
|
|
@@ -423,25 +400,9 @@ class Workspace {
|
|
|
423
400
|
const ui = this.ui;
|
|
424
401
|
if (!ui.workspaceRoot) return Promise.resolve();
|
|
425
402
|
ui.flushReview();
|
|
426
|
-
ui.
|
|
427
|
-
|
|
428
|
-
ui.
|
|
429
|
-
ui.cwd = ui.workspaceRoot;
|
|
430
|
-
ui.top = ui.workspaceRoot;
|
|
431
|
-
ui.paths = [];
|
|
432
|
-
ui.repoName = path.basename(ui.workspaceRoot);
|
|
433
|
-
ui.branch = '';
|
|
434
|
-
ui.rev = null;
|
|
435
|
-
ui.revShort = '';
|
|
436
|
-
ui.change = null;
|
|
437
|
-
ui.dashboardHome = false;
|
|
438
|
-
ui.collection.reset();
|
|
439
|
-
ui.review.reset();
|
|
440
|
-
ui.nav.pane = 'repos';
|
|
441
|
-
ui.nav.tasksOpen = false;
|
|
442
|
-
ui.nav.scroll = 0;
|
|
443
|
-
ui.nav.clearSelection();
|
|
444
|
-
ui.status = '';
|
|
403
|
+
const root = ui.workspaceRoot;
|
|
404
|
+
const repoName = path.basename(root);
|
|
405
|
+
ui.retarget({ cwd: root, top: root, repoName, pane: 'repos' });
|
|
445
406
|
ui.loader.didLoad = true;
|
|
446
407
|
this.start();
|
|
447
408
|
if (ui.uiOpen) {
|
|
@@ -452,4 +413,4 @@ class Workspace {
|
|
|
452
413
|
}
|
|
453
414
|
}
|
|
454
415
|
|
|
455
|
-
module.exports = { Workspace
|
|
416
|
+
module.exports = { Workspace };
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
const READ_METHODS = [
|
|
4
4
|
'load',
|
|
5
|
-
'loadAsync',
|
|
6
5
|
'loadExtras',
|
|
7
6
|
'toplevel',
|
|
8
7
|
'resolveRev',
|
|
@@ -12,7 +11,6 @@ const READ_METHODS = [
|
|
|
12
11
|
|
|
13
12
|
const CHANGE_METHODS = [
|
|
14
13
|
'add',
|
|
15
|
-
'addAsync',
|
|
16
14
|
'unstage',
|
|
17
15
|
'revert',
|
|
18
16
|
'revertFile',
|
|
@@ -23,7 +21,7 @@ const CHANGE_METHODS = [
|
|
|
23
21
|
|
|
24
22
|
const BRANCH_METHODS = [
|
|
25
23
|
'commit',
|
|
26
|
-
'
|
|
24
|
+
'updateCommit',
|
|
27
25
|
'hasStaged',
|
|
28
26
|
'lastMessage',
|
|
29
27
|
'commitMessage',
|
|
@@ -31,23 +29,14 @@ const BRANCH_METHODS = [
|
|
|
31
29
|
'listCommits',
|
|
32
30
|
'currentBranch',
|
|
33
31
|
'checkout',
|
|
34
|
-
'checkoutAsync',
|
|
35
32
|
'createBranch',
|
|
36
|
-
'createBranchAsync',
|
|
37
33
|
'rebase',
|
|
38
|
-
'rebaseAsync',
|
|
39
34
|
'drop',
|
|
40
|
-
'dropAsync',
|
|
41
35
|
'dropCommit',
|
|
42
|
-
'dropCommitAsync',
|
|
43
36
|
'applyFixup',
|
|
44
|
-
'applyFixupAsync',
|
|
45
37
|
'reword',
|
|
46
|
-
'rewordAsync',
|
|
47
38
|
'pull',
|
|
48
|
-
'pullAsync',
|
|
49
39
|
'push',
|
|
50
|
-
'pushAsync',
|
|
51
40
|
];
|
|
52
41
|
|
|
53
42
|
const VIEW_CAPS = { read: true, changes: false, branches: false, review: true };
|
|
@@ -57,6 +46,8 @@ const SOURCE_CAPS = {
|
|
|
57
46
|
commit: VIEW_CAPS,
|
|
58
47
|
pr: VIEW_CAPS,
|
|
59
48
|
mr: VIEW_CAPS,
|
|
49
|
+
issue: VIEW_CAPS,
|
|
50
|
+
'gl-issue': VIEW_CAPS,
|
|
60
51
|
readonly: VIEW_CAPS,
|
|
61
52
|
};
|
|
62
53
|
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const { isPathInScope } = files;
|
|
3
|
+
const { isHashObject } = require('metautil');
|
|
4
|
+
const { isPathInScope } = require('../common/files.js');
|
|
5
|
+
const { resource } = require('../common/utilities.js');
|
|
7
6
|
const remote = require('./remote.js');
|
|
8
7
|
const { RemoteClient, isAbortError, listUrl, parseLinkNext } = remote;
|
|
9
8
|
const { commentBody, importedTodo, noteFromLocation } = remote;
|
|
10
|
-
const { splitNotes, loadChange } = remote;
|
|
9
|
+
const { splitNotes, loadChange, stateIs, loadIssue } = remote;
|
|
11
10
|
|
|
12
11
|
const GITHUB_API = 'https://api.github.com';
|
|
13
12
|
const API_VERSION = '2022-11-28';
|
|
@@ -17,14 +16,9 @@ const DIFF_ACCEPT = 'application/vnd.github.diff';
|
|
|
17
16
|
const PR_TODO_FILE = 'pull request';
|
|
18
17
|
const IMPORT_HOST = 'github';
|
|
19
18
|
const GRAPHQL_URL = `${GITHUB_API}/graphql`;
|
|
20
|
-
const REVIEW_THREADS_QUERY =
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
' pullRequest(number: $number) {' +
|
|
24
|
-
' reviewThreads(first: 100, after: $after) {' +
|
|
25
|
-
' pageInfo { hasNextPage endCursor }' +
|
|
26
|
-
' nodes { isResolved comments(first: 100) { nodes { databaseId } } }' +
|
|
27
|
-
' } } } }';
|
|
19
|
+
const REVIEW_THREADS_QUERY = resource(__dirname, 'review-threads.graphql')
|
|
20
|
+
.replace(/\s+/g, ' ')
|
|
21
|
+
.trim();
|
|
28
22
|
|
|
29
23
|
const GITHUB_PR_RE = new RegExp(
|
|
30
24
|
String.raw`^(?:https?://)?(?:www\.)?github\.com/` +
|
|
@@ -35,18 +29,41 @@ const GITHUB_PR_RE = new RegExp(
|
|
|
35
29
|
'i',
|
|
36
30
|
);
|
|
37
31
|
|
|
38
|
-
const
|
|
32
|
+
const GITHUB_ISSUE_RE = new RegExp(
|
|
33
|
+
String.raw`^(?:https?://)?(?:www\.)?github\.com/` +
|
|
34
|
+
String.raw`([^/?#]+)/([^/?#]+)/issues/(\d+)` +
|
|
35
|
+
String.raw`/?(?:[?#].*)?$`,
|
|
36
|
+
'i',
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const GITHUB_ISSUE_LIST_RE = new RegExp(
|
|
40
|
+
String.raw`^(?:https?://)?(?:www\.)?github\.com/` +
|
|
41
|
+
String.raw`([^/?#]+)/([^/?#]+?)(?:\.git)?` +
|
|
42
|
+
String.raw`(?:/issues)?` +
|
|
43
|
+
String.raw`/?(?:[?#].*)?$`,
|
|
44
|
+
'i',
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const parseGithubUrl = (text, pattern) => {
|
|
39
48
|
if (typeof text !== 'string' || !text) return null;
|
|
40
|
-
const match =
|
|
49
|
+
const match = pattern.exec(text.trim());
|
|
41
50
|
if (!match) return null;
|
|
42
51
|
const owner = match[1];
|
|
43
52
|
const repo = match[2].replace(/\.git$/i, '');
|
|
53
|
+
if (!owner || !repo) return null;
|
|
54
|
+
if (!match[3]) return { owner, repo, list: true };
|
|
44
55
|
const number = parseInt(match[3], 10);
|
|
45
|
-
|
|
46
|
-
if (!owner || !repo || !hasNumber) return null;
|
|
56
|
+
if (!Number.isFinite(number) || number < 1) return null;
|
|
47
57
|
return { owner, repo, number };
|
|
48
58
|
};
|
|
49
59
|
|
|
60
|
+
const parseGithubPrUrl = (text) => parseGithubUrl(text, GITHUB_PR_RE);
|
|
61
|
+
|
|
62
|
+
const parseGithubIssueUrl = (text) => parseGithubUrl(text, GITHUB_ISSUE_RE);
|
|
63
|
+
|
|
64
|
+
const parseGithubIssueListUrl = (text) =>
|
|
65
|
+
parseGithubUrl(text, GITHUB_ISSUE_LIST_RE);
|
|
66
|
+
|
|
50
67
|
const githubToken = (env) => {
|
|
51
68
|
const from = env ?? process.env;
|
|
52
69
|
return from.GITHUB_TOKEN || from.GH_TOKEN || '';
|
|
@@ -69,12 +86,13 @@ const client = new RemoteClient({
|
|
|
69
86
|
nextPage: parseLinkNext,
|
|
70
87
|
});
|
|
71
88
|
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
return `${GITHUB_API}/repos/${owner}/${repo}/pulls/${pr.number}`;
|
|
89
|
+
const repositoryApiUrl = ({ owner, repo }) => {
|
|
90
|
+
const name = `${encodeURIComponent(owner)}/${encodeURIComponent(repo)}`;
|
|
91
|
+
return `${GITHUB_API}/repos/${name}`;
|
|
76
92
|
};
|
|
77
93
|
|
|
94
|
+
const prApiUrl = (pr) => `${repositoryApiUrl(pr)}/pulls/${pr.number}`;
|
|
95
|
+
|
|
78
96
|
const loginOf = (user) => user?.login || '';
|
|
79
97
|
|
|
80
98
|
const toChange = (pr, data) => {
|
|
@@ -131,12 +149,15 @@ const discussionToNotes = (discussion, items, paths = []) => {
|
|
|
131
149
|
const comments = discussion.reviewComments ?? [];
|
|
132
150
|
const reviews = discussion.reviews ?? [];
|
|
133
151
|
const issueComments = discussion.issueComments ?? [];
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
]
|
|
152
|
+
const reviewNote = (comment) => {
|
|
153
|
+
const note = reviewCommentToNote(comment, items, resolvedById, paths);
|
|
154
|
+
return note;
|
|
155
|
+
};
|
|
156
|
+
const reviewNotes = comments.map(reviewNote);
|
|
157
|
+
const summaries = [...reviews, ...issueComments];
|
|
158
|
+
const summaryNotes = summaries.map(summaryToTodo);
|
|
159
|
+
const notes = [...reviewNotes, ...summaryNotes];
|
|
160
|
+
return splitNotes(notes);
|
|
140
161
|
};
|
|
141
162
|
|
|
142
163
|
const requestThreads = async (variables, options) => {
|
|
@@ -184,12 +205,15 @@ const loadResolvedById = async (pr, options) => {
|
|
|
184
205
|
|
|
185
206
|
const loadDiscussion = async (pr, options) => {
|
|
186
207
|
const pull = prApiUrl(pr);
|
|
187
|
-
const issue =
|
|
208
|
+
const issue = `${repositoryApiUrl(pr)}/issues/${pr.number}`;
|
|
188
209
|
const list = (url) => client.list(listUrl(url), options);
|
|
189
|
-
const reviewComments
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
210
|
+
const [reviewComments, reviews, issueComments, resolvedById] =
|
|
211
|
+
await Promise.all([
|
|
212
|
+
list(`${pull}/comments`),
|
|
213
|
+
list(`${pull}/reviews`),
|
|
214
|
+
list(`${issue}/comments`),
|
|
215
|
+
loadResolvedById(pr, options),
|
|
216
|
+
]);
|
|
193
217
|
return { reviewComments, reviews, issueComments, resolvedById };
|
|
194
218
|
};
|
|
195
219
|
|
|
@@ -204,10 +228,56 @@ const loadPullRequest = (pr, options = {}) =>
|
|
|
204
228
|
discussionToNotes,
|
|
205
229
|
});
|
|
206
230
|
|
|
231
|
+
const issueSite = (issue) => {
|
|
232
|
+
const repository = `${issue.owner}/${issue.repo}`;
|
|
233
|
+
return {
|
|
234
|
+
repository,
|
|
235
|
+
id: `github.com/${repository}/issues`,
|
|
236
|
+
page: `https://github.com/${repository}/issues`,
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const readIssue = (data) => ({
|
|
241
|
+
number: data.number,
|
|
242
|
+
title: data.title,
|
|
243
|
+
body: data.body,
|
|
244
|
+
author: loginOf(data.user),
|
|
245
|
+
state: data.state,
|
|
246
|
+
url: data['html_url'],
|
|
247
|
+
comments: data.comments,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const readIssueComment = (comment) => ({
|
|
251
|
+
reviewer: loginOf(comment.user),
|
|
252
|
+
body: comment.body,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const isOpenIssue = (data) => {
|
|
256
|
+
if (!isHashObject(data)) return false;
|
|
257
|
+
if (isHashObject(data['pull_request'])) return false;
|
|
258
|
+
return stateIs(data.state, 'open');
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const loadGithubIssue = async (issue, options = {}) =>
|
|
262
|
+
loadIssue(issue, options, {
|
|
263
|
+
client,
|
|
264
|
+
host: IMPORT_HOST,
|
|
265
|
+
site: issueSite(issue),
|
|
266
|
+
issuesUrl: `${repositoryApiUrl(issue)}/issues`,
|
|
267
|
+
openState: 'open',
|
|
268
|
+
comments: 'comments',
|
|
269
|
+
isOpen: isOpenIssue,
|
|
270
|
+
readIssue,
|
|
271
|
+
readComment: readIssueComment,
|
|
272
|
+
});
|
|
273
|
+
|
|
207
274
|
module.exports = {
|
|
208
275
|
parseGithubPrUrl,
|
|
276
|
+
parseGithubIssueUrl,
|
|
277
|
+
parseGithubIssueListUrl,
|
|
209
278
|
githubToken,
|
|
210
279
|
prApiUrl,
|
|
211
280
|
discussionToNotes,
|
|
212
281
|
loadPullRequest,
|
|
282
|
+
loadGithubIssue,
|
|
213
283
|
};
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const files = require('./files.js');
|
|
6
|
-
const { isPathInScope } = files;
|
|
3
|
+
const { isHashObject } = require('metautil');
|
|
4
|
+
const { isPathInScope } = require('../common/files.js');
|
|
7
5
|
const remote = require('./remote.js');
|
|
8
6
|
const { RemoteClient, listUrl, parseNextPage, commentBody } = remote;
|
|
9
7
|
const { importedTodo, noteFromLocation, splitNotes, loadChange } = remote;
|
|
8
|
+
const { stateIs, loadIssue } = remote;
|
|
10
9
|
|
|
11
10
|
const USER_AGENT = 'reslop';
|
|
12
11
|
const JSON_ACCEPT = 'application/json';
|
|
@@ -24,9 +23,16 @@ const GITLAB_MR_RE = new RegExp(
|
|
|
24
23
|
'i',
|
|
25
24
|
);
|
|
26
25
|
|
|
27
|
-
const
|
|
26
|
+
const GITLAB_ISSUE_RE = new RegExp(
|
|
27
|
+
String.raw`^(?:(https?)://)?(?:www\.)?([^/?#]+)/` +
|
|
28
|
+
String.raw`(.+?)(?:/-)?/issues/(\d+)` +
|
|
29
|
+
String.raw`/?(?:[?#].*)?$`,
|
|
30
|
+
'i',
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const parseGitlabUrl = (text, pattern) => {
|
|
28
34
|
if (typeof text !== 'string' || !text) return null;
|
|
29
|
-
const match =
|
|
35
|
+
const match = pattern.exec(text.trim());
|
|
30
36
|
if (!match) return null;
|
|
31
37
|
const scheme = (match[1] || 'https').toLowerCase();
|
|
32
38
|
const host = match[2];
|
|
@@ -37,6 +43,35 @@ const parseGitlabMrUrl = (text) => {
|
|
|
37
43
|
return { host, project, number, origin: `${scheme}://${host}` };
|
|
38
44
|
};
|
|
39
45
|
|
|
46
|
+
const GITLAB_HOST_RE = new RegExp(
|
|
47
|
+
String.raw`^(?:(https?)://)?(?:www\.)?([^/?#]+\.[^/?#]+)/([^?#]+)`,
|
|
48
|
+
'i',
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const parseGitlabMrUrl = (text) => parseGitlabUrl(text, GITLAB_MR_RE);
|
|
52
|
+
|
|
53
|
+
const parseGitlabIssueUrl = (text) => {
|
|
54
|
+
const issue = parseGitlabUrl(text, GITLAB_ISSUE_RE);
|
|
55
|
+
return issue?.host.toLowerCase() === 'github.com' ? null : issue;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const parseGitlabIssueListUrl = (text) => {
|
|
59
|
+
if (typeof text !== 'string' || !text) return null;
|
|
60
|
+
const match = GITLAB_HOST_RE.exec(text.trim());
|
|
61
|
+
if (!match) return null;
|
|
62
|
+
const scheme = (match[1] || 'https').toLowerCase();
|
|
63
|
+
const host = match[2];
|
|
64
|
+
if (!host || host.toLowerCase() === 'github.com') return null;
|
|
65
|
+
let path = match[3].replace(/\/+$/, '');
|
|
66
|
+
if (/\/issues\/\d+/.test(path)) return null;
|
|
67
|
+
const listed = /(?:\/-)?\/issues$/.test(path);
|
|
68
|
+
if (listed) path = path.replace(/(?:\/-)?\/issues$/, '');
|
|
69
|
+
else if (path.includes('/-/')) return null;
|
|
70
|
+
path = path.replace(/\.git$/i, '');
|
|
71
|
+
if (!path.includes('/')) return null;
|
|
72
|
+
return { host, project: path, origin: `${scheme}://${host}`, list: true };
|
|
73
|
+
};
|
|
74
|
+
|
|
40
75
|
const gitlabToken = (env) => {
|
|
41
76
|
const from = env ?? process.env;
|
|
42
77
|
return from.GITLAB_TOKEN || from.GL_TOKEN || '';
|
|
@@ -57,10 +92,10 @@ const client = new RemoteClient({
|
|
|
57
92
|
limitStatus: 429,
|
|
58
93
|
});
|
|
59
94
|
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
95
|
+
const projectApiUrl = (ref) =>
|
|
96
|
+
`${ref.origin}/api/v4/projects/${encodeURIComponent(ref.project)}`;
|
|
97
|
+
|
|
98
|
+
const mrApiUrl = (mr) => `${projectApiUrl(mr)}/merge_requests/${mr.number}`;
|
|
64
99
|
|
|
65
100
|
const usernameOf = (user) => user?.username || '';
|
|
66
101
|
|
|
@@ -133,6 +168,45 @@ const discussionToNotes = (discussions, items, paths = []) => {
|
|
|
133
168
|
const loadDiscussion = (mr, options) =>
|
|
134
169
|
client.list(listUrl(`${mrApiUrl(mr)}/discussions`), options);
|
|
135
170
|
|
|
171
|
+
const issueSite = (issue) => ({
|
|
172
|
+
repository: issue.project,
|
|
173
|
+
id: `${issue.host}/${issue.project}/issues`,
|
|
174
|
+
page: `${issue.origin}/${issue.project}/-/issues`,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const readIssue = (data) => ({
|
|
178
|
+
number: data.iid,
|
|
179
|
+
title: data.title,
|
|
180
|
+
body: data.description,
|
|
181
|
+
author: usernameOf(data.author),
|
|
182
|
+
state: data.state,
|
|
183
|
+
url: data.web_url,
|
|
184
|
+
comments: data.user_notes_count,
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
const readIssueNote = (note) => {
|
|
188
|
+
if (!isHashObject(note) || note.system === true) return null;
|
|
189
|
+
return { reviewer: usernameOf(note.author), body: note.body };
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const isOpenIssue = (data) => {
|
|
193
|
+
if (!isHashObject(data)) return false;
|
|
194
|
+
return stateIs(data.state, 'opened');
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const loadGitlabIssue = async (issue, options = {}) =>
|
|
198
|
+
loadIssue(issue, options, {
|
|
199
|
+
client,
|
|
200
|
+
host: IMPORT_HOST,
|
|
201
|
+
site: issueSite(issue),
|
|
202
|
+
issuesUrl: `${projectApiUrl(issue)}/issues`,
|
|
203
|
+
openState: 'opened',
|
|
204
|
+
comments: 'notes',
|
|
205
|
+
isOpen: isOpenIssue,
|
|
206
|
+
readIssue,
|
|
207
|
+
readComment: readIssueNote,
|
|
208
|
+
});
|
|
209
|
+
|
|
136
210
|
const loadMergeRequest = (mr, options = {}) => {
|
|
137
211
|
const url = mrApiUrl(mr);
|
|
138
212
|
return loadChange(mr, options, {
|
|
@@ -149,8 +223,11 @@ const loadMergeRequest = (mr, options = {}) => {
|
|
|
149
223
|
|
|
150
224
|
module.exports = {
|
|
151
225
|
parseGitlabMrUrl,
|
|
226
|
+
parseGitlabIssueUrl,
|
|
227
|
+
parseGitlabIssueListUrl,
|
|
152
228
|
gitlabToken,
|
|
153
229
|
mrApiUrl,
|
|
154
230
|
discussionToNotes,
|
|
231
|
+
loadGitlabIssue,
|
|
155
232
|
loadMergeRequest,
|
|
156
233
|
};
|