@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.
Files changed (3) hide show
  1. package/assets/ui.js +14 -14
  2. package/dist/index.js +399 -366
  3. 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/githubMapping.ts
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/githubCommands.ts
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/gitlabCommands.ts
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
- var getViewer2 = async (ctx) => {
2706
- const result = await callGitLabApi(ctx, {
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: 5,
4522
+ apiVersion: 6,
4492
4523
  activate: (ctx) => {
4493
4524
  const service = createPullRequestService({
4494
- listProjects: () => ctx.projections.listProjects(),
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.projections.listProjects();
4676
+ const projects = (await ctx.projects.list({})).map(workspaceShellOf);
4645
4677
  return {
4646
- projects: projects.filter((project) => !isArchivedWorkspaceProject(project)).map(workspaceProjectOf)
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 ctx.projections.getProject(
4699
+ const project = await projectOrNull(
4700
+ ctx.projects,
4668
4701
  as(payload).projectId
4669
4702
  );
4670
- return { project: project === null ? null : workspaceProjectOf(project) };
4703
+ return { project: project === null ? null : workspaceShellOf(project) };
4671
4704
  }
4672
4705
  });
4673
4706
  ctx.settingsCard({