@ycodium-ai/plugin-pull-requests 0.2.2996 → 0.2.3000
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/assets/ui.js +14 -14
- package/dist/index.js +399 -366
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { definePlugin } from "@ycodium-ai/plugin-api";
|
|
2
|
+
import { definePlugin, projectOrNull } from "@ycodium-ai/plugin-api";
|
|
3
|
+
|
|
4
|
+
// src/hostProject.ts
|
|
5
|
+
var providerOf = (provider) => provider === "github" || provider === "gitlab" ? provider : "unknown";
|
|
6
|
+
var workspaceShellOf = (project) => {
|
|
7
|
+
const identity = project.repositoryIdentity;
|
|
8
|
+
return {
|
|
9
|
+
projectId: project.id,
|
|
10
|
+
title: project.title,
|
|
11
|
+
workspaceRoot: project.workspaceRoot,
|
|
12
|
+
repositoryIdentity: identity == null ? null : {
|
|
13
|
+
provider: providerOf(identity.provider),
|
|
14
|
+
canonicalKey: identity.canonicalKey,
|
|
15
|
+
remoteName: identity.locator.remoteName,
|
|
16
|
+
remoteUrl: identity.locator.remoteUrl,
|
|
17
|
+
...identity.displayName === void 0 ? {} : { displayName: identity.displayName },
|
|
18
|
+
...identity.owner === void 0 ? {} : { owner: identity.owner },
|
|
19
|
+
...identity.name === void 0 ? {} : { name: identity.name },
|
|
20
|
+
...identity.rootPath === void 0 ? {} : { rootPath: identity.rootPath }
|
|
21
|
+
},
|
|
22
|
+
...project.archivedAt == null ? {} : { archivedAt: project.archivedAt }
|
|
23
|
+
};
|
|
24
|
+
};
|
|
3
25
|
|
|
4
26
|
// src/service/backoff.ts
|
|
5
27
|
var FALLBACK_COOLDOWN_MS = 3e4;
|
|
@@ -493,7 +515,7 @@ var callGitHubGraphQl = async (ctx, input) => {
|
|
|
493
515
|
return data;
|
|
494
516
|
};
|
|
495
517
|
|
|
496
|
-
// src/
|
|
518
|
+
// src/githubValues.ts
|
|
497
519
|
var asObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
498
520
|
var asArray = (value) => Array.isArray(value) ? value : [];
|
|
499
521
|
var text2 = (value) => {
|
|
@@ -710,6 +732,8 @@ var toReactions = (value, viewer) => {
|
|
|
710
732
|
}
|
|
711
733
|
return reactions;
|
|
712
734
|
};
|
|
735
|
+
|
|
736
|
+
// src/githubGraphqlDocuments.ts
|
|
713
737
|
var PAGE_SIZE = 100;
|
|
714
738
|
var SEARCH_MAX_ROWS = PAGE_SIZE;
|
|
715
739
|
var reactionGroups = (canReadOrg2) => `reactionGroups {
|
|
@@ -958,6 +982,50 @@ ${selections.join("\n")}
|
|
|
958
982
|
${RATE_LIMIT_SELECTION}
|
|
959
983
|
}`;
|
|
960
984
|
};
|
|
985
|
+
|
|
986
|
+
// src/githubFilesPatch.ts
|
|
987
|
+
var decodeFiles = (value) => {
|
|
988
|
+
if (!Array.isArray(value)) return null;
|
|
989
|
+
const sections = [];
|
|
990
|
+
const omittedFileStats = [];
|
|
991
|
+
let truncated = false;
|
|
992
|
+
for (const entry of value) {
|
|
993
|
+
const raw = asObject2(entry);
|
|
994
|
+
const filename = text2(raw?.["filename"]);
|
|
995
|
+
if (filename === null) continue;
|
|
996
|
+
const hunks = typeof raw?.["patch"] === "string" ? raw["patch"] : "";
|
|
997
|
+
const status = text2(raw?.["status"])?.toLowerCase();
|
|
998
|
+
if (hunks.length === 0) {
|
|
999
|
+
const additions = int(raw?.["additions"]) ?? 0;
|
|
1000
|
+
const deletions = int(raw?.["deletions"]) ?? 0;
|
|
1001
|
+
if (additions + deletions > 0) {
|
|
1002
|
+
truncated = true;
|
|
1003
|
+
omittedFileStats.push({ path: filename, additions, deletions });
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
const oldPath = status === "renamed" ? text2(raw?.["previous_filename"]) ?? filename : filename;
|
|
1007
|
+
const header = [
|
|
1008
|
+
`diff --git a/${oldPath} b/${filename}`,
|
|
1009
|
+
// files 端点不报文件模式,用寻常那个顶上:查看器读这两行是"新增"和"删除",不是读模式。
|
|
1010
|
+
...status === "added" ? ["new file mode 100644"] : [],
|
|
1011
|
+
...status === "removed" ? ["deleted file mode 100644"] : [],
|
|
1012
|
+
...status === "renamed" ? [`rename from ${oldPath}`, `rename to ${filename}`] : [],
|
|
1013
|
+
`--- ${status === "added" ? "/dev/null" : `a/${oldPath}`}`,
|
|
1014
|
+
`+++ ${status === "removed" ? "/dev/null" : `b/${filename}`}`
|
|
1015
|
+
].join("\n");
|
|
1016
|
+
sections.push(hunks.length === 0 ? `${header}
|
|
1017
|
+
` : `${header}
|
|
1018
|
+
${hunks.replace(/\n?$/, "\n")}`);
|
|
1019
|
+
}
|
|
1020
|
+
return { patch: sections.join(""), truncated, rawCount: value.length, omittedFileStats };
|
|
1021
|
+
};
|
|
1022
|
+
var decodeRevisions = (value, kind) => {
|
|
1023
|
+
const raw = asObject2(value);
|
|
1024
|
+
if (raw === null) return null;
|
|
1025
|
+
return kind === "pull-request" ? { baseRef: text2(at(raw, "base", "sha")), headRef: text2(at(raw, "head", "sha")) } : { baseRef: text2(at(asArray(raw["parents"])[0], "sha")), headRef: text2(raw["sha"]) };
|
|
1026
|
+
};
|
|
1027
|
+
|
|
1028
|
+
// src/githubMapping.ts
|
|
961
1029
|
var decodeViewerLogin = (value) => text2(asObject2(value)?.["login"]);
|
|
962
1030
|
var toReviewRequestLogins = (value) => nodes(value).flatMap((node) => text2(at(node, "requestedReviewer", "login")) ?? []);
|
|
963
1031
|
var toRow = (raw) => {
|
|
@@ -1351,99 +1419,8 @@ var decodeReviewerCandidates = (value) => {
|
|
|
1351
1419
|
truncated: at(assignable, "pageInfo", "hasNextPage") === true
|
|
1352
1420
|
};
|
|
1353
1421
|
};
|
|
1354
|
-
var decodeFiles = (value) => {
|
|
1355
|
-
if (!Array.isArray(value)) return null;
|
|
1356
|
-
const sections = [];
|
|
1357
|
-
const omittedFileStats = [];
|
|
1358
|
-
let truncated = false;
|
|
1359
|
-
for (const entry of value) {
|
|
1360
|
-
const raw = asObject2(entry);
|
|
1361
|
-
const filename = text2(raw?.["filename"]);
|
|
1362
|
-
if (filename === null) continue;
|
|
1363
|
-
const hunks = typeof raw?.["patch"] === "string" ? raw["patch"] : "";
|
|
1364
|
-
const status = text2(raw?.["status"])?.toLowerCase();
|
|
1365
|
-
if (hunks.length === 0) {
|
|
1366
|
-
const additions = int(raw?.["additions"]) ?? 0;
|
|
1367
|
-
const deletions = int(raw?.["deletions"]) ?? 0;
|
|
1368
|
-
if (additions + deletions > 0) {
|
|
1369
|
-
truncated = true;
|
|
1370
|
-
omittedFileStats.push({ path: filename, additions, deletions });
|
|
1371
|
-
}
|
|
1372
|
-
}
|
|
1373
|
-
const oldPath = status === "renamed" ? text2(raw?.["previous_filename"]) ?? filename : filename;
|
|
1374
|
-
const header = [
|
|
1375
|
-
`diff --git a/${oldPath} b/${filename}`,
|
|
1376
|
-
// files 端点不报文件模式,用寻常那个顶上:查看器读这两行是"新增"和"删除",不是读模式。
|
|
1377
|
-
...status === "added" ? ["new file mode 100644"] : [],
|
|
1378
|
-
...status === "removed" ? ["deleted file mode 100644"] : [],
|
|
1379
|
-
...status === "renamed" ? [`rename from ${oldPath}`, `rename to ${filename}`] : [],
|
|
1380
|
-
`--- ${status === "added" ? "/dev/null" : `a/${oldPath}`}`,
|
|
1381
|
-
`+++ ${status === "removed" ? "/dev/null" : `b/${filename}`}`
|
|
1382
|
-
].join("\n");
|
|
1383
|
-
sections.push(hunks.length === 0 ? `${header}
|
|
1384
|
-
` : `${header}
|
|
1385
|
-
${hunks.replace(/\n?$/, "\n")}`);
|
|
1386
|
-
}
|
|
1387
|
-
return { patch: sections.join(""), truncated, rawCount: value.length, omittedFileStats };
|
|
1388
|
-
};
|
|
1389
|
-
var decodeRevisions = (value, kind) => {
|
|
1390
|
-
const raw = asObject2(value);
|
|
1391
|
-
if (raw === null) return null;
|
|
1392
|
-
return kind === "pull-request" ? { baseRef: text2(at(raw, "base", "sha")), headRef: text2(at(raw, "head", "sha")) } : { baseRef: text2(at(asArray(raw["parents"])[0], "sha")), headRef: text2(raw["sha"]) };
|
|
1393
|
-
};
|
|
1394
1422
|
|
|
1395
|
-
// src/
|
|
1396
|
-
var CAPABILITIES = {
|
|
1397
|
-
diff: true,
|
|
1398
|
-
comment: true,
|
|
1399
|
-
actions: [
|
|
1400
|
-
"merge",
|
|
1401
|
-
"ready",
|
|
1402
|
-
"draft",
|
|
1403
|
-
"close",
|
|
1404
|
-
"reopen",
|
|
1405
|
-
"update-branch",
|
|
1406
|
-
"enable-auto-merge",
|
|
1407
|
-
"disable-auto-merge"
|
|
1408
|
-
],
|
|
1409
|
-
mergeMethods: ["merge", "squash", "rebase"],
|
|
1410
|
-
updateMethods: ["merge", "rebase"],
|
|
1411
|
-
search: true,
|
|
1412
|
-
reactions: true,
|
|
1413
|
-
review: {
|
|
1414
|
-
inlineComment: true,
|
|
1415
|
-
reply: true,
|
|
1416
|
-
resolve: true,
|
|
1417
|
-
verdicts: ["comment", "approve", "request-changes"]
|
|
1418
|
-
},
|
|
1419
|
-
reviewers: { request: true, listCandidates: true },
|
|
1420
|
-
edit: { changeRequest: true, comment: true }
|
|
1421
|
-
};
|
|
1422
|
-
var DIFF_MAX_BYTES = 8 * 1024 * 1024;
|
|
1423
|
-
var DIFF_TIMEOUT_MS = 6e4;
|
|
1424
|
-
var DIFF_FILE_MAX_BYTES = 1024 * 1024;
|
|
1425
|
-
var DIFF_FILES_PAGE_SIZE = 100;
|
|
1426
|
-
var REVIEW_THREAD_PAGES = 10;
|
|
1427
|
-
var FALLBACK_MAX_ROWS = 1e3;
|
|
1428
|
-
var REST_PAGE_SIZE = 100;
|
|
1429
|
-
var STAT_ALIASES_PER_REQUEST = 25;
|
|
1430
|
-
var STAT_REQUEST_CONCURRENCY = 4;
|
|
1431
|
-
var gitHubViewerPermissions = (access) => ({
|
|
1432
|
-
actions: [
|
|
1433
|
-
// 布置自动合并与撤销布置就是延后的合并:这里不能合的人,也不该留下一条以后合的指令。
|
|
1434
|
-
...access.canWrite ? ["merge", "enable-auto-merge", "disable-auto-merge"] : [],
|
|
1435
|
-
...access.canUpdate ? ["ready", "draft", "close", "reopen"] : [],
|
|
1436
|
-
// 能不能更新分支是 GitHub 自己的答案;答不上来就谁都不给,而不是谁都给。
|
|
1437
|
-
...access.canUpdateBranch ? ["update-branch"] : []
|
|
1438
|
-
],
|
|
1439
|
-
comment: true,
|
|
1440
|
-
resolve: access.canWrite || access.didAuthor,
|
|
1441
|
-
// 谁都能评审自己看得见的合并请求,除了自己那条:GitHub 拒绝作者的批准与要求修改
|
|
1442
|
-
// ("Can not approve your own pull request"),只留下评论——那也正是作者对自己改动能说的。
|
|
1443
|
-
verdicts: access.didAuthor ? ["comment"] : CAPABILITIES.review.verdicts,
|
|
1444
|
-
requestReviewers: access.canWrite,
|
|
1445
|
-
...access.canUpdateBranch ? { updateMethods: CAPABILITIES.updateMethods } : {}
|
|
1446
|
-
});
|
|
1423
|
+
// src/githubRepository.ts
|
|
1447
1424
|
var failed = (operation, detail) => new GitHubError({ code: "failed", operation, detail });
|
|
1448
1425
|
var parseRepositorySelector = (value) => {
|
|
1449
1426
|
const parts = value.trim().split("/").filter(Boolean);
|
|
@@ -1456,13 +1433,145 @@ var selector = (input, operation) => {
|
|
|
1456
1433
|
}
|
|
1457
1434
|
return parsed;
|
|
1458
1435
|
};
|
|
1459
|
-
var path = (value) => value.split("/").map(encodeURIComponent).join("/");
|
|
1460
1436
|
var repositoryPath = (input, operation) => {
|
|
1461
1437
|
const { owner, name } = selector(input, operation);
|
|
1462
1438
|
return `repos/${encodeURIComponent(owner)}/${encodeURIComponent(name)}`;
|
|
1463
1439
|
};
|
|
1440
|
+
|
|
1441
|
+
// src/githubDiff.ts
|
|
1442
|
+
var DIFF_MAX_BYTES = 8 * 1024 * 1024;
|
|
1443
|
+
var DIFF_TIMEOUT_MS = 6e4;
|
|
1444
|
+
var DIFF_FILE_MAX_BYTES = 1024 * 1024;
|
|
1445
|
+
var DIFF_FILES_PAGE_SIZE = 100;
|
|
1446
|
+
var path = (value) => value.split("/").map(encodeURIComponent).join("/");
|
|
1464
1447
|
var diffCursorPage = (cursor) => /^[1-9][0-9]{0,6}$/.test(cursor) ? Number(cursor) : null;
|
|
1465
1448
|
var isCommitSha = (value) => /^[0-9a-f]{7,64}$/i.test(value);
|
|
1449
|
+
var diffFilesPage = async (ctx, input) => {
|
|
1450
|
+
const repository = repositoryPath(input, "getDiff");
|
|
1451
|
+
const paging = `per_page=${DIFF_FILES_PAGE_SIZE}&page=${input.page}`;
|
|
1452
|
+
const result = await callGitHubApi(ctx, {
|
|
1453
|
+
operation: "getDiff",
|
|
1454
|
+
host: input.host,
|
|
1455
|
+
path: input.commit === void 0 ? `${repository}/pulls/${input.number}/files?${paging}` : `${repository}/commits/${input.commit}?${paging}`,
|
|
1456
|
+
maxBytes: DIFF_MAX_BYTES,
|
|
1457
|
+
timeoutMs: DIFF_TIMEOUT_MS
|
|
1458
|
+
});
|
|
1459
|
+
if (result.truncated) {
|
|
1460
|
+
throw failed("getDiff", `Page ${input.page} of the changed files was too large to read.`);
|
|
1461
|
+
}
|
|
1462
|
+
let body;
|
|
1463
|
+
try {
|
|
1464
|
+
body = JSON.parse(result.body.trim());
|
|
1465
|
+
} catch {
|
|
1466
|
+
throw readError("getDiff", "changed files");
|
|
1467
|
+
}
|
|
1468
|
+
const files = input.commit === void 0 ? body : body?.files ?? [];
|
|
1469
|
+
const decoded = decodeFiles(files);
|
|
1470
|
+
if (decoded === null) throw readError("getDiff", "changed files");
|
|
1471
|
+
const morePages = decoded.rawCount >= DIFF_FILES_PAGE_SIZE;
|
|
1472
|
+
return {
|
|
1473
|
+
patch: decoded.patch,
|
|
1474
|
+
truncated: decoded.truncated,
|
|
1475
|
+
nextCursor: morePages ? String(input.page + 1) : null,
|
|
1476
|
+
...decoded.omittedFileStats.length === 0 ? {} : { omittedFileStats: decoded.omittedFileStats }
|
|
1477
|
+
};
|
|
1478
|
+
};
|
|
1479
|
+
var getDiff = async (ctx, input) => {
|
|
1480
|
+
if (input.commit !== void 0 && !isCommitSha(input.commit)) {
|
|
1481
|
+
throw failed("getDiff", "The named commit was not a commit sha.");
|
|
1482
|
+
}
|
|
1483
|
+
const target = {
|
|
1484
|
+
cwd: input.cwd,
|
|
1485
|
+
repository: input.repository,
|
|
1486
|
+
host: input.host,
|
|
1487
|
+
number: input.number,
|
|
1488
|
+
...input.commit === void 0 ? {} : { commit: input.commit }
|
|
1489
|
+
};
|
|
1490
|
+
if (input.cursor !== void 0) {
|
|
1491
|
+
const page = diffCursorPage(input.cursor);
|
|
1492
|
+
if (page === null) {
|
|
1493
|
+
throw failed("getDiff", "The diff cursor was not one this pull request handed out.");
|
|
1494
|
+
}
|
|
1495
|
+
return diffFilesPage(ctx, { ...target, page });
|
|
1496
|
+
}
|
|
1497
|
+
if (input.commit !== void 0) return diffFilesPage(ctx, { ...target, page: 1 });
|
|
1498
|
+
const whole = async () => {
|
|
1499
|
+
const result = await callGitHubApi(ctx, {
|
|
1500
|
+
operation: "getDiff",
|
|
1501
|
+
host: input.host,
|
|
1502
|
+
path: `${repositoryPath(input, "getDiff")}/pulls/${input.number}`,
|
|
1503
|
+
accept: "application/vnd.github.diff",
|
|
1504
|
+
maxBytes: DIFF_MAX_BYTES,
|
|
1505
|
+
timeoutMs: DIFF_TIMEOUT_MS
|
|
1506
|
+
});
|
|
1507
|
+
return result.truncated ? null : { patch: result.body, truncated: false, nextCursor: null };
|
|
1508
|
+
};
|
|
1509
|
+
try {
|
|
1510
|
+
const slice = await whole();
|
|
1511
|
+
return slice ?? await diffFilesPage(ctx, { ...target, page: 1 });
|
|
1512
|
+
} catch (error) {
|
|
1513
|
+
if (error instanceof GitHubError && error.code !== "failed") throw error;
|
|
1514
|
+
try {
|
|
1515
|
+
return await diffFilesPage(ctx, { ...target, page: 1 });
|
|
1516
|
+
} catch {
|
|
1517
|
+
throw error;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
var getDiffFileContents = async (ctx, input) => {
|
|
1522
|
+
const operation = "getDiffFileContents";
|
|
1523
|
+
if (input.commit !== void 0 && !isCommitSha(input.commit)) {
|
|
1524
|
+
throw failed(operation, "The named commit was not a commit sha.");
|
|
1525
|
+
}
|
|
1526
|
+
const repository = repositoryPath(input, operation);
|
|
1527
|
+
const revisions = decodeRevisions(
|
|
1528
|
+
await callGitHubJson(ctx, {
|
|
1529
|
+
operation,
|
|
1530
|
+
host: input.host,
|
|
1531
|
+
path: input.commit === void 0 ? `${repository}/pulls/${input.number}` : `${repository}/commits/${input.commit}`,
|
|
1532
|
+
maxBytes: DIFF_MAX_BYTES,
|
|
1533
|
+
timeoutMs: DIFF_TIMEOUT_MS
|
|
1534
|
+
}),
|
|
1535
|
+
input.commit === void 0 ? "pull-request" : "commit"
|
|
1536
|
+
);
|
|
1537
|
+
const rootCommitNewFile = input.commit !== void 0 && input.changeType === "new" && revisions?.baseRef === null;
|
|
1538
|
+
const baseRef = revisions?.baseRef ?? null;
|
|
1539
|
+
const headRef = revisions?.headRef ?? null;
|
|
1540
|
+
if (headRef === null || !isCommitSha(headRef) || !rootCommitNewFile && (baseRef === null || !isCommitSha(baseRef))) {
|
|
1541
|
+
throw failed(
|
|
1542
|
+
operation,
|
|
1543
|
+
input.commit === void 0 ? `Pull request #${input.number} reported no usable base and head revisions.` : `Commit ${input.commit} reported no usable revisions for this file.`
|
|
1544
|
+
);
|
|
1545
|
+
}
|
|
1546
|
+
const readFile = async (revision, filePath) => {
|
|
1547
|
+
const result = await callGitHubApi(ctx, {
|
|
1548
|
+
operation,
|
|
1549
|
+
host: input.host,
|
|
1550
|
+
path: `${repository}/contents/${path(filePath)}?ref=${encodeURIComponent(revision)}`,
|
|
1551
|
+
accept: "application/vnd.github.raw",
|
|
1552
|
+
maxBytes: DIFF_FILE_MAX_BYTES,
|
|
1553
|
+
timeoutMs: DIFF_TIMEOUT_MS
|
|
1554
|
+
});
|
|
1555
|
+
if (result.truncated) {
|
|
1556
|
+
throw failed(operation, `The diff file '${filePath}' exceeds the 1 MB expansion limit.`);
|
|
1557
|
+
}
|
|
1558
|
+
if (result.body.includes("\0")) {
|
|
1559
|
+
throw failed(operation, `The diff file '${filePath}' is binary.`);
|
|
1560
|
+
}
|
|
1561
|
+
return result.body;
|
|
1562
|
+
};
|
|
1563
|
+
const [oldContents, newContents] = await Promise.all([
|
|
1564
|
+
input.changeType === "new" ? Promise.resolve("") : readFile(baseRef, input.oldPath),
|
|
1565
|
+
input.changeType === "deleted" ? Promise.resolve("") : readFile(headRef, input.newPath)
|
|
1566
|
+
]);
|
|
1567
|
+
return { oldContents, newContents };
|
|
1568
|
+
};
|
|
1569
|
+
|
|
1570
|
+
// src/githubSearch.ts
|
|
1571
|
+
var FALLBACK_MAX_ROWS = 1e3;
|
|
1572
|
+
var REST_PAGE_SIZE = 100;
|
|
1573
|
+
var STAT_ALIASES_PER_REQUEST = 25;
|
|
1574
|
+
var STAT_REQUEST_CONCURRENCY = 4;
|
|
1466
1575
|
var searchPhrase = (query2) => `"${query2.replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
|
|
1467
1576
|
var REVIEW_QUALIFIERS = {
|
|
1468
1577
|
approved: "approved",
|
|
@@ -1525,15 +1634,6 @@ var matchesFallback = (item, input) => {
|
|
|
1525
1634
|
const holds = (label) => labels.has(label.trim().toLowerCase());
|
|
1526
1635
|
return (filters.draft === void 0 || item.isDraft === (filters.draft === "only")) && (filters.labels === void 0 || filters.labels.every((group) => group.some(holds))) && (filters.excludedLabels === void 0 || !filters.excludedLabels.some(holds)) && (filters.author === void 0 || item.author?.login.toLowerCase() === resolveAuthor(filters.author, input.viewer).toLowerCase());
|
|
1527
1636
|
};
|
|
1528
|
-
var getViewer = async (ctx) => {
|
|
1529
|
-
const login = decodeViewerLogin(
|
|
1530
|
-
await callGitHubJson(ctx, { operation: "getViewer", path: "user" })
|
|
1531
|
-
);
|
|
1532
|
-
if (login === null) {
|
|
1533
|
-
throw failed("getViewer", "GitHub returned no login for the authenticated account.");
|
|
1534
|
-
}
|
|
1535
|
-
return login;
|
|
1536
|
-
};
|
|
1537
1637
|
var searchRows = (limit) => Math.min(limit + 1, SEARCH_MAX_ROWS);
|
|
1538
1638
|
var listFallback = async (ctx, input) => {
|
|
1539
1639
|
const repository = repositoryPath(input, "listChangeRequests");
|
|
@@ -1642,6 +1742,60 @@ var listChangeRequestStats = async (ctx, input) => {
|
|
|
1642
1742
|
}
|
|
1643
1743
|
return results;
|
|
1644
1744
|
};
|
|
1745
|
+
|
|
1746
|
+
// src/githubCommands.ts
|
|
1747
|
+
var CAPABILITIES = {
|
|
1748
|
+
diff: true,
|
|
1749
|
+
comment: true,
|
|
1750
|
+
actions: [
|
|
1751
|
+
"merge",
|
|
1752
|
+
"ready",
|
|
1753
|
+
"draft",
|
|
1754
|
+
"close",
|
|
1755
|
+
"reopen",
|
|
1756
|
+
"update-branch",
|
|
1757
|
+
"enable-auto-merge",
|
|
1758
|
+
"disable-auto-merge"
|
|
1759
|
+
],
|
|
1760
|
+
mergeMethods: ["merge", "squash", "rebase"],
|
|
1761
|
+
updateMethods: ["merge", "rebase"],
|
|
1762
|
+
search: true,
|
|
1763
|
+
reactions: true,
|
|
1764
|
+
review: {
|
|
1765
|
+
inlineComment: true,
|
|
1766
|
+
reply: true,
|
|
1767
|
+
resolve: true,
|
|
1768
|
+
verdicts: ["comment", "approve", "request-changes"]
|
|
1769
|
+
},
|
|
1770
|
+
reviewers: { request: true, listCandidates: true },
|
|
1771
|
+
edit: { changeRequest: true, comment: true }
|
|
1772
|
+
};
|
|
1773
|
+
var REVIEW_THREAD_PAGES = 10;
|
|
1774
|
+
var gitHubViewerPermissions = (access) => ({
|
|
1775
|
+
actions: [
|
|
1776
|
+
// 布置自动合并与撤销布置就是延后的合并:这里不能合的人,也不该留下一条以后合的指令。
|
|
1777
|
+
...access.canWrite ? ["merge", "enable-auto-merge", "disable-auto-merge"] : [],
|
|
1778
|
+
...access.canUpdate ? ["ready", "draft", "close", "reopen"] : [],
|
|
1779
|
+
// 能不能更新分支是 GitHub 自己的答案;答不上来就谁都不给,而不是谁都给。
|
|
1780
|
+
...access.canUpdateBranch ? ["update-branch"] : []
|
|
1781
|
+
],
|
|
1782
|
+
comment: true,
|
|
1783
|
+
resolve: access.canWrite || access.didAuthor,
|
|
1784
|
+
// 谁都能评审自己看得见的合并请求,除了自己那条:GitHub 拒绝作者的批准与要求修改
|
|
1785
|
+
// ("Can not approve your own pull request"),只留下评论——那也正是作者对自己改动能说的。
|
|
1786
|
+
verdicts: access.didAuthor ? ["comment"] : CAPABILITIES.review.verdicts,
|
|
1787
|
+
requestReviewers: access.canWrite,
|
|
1788
|
+
...access.canUpdateBranch ? { updateMethods: CAPABILITIES.updateMethods } : {}
|
|
1789
|
+
});
|
|
1790
|
+
var getViewer = async (ctx) => {
|
|
1791
|
+
const login = decodeViewerLogin(
|
|
1792
|
+
await callGitHubJson(ctx, { operation: "getViewer", path: "user" })
|
|
1793
|
+
);
|
|
1794
|
+
if (login === null) {
|
|
1795
|
+
throw failed("getViewer", "GitHub returned no login for the authenticated account.");
|
|
1796
|
+
}
|
|
1797
|
+
return login;
|
|
1798
|
+
};
|
|
1645
1799
|
var detailOf = async (ctx, input, operation) => {
|
|
1646
1800
|
const { owner, name } = selector(input, operation);
|
|
1647
1801
|
const decoded = decodeDetail(
|
|
@@ -1748,131 +1902,11 @@ var getViewerPermissions = async (ctx, input) => {
|
|
|
1748
1902
|
operation: "getViewerPermissions",
|
|
1749
1903
|
host: input.host,
|
|
1750
1904
|
query: VIEWER_PERMISSIONS_QUERY,
|
|
1751
|
-
variables: { owner, name, number: input.number }
|
|
1752
|
-
})
|
|
1753
|
-
);
|
|
1754
|
-
if (access === null) throw readError("getViewerPermissions", "viewer permissions");
|
|
1755
|
-
return gitHubViewerPermissions(access);
|
|
1756
|
-
};
|
|
1757
|
-
var diffFilesPage = async (ctx, input) => {
|
|
1758
|
-
const repository = repositoryPath(input, "getDiff");
|
|
1759
|
-
const paging = `per_page=${DIFF_FILES_PAGE_SIZE}&page=${input.page}`;
|
|
1760
|
-
const result = await callGitHubApi(ctx, {
|
|
1761
|
-
operation: "getDiff",
|
|
1762
|
-
host: input.host,
|
|
1763
|
-
path: input.commit === void 0 ? `${repository}/pulls/${input.number}/files?${paging}` : `${repository}/commits/${input.commit}?${paging}`,
|
|
1764
|
-
maxBytes: DIFF_MAX_BYTES,
|
|
1765
|
-
timeoutMs: DIFF_TIMEOUT_MS
|
|
1766
|
-
});
|
|
1767
|
-
if (result.truncated) {
|
|
1768
|
-
throw failed("getDiff", `Page ${input.page} of the changed files was too large to read.`);
|
|
1769
|
-
}
|
|
1770
|
-
let body;
|
|
1771
|
-
try {
|
|
1772
|
-
body = JSON.parse(result.body.trim());
|
|
1773
|
-
} catch {
|
|
1774
|
-
throw readError("getDiff", "changed files");
|
|
1775
|
-
}
|
|
1776
|
-
const files = input.commit === void 0 ? body : body?.files ?? [];
|
|
1777
|
-
const decoded = decodeFiles(files);
|
|
1778
|
-
if (decoded === null) throw readError("getDiff", "changed files");
|
|
1779
|
-
const morePages = decoded.rawCount >= DIFF_FILES_PAGE_SIZE;
|
|
1780
|
-
return {
|
|
1781
|
-
patch: decoded.patch,
|
|
1782
|
-
truncated: decoded.truncated,
|
|
1783
|
-
nextCursor: morePages ? String(input.page + 1) : null,
|
|
1784
|
-
...decoded.omittedFileStats.length === 0 ? {} : { omittedFileStats: decoded.omittedFileStats }
|
|
1785
|
-
};
|
|
1786
|
-
};
|
|
1787
|
-
var getDiff = async (ctx, input) => {
|
|
1788
|
-
if (input.commit !== void 0 && !isCommitSha(input.commit)) {
|
|
1789
|
-
throw failed("getDiff", "The named commit was not a commit sha.");
|
|
1790
|
-
}
|
|
1791
|
-
const target = {
|
|
1792
|
-
cwd: input.cwd,
|
|
1793
|
-
repository: input.repository,
|
|
1794
|
-
host: input.host,
|
|
1795
|
-
number: input.number,
|
|
1796
|
-
...input.commit === void 0 ? {} : { commit: input.commit }
|
|
1797
|
-
};
|
|
1798
|
-
if (input.cursor !== void 0) {
|
|
1799
|
-
const page = diffCursorPage(input.cursor);
|
|
1800
|
-
if (page === null) {
|
|
1801
|
-
throw failed("getDiff", "The diff cursor was not one this pull request handed out.");
|
|
1802
|
-
}
|
|
1803
|
-
return diffFilesPage(ctx, { ...target, page });
|
|
1804
|
-
}
|
|
1805
|
-
if (input.commit !== void 0) return diffFilesPage(ctx, { ...target, page: 1 });
|
|
1806
|
-
const whole = async () => {
|
|
1807
|
-
const result = await callGitHubApi(ctx, {
|
|
1808
|
-
operation: "getDiff",
|
|
1809
|
-
host: input.host,
|
|
1810
|
-
path: `${repositoryPath(input, "getDiff")}/pulls/${input.number}`,
|
|
1811
|
-
accept: "application/vnd.github.diff",
|
|
1812
|
-
maxBytes: DIFF_MAX_BYTES,
|
|
1813
|
-
timeoutMs: DIFF_TIMEOUT_MS
|
|
1814
|
-
});
|
|
1815
|
-
return result.truncated ? null : { patch: result.body, truncated: false, nextCursor: null };
|
|
1816
|
-
};
|
|
1817
|
-
try {
|
|
1818
|
-
const slice = await whole();
|
|
1819
|
-
return slice ?? await diffFilesPage(ctx, { ...target, page: 1 });
|
|
1820
|
-
} catch (error) {
|
|
1821
|
-
if (error instanceof GitHubError && error.code !== "failed") throw error;
|
|
1822
|
-
try {
|
|
1823
|
-
return await diffFilesPage(ctx, { ...target, page: 1 });
|
|
1824
|
-
} catch {
|
|
1825
|
-
throw error;
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
};
|
|
1829
|
-
var getDiffFileContents = async (ctx, input) => {
|
|
1830
|
-
const operation = "getDiffFileContents";
|
|
1831
|
-
if (input.commit !== void 0 && !isCommitSha(input.commit)) {
|
|
1832
|
-
throw failed(operation, "The named commit was not a commit sha.");
|
|
1833
|
-
}
|
|
1834
|
-
const repository = repositoryPath(input, operation);
|
|
1835
|
-
const revisions = decodeRevisions(
|
|
1836
|
-
await callGitHubJson(ctx, {
|
|
1837
|
-
operation,
|
|
1838
|
-
host: input.host,
|
|
1839
|
-
path: input.commit === void 0 ? `${repository}/pulls/${input.number}` : `${repository}/commits/${input.commit}`,
|
|
1840
|
-
maxBytes: DIFF_MAX_BYTES,
|
|
1841
|
-
timeoutMs: DIFF_TIMEOUT_MS
|
|
1842
|
-
}),
|
|
1843
|
-
input.commit === void 0 ? "pull-request" : "commit"
|
|
1844
|
-
);
|
|
1845
|
-
const rootCommitNewFile = input.commit !== void 0 && input.changeType === "new" && revisions?.baseRef === null;
|
|
1846
|
-
const baseRef = revisions?.baseRef ?? null;
|
|
1847
|
-
const headRef = revisions?.headRef ?? null;
|
|
1848
|
-
if (headRef === null || !isCommitSha(headRef) || !rootCommitNewFile && (baseRef === null || !isCommitSha(baseRef))) {
|
|
1849
|
-
throw failed(
|
|
1850
|
-
operation,
|
|
1851
|
-
input.commit === void 0 ? `Pull request #${input.number} reported no usable base and head revisions.` : `Commit ${input.commit} reported no usable revisions for this file.`
|
|
1852
|
-
);
|
|
1853
|
-
}
|
|
1854
|
-
const readFile = async (revision, filePath) => {
|
|
1855
|
-
const result = await callGitHubApi(ctx, {
|
|
1856
|
-
operation,
|
|
1857
|
-
host: input.host,
|
|
1858
|
-
path: `${repository}/contents/${path(filePath)}?ref=${encodeURIComponent(revision)}`,
|
|
1859
|
-
accept: "application/vnd.github.raw",
|
|
1860
|
-
maxBytes: DIFF_FILE_MAX_BYTES,
|
|
1861
|
-
timeoutMs: DIFF_TIMEOUT_MS
|
|
1862
|
-
});
|
|
1863
|
-
if (result.truncated) {
|
|
1864
|
-
throw failed(operation, `The diff file '${filePath}' exceeds the 1 MB expansion limit.`);
|
|
1865
|
-
}
|
|
1866
|
-
if (result.body.includes("\0")) {
|
|
1867
|
-
throw failed(operation, `The diff file '${filePath}' is binary.`);
|
|
1868
|
-
}
|
|
1869
|
-
return result.body;
|
|
1870
|
-
};
|
|
1871
|
-
const [oldContents, newContents] = await Promise.all([
|
|
1872
|
-
input.changeType === "new" ? Promise.resolve("") : readFile(baseRef, input.oldPath),
|
|
1873
|
-
input.changeType === "deleted" ? Promise.resolve("") : readFile(headRef, input.newPath)
|
|
1874
|
-
]);
|
|
1875
|
-
return { oldContents, newContents };
|
|
1905
|
+
variables: { owner, name, number: input.number }
|
|
1906
|
+
})
|
|
1907
|
+
);
|
|
1908
|
+
if (access === null) throw readError("getViewerPermissions", "viewer permissions");
|
|
1909
|
+
return gitHubViewerPermissions(access);
|
|
1876
1910
|
};
|
|
1877
1911
|
var listReviewerCandidates = async (ctx, input) => {
|
|
1878
1912
|
const { owner, name } = selector(input, "listReviewerCandidates");
|
|
@@ -2613,7 +2647,7 @@ var decodeOwnAwardId = (raw, input) => {
|
|
|
2613
2647
|
return { awardId: null };
|
|
2614
2648
|
};
|
|
2615
2649
|
|
|
2616
|
-
// src/
|
|
2650
|
+
// src/gitlabCommandQuery.ts
|
|
2617
2651
|
var CAPABILITIES2 = {
|
|
2618
2652
|
diff: true,
|
|
2619
2653
|
comment: true,
|
|
@@ -2702,82 +2736,8 @@ var reviewPositionLines = (position) => {
|
|
|
2702
2736
|
}
|
|
2703
2737
|
};
|
|
2704
2738
|
var mergeRequestPath = (input) => `projects/${projectPath(input.repository)}/merge_requests/${input.number}`;
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
operation: "getViewer",
|
|
2708
|
-
path: "user"
|
|
2709
|
-
});
|
|
2710
|
-
const decoded = decodeViewer(result.body.trim());
|
|
2711
|
-
if (decoded === null) throw readError2("getViewer", "getViewerUsername");
|
|
2712
|
-
if (decoded.username === null) {
|
|
2713
|
-
throw new GitLabError({
|
|
2714
|
-
code: "failed",
|
|
2715
|
-
operation: "getViewer",
|
|
2716
|
-
detail: "GitLab returned no username for the authenticated account."
|
|
2717
|
-
});
|
|
2718
|
-
}
|
|
2719
|
-
return decoded.username;
|
|
2720
|
-
};
|
|
2721
|
-
var listChangeRequests2 = async (ctx, input) => {
|
|
2722
|
-
const delivered = input.cursor?.delivered ?? 0;
|
|
2723
|
-
const perPage = Math.min(input.limit + 1, MAX_PAGE_SIZE);
|
|
2724
|
-
const firstPage = Math.floor(delivered / perPage) + 1;
|
|
2725
|
-
const lastPage = Math.floor((delivered + input.limit) / perPage) + 1;
|
|
2726
|
-
let page = firstPage;
|
|
2727
|
-
let collected = [];
|
|
2728
|
-
let cursorAdvance = 0;
|
|
2729
|
-
for (; ; ) {
|
|
2730
|
-
const skipOnFirstPage = page === firstPage ? delivered % perPage : 0;
|
|
2731
|
-
const result = await callGitLabApi(ctx, {
|
|
2732
|
-
operation: "listChangeRequests",
|
|
2733
|
-
host: input.host,
|
|
2734
|
-
path: `projects/${projectPath(input.repository)}/merge_requests?${query([
|
|
2735
|
-
["state", stateParam(input.state)],
|
|
2736
|
-
...involvementParams(input),
|
|
2737
|
-
// 搜索用 REST 自己的 `search` 参数(匹配标题与描述),并像别的值一样 URL
|
|
2738
|
-
// 编码,里面的文字变不成另一个参数。
|
|
2739
|
-
...searchParams(input.query),
|
|
2740
|
-
["order_by", "updated_at"],
|
|
2741
|
-
["sort", "desc"],
|
|
2742
|
-
["per_page", String(perPage)],
|
|
2743
|
-
["page", String(page)]
|
|
2744
|
-
])}`
|
|
2745
|
-
});
|
|
2746
|
-
const raw = result.body.trim();
|
|
2747
|
-
if (raw.length === 0) {
|
|
2748
|
-
return { items: collected, truncated: false, cursorAdvance, continues: true };
|
|
2749
|
-
}
|
|
2750
|
-
const decoded = decodeMergeRequestList(raw);
|
|
2751
|
-
if (decoded === null) throw readError2("listChangeRequests", "listMergeRequests");
|
|
2752
|
-
const pageItems = [];
|
|
2753
|
-
const pageRawIndexes = [];
|
|
2754
|
-
for (const [index, item] of decoded.items.entries()) {
|
|
2755
|
-
const rawIndex = decoded.rawIndexes[index];
|
|
2756
|
-
if (rawIndex < skipOnFirstPage) continue;
|
|
2757
|
-
pageItems.push(item);
|
|
2758
|
-
pageRawIndexes.push(rawIndex);
|
|
2759
|
-
}
|
|
2760
|
-
const remaining = input.limit - collected.length;
|
|
2761
|
-
const lastItemRawIndex = pageRawIndexes[remaining - 1];
|
|
2762
|
-
if (lastItemRawIndex !== void 0) {
|
|
2763
|
-
return {
|
|
2764
|
-
items: [...collected, ...pageItems.slice(0, remaining)],
|
|
2765
|
-
truncated: lastItemRawIndex + 1 < decoded.rawCount || decoded.rawCount === perPage,
|
|
2766
|
-
cursorAdvance: cursorAdvance + (lastItemRawIndex + 1 - skipOnFirstPage),
|
|
2767
|
-
continues: true
|
|
2768
|
-
};
|
|
2769
|
-
}
|
|
2770
|
-
collected = [...collected, ...pageItems];
|
|
2771
|
-
cursorAdvance += Math.max(0, decoded.rawCount - skipOnFirstPage);
|
|
2772
|
-
if (decoded.rawCount < perPage) {
|
|
2773
|
-
return { items: collected, truncated: false, cursorAdvance, continues: true };
|
|
2774
|
-
}
|
|
2775
|
-
if (page >= lastPage) {
|
|
2776
|
-
return { items: collected, truncated: true, cursorAdvance, continues: true };
|
|
2777
|
-
}
|
|
2778
|
-
page += 1;
|
|
2779
|
-
}
|
|
2780
|
-
};
|
|
2739
|
+
|
|
2740
|
+
// src/gitlabCommandReads.ts
|
|
2781
2741
|
var mergeRequestDetail = async (ctx, input, operation) => {
|
|
2782
2742
|
const result = await callGitLabApi(ctx, {
|
|
2783
2743
|
operation,
|
|
@@ -2800,25 +2760,6 @@ var projectMergeCapabilities = async (ctx, input, operation) => {
|
|
|
2800
2760
|
if (decoded === null) throw readError2(operation, "getProjectMergeCapabilities");
|
|
2801
2761
|
return decoded;
|
|
2802
2762
|
};
|
|
2803
|
-
var getChangeRequest2 = async (ctx, input) => {
|
|
2804
|
-
const [mergeRequest, mergeCapabilities] = await Promise.all([
|
|
2805
|
-
mergeRequestDetail(ctx, input, "getChangeRequest"),
|
|
2806
|
-
projectMergeCapabilities(ctx, input, "getChangeRequest")
|
|
2807
|
-
]);
|
|
2808
|
-
return {
|
|
2809
|
-
...mergeRequest,
|
|
2810
|
-
mergeCapabilities,
|
|
2811
|
-
viewerPermissions: gitLabViewerPermissions(mergeRequest),
|
|
2812
|
-
// 数不出分歧数的老 GitLab 什么都不说,而不是说「已是最新」:横幅丢了不要紧,一个
|
|
2813
|
-
// 错误的「一切正常」不值得显示。
|
|
2814
|
-
baseComparison: mergeRequest.divergedCommits === void 0 ? "unknown" : mergeRequest.divergedCommits > 0 ? "behind" : "up-to-date",
|
|
2815
|
-
...mergeRequest.divergedCommits === void 0 ? {} : { behindBy: mergeRequest.divergedCommits }
|
|
2816
|
-
};
|
|
2817
|
-
};
|
|
2818
|
-
var getViewerPermissions2 = async (ctx, input) => (
|
|
2819
|
-
// 与 detail 取自同一次读:`user.can_merge` 就长在合并请求上,没有更便宜的问法。
|
|
2820
|
-
gitLabViewerPermissions(await mergeRequestDetail(ctx, input, "getViewerPermissions"))
|
|
2821
|
-
);
|
|
2822
2763
|
var listNotes = async (ctx, input) => {
|
|
2823
2764
|
let collected = [];
|
|
2824
2765
|
for (let page = 1; ; page += 1) {
|
|
@@ -2896,6 +2837,131 @@ var listReactions = async (ctx, input) => {
|
|
|
2896
2837
|
}
|
|
2897
2838
|
};
|
|
2898
2839
|
var orElse = (promise, fallback) => promise.catch(() => fallback);
|
|
2840
|
+
var diffPage = async (ctx, input) => {
|
|
2841
|
+
const result = await callGitLabApi(ctx, {
|
|
2842
|
+
operation: "getDiff",
|
|
2843
|
+
host: input.host,
|
|
2844
|
+
path: `projects/${projectPath(input.repository)}/${input.commit === void 0 ? `merge_requests/${input.number}/diffs` : `repository/commits/${input.commit}/diff`}?${query([
|
|
2845
|
+
["per_page", String(MAX_PAGE_SIZE)],
|
|
2846
|
+
["page", String(input.page)]
|
|
2847
|
+
])}`,
|
|
2848
|
+
maxBytes: DIFF_MAX_BYTES2,
|
|
2849
|
+
timeoutMs: DIFF_TIMEOUT_MS2
|
|
2850
|
+
});
|
|
2851
|
+
if (result.truncated) {
|
|
2852
|
+
throw new GitLabError({
|
|
2853
|
+
code: "failed",
|
|
2854
|
+
operation: "getDiff",
|
|
2855
|
+
detail: `Page ${input.page} of the merge request diff was too large to read.`
|
|
2856
|
+
});
|
|
2857
|
+
}
|
|
2858
|
+
const decoded = decodeMergeRequestDiffs(result.body.trim());
|
|
2859
|
+
if (decoded === null) throw readError2("getDiff", "getMergeRequestDiff");
|
|
2860
|
+
const morePages = decoded.rawCount >= MAX_PAGE_SIZE;
|
|
2861
|
+
return {
|
|
2862
|
+
// 切片以换行结尾,GitLab 只给了头没给 hunk 的文件才不会撞进下一段的第一行。
|
|
2863
|
+
patch: decoded.patch.length === 0 ? decoded.patch : decoded.patch.replace(/\n?$/, "\n"),
|
|
2864
|
+
truncated: decoded.truncated,
|
|
2865
|
+
nextCursor: morePages ? String(input.page + 1) : null
|
|
2866
|
+
};
|
|
2867
|
+
};
|
|
2868
|
+
|
|
2869
|
+
// src/gitlabCommands.ts
|
|
2870
|
+
var getViewer2 = async (ctx) => {
|
|
2871
|
+
const result = await callGitLabApi(ctx, {
|
|
2872
|
+
operation: "getViewer",
|
|
2873
|
+
path: "user"
|
|
2874
|
+
});
|
|
2875
|
+
const decoded = decodeViewer(result.body.trim());
|
|
2876
|
+
if (decoded === null) throw readError2("getViewer", "getViewerUsername");
|
|
2877
|
+
if (decoded.username === null) {
|
|
2878
|
+
throw new GitLabError({
|
|
2879
|
+
code: "failed",
|
|
2880
|
+
operation: "getViewer",
|
|
2881
|
+
detail: "GitLab returned no username for the authenticated account."
|
|
2882
|
+
});
|
|
2883
|
+
}
|
|
2884
|
+
return decoded.username;
|
|
2885
|
+
};
|
|
2886
|
+
var listChangeRequests2 = async (ctx, input) => {
|
|
2887
|
+
const delivered = input.cursor?.delivered ?? 0;
|
|
2888
|
+
const perPage = Math.min(input.limit + 1, MAX_PAGE_SIZE);
|
|
2889
|
+
const firstPage = Math.floor(delivered / perPage) + 1;
|
|
2890
|
+
const lastPage = Math.floor((delivered + input.limit) / perPage) + 1;
|
|
2891
|
+
let page = firstPage;
|
|
2892
|
+
let collected = [];
|
|
2893
|
+
let cursorAdvance = 0;
|
|
2894
|
+
for (; ; ) {
|
|
2895
|
+
const skipOnFirstPage = page === firstPage ? delivered % perPage : 0;
|
|
2896
|
+
const result = await callGitLabApi(ctx, {
|
|
2897
|
+
operation: "listChangeRequests",
|
|
2898
|
+
host: input.host,
|
|
2899
|
+
path: `projects/${projectPath(input.repository)}/merge_requests?${query([
|
|
2900
|
+
["state", stateParam(input.state)],
|
|
2901
|
+
...involvementParams(input),
|
|
2902
|
+
// 搜索用 REST 自己的 `search` 参数(匹配标题与描述),并像别的值一样 URL
|
|
2903
|
+
// 编码,里面的文字变不成另一个参数。
|
|
2904
|
+
...searchParams(input.query),
|
|
2905
|
+
["order_by", "updated_at"],
|
|
2906
|
+
["sort", "desc"],
|
|
2907
|
+
["per_page", String(perPage)],
|
|
2908
|
+
["page", String(page)]
|
|
2909
|
+
])}`
|
|
2910
|
+
});
|
|
2911
|
+
const raw = result.body.trim();
|
|
2912
|
+
if (raw.length === 0) {
|
|
2913
|
+
return { items: collected, truncated: false, cursorAdvance, continues: true };
|
|
2914
|
+
}
|
|
2915
|
+
const decoded = decodeMergeRequestList(raw);
|
|
2916
|
+
if (decoded === null) throw readError2("listChangeRequests", "listMergeRequests");
|
|
2917
|
+
const pageItems = [];
|
|
2918
|
+
const pageRawIndexes = [];
|
|
2919
|
+
for (const [index, item] of decoded.items.entries()) {
|
|
2920
|
+
const rawIndex = decoded.rawIndexes[index];
|
|
2921
|
+
if (rawIndex < skipOnFirstPage) continue;
|
|
2922
|
+
pageItems.push(item);
|
|
2923
|
+
pageRawIndexes.push(rawIndex);
|
|
2924
|
+
}
|
|
2925
|
+
const remaining = input.limit - collected.length;
|
|
2926
|
+
const lastItemRawIndex = pageRawIndexes[remaining - 1];
|
|
2927
|
+
if (lastItemRawIndex !== void 0) {
|
|
2928
|
+
return {
|
|
2929
|
+
items: [...collected, ...pageItems.slice(0, remaining)],
|
|
2930
|
+
truncated: lastItemRawIndex + 1 < decoded.rawCount || decoded.rawCount === perPage,
|
|
2931
|
+
cursorAdvance: cursorAdvance + (lastItemRawIndex + 1 - skipOnFirstPage),
|
|
2932
|
+
continues: true
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
collected = [...collected, ...pageItems];
|
|
2936
|
+
cursorAdvance += Math.max(0, decoded.rawCount - skipOnFirstPage);
|
|
2937
|
+
if (decoded.rawCount < perPage) {
|
|
2938
|
+
return { items: collected, truncated: false, cursorAdvance, continues: true };
|
|
2939
|
+
}
|
|
2940
|
+
if (page >= lastPage) {
|
|
2941
|
+
return { items: collected, truncated: true, cursorAdvance, continues: true };
|
|
2942
|
+
}
|
|
2943
|
+
page += 1;
|
|
2944
|
+
}
|
|
2945
|
+
};
|
|
2946
|
+
var getChangeRequest2 = async (ctx, input) => {
|
|
2947
|
+
const [mergeRequest, mergeCapabilities] = await Promise.all([
|
|
2948
|
+
mergeRequestDetail(ctx, input, "getChangeRequest"),
|
|
2949
|
+
projectMergeCapabilities(ctx, input, "getChangeRequest")
|
|
2950
|
+
]);
|
|
2951
|
+
return {
|
|
2952
|
+
...mergeRequest,
|
|
2953
|
+
mergeCapabilities,
|
|
2954
|
+
viewerPermissions: gitLabViewerPermissions(mergeRequest),
|
|
2955
|
+
// 数不出分歧数的老 GitLab 什么都不说,而不是说「已是最新」:横幅丢了不要紧,一个
|
|
2956
|
+
// 错误的「一切正常」不值得显示。
|
|
2957
|
+
baseComparison: mergeRequest.divergedCommits === void 0 ? "unknown" : mergeRequest.divergedCommits > 0 ? "behind" : "up-to-date",
|
|
2958
|
+
...mergeRequest.divergedCommits === void 0 ? {} : { behindBy: mergeRequest.divergedCommits }
|
|
2959
|
+
};
|
|
2960
|
+
};
|
|
2961
|
+
var getViewerPermissions2 = async (ctx, input) => (
|
|
2962
|
+
// 与 detail 取自同一次读:`user.can_merge` 就长在合并请求上,没有更便宜的问法。
|
|
2963
|
+
gitLabViewerPermissions(await mergeRequestDetail(ctx, input, "getViewerPermissions"))
|
|
2964
|
+
);
|
|
2899
2965
|
var getChangeRequestActivity2 = async (ctx, input) => {
|
|
2900
2966
|
const [notes, commits, discussions, awards] = await Promise.all([
|
|
2901
2967
|
orElse(listNotes(ctx, input), {
|
|
@@ -2933,34 +2999,6 @@ var getChangeRequestActivity2 = async (ctx, input) => {
|
|
|
2933
2999
|
commits
|
|
2934
3000
|
};
|
|
2935
3001
|
};
|
|
2936
|
-
var diffPage = async (ctx, input) => {
|
|
2937
|
-
const result = await callGitLabApi(ctx, {
|
|
2938
|
-
operation: "getDiff",
|
|
2939
|
-
host: input.host,
|
|
2940
|
-
path: `projects/${projectPath(input.repository)}/${input.commit === void 0 ? `merge_requests/${input.number}/diffs` : `repository/commits/${input.commit}/diff`}?${query([
|
|
2941
|
-
["per_page", String(MAX_PAGE_SIZE)],
|
|
2942
|
-
["page", String(input.page)]
|
|
2943
|
-
])}`,
|
|
2944
|
-
maxBytes: DIFF_MAX_BYTES2,
|
|
2945
|
-
timeoutMs: DIFF_TIMEOUT_MS2
|
|
2946
|
-
});
|
|
2947
|
-
if (result.truncated) {
|
|
2948
|
-
throw new GitLabError({
|
|
2949
|
-
code: "failed",
|
|
2950
|
-
operation: "getDiff",
|
|
2951
|
-
detail: `Page ${input.page} of the merge request diff was too large to read.`
|
|
2952
|
-
});
|
|
2953
|
-
}
|
|
2954
|
-
const decoded = decodeMergeRequestDiffs(result.body.trim());
|
|
2955
|
-
if (decoded === null) throw readError2("getDiff", "getMergeRequestDiff");
|
|
2956
|
-
const morePages = decoded.rawCount >= MAX_PAGE_SIZE;
|
|
2957
|
-
return {
|
|
2958
|
-
// 切片以换行结尾,GitLab 只给了头没给 hunk 的文件才不会撞进下一段的第一行。
|
|
2959
|
-
patch: decoded.patch.length === 0 ? decoded.patch : decoded.patch.replace(/\n?$/, "\n"),
|
|
2960
|
-
truncated: decoded.truncated,
|
|
2961
|
-
nextCursor: morePages ? String(input.page + 1) : null
|
|
2962
|
-
};
|
|
2963
|
-
};
|
|
2964
3002
|
var getDiff2 = (ctx, input) => {
|
|
2965
3003
|
if (input.commit !== void 0 && !isCommitSha2(input.commit)) {
|
|
2966
3004
|
return Promise.reject(
|
|
@@ -4446,13 +4484,6 @@ var WORKSPACE_PROJECT_SCHEMA = {
|
|
|
4446
4484
|
}
|
|
4447
4485
|
}
|
|
4448
4486
|
};
|
|
4449
|
-
var workspaceProjectOf = (project) => ({
|
|
4450
|
-
projectId: project.projectId,
|
|
4451
|
-
title: project.title,
|
|
4452
|
-
workspaceRoot: project.workspaceRoot,
|
|
4453
|
-
repositoryIdentity: project.repositoryIdentity,
|
|
4454
|
-
...project.archivedAt == null ? {} : { archivedAt: project.archivedAt }
|
|
4455
|
-
});
|
|
4456
4487
|
var DIFF_STREAM_ITEM_SCHEMA = {
|
|
4457
4488
|
oneOf: [
|
|
4458
4489
|
{
|
|
@@ -4488,10 +4519,11 @@ var DIFF_STREAM_ITEM_SCHEMA = {
|
|
|
4488
4519
|
};
|
|
4489
4520
|
var index_default = definePlugin({
|
|
4490
4521
|
id: "pull-requests",
|
|
4491
|
-
apiVersion:
|
|
4522
|
+
apiVersion: 6,
|
|
4492
4523
|
activate: (ctx) => {
|
|
4493
4524
|
const service = createPullRequestService({
|
|
4494
|
-
|
|
4525
|
+
// 要全部(含已归档,按号点名时用)并解析仓库身份:项目选择器与认领都靠它。
|
|
4526
|
+
listProjects: async () => (await ctx.projects.list({})).map(workspaceShellOf),
|
|
4495
4527
|
apis: [gitlabApi(ctx), githubApi(ctx)],
|
|
4496
4528
|
onRefreshFailure: (error) => ctx.log.warn("stale diff refresh failed", { detail: String(error) }),
|
|
4497
4529
|
// 编排事件流里没有「合并了」这个转移,所以这条触发事件只能从这里报。不 await:
|
|
@@ -4641,9 +4673,9 @@ var index_default = definePlugin({
|
|
|
4641
4673
|
properties: { projects: { type: "array", items: WORKSPACE_PROJECT_SCHEMA } }
|
|
4642
4674
|
},
|
|
4643
4675
|
handle: async () => {
|
|
4644
|
-
const projects = await ctx.
|
|
4676
|
+
const projects = (await ctx.projects.list({})).map(workspaceShellOf);
|
|
4645
4677
|
return {
|
|
4646
|
-
projects: projects.filter((project) => !isArchivedWorkspaceProject(project))
|
|
4678
|
+
projects: projects.filter((project) => !isArchivedWorkspaceProject(project))
|
|
4647
4679
|
};
|
|
4648
4680
|
}
|
|
4649
4681
|
});
|
|
@@ -4664,10 +4696,11 @@ var index_default = definePlugin({
|
|
|
4664
4696
|
properties: { project: { oneOf: [WORKSPACE_PROJECT_SCHEMA, { type: "null" }] } }
|
|
4665
4697
|
},
|
|
4666
4698
|
handle: async (payload) => {
|
|
4667
|
-
const project = await
|
|
4699
|
+
const project = await projectOrNull(
|
|
4700
|
+
ctx.projects,
|
|
4668
4701
|
as(payload).projectId
|
|
4669
4702
|
);
|
|
4670
|
-
return { project: project === null ? null :
|
|
4703
|
+
return { project: project === null ? null : workspaceShellOf(project) };
|
|
4671
4704
|
}
|
|
4672
4705
|
});
|
|
4673
4706
|
ctx.settingsCard({
|