reslop 0.1.9 → 0.2.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 +12 -1
- package/README.md +2 -1
- package/bin/reslop.js +2 -2
- package/lib/ansi.js +4 -2
- package/lib/api.js +2 -2
- package/lib/capabilities.js +7 -14
- package/lib/cli.js +28 -47
- package/lib/clipboard.js +1 -1
- package/lib/{deps/changes.js → deps-diff.js} +309 -19
- package/lib/deps.js +498 -29
- package/lib/detect.js +1 -7
- package/lib/diff/align.js +7 -10
- package/lib/diff/diff.js +53 -0
- package/lib/diff/display.js +5 -4
- package/lib/diff/inline.js +26 -21
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +1 -3
- package/lib/diff/unit.js +392 -0
- package/lib/files.js +5 -8
- package/lib/{git/branches.js → git-branches.js} +6 -5
- package/lib/{git/dependencies.js → git-deps.js} +111 -38
- package/lib/git.js +589 -68
- package/lib/github.js +28 -197
- package/lib/gitlab.js +24 -195
- package/lib/highlight/bash.js +1 -3
- package/lib/highlight/css.js +1 -3
- package/lib/highlight/js.js +11 -14
- package/lib/highlight.js +6 -9
- package/lib/keys.js +14 -8
- package/lib/manifest.js +330 -0
- package/lib/remote.js +374 -0
- package/lib/render/branches.js +14 -68
- package/lib/render/chrome.js +24 -9
- package/lib/render/commits.js +6 -59
- package/lib/render/diff.js +16 -16
- package/lib/render/files.js +6 -34
- package/lib/render/frame.js +23 -12
- package/lib/render/notes.js +4 -3
- package/lib/render/primitives.js +45 -0
- package/lib/{render.js → render/render.js} +5 -5
- package/lib/render/unit.js +41 -0
- package/lib/review.js +750 -14
- package/lib/select.js +5 -10
- package/lib/session/accessors.js +9 -1
- package/lib/session/actions.js +32 -16
- package/lib/session/branches.js +116 -128
- package/lib/session/change-actions.js +202 -123
- package/lib/session/commit.js +105 -112
- package/lib/session/compose.js +158 -13
- package/lib/session/dispatch.js +104 -60
- package/lib/session/files-pane.js +142 -148
- package/lib/session/items.js +22 -30
- package/lib/session/lifecycle.js +44 -44
- package/lib/session/load.js +19 -33
- package/lib/session/navigation.js +12 -1
- package/lib/session/ops.js +28 -41
- package/lib/session/pointer.js +54 -57
- package/lib/session/review.js +23 -37
- package/lib/session/terminal.js +6 -12
- package/lib/session/todos.js +8 -12
- package/lib/session/unit.js +208 -0
- package/lib/session/update.js +33 -46
- package/lib/session/view.js +78 -9
- package/lib/session/watch.js +11 -19
- package/lib/session.js +21 -246
- package/lib/source.js +3 -3
- package/lib/update.js +40 -75
- package/lib/{git/proc.js → utilities.js} +75 -11
- package/lib/wrap.js +1 -3
- package/package.json +1 -1
- package/lib/deps/items.js +0 -134
- package/lib/deps/manifest.js +0 -152
- package/lib/deps/merge.js +0 -120
- package/lib/deps/proposals.js +0 -164
- package/lib/deps/reports.js +0 -180
- package/lib/deps/rows.js +0 -176
- package/lib/deps/scan.js +0 -138
- package/lib/deps/usage.js +0 -68
- package/lib/deps/versions.js +0 -119
- package/lib/diff.js +0 -33
- package/lib/git/changes.js +0 -164
- package/lib/git/read.js +0 -253
- package/lib/git/run.js +0 -100
- package/lib/npm/commands.js +0 -100
- package/lib/remote/changes.js +0 -37
- package/lib/remote/headers.js +0 -36
- package/lib/remote/http.js +0 -122
- package/lib/remote/notes.js +0 -94
- package/lib/review/format.js +0 -434
- package/lib/review/storage.js +0 -150
- package/lib/review/store.js +0 -178
- package/lib/review/templates.js +0 -49
- package/lib/session/origins.js +0 -7
- package/lib/sys.js +0 -84
package/lib/github.js
CHANGED
|
@@ -1,18 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { jsonParse } = require('metautil');
|
|
4
|
-
|
|
5
|
-
const diff = require('./diff.js');
|
|
6
|
-
const { parseDiff, itemsFromFiles } = diff;
|
|
7
3
|
const { isPathInScope } = require('./files.js');
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
12
|
-
const {
|
|
13
|
-
const { filterChangeFiles, changeFiles } = require('./remote/changes.js');
|
|
14
|
-
const remoteNotes = require('./remote/notes.js');
|
|
15
|
-
const { importedText, importedTodo, noteFromLocation } = remoteNotes;
|
|
4
|
+
const remote = require('./remote.js');
|
|
5
|
+
const { isRecord, isAbortError } = remote;
|
|
6
|
+
const { createClient, listUrl, parseLinkNext } = remote;
|
|
7
|
+
const { filterChangeFiles, changeFiles, loadChange } = remote;
|
|
8
|
+
const { importedText, importedTodo, noteFromLocation } = remote;
|
|
16
9
|
|
|
17
10
|
const GITHUB_API = 'https://api.github.com';
|
|
18
11
|
const API_VERSION = '2022-11-28';
|
|
@@ -20,7 +13,6 @@ const USER_AGENT = 'reslop';
|
|
|
20
13
|
const JSON_ACCEPT = 'application/vnd.github+json';
|
|
21
14
|
const DIFF_ACCEPT = 'application/vnd.github.diff';
|
|
22
15
|
const PR_TODO_FILE = 'pull request';
|
|
23
|
-
const LIST_PER_PAGE = 100;
|
|
24
16
|
const IMPORT_HOST = 'github';
|
|
25
17
|
const GRAPHQL_URL = `${GITHUB_API}/graphql`;
|
|
26
18
|
const REVIEW_THREADS_QUERY =
|
|
@@ -41,17 +33,6 @@ const GITHUB_PR_RE = new RegExp(
|
|
|
41
33
|
'i',
|
|
42
34
|
);
|
|
43
35
|
|
|
44
|
-
const githubError = (code, message) => {
|
|
45
|
-
const error = new Error(message);
|
|
46
|
-
error.code = code;
|
|
47
|
-
return error;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const withStatus = (error, status) => {
|
|
51
|
-
error.status = status;
|
|
52
|
-
return error;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
36
|
const asRecord = (value) => (isRecord(value) ? value : {});
|
|
56
37
|
|
|
57
38
|
const parseGithubPrUrl = (text) => {
|
|
@@ -72,59 +53,7 @@ const githubToken = (env) => {
|
|
|
72
53
|
return from.GITHUB_TOKEN || from.GH_TOKEN || '';
|
|
73
54
|
};
|
|
74
55
|
|
|
75
|
-
const
|
|
76
|
-
const text = `${body ?? ''}`.trim();
|
|
77
|
-
if (!text) return '';
|
|
78
|
-
const parsed = jsonParse(text);
|
|
79
|
-
if (isRecord(parsed) && typeof parsed.message === 'string') {
|
|
80
|
-
return parsed.message;
|
|
81
|
-
}
|
|
82
|
-
return oneLine(text);
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
const githubHttpError = (status, body, token) => {
|
|
86
|
-
const message = parseApiMessage(body);
|
|
87
|
-
if (status === 404) {
|
|
88
|
-
const hint = token ? '' : ' (set GITHUB_TOKEN for private repositories)';
|
|
89
|
-
const error = githubError(
|
|
90
|
-
'GITHUB_NOT_FOUND',
|
|
91
|
-
`GitHub pull request not found${hint}`,
|
|
92
|
-
);
|
|
93
|
-
return withStatus(error, status);
|
|
94
|
-
}
|
|
95
|
-
if (status === 401) {
|
|
96
|
-
const error = githubError('GITHUB_AUTH', 'GitHub authentication failed');
|
|
97
|
-
return withStatus(error, status);
|
|
98
|
-
}
|
|
99
|
-
if (status === 403 && /rate limit/i.test(message)) {
|
|
100
|
-
const error = githubError(
|
|
101
|
-
'GITHUB_RATE_LIMIT',
|
|
102
|
-
'GitHub API rate limit exceeded',
|
|
103
|
-
);
|
|
104
|
-
return withStatus(error, status);
|
|
105
|
-
}
|
|
106
|
-
let detail = `GitHub API HTTP ${status}`;
|
|
107
|
-
if (message) detail = `GitHub API: ${oneLine(message)}`;
|
|
108
|
-
return withStatus(githubError('GITHUB_HTTP', detail), status);
|
|
109
|
-
};
|
|
110
|
-
|
|
111
|
-
const wrapNetworkError = (error) => {
|
|
112
|
-
const detail = oneLine(error && error.message);
|
|
113
|
-
const suffix = detail ? `: ${detail}` : '';
|
|
114
|
-
const wrapped = githubError(
|
|
115
|
-
'GITHUB_NETWORK',
|
|
116
|
-
`GitHub request failed${suffix}`,
|
|
117
|
-
);
|
|
118
|
-
wrapped.cause = error;
|
|
119
|
-
return wrapped;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const isRetryable = (error) => {
|
|
123
|
-
if (error.code === 'GITHUB_NETWORK') return true;
|
|
124
|
-
return TRANSIENT_STATUS.includes(error.status);
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
const githubHeaders = (token, accept) => {
|
|
56
|
+
const githubHeaders = (token, accept = JSON_ACCEPT) => {
|
|
128
57
|
const headers = {
|
|
129
58
|
Accept: accept,
|
|
130
59
|
'User-Agent': USER_AGENT,
|
|
@@ -134,62 +63,17 @@ const githubHeaders = (token, accept) => {
|
|
|
134
63
|
return { ...headers, Authorization: `Bearer ${token}` };
|
|
135
64
|
};
|
|
136
65
|
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
createTransport({
|
|
149
|
-
fetch: options.fetch,
|
|
150
|
-
delay: options.delay,
|
|
151
|
-
signal: options.signal,
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const requestPolicy = (options) => {
|
|
155
|
-
const token = options.token || '';
|
|
156
|
-
return {
|
|
157
|
-
retry: options.retry ?? {},
|
|
158
|
-
isRetryable,
|
|
159
|
-
wrapNetwork: wrapNetworkError,
|
|
160
|
-
missingFetch: () => githubError('GITHUB_FETCH', 'fetch is not available'),
|
|
161
|
-
toHttpError: (status, body) => githubHttpError(status, body, token),
|
|
162
|
-
};
|
|
163
|
-
};
|
|
164
|
-
|
|
165
|
-
const requestGithub = async (url, options) => {
|
|
166
|
-
const token = options.token || '';
|
|
167
|
-
const headers = { ...githubHeaders(token, options.accept) };
|
|
168
|
-
if (options.contentType) headers['Content-Type'] = options.contentType;
|
|
169
|
-
return transportOf(options).request(url, {
|
|
170
|
-
...requestPolicy(options),
|
|
171
|
-
method: options.method || 'GET',
|
|
172
|
-
headers,
|
|
173
|
-
body: options.body,
|
|
174
|
-
});
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
const requestGithubList = async (url, options) => {
|
|
178
|
-
const token = options.token || '';
|
|
179
|
-
return transportOf(options).listPages(url, {
|
|
180
|
-
...requestPolicy(options),
|
|
181
|
-
method: 'GET',
|
|
182
|
-
headers: githubHeaders(token, JSON_ACCEPT),
|
|
183
|
-
parsePage: (text) => {
|
|
184
|
-
const data = parseGithubJson(text, 'list payload');
|
|
185
|
-
if (!Array.isArray(data)) {
|
|
186
|
-
throw githubError('GITHUB_PARSE', 'GitHub API: invalid list payload');
|
|
187
|
-
}
|
|
188
|
-
return data;
|
|
189
|
-
},
|
|
190
|
-
nextPage: (headers) => parseLinkNext(headers),
|
|
191
|
-
});
|
|
192
|
-
};
|
|
66
|
+
const client = createClient({
|
|
67
|
+
name: 'GitHub',
|
|
68
|
+
subject: 'pull request',
|
|
69
|
+
headers: githubHeaders,
|
|
70
|
+
nextPage: parseLinkNext,
|
|
71
|
+
});
|
|
72
|
+
const {
|
|
73
|
+
request: requestGithub,
|
|
74
|
+
list: requestGithubList,
|
|
75
|
+
parseJson: parseGithubJson,
|
|
76
|
+
} = client;
|
|
193
77
|
|
|
194
78
|
const requestGraphql = async (query, variables, options) => {
|
|
195
79
|
const payload = JSON.stringify({ query, variables });
|
|
@@ -200,11 +84,7 @@ const requestGraphql = async (query, variables, options) => {
|
|
|
200
84
|
contentType: 'application/json',
|
|
201
85
|
body: payload,
|
|
202
86
|
});
|
|
203
|
-
|
|
204
|
-
if (!isRecord(data)) {
|
|
205
|
-
throw githubError('GITHUB_PARSE', 'GitHub API: invalid GraphQL payload');
|
|
206
|
-
}
|
|
207
|
-
return data;
|
|
87
|
+
return parseGithubJson(page.body, 'GraphQL payload');
|
|
208
88
|
};
|
|
209
89
|
|
|
210
90
|
const prApiUrl = (pr) => {
|
|
@@ -213,12 +93,6 @@ const prApiUrl = (pr) => {
|
|
|
213
93
|
return `${GITHUB_API}/repos/${owner}/${repo}/pulls/${pr.number}`;
|
|
214
94
|
};
|
|
215
95
|
|
|
216
|
-
const listUrl = (base) => {
|
|
217
|
-
const url = new URL(base);
|
|
218
|
-
url.searchParams.set('per_page', `${LIST_PER_PAGE}`);
|
|
219
|
-
return url.toString();
|
|
220
|
-
};
|
|
221
|
-
|
|
222
96
|
const discussionUrls = (pr) => {
|
|
223
97
|
const pull = prApiUrl(pr);
|
|
224
98
|
const issue = pull.replace('/pulls/', '/issues/');
|
|
@@ -247,17 +121,6 @@ const readRepoName = (data, pr) => {
|
|
|
247
121
|
return `${pr.owner}/${pr.repo}`;
|
|
248
122
|
};
|
|
249
123
|
|
|
250
|
-
const parsePullRequestPayload = (text) => {
|
|
251
|
-
const data = parseGithubJson(text, 'pull request payload');
|
|
252
|
-
if (!isRecord(data)) {
|
|
253
|
-
throw githubError(
|
|
254
|
-
'GITHUB_PARSE',
|
|
255
|
-
'GitHub API: invalid pull request payload',
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
return data;
|
|
259
|
-
};
|
|
260
|
-
|
|
261
124
|
const toChange = (pr, data, items) => {
|
|
262
125
|
const number = data.number || pr.number;
|
|
263
126
|
const repository = readRepoName(data, pr);
|
|
@@ -271,7 +134,7 @@ const toChange = (pr, data, items) => {
|
|
|
271
134
|
const id = `github.com/${repository}/pull/${number}`;
|
|
272
135
|
return {
|
|
273
136
|
id,
|
|
274
|
-
source: '
|
|
137
|
+
source: 'pr',
|
|
275
138
|
title,
|
|
276
139
|
author,
|
|
277
140
|
files,
|
|
@@ -441,48 +304,16 @@ const loadDiscussion = async (pr, options) => {
|
|
|
441
304
|
return { reviewComments, reviews, issueComments, resolvedById };
|
|
442
305
|
};
|
|
443
306
|
|
|
444
|
-
const
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
const loadPullRequest = async (pr, options = {}) => {
|
|
456
|
-
const token = options.token ?? '';
|
|
457
|
-
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
458
|
-
const paths = options.paths ?? [];
|
|
459
|
-
const retryOpts = options.retry ?? {};
|
|
460
|
-
const url = prApiUrl(pr);
|
|
461
|
-
const request = {
|
|
462
|
-
fetch: fetchImpl,
|
|
463
|
-
token,
|
|
464
|
-
retry: retryOpts,
|
|
465
|
-
delay: options.delay,
|
|
466
|
-
signal: options.signal,
|
|
467
|
-
};
|
|
468
|
-
const jsonPage = await requestGithub(url, {
|
|
469
|
-
...request,
|
|
470
|
-
accept: JSON_ACCEPT,
|
|
471
|
-
});
|
|
472
|
-
const data = parsePullRequestPayload(jsonPage.body);
|
|
473
|
-
const diffPage = await requestGithub(url, {
|
|
474
|
-
...request,
|
|
475
|
-
accept: DIFF_ACCEPT,
|
|
307
|
+
const loadPullRequest = (pr, options = {}) =>
|
|
308
|
+
loadChange(pr, options, {
|
|
309
|
+
client,
|
|
310
|
+
url: prApiUrl(pr),
|
|
311
|
+
diffAccept: DIFF_ACCEPT,
|
|
312
|
+
prefix: '#',
|
|
313
|
+
toChange,
|
|
314
|
+
loadDiscussion,
|
|
315
|
+
discussionToNotes,
|
|
476
316
|
});
|
|
477
|
-
const parsed = parseDiff(diffPage.body);
|
|
478
|
-
const files = filterChangeFiles(parsed, paths);
|
|
479
|
-
const parsedItems = itemsFromFiles(files, 'pr');
|
|
480
|
-
const items = foldDepItems(parsedItems);
|
|
481
|
-
const change = toChange(pr, data, items);
|
|
482
|
-
const sourceLabel = `#${change.number}`;
|
|
483
|
-
const imported = await loadImportedNotes(pr, items, paths, request);
|
|
484
|
-
return { top: options.cwd, items, sourceLabel, change, imported };
|
|
485
|
-
};
|
|
486
317
|
|
|
487
318
|
module.exports = {
|
|
488
319
|
parseGithubPrUrl,
|
package/lib/gitlab.js
CHANGED
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { jsonParse } = require('metautil');
|
|
4
|
-
|
|
5
|
-
const diff = require('./diff.js');
|
|
6
|
-
const { parseDiff, itemsFromFiles } = diff;
|
|
7
3
|
const { isPathInScope } = require('./files.js');
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const {
|
|
11
|
-
const {
|
|
12
|
-
const {
|
|
13
|
-
const { filterChangeFiles, changeFiles } = require('./remote/changes.js');
|
|
14
|
-
const remoteNotes = require('./remote/notes.js');
|
|
15
|
-
const { importedText, importedTodo, noteFromLocation } = remoteNotes;
|
|
4
|
+
const remote = require('./remote.js');
|
|
5
|
+
const { isRecord } = remote;
|
|
6
|
+
const { createClient, listUrl, parseNextPage } = remote;
|
|
7
|
+
const { filterChangeFiles, changeFiles, loadChange } = remote;
|
|
8
|
+
const { importedText, importedTodo, noteFromLocation } = remote;
|
|
16
9
|
|
|
17
10
|
const USER_AGENT = 'reslop';
|
|
18
11
|
const JSON_ACCEPT = 'application/json';
|
|
19
12
|
const DIFF_ACCEPT = 'text/plain';
|
|
20
13
|
const MR_TODO_FILE = 'merge request';
|
|
21
|
-
const LIST_PER_PAGE = 100;
|
|
22
14
|
const IMPORT_HOST = 'gitlab';
|
|
23
15
|
|
|
24
16
|
const GITLAB_MR_RE = new RegExp(
|
|
@@ -30,17 +22,6 @@ const GITLAB_MR_RE = new RegExp(
|
|
|
30
22
|
'i',
|
|
31
23
|
);
|
|
32
24
|
|
|
33
|
-
const gitlabError = (code, message) => {
|
|
34
|
-
const error = new Error(message);
|
|
35
|
-
error.code = code;
|
|
36
|
-
return error;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const withStatus = (error, status) => {
|
|
40
|
-
error.status = status;
|
|
41
|
-
return error;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
25
|
const parseGitlabMrUrl = (text) => {
|
|
45
26
|
if (typeof text !== 'string' || !text) return null;
|
|
46
27
|
const match = GITLAB_MR_RE.exec(text.trim());
|
|
@@ -61,67 +42,7 @@ const gitlabToken = (env) => {
|
|
|
61
42
|
return from.GITLAB_TOKEN || from.GL_TOKEN || '';
|
|
62
43
|
};
|
|
63
44
|
|
|
64
|
-
const
|
|
65
|
-
const text = `${body ?? ''}`.trim();
|
|
66
|
-
if (!text) return '';
|
|
67
|
-
const parsed = jsonParse(text);
|
|
68
|
-
if (isRecord(parsed)) {
|
|
69
|
-
if (typeof parsed.message === 'string') return parsed.message;
|
|
70
|
-
if (typeof parsed.error === 'string') return parsed.error;
|
|
71
|
-
}
|
|
72
|
-
return oneLine(text);
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const gitlabHttpError = (status, body, token) => {
|
|
76
|
-
const message = parseApiMessage(body);
|
|
77
|
-
if (status === 404) {
|
|
78
|
-
const hint = token ? '' : ' (set GITLAB_TOKEN for private repositories)';
|
|
79
|
-
const error = gitlabError(
|
|
80
|
-
'GITLAB_NOT_FOUND',
|
|
81
|
-
`GitLab merge request not found${hint}`,
|
|
82
|
-
);
|
|
83
|
-
return withStatus(error, status);
|
|
84
|
-
}
|
|
85
|
-
if (status === 401) {
|
|
86
|
-
const error = gitlabError('GITLAB_AUTH', 'GitLab authentication failed');
|
|
87
|
-
return withStatus(error, status);
|
|
88
|
-
}
|
|
89
|
-
if (status === 429) {
|
|
90
|
-
const error = gitlabError(
|
|
91
|
-
'GITLAB_RATE_LIMIT',
|
|
92
|
-
'GitLab API rate limit exceeded',
|
|
93
|
-
);
|
|
94
|
-
return withStatus(error, status);
|
|
95
|
-
}
|
|
96
|
-
if (status === 403 && /rate limit/i.test(message)) {
|
|
97
|
-
const error = gitlabError(
|
|
98
|
-
'GITLAB_RATE_LIMIT',
|
|
99
|
-
'GitLab API rate limit exceeded',
|
|
100
|
-
);
|
|
101
|
-
return withStatus(error, status);
|
|
102
|
-
}
|
|
103
|
-
let detail = `GitLab API HTTP ${status}`;
|
|
104
|
-
if (message) detail = `GitLab API: ${oneLine(message)}`;
|
|
105
|
-
return withStatus(gitlabError('GITLAB_HTTP', detail), status);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
const wrapNetworkError = (error) => {
|
|
109
|
-
const detail = oneLine(error && error.message);
|
|
110
|
-
const suffix = detail ? `: ${detail}` : '';
|
|
111
|
-
const wrapped = gitlabError(
|
|
112
|
-
'GITLAB_NETWORK',
|
|
113
|
-
`GitLab request failed${suffix}`,
|
|
114
|
-
);
|
|
115
|
-
wrapped.cause = error;
|
|
116
|
-
return wrapped;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
const isRetryable = (error) => {
|
|
120
|
-
if (error.code === 'GITLAB_NETWORK') return true;
|
|
121
|
-
return TRANSIENT_STATUS.includes(error.status);
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const gitlabHeaders = (token, accept) => {
|
|
45
|
+
const gitlabHeaders = (token, accept = JSON_ACCEPT) => {
|
|
125
46
|
const headers = {
|
|
126
47
|
Accept: accept,
|
|
127
48
|
'User-Agent': USER_AGENT,
|
|
@@ -130,59 +51,13 @@ const gitlabHeaders = (token, accept) => {
|
|
|
130
51
|
return { ...headers, 'PRIVATE-TOKEN': token };
|
|
131
52
|
};
|
|
132
53
|
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
return data;
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
const transportOf = (options) =>
|
|
144
|
-
createTransport({
|
|
145
|
-
fetch: options.fetch,
|
|
146
|
-
delay: options.delay,
|
|
147
|
-
signal: options.signal,
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const requestPolicy = (options) => {
|
|
151
|
-
const token = options.token || '';
|
|
152
|
-
return {
|
|
153
|
-
retry: options.retry ?? {},
|
|
154
|
-
isRetryable,
|
|
155
|
-
wrapNetwork: wrapNetworkError,
|
|
156
|
-
missingFetch: () => gitlabError('GITLAB_FETCH', 'fetch is not available'),
|
|
157
|
-
toHttpError: (status, body) => gitlabHttpError(status, body, token),
|
|
158
|
-
};
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const requestGitlab = async (url, options) => {
|
|
162
|
-
const token = options.token || '';
|
|
163
|
-
return transportOf(options).request(url, {
|
|
164
|
-
...requestPolicy(options),
|
|
165
|
-
method: options.method || 'GET',
|
|
166
|
-
headers: gitlabHeaders(token, options.accept),
|
|
167
|
-
});
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const requestGitlabList = async (url, options) => {
|
|
171
|
-
const token = options.token || '';
|
|
172
|
-
return transportOf(options).listPages(url, {
|
|
173
|
-
...requestPolicy(options),
|
|
174
|
-
method: 'GET',
|
|
175
|
-
headers: gitlabHeaders(token, JSON_ACCEPT),
|
|
176
|
-
parsePage: (text) => {
|
|
177
|
-
const data = parseGitlabJson(text, 'list payload');
|
|
178
|
-
if (!Array.isArray(data)) {
|
|
179
|
-
throw gitlabError('GITLAB_PARSE', 'GitLab API: invalid list payload');
|
|
180
|
-
}
|
|
181
|
-
return data;
|
|
182
|
-
},
|
|
183
|
-
nextPage: parseNextPage,
|
|
184
|
-
});
|
|
185
|
-
};
|
|
54
|
+
const client = createClient({
|
|
55
|
+
name: 'GitLab',
|
|
56
|
+
subject: 'merge request',
|
|
57
|
+
headers: gitlabHeaders,
|
|
58
|
+
nextPage: parseNextPage,
|
|
59
|
+
});
|
|
60
|
+
const { list: requestGitlabList } = client;
|
|
186
61
|
|
|
187
62
|
const mrApiUrl = (mr) => {
|
|
188
63
|
const project = encodeURIComponent(mr.project);
|
|
@@ -190,12 +65,6 @@ const mrApiUrl = (mr) => {
|
|
|
190
65
|
return `${base}/merge_requests/${mr.number}`;
|
|
191
66
|
};
|
|
192
67
|
|
|
193
|
-
const listUrl = (base) => {
|
|
194
|
-
const url = new URL(base);
|
|
195
|
-
url.searchParams.set('per_page', `${LIST_PER_PAGE}`);
|
|
196
|
-
return url.toString();
|
|
197
|
-
};
|
|
198
|
-
|
|
199
68
|
const readUsername = (user) => {
|
|
200
69
|
if (!isRecord(user)) return '';
|
|
201
70
|
return user.username || '';
|
|
@@ -211,17 +80,6 @@ const readProject = (data, mr) => {
|
|
|
211
80
|
return mr.project;
|
|
212
81
|
};
|
|
213
82
|
|
|
214
|
-
const parseMergeRequestPayload = (text) => {
|
|
215
|
-
const data = parseGitlabJson(text, 'merge request payload');
|
|
216
|
-
if (!isRecord(data)) {
|
|
217
|
-
throw gitlabError(
|
|
218
|
-
'GITLAB_PARSE',
|
|
219
|
-
'GitLab API: invalid merge request payload',
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
return data;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
83
|
const toChange = (mr, data, items) => {
|
|
226
84
|
const number = data.iid || mr.number;
|
|
227
85
|
const repository = readProject(data, mr);
|
|
@@ -235,7 +93,7 @@ const toChange = (mr, data, items) => {
|
|
|
235
93
|
const id = `${mr.host}/${repository}/merge_requests/${number}`;
|
|
236
94
|
return {
|
|
237
95
|
id,
|
|
238
|
-
source: '
|
|
96
|
+
source: 'mr',
|
|
239
97
|
title,
|
|
240
98
|
author,
|
|
241
99
|
files,
|
|
@@ -322,47 +180,18 @@ const loadDiscussion = async (mr, options) => {
|
|
|
322
180
|
return requestGitlabList(url, options);
|
|
323
181
|
};
|
|
324
182
|
|
|
325
|
-
const
|
|
326
|
-
try {
|
|
327
|
-
const discussions = await loadDiscussion(mr, options);
|
|
328
|
-
return discussionToNotes(discussions, items, paths);
|
|
329
|
-
} catch (error) {
|
|
330
|
-
if (isAbortError(error, options.signal)) throw error;
|
|
331
|
-
// ignore: discussion import is optional
|
|
332
|
-
return { feedback: [], todos: [] };
|
|
333
|
-
}
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
const loadMergeRequest = async (mr, options = {}) => {
|
|
337
|
-
const token = options.token ?? '';
|
|
338
|
-
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
339
|
-
const paths = options.paths ?? [];
|
|
340
|
-
const retryOpts = options.retry ?? {};
|
|
183
|
+
const loadMergeRequest = (mr, options = {}) => {
|
|
341
184
|
const url = mrApiUrl(mr);
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
accept: JSON_ACCEPT,
|
|
352
|
-
});
|
|
353
|
-
const data = parseMergeRequestPayload(jsonPage.body);
|
|
354
|
-
const diffPage = await requestGitlab(`${url}/raw_diffs`, {
|
|
355
|
-
...request,
|
|
356
|
-
accept: DIFF_ACCEPT,
|
|
185
|
+
return loadChange(mr, options, {
|
|
186
|
+
client,
|
|
187
|
+
url,
|
|
188
|
+
diffUrl: `${url}/raw_diffs`,
|
|
189
|
+
diffAccept: DIFF_ACCEPT,
|
|
190
|
+
prefix: '!',
|
|
191
|
+
toChange,
|
|
192
|
+
loadDiscussion,
|
|
193
|
+
discussionToNotes,
|
|
357
194
|
});
|
|
358
|
-
const parsed = parseDiff(diffPage.body);
|
|
359
|
-
const files = filterChangeFiles(parsed, paths);
|
|
360
|
-
const parsedItems = itemsFromFiles(files, 'pr');
|
|
361
|
-
const items = foldDepItems(parsedItems);
|
|
362
|
-
const change = toChange(mr, data, items);
|
|
363
|
-
const sourceLabel = `!${change.number}`;
|
|
364
|
-
const imported = await loadImportedNotes(mr, items, paths, request);
|
|
365
|
-
return { top: options.cwd, items, sourceLabel, change, imported };
|
|
366
195
|
};
|
|
367
196
|
|
|
368
197
|
module.exports = {
|
package/lib/highlight/bash.js
CHANGED
|
@@ -143,8 +143,6 @@ const bashWordStyle = (word, nextCh) => {
|
|
|
143
143
|
return 'plain';
|
|
144
144
|
};
|
|
145
145
|
|
|
146
|
-
const bashOpLen = (ch, next) => ('|&><'.includes(ch) && next === ch ? 2 : 1);
|
|
147
|
-
|
|
148
146
|
const highlight = (source) => {
|
|
149
147
|
const out = [];
|
|
150
148
|
let i = 0;
|
|
@@ -205,7 +203,7 @@ const highlight = (source) => {
|
|
|
205
203
|
}
|
|
206
204
|
|
|
207
205
|
if ('|&;<>(){}'.includes(ch)) {
|
|
208
|
-
const j = i +
|
|
206
|
+
const j = i + ('|&><'.includes(ch) && next === ch ? 2 : 1);
|
|
209
207
|
emit(out, 'operator', source.slice(i, j));
|
|
210
208
|
i = j;
|
|
211
209
|
continue;
|
package/lib/highlight/css.js
CHANGED
|
@@ -7,8 +7,6 @@ const CSS_NEXT_STYLE = {
|
|
|
7
7
|
'{': 'tag',
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
const cssWordStyle = (next) => CSS_NEXT_STYLE[next] ?? 'plain';
|
|
11
|
-
|
|
12
10
|
const highlight = (source) => {
|
|
13
11
|
const out = [];
|
|
14
12
|
let i = 0;
|
|
@@ -56,7 +54,7 @@ const highlight = (source) => {
|
|
|
56
54
|
const word = source.slice(i, j);
|
|
57
55
|
let k = j;
|
|
58
56
|
while (k < n && /\s/.test(source[k])) k += 1;
|
|
59
|
-
emit(out,
|
|
57
|
+
emit(out, CSS_NEXT_STYLE[source[k]] ?? 'plain', word);
|
|
60
58
|
i = j;
|
|
61
59
|
continue;
|
|
62
60
|
}
|
package/lib/highlight/js.js
CHANGED
|
@@ -178,10 +178,6 @@ const TS_WORD_STYLE = {
|
|
|
178
178
|
...wordStyle(TYPES, 'type'),
|
|
179
179
|
};
|
|
180
180
|
|
|
181
|
-
const plainTail = (out) => tokensText(out).slice(-8);
|
|
182
|
-
|
|
183
|
-
const styleExpected = ({ expectName }) => EXPECT_STYLE[expectName];
|
|
184
|
-
|
|
185
181
|
const styleKeyword = ({ word, ts }) => {
|
|
186
182
|
const table = ts ? TS_WORD_STYLE : WORD_STYLE;
|
|
187
183
|
if (!Object.hasOwn(table, word)) return null;
|
|
@@ -199,12 +195,12 @@ const styleAssign = ({ after, source, k }) => {
|
|
|
199
195
|
};
|
|
200
196
|
|
|
201
197
|
const styleProperty = ({ out }) => {
|
|
202
|
-
const prev =
|
|
198
|
+
const prev = tokensText(out).slice(-8);
|
|
203
199
|
return /\.$/.test(prev) ? 'property' : null;
|
|
204
200
|
};
|
|
205
201
|
|
|
206
202
|
const IDENT_RULES = [
|
|
207
|
-
|
|
203
|
+
({ expectName }) => EXPECT_STYLE[expectName],
|
|
208
204
|
styleKeyword,
|
|
209
205
|
styleConstant,
|
|
210
206
|
stylePascal,
|
|
@@ -496,9 +492,6 @@ const readPunctAt = (source, i) => {
|
|
|
496
492
|
return { end: i + 1, tokens: [{ text, style: 'punct' }] };
|
|
497
493
|
};
|
|
498
494
|
|
|
499
|
-
const readDotToken = (source, i) =>
|
|
500
|
-
readNumberAt(source, i) || readPunctAt(source, i);
|
|
501
|
-
|
|
502
495
|
const readDecorator = (source, i) => {
|
|
503
496
|
let j = i + 1;
|
|
504
497
|
while (j < source.length && /[A-Za-z0-9_$]/.test(source[j])) j += 1;
|
|
@@ -534,9 +527,6 @@ const readJsxAt = (source, i, scan) => {
|
|
|
534
527
|
return { end, tokens, expectName: null };
|
|
535
528
|
};
|
|
536
529
|
|
|
537
|
-
const readLessToken = (source, i, scan) =>
|
|
538
|
-
readJsxAt(source, i, scan) || readOperatorAt(source, i);
|
|
539
|
-
|
|
540
530
|
const readPlain = (source, i) => {
|
|
541
531
|
const text = source[i];
|
|
542
532
|
return { end: i + 1, tokens: [{ text, style: 'plain' }] };
|
|
@@ -544,8 +534,15 @@ const readPlain = (source, i) => {
|
|
|
544
534
|
|
|
545
535
|
const CHAR_READER_GROUPS = [
|
|
546
536
|
{ chars: '/', reader: readSlashToken },
|
|
547
|
-
{
|
|
548
|
-
|
|
537
|
+
{
|
|
538
|
+
chars: '<',
|
|
539
|
+
reader: (source, i, scan) =>
|
|
540
|
+
readJsxAt(source, i, scan) || readOperatorAt(source, i),
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
chars: '.',
|
|
544
|
+
reader: (source, i) => readNumberAt(source, i) || readPunctAt(source, i),
|
|
545
|
+
},
|
|
549
546
|
{ chars: '`', reader: readTemplateAt },
|
|
550
547
|
{ chars: QUOTES, reader: readQuoted },
|
|
551
548
|
{ chars: '@', reader: readDecorator },
|