@mrkaran/hodor 0.7.3 → 0.7.4
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/README.md +7 -4
- package/dist/{chunk-GISFKKMM.js → chunk-AFEJ4DRL.js} +971 -135
- package/dist/chunk-AFEJ4DRL.js.map +1 -0
- package/dist/cli.js +84 -48
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +17 -2
- package/dist/index.js +2 -5
- package/package.json +1 -1
- package/dist/chunk-AMUK6GDX.js +0 -23
- package/dist/chunk-AMUK6GDX.js.map +0 -1
- package/dist/chunk-DALI4QRT.js +0 -674
- package/dist/chunk-DALI4QRT.js.map +0 -1
- package/dist/chunk-GISFKKMM.js.map +0 -1
- package/dist/codequality-DTJK2LGF.js +0 -42
- package/dist/codequality-DTJK2LGF.js.map +0 -1
- package/dist/gitlab-JSVU4YFQ.js +0 -35
- package/dist/gitlab-JSVU4YFQ.js.map +0 -1
package/dist/chunk-DALI4QRT.js
DELETED
|
@@ -1,674 +0,0 @@
|
|
|
1
|
-
// src/utils/exec.ts
|
|
2
|
-
import { execFile, spawn } from "child_process";
|
|
3
|
-
import { promisify } from "util";
|
|
4
|
-
var execFileAsync = promisify(execFile);
|
|
5
|
-
async function exec(cmd, args, opts) {
|
|
6
|
-
if (typeof opts?.input === "string") {
|
|
7
|
-
return new Promise((resolve, reject) => {
|
|
8
|
-
const child = spawn(cmd, args, {
|
|
9
|
-
cwd: opts.cwd,
|
|
10
|
-
env: opts.env ?? process.env,
|
|
11
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
12
|
-
});
|
|
13
|
-
let stdout2 = "";
|
|
14
|
-
let stderr2 = "";
|
|
15
|
-
child.stdout.on("data", (chunk) => {
|
|
16
|
-
stdout2 += chunk.toString();
|
|
17
|
-
});
|
|
18
|
-
child.stderr.on("data", (chunk) => {
|
|
19
|
-
stderr2 += chunk.toString();
|
|
20
|
-
});
|
|
21
|
-
child.on("error", (error) => {
|
|
22
|
-
reject(error);
|
|
23
|
-
});
|
|
24
|
-
child.on("close", (code, signal) => {
|
|
25
|
-
if (code === 0) {
|
|
26
|
-
resolve({ stdout: stdout2, stderr: stderr2 });
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const parts = [`Command failed: ${cmd} ${args.join(" ")}`];
|
|
30
|
-
if (stderr2.trim()) {
|
|
31
|
-
parts.push(`stderr:
|
|
32
|
-
${stderr2.trim()}`);
|
|
33
|
-
}
|
|
34
|
-
if (stdout2.trim()) {
|
|
35
|
-
parts.push(`stdout:
|
|
36
|
-
${stdout2.trim()}`);
|
|
37
|
-
}
|
|
38
|
-
if (signal) {
|
|
39
|
-
parts.push(`signal: ${signal}`);
|
|
40
|
-
}
|
|
41
|
-
const error = new Error(parts.join("\n"));
|
|
42
|
-
reject(error);
|
|
43
|
-
});
|
|
44
|
-
child.stdin.write(opts.input);
|
|
45
|
-
child.stdin.end();
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
const { stdout, stderr } = await execFileAsync(cmd, args, {
|
|
49
|
-
cwd: opts?.cwd,
|
|
50
|
-
env: opts?.env ?? process.env,
|
|
51
|
-
maxBuffer: 50 * 1024 * 1024
|
|
52
|
-
// 50MB
|
|
53
|
-
});
|
|
54
|
-
return { stdout, stderr };
|
|
55
|
-
}
|
|
56
|
-
async function execJson(cmd, args, opts) {
|
|
57
|
-
const { stdout } = await exec(cmd, args, opts);
|
|
58
|
-
return JSON.parse(stdout.trim());
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// src/utils/logger.ts
|
|
62
|
-
import chalk from "chalk";
|
|
63
|
-
var currentLevel = "warn";
|
|
64
|
-
var LEVELS = {
|
|
65
|
-
debug: 0,
|
|
66
|
-
info: 1,
|
|
67
|
-
warn: 2,
|
|
68
|
-
error: 3
|
|
69
|
-
};
|
|
70
|
-
function setLogLevel(level) {
|
|
71
|
-
currentLevel = level;
|
|
72
|
-
}
|
|
73
|
-
function shouldLog(level) {
|
|
74
|
-
return LEVELS[level] >= LEVELS[currentLevel];
|
|
75
|
-
}
|
|
76
|
-
function timestamp() {
|
|
77
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
78
|
-
}
|
|
79
|
-
var logger = {
|
|
80
|
-
debug(msg) {
|
|
81
|
-
if (shouldLog("debug")) {
|
|
82
|
-
process.stderr.write(`${chalk.gray(timestamp())} ${chalk.gray("DEBUG")} ${msg}
|
|
83
|
-
`);
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
|
-
info(msg) {
|
|
87
|
-
if (shouldLog("info")) {
|
|
88
|
-
process.stderr.write(`${chalk.gray(timestamp())} ${chalk.blue("INFO")} ${msg}
|
|
89
|
-
`);
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
warn(msg) {
|
|
93
|
-
if (shouldLog("warn")) {
|
|
94
|
-
process.stderr.write(`${chalk.gray(timestamp())} ${chalk.yellow("WARN")} ${msg}
|
|
95
|
-
`);
|
|
96
|
-
}
|
|
97
|
-
},
|
|
98
|
-
error(msg) {
|
|
99
|
-
if (shouldLog("error")) {
|
|
100
|
-
process.stderr.write(`${chalk.gray(timestamp())} ${chalk.red("ERROR")} ${msg}
|
|
101
|
-
`);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// src/render.ts
|
|
107
|
-
var HODOR_REVIEW_MARKER = "<!-- hodor-review -->";
|
|
108
|
-
function renderMarkdown(review) {
|
|
109
|
-
const lines = [HODOR_REVIEW_MARKER];
|
|
110
|
-
const critical = [];
|
|
111
|
-
const important = [];
|
|
112
|
-
const minor = [];
|
|
113
|
-
for (const f of review.findings) {
|
|
114
|
-
const p = f.priority;
|
|
115
|
-
if (p <= 1) critical.push(f);
|
|
116
|
-
else if (p === 2) important.push(f);
|
|
117
|
-
else minor.push(f);
|
|
118
|
-
}
|
|
119
|
-
lines.push("### Issues Found");
|
|
120
|
-
lines.push("");
|
|
121
|
-
if (review.findings.length === 0) {
|
|
122
|
-
lines.push("No issues found.");
|
|
123
|
-
lines.push("");
|
|
124
|
-
}
|
|
125
|
-
if (critical.length > 0) {
|
|
126
|
-
lines.push("**Critical (P0/P1)**");
|
|
127
|
-
for (const f of critical) {
|
|
128
|
-
lines.push(formatFinding(f));
|
|
129
|
-
}
|
|
130
|
-
lines.push("");
|
|
131
|
-
}
|
|
132
|
-
if (important.length > 0) {
|
|
133
|
-
lines.push("**Important (P2)**");
|
|
134
|
-
for (const f of important) {
|
|
135
|
-
lines.push(formatFinding(f));
|
|
136
|
-
}
|
|
137
|
-
lines.push("");
|
|
138
|
-
}
|
|
139
|
-
if (minor.length > 0) {
|
|
140
|
-
lines.push("**Minor (P3)**");
|
|
141
|
-
for (const f of minor) {
|
|
142
|
-
lines.push(formatFinding(f));
|
|
143
|
-
}
|
|
144
|
-
lines.push("");
|
|
145
|
-
}
|
|
146
|
-
lines.push("### Summary");
|
|
147
|
-
lines.push(
|
|
148
|
-
`Total issues: ${critical.length} critical, ${important.length} important, ${minor.length} minor.`
|
|
149
|
-
);
|
|
150
|
-
lines.push("");
|
|
151
|
-
lines.push("### Overall Verdict");
|
|
152
|
-
const isCorrect = review.overall_correctness === "patch is correct";
|
|
153
|
-
lines.push(
|
|
154
|
-
`**Status**: ${isCorrect ? "Patch is correct" : "Patch has blocking issues"}`
|
|
155
|
-
);
|
|
156
|
-
lines.push("");
|
|
157
|
-
if (review.overall_explanation) {
|
|
158
|
-
lines.push(`**Explanation**: ${review.overall_explanation}`);
|
|
159
|
-
}
|
|
160
|
-
return lines.join("\n").trimEnd() + "\n";
|
|
161
|
-
}
|
|
162
|
-
function renderSummaryMarkdown(review) {
|
|
163
|
-
const lines = [HODOR_REVIEW_MARKER];
|
|
164
|
-
const counts = { critical: 0, important: 0, minor: 0 };
|
|
165
|
-
for (const f of review.findings) {
|
|
166
|
-
if (f.priority <= 1) counts.critical++;
|
|
167
|
-
else if (f.priority === 2) counts.important++;
|
|
168
|
-
else counts.minor++;
|
|
169
|
-
}
|
|
170
|
-
lines.push("");
|
|
171
|
-
lines.push("| Category | Count |");
|
|
172
|
-
lines.push("| --- | ---: |");
|
|
173
|
-
lines.push(`| Critical (P0/P1) | ${counts.critical} |`);
|
|
174
|
-
lines.push(`| Important (P2) | ${counts.important} |`);
|
|
175
|
-
lines.push(`| Minor (P3) | ${counts.minor} |`);
|
|
176
|
-
const isCorrect = review.overall_correctness === "patch is correct";
|
|
177
|
-
lines.push("");
|
|
178
|
-
lines.push(`**Overall verdict**: ${isCorrect ? "Patch is correct" : "Patch has blocking issues"}`);
|
|
179
|
-
lines.push("");
|
|
180
|
-
lines.push(`**Explanation**: ${review.overall_explanation}`);
|
|
181
|
-
if (review.findings.length > 0) {
|
|
182
|
-
lines.push("");
|
|
183
|
-
lines.push("| Finding | Location | Priority |");
|
|
184
|
-
lines.push("| --- | --- | --- |");
|
|
185
|
-
for (const f of review.findings) {
|
|
186
|
-
const loc = formatLocation(f.code_location);
|
|
187
|
-
const safeTitle = f.title.replace(/\|/g, "\\|");
|
|
188
|
-
lines.push(`| ${safeTitle} | \`${loc}\` | P${f.priority} |`);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return lines.join("\n").trimEnd() + "\n";
|
|
192
|
-
}
|
|
193
|
-
function formatFinding(f) {
|
|
194
|
-
const loc = ` (\`${formatLocation(f.code_location)}\`)`;
|
|
195
|
-
const title = `- **${f.title}**${loc}`;
|
|
196
|
-
const body = ` - ${f.body}`;
|
|
197
|
-
return `${title}
|
|
198
|
-
${body}`;
|
|
199
|
-
}
|
|
200
|
-
function formatLocation(loc) {
|
|
201
|
-
let filePath = loc.absolute_file_path;
|
|
202
|
-
const buildsMatch = filePath.match(/\/builds\/[^/]+\/[^/]+\/(.+)/);
|
|
203
|
-
if (buildsMatch) {
|
|
204
|
-
filePath = buildsMatch[1];
|
|
205
|
-
} else if (filePath.includes("/workspace/")) {
|
|
206
|
-
filePath = filePath.slice(filePath.indexOf("/workspace/") + "/workspace/".length);
|
|
207
|
-
} else {
|
|
208
|
-
filePath = filePath.replace(/^.*\/hodor-review-[^/]+\//, "");
|
|
209
|
-
}
|
|
210
|
-
const { start, end } = loc.line_range;
|
|
211
|
-
return start === end ? `${filePath}:${start}` : `${filePath}:${start}-${end}`;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// src/gitlab.ts
|
|
215
|
-
var DEFAULT_GITLAB_HOST = "gitlab.com";
|
|
216
|
-
var HODOR_NOTE_PREFIX_RE = /^\s*<!--\s*hodor[-:]/;
|
|
217
|
-
var HODOR_CACHE_MARKER_RE = /<!--\s*hodor:cache:v1:[A-Za-z0-9_-]+\s*-->\s*/g;
|
|
218
|
-
function isHodorNote(body, marker = HODOR_REVIEW_MARKER) {
|
|
219
|
-
if (typeof body !== "string") return false;
|
|
220
|
-
if (body.trimStart().startsWith(marker)) return true;
|
|
221
|
-
if (marker === HODOR_REVIEW_MARKER && HODOR_NOTE_PREFIX_RE.test(body)) {
|
|
222
|
-
return body.includes(HODOR_REVIEW_MARKER);
|
|
223
|
-
}
|
|
224
|
-
return false;
|
|
225
|
-
}
|
|
226
|
-
function isHodorGeneratedNote(body) {
|
|
227
|
-
return isHodorNote(body);
|
|
228
|
-
}
|
|
229
|
-
function parseGlabPaginatedJson(raw) {
|
|
230
|
-
const trimmed = raw.trim();
|
|
231
|
-
if (!trimmed) return [];
|
|
232
|
-
const chunks = [];
|
|
233
|
-
let depth = 0;
|
|
234
|
-
let inString = false;
|
|
235
|
-
let escaped = false;
|
|
236
|
-
let start = -1;
|
|
237
|
-
for (let i = 0; i < trimmed.length; i++) {
|
|
238
|
-
const ch = trimmed[i];
|
|
239
|
-
if (escaped) {
|
|
240
|
-
escaped = false;
|
|
241
|
-
continue;
|
|
242
|
-
}
|
|
243
|
-
if (ch === "\\" && inString) {
|
|
244
|
-
escaped = true;
|
|
245
|
-
continue;
|
|
246
|
-
}
|
|
247
|
-
if (ch === '"') {
|
|
248
|
-
inString = !inString;
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
if (inString) continue;
|
|
252
|
-
if (ch === "[") {
|
|
253
|
-
if (depth === 0) start = i;
|
|
254
|
-
depth++;
|
|
255
|
-
} else if (ch === "]") {
|
|
256
|
-
depth--;
|
|
257
|
-
if (depth === 0 && start >= 0) {
|
|
258
|
-
chunks.push(trimmed.slice(start, i + 1));
|
|
259
|
-
start = -1;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
const results = [];
|
|
264
|
-
for (const chunk of chunks) {
|
|
265
|
-
try {
|
|
266
|
-
const parsed = JSON.parse(chunk);
|
|
267
|
-
if (Array.isArray(parsed)) results.push(...parsed);
|
|
268
|
-
} catch (err) {
|
|
269
|
-
logger.warn(
|
|
270
|
-
`Skipping malformed glab pagination chunk: ${err instanceof Error ? err.message : err}`
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return results;
|
|
275
|
-
}
|
|
276
|
-
var GitLabAPIError = class extends Error {
|
|
277
|
-
constructor(message) {
|
|
278
|
-
super(message);
|
|
279
|
-
this.name = "GitLabAPIError";
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
function normalizeBaseUrl(host) {
|
|
283
|
-
const candidate = host || process.env.GITLAB_HOST || process.env.CI_SERVER_URL || DEFAULT_GITLAB_HOST;
|
|
284
|
-
const trimmed = candidate.trim() || DEFAULT_GITLAB_HOST;
|
|
285
|
-
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
|
|
286
|
-
return trimmed.replace(/\/+$/, "");
|
|
287
|
-
}
|
|
288
|
-
return `https://${trimmed}`.replace(/\/+$/, "");
|
|
289
|
-
}
|
|
290
|
-
function encodedProjectPath(owner, repo) {
|
|
291
|
-
const projectPath = [owner.replace(/^\/+|\/+$/g, ""), repo.replace(/^\/+|\/+$/g, "")].filter(Boolean).join("/");
|
|
292
|
-
return encodeURIComponent(projectPath);
|
|
293
|
-
}
|
|
294
|
-
function glabEnv(host) {
|
|
295
|
-
const env = { ...process.env };
|
|
296
|
-
const baseUrl = normalizeBaseUrl(host);
|
|
297
|
-
const hostname = baseUrl.replace(/^https?:\/\//, "");
|
|
298
|
-
env.GITLAB_HOST = hostname;
|
|
299
|
-
return env;
|
|
300
|
-
}
|
|
301
|
-
async function fetchGitlabMrInfo(owner, repo, mrNumber, host, options) {
|
|
302
|
-
const encoded = encodedProjectPath(owner, repo);
|
|
303
|
-
const env = glabEnv(host);
|
|
304
|
-
let mrData;
|
|
305
|
-
try {
|
|
306
|
-
mrData = await execJson(
|
|
307
|
-
"glab",
|
|
308
|
-
["api", `projects/${encoded}/merge_requests/${mrNumber}`],
|
|
309
|
-
{ env }
|
|
310
|
-
);
|
|
311
|
-
} catch (err) {
|
|
312
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
313
|
-
throw new GitLabAPIError(`Failed to fetch MR !${mrNumber}: ${msg}`);
|
|
314
|
-
}
|
|
315
|
-
const metadata = {
|
|
316
|
-
title: mrData.title,
|
|
317
|
-
description: mrData.description ?? "",
|
|
318
|
-
source_branch: mrData.source_branch,
|
|
319
|
-
target_branch: mrData.target_branch,
|
|
320
|
-
changes_count: mrData.changes_count,
|
|
321
|
-
labels: mrData.labels,
|
|
322
|
-
author: mrData.author,
|
|
323
|
-
pipeline: mrData.pipeline,
|
|
324
|
-
state: mrData.state
|
|
325
|
-
};
|
|
326
|
-
if (options?.includeComments) {
|
|
327
|
-
try {
|
|
328
|
-
const { stdout: rawNotes } = await exec(
|
|
329
|
-
"glab",
|
|
330
|
-
["api", `projects/${encoded}/merge_requests/${mrNumber}/notes`, "--paginate"],
|
|
331
|
-
{ env }
|
|
332
|
-
);
|
|
333
|
-
const notes = parseGlabPaginatedJson(rawNotes);
|
|
334
|
-
metadata.Notes = notes.map((n) => ({
|
|
335
|
-
body: n.body ?? "",
|
|
336
|
-
author: n.author,
|
|
337
|
-
created_at: n.created_at,
|
|
338
|
-
system: n.system
|
|
339
|
-
}));
|
|
340
|
-
} catch (err) {
|
|
341
|
-
logger.warn(`Failed to fetch MR notes: ${err instanceof Error ? err.message : err}`);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
return metadata;
|
|
345
|
-
}
|
|
346
|
-
async function postGitlabMrComment(owner, repo, mrNumber, body, host) {
|
|
347
|
-
const encoded = encodedProjectPath(owner, repo);
|
|
348
|
-
const env = glabEnv(host);
|
|
349
|
-
try {
|
|
350
|
-
await exec(
|
|
351
|
-
"glab",
|
|
352
|
-
[
|
|
353
|
-
"api",
|
|
354
|
-
`projects/${encoded}/merge_requests/${mrNumber}/notes`,
|
|
355
|
-
"--method",
|
|
356
|
-
"POST",
|
|
357
|
-
"-H",
|
|
358
|
-
"Content-Type: application/json",
|
|
359
|
-
"--input",
|
|
360
|
-
"-"
|
|
361
|
-
],
|
|
362
|
-
{ env, input: JSON.stringify({ body }) }
|
|
363
|
-
);
|
|
364
|
-
} catch (err) {
|
|
365
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
366
|
-
throw new GitLabAPIError(`Failed to post comment to MR !${mrNumber}: ${msg}`);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
function summarizeGitlabNotes(notes, maxEntries = 5) {
|
|
370
|
-
return summarizeNotes(notes, maxEntries, (note) => !isHodorNote(note.body));
|
|
371
|
-
}
|
|
372
|
-
function summarizeHodorNotes(notes, maxEntries = 5) {
|
|
373
|
-
return summarizeNotes(notes, maxEntries, (note) => isHodorNote(note.body));
|
|
374
|
-
}
|
|
375
|
-
function summarizeNotes(notes, maxEntries, include) {
|
|
376
|
-
if (!notes || notes.length === 0) return "";
|
|
377
|
-
const trivialPatterns = /* @__PURE__ */ new Set([
|
|
378
|
-
"lgtm",
|
|
379
|
-
"+1",
|
|
380
|
-
"-1",
|
|
381
|
-
"\u{1F44D}",
|
|
382
|
-
"\u{1F44E}",
|
|
383
|
-
"thanks",
|
|
384
|
-
"thank you",
|
|
385
|
-
"looks good",
|
|
386
|
-
"approved",
|
|
387
|
-
"\u{1F680}",
|
|
388
|
-
"\u2705",
|
|
389
|
-
"\u274C"
|
|
390
|
-
]);
|
|
391
|
-
const filtered = [];
|
|
392
|
-
for (const note of notes) {
|
|
393
|
-
if (!include(note)) continue;
|
|
394
|
-
const body = (note.body ?? "").replace(HODOR_CACHE_MARKER_RE, "").trim();
|
|
395
|
-
if (!body) continue;
|
|
396
|
-
if (note.system) continue;
|
|
397
|
-
if (body.length < 20) continue;
|
|
398
|
-
const bodyLower = body.toLowerCase();
|
|
399
|
-
let isTrivial = false;
|
|
400
|
-
for (const pattern of trivialPatterns) {
|
|
401
|
-
if (bodyLower.includes(pattern) && body.length < 50) {
|
|
402
|
-
isTrivial = true;
|
|
403
|
-
break;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (isTrivial) continue;
|
|
407
|
-
const username = note.author?.username ?? note.author?.name ?? "unknown";
|
|
408
|
-
filtered.push({ username, body, createdAt: note.created_at ?? "" });
|
|
409
|
-
}
|
|
410
|
-
filtered.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
|
411
|
-
const recent = filtered.slice(-maxEntries);
|
|
412
|
-
const lines = [];
|
|
413
|
-
for (const { username, body, createdAt } of recent) {
|
|
414
|
-
let timestampStr = "";
|
|
415
|
-
if (createdAt) {
|
|
416
|
-
try {
|
|
417
|
-
const dt = new Date(createdAt);
|
|
418
|
-
timestampStr = dt.toISOString().replace("T", " ").slice(0, 16);
|
|
419
|
-
} catch {
|
|
420
|
-
timestampStr = createdAt.slice(0, 10);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
const header = timestampStr ? `- ${timestampStr} @${username}:` : `- @${username}:`;
|
|
424
|
-
const boundedBody = body.length > 2e3 ? `${body.slice(0, 1999).trimEnd()}\u2026` : body;
|
|
425
|
-
const indentedBody = boundedBody.split("\n").join("\n ");
|
|
426
|
-
lines.push(`${header}
|
|
427
|
-
${indentedBody}`);
|
|
428
|
-
}
|
|
429
|
-
return lines.join("\n");
|
|
430
|
-
}
|
|
431
|
-
async function getGitlabMrDiffRefs(owner, repo, mrNumber, host) {
|
|
432
|
-
const encoded = encodedProjectPath(owner, repo);
|
|
433
|
-
const env = glabEnv(host);
|
|
434
|
-
let mrData;
|
|
435
|
-
try {
|
|
436
|
-
mrData = await execJson(
|
|
437
|
-
"glab",
|
|
438
|
-
["api", `projects/${encoded}/merge_requests/${mrNumber}`],
|
|
439
|
-
{ env }
|
|
440
|
-
);
|
|
441
|
-
} catch (err) {
|
|
442
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
443
|
-
throw new GitLabAPIError(`Failed to fetch diff refs for MR !${mrNumber}: ${msg}`);
|
|
444
|
-
}
|
|
445
|
-
const diffRefs = mrData.diff_refs;
|
|
446
|
-
const base_sha = diffRefs?.base_sha;
|
|
447
|
-
const head_sha = diffRefs?.head_sha;
|
|
448
|
-
const start_sha = diffRefs?.start_sha;
|
|
449
|
-
if (typeof base_sha !== "string" || typeof head_sha !== "string" || typeof start_sha !== "string" || !base_sha || !head_sha || !start_sha) {
|
|
450
|
-
throw new GitLabAPIError(`MR !${mrNumber} has missing or incomplete diff_refs`);
|
|
451
|
-
}
|
|
452
|
-
return { base_sha, head_sha, start_sha };
|
|
453
|
-
}
|
|
454
|
-
async function createGitlabDraftNote(owner, repo, mrNumber, body, host, opts) {
|
|
455
|
-
const encoded = encodedProjectPath(owner, repo);
|
|
456
|
-
const env = glabEnv(host);
|
|
457
|
-
const endpoint = `projects/${encoded}/merge_requests/${mrNumber}/draft_notes`;
|
|
458
|
-
const payload = {
|
|
459
|
-
note: body
|
|
460
|
-
};
|
|
461
|
-
if (opts?.filePath && typeof opts.line === "number" && opts.diffRefs) {
|
|
462
|
-
payload.position = {
|
|
463
|
-
base_sha: opts.diffRefs.base_sha,
|
|
464
|
-
head_sha: opts.diffRefs.head_sha,
|
|
465
|
-
start_sha: opts.diffRefs.start_sha,
|
|
466
|
-
position_type: "text",
|
|
467
|
-
old_path: opts.filePath,
|
|
468
|
-
new_path: opts.filePath,
|
|
469
|
-
new_line: opts.line
|
|
470
|
-
};
|
|
471
|
-
}
|
|
472
|
-
try {
|
|
473
|
-
return await execJson(
|
|
474
|
-
"glab",
|
|
475
|
-
["api", endpoint, "--method", "POST", "-H", "Content-Type: application/json", "--input", "-"],
|
|
476
|
-
{
|
|
477
|
-
env,
|
|
478
|
-
input: JSON.stringify(payload)
|
|
479
|
-
}
|
|
480
|
-
);
|
|
481
|
-
} catch (err) {
|
|
482
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
483
|
-
throw new GitLabAPIError(`Failed to create draft note for MR !${mrNumber}: ${msg}`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
async function bulkPublishGitlabDraftNotes(owner, repo, mrNumber, host) {
|
|
487
|
-
const encoded = encodedProjectPath(owner, repo);
|
|
488
|
-
const env = glabEnv(host);
|
|
489
|
-
try {
|
|
490
|
-
await exec(
|
|
491
|
-
"glab",
|
|
492
|
-
[
|
|
493
|
-
"api",
|
|
494
|
-
`projects/${encoded}/merge_requests/${mrNumber}/draft_notes/bulk_publish`,
|
|
495
|
-
"--method",
|
|
496
|
-
"POST"
|
|
497
|
-
],
|
|
498
|
-
{ env }
|
|
499
|
-
);
|
|
500
|
-
} catch (err) {
|
|
501
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
502
|
-
throw new GitLabAPIError(`Failed to bulk publish draft notes for MR !${mrNumber}: ${msg}`);
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
async function publishGitlabDraftNote(owner, repo, mrNumber, draftNoteId, host) {
|
|
506
|
-
const encoded = encodedProjectPath(owner, repo);
|
|
507
|
-
const env = glabEnv(host);
|
|
508
|
-
try {
|
|
509
|
-
await exec(
|
|
510
|
-
"glab",
|
|
511
|
-
[
|
|
512
|
-
"api",
|
|
513
|
-
`projects/${encoded}/merge_requests/${mrNumber}/draft_notes/${draftNoteId}/publish`,
|
|
514
|
-
"--method",
|
|
515
|
-
"PUT"
|
|
516
|
-
],
|
|
517
|
-
{ env }
|
|
518
|
-
);
|
|
519
|
-
} catch (err) {
|
|
520
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
521
|
-
throw new GitLabAPIError(`Failed to publish draft note ${draftNoteId} for MR !${mrNumber}: ${msg}`);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
async function postGitlabCommitStatus(owner, repo, sha, state, host, opts) {
|
|
525
|
-
const allowedStates = /* @__PURE__ */ new Set([
|
|
526
|
-
"pending",
|
|
527
|
-
"running",
|
|
528
|
-
"success",
|
|
529
|
-
"failed",
|
|
530
|
-
"canceled"
|
|
531
|
-
]);
|
|
532
|
-
if (!allowedStates.has(state)) {
|
|
533
|
-
throw new GitLabAPIError(`Invalid GitLab commit status state: ${state}`);
|
|
534
|
-
}
|
|
535
|
-
const encoded = encodedProjectPath(owner, repo);
|
|
536
|
-
const env = glabEnv(host);
|
|
537
|
-
const endpoint = `projects/${encoded}/statuses/${sha}`;
|
|
538
|
-
const payload = {
|
|
539
|
-
state,
|
|
540
|
-
name: opts?.name ?? "hodor"
|
|
541
|
-
};
|
|
542
|
-
if (opts?.description) {
|
|
543
|
-
payload.description = opts.description;
|
|
544
|
-
}
|
|
545
|
-
if (opts?.targetUrl) {
|
|
546
|
-
payload.target_url = opts.targetUrl;
|
|
547
|
-
}
|
|
548
|
-
try {
|
|
549
|
-
await exec(
|
|
550
|
-
"glab",
|
|
551
|
-
["api", endpoint, "--method", "POST", "-H", "Content-Type: application/json", "--input", "-"],
|
|
552
|
-
{
|
|
553
|
-
env,
|
|
554
|
-
input: JSON.stringify(payload)
|
|
555
|
-
}
|
|
556
|
-
);
|
|
557
|
-
} catch (err) {
|
|
558
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
559
|
-
throw new GitLabAPIError(`Failed to post commit status for ${sha}: ${msg}`);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
async function listHodorDiscussions(owner, repo, mrNumber, host, marker = HODOR_REVIEW_MARKER) {
|
|
563
|
-
const encoded = encodedProjectPath(owner, repo);
|
|
564
|
-
const env = glabEnv(host);
|
|
565
|
-
let discussions;
|
|
566
|
-
try {
|
|
567
|
-
const { stdout: rawDiscussions } = await exec(
|
|
568
|
-
"glab",
|
|
569
|
-
[
|
|
570
|
-
"api",
|
|
571
|
-
`projects/${encoded}/merge_requests/${mrNumber}/discussions?per_page=100`,
|
|
572
|
-
"--paginate"
|
|
573
|
-
],
|
|
574
|
-
{ env }
|
|
575
|
-
);
|
|
576
|
-
discussions = parseGlabPaginatedJson(rawDiscussions);
|
|
577
|
-
} catch (err) {
|
|
578
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
579
|
-
throw new GitLabAPIError(`Failed to list discussions for MR !${mrNumber}: ${msg}`);
|
|
580
|
-
}
|
|
581
|
-
const results = [];
|
|
582
|
-
for (const discussion of discussions) {
|
|
583
|
-
const discussionId = discussion.id;
|
|
584
|
-
if (typeof discussionId !== "string") {
|
|
585
|
-
continue;
|
|
586
|
-
}
|
|
587
|
-
const notes = discussion.notes;
|
|
588
|
-
if (!Array.isArray(notes)) {
|
|
589
|
-
continue;
|
|
590
|
-
}
|
|
591
|
-
for (const note of notes) {
|
|
592
|
-
if (!note || typeof note !== "object") {
|
|
593
|
-
continue;
|
|
594
|
-
}
|
|
595
|
-
const noteObj = note;
|
|
596
|
-
const noteId = noteObj.id;
|
|
597
|
-
const body = noteObj.body;
|
|
598
|
-
if (typeof noteId !== "number" || typeof body !== "string" || !isHodorNote(body, marker)) {
|
|
599
|
-
continue;
|
|
600
|
-
}
|
|
601
|
-
const position = noteObj.position && typeof noteObj.position === "object" ? noteObj.position : void 0;
|
|
602
|
-
const filePath = typeof position?.new_path === "string" ? position.new_path : void 0;
|
|
603
|
-
const line = typeof position?.new_line === "number" ? position.new_line : void 0;
|
|
604
|
-
if (noteObj.resolvable !== true) {
|
|
605
|
-
continue;
|
|
606
|
-
}
|
|
607
|
-
results.push({
|
|
608
|
-
discussionId,
|
|
609
|
-
noteId,
|
|
610
|
-
body,
|
|
611
|
-
resolved: Boolean(noteObj.resolved),
|
|
612
|
-
filePath,
|
|
613
|
-
line
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
return results;
|
|
618
|
-
}
|
|
619
|
-
async function resolveGitlabDiscussions(owner, repo, mrNumber, discussionIds, host) {
|
|
620
|
-
const encoded = encodedProjectPath(owner, repo);
|
|
621
|
-
const env = glabEnv(host);
|
|
622
|
-
let resolvedCount = 0;
|
|
623
|
-
for (const discussionId of discussionIds) {
|
|
624
|
-
try {
|
|
625
|
-
await exec(
|
|
626
|
-
"glab",
|
|
627
|
-
[
|
|
628
|
-
"api",
|
|
629
|
-
`projects/${encoded}/merge_requests/${mrNumber}/discussions/${discussionId}`,
|
|
630
|
-
"--method",
|
|
631
|
-
"PUT",
|
|
632
|
-
"-H",
|
|
633
|
-
"Content-Type: application/json",
|
|
634
|
-
"--input",
|
|
635
|
-
"-"
|
|
636
|
-
],
|
|
637
|
-
{
|
|
638
|
-
env,
|
|
639
|
-
input: JSON.stringify({ resolved: true })
|
|
640
|
-
}
|
|
641
|
-
);
|
|
642
|
-
resolvedCount += 1;
|
|
643
|
-
} catch (err) {
|
|
644
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
645
|
-
logger.warn(`Failed to resolve discussion ${discussionId} on MR !${mrNumber}: ${msg}`);
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
return resolvedCount;
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
export {
|
|
652
|
-
setLogLevel,
|
|
653
|
-
logger,
|
|
654
|
-
exec,
|
|
655
|
-
execJson,
|
|
656
|
-
HODOR_REVIEW_MARKER,
|
|
657
|
-
renderMarkdown,
|
|
658
|
-
renderSummaryMarkdown,
|
|
659
|
-
isHodorGeneratedNote,
|
|
660
|
-
parseGlabPaginatedJson,
|
|
661
|
-
GitLabAPIError,
|
|
662
|
-
fetchGitlabMrInfo,
|
|
663
|
-
postGitlabMrComment,
|
|
664
|
-
summarizeGitlabNotes,
|
|
665
|
-
summarizeHodorNotes,
|
|
666
|
-
getGitlabMrDiffRefs,
|
|
667
|
-
createGitlabDraftNote,
|
|
668
|
-
bulkPublishGitlabDraftNotes,
|
|
669
|
-
publishGitlabDraftNote,
|
|
670
|
-
postGitlabCommitStatus,
|
|
671
|
-
listHodorDiscussions,
|
|
672
|
-
resolveGitlabDiscussions
|
|
673
|
-
};
|
|
674
|
-
//# sourceMappingURL=chunk-DALI4QRT.js.map
|