@reviewflow/sdk 6.25.0 → 6.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,77 @@
1
+ const sameRepo = (left, right) => left !== null && left.toLowerCase() === right.toLowerCase();
2
+ const pullRequestOfBranch = (pullRequests, ownerRepo, branch) => pullRequests.find(
3
+ (pullRequest) => pullRequest.headRefName === branch && sameRepo(pullRequest.headOwnerRepo, ownerRepo)
4
+ ) ?? null;
5
+ const pullRequestsWithoutBranch = (pullRequests, ownerRepo, branches) => {
6
+ const names = new Set(branches.map(({ name }) => name));
7
+ return pullRequests.filter(
8
+ (pullRequest) => !names.has(pullRequest.headRefName) || !sameRepo(pullRequest.headOwnerRepo, ownerRepo)
9
+ );
10
+ };
11
+
12
+ const upstreamGone = (upstream) => `${upstream} no longer exists on the remote`;
13
+ const pullRefusal = (status) => {
14
+ if (status.branch === null) return "a detached HEAD has nothing to pull into";
15
+ if (status.upstream === null) {
16
+ return `${status.branch} tracks no remote branch`;
17
+ }
18
+ if (status.behind === null) return upstreamGone(status.upstream);
19
+ if (status.behind === 0) {
20
+ return `${status.branch} is not behind ${status.upstream}`;
21
+ }
22
+ return null;
23
+ };
24
+ const pushRefusal = (status, hasOrigin) => {
25
+ if (status.branch === null) return "a detached HEAD has no branch to push";
26
+ if (status.upstream === null) {
27
+ return hasOrigin ? null : "this repository has no origin remote";
28
+ }
29
+ if (status.ahead === null) return upstreamGone(status.upstream);
30
+ if (status.ahead === 0) {
31
+ return `${status.branch} has nothing to push to ${status.upstream}`;
32
+ }
33
+ return null;
34
+ };
35
+
36
+ const fileCount = (count, kind) => count === 0 ? null : `${String(count)} ${kind} file${count === 1 ? "" : "s"}`;
37
+ const mainWorktreeRefusal = "This is the main checkout, which holds the repository: it is never removed.";
38
+ const dirtyRefusal = ({
39
+ modified,
40
+ untracked
41
+ }) => {
42
+ const parts = [
43
+ fileCount(modified, "modified"),
44
+ fileCount(untracked, "untracked")
45
+ ].filter((part) => part !== null);
46
+ if (parts.length === 0) return null;
47
+ return `${parts.join(" and ")}: git would refuse to remove this worktree. Commit or discard them first.`;
48
+ };
49
+ const unpushedRefusal = (count) => {
50
+ if (count === 0) return null;
51
+ const subject = count === 1 ? "1 commit exists" : `${String(count)} commits exist`;
52
+ return `${subject} only in this clone, on no remote branch and in no pull request. Push them first.`;
53
+ };
54
+ const lockRefusal = (lock) => {
55
+ if (lock === null) return null;
56
+ const reason = lock === "" ? "" : ` (${lock})`;
57
+ return `git holds this worktree locked${reason}, usually by a Claude Code process still running in it. Exit it first.`;
58
+ };
59
+ const processesRefusal = (count) => {
60
+ if (count === null || count === 0) return null;
61
+ const subject = count === 1 ? "1 process runs" : `${String(count)} processes run`;
62
+ return `${subject} inside this worktree (a shell, an editor or an agent): removing it would pull the directory out from under them. Exit them first.`;
63
+ };
64
+ const worktreeRemovalRefusals = (status) => {
65
+ if (status.main) return [mainWorktreeRefusal];
66
+ return [
67
+ dirtyRefusal(status),
68
+ unpushedRefusal(status.unpushedCommits),
69
+ lockRefusal(status.lock),
70
+ processesRefusal(status.processesInside)
71
+ ].filter((refusal) => refusal !== null);
72
+ };
73
+ const worktreeRemovalRefusal = (status) => worktreeRemovalRefusals(status)[0] ?? null;
74
+
1
75
  const cEscapes = {
2
76
  a: 7,
3
77
  b: 8,
@@ -196,6 +270,7 @@ const diffFontOptions = [
196
270
  ];
197
271
 
198
272
  const isDiffWhitespace = (value) => value === "hide" || value === "show";
273
+ const isBranchDiffScope = (value) => value === "committed" || value === "uncommitted";
199
274
 
200
275
  const fenceOpen = /^( {0,3})(`{3,}(?!`)|~{3,}(?!~))(.*)$/;
201
276
  const withoutIndent = (line, indent) => {
@@ -669,7 +744,7 @@ const pullRequestReactionContents = [
669
744
  ];
670
745
  const isPullRequestReactionContent = (value) => pullRequestReactionContents.includes(value);
671
746
 
672
- const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
747
+ const isStringArray$1 = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
673
748
  const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
674
749
  const isPositiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0;
675
750
  const isSha = (value) => typeof value === "string" && /^[\da-f]{40}$/i.test(value);
@@ -794,13 +869,13 @@ const parsePullRequestEdit = (value) => {
794
869
  return { kind: "title", title: edit.title.trim() };
795
870
  }
796
871
  case "labels": {
797
- if (!isStringArray(edit.add) || !isStringArray(edit.remove)) {
872
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove)) {
798
873
  return "labels take add and remove, arrays of names";
799
874
  }
800
875
  return { kind: "labels", add: edit.add, remove: edit.remove };
801
876
  }
802
877
  case "reviewers": {
803
- if (!isStringArray(edit.add) || !isStringArray(edit.remove) || !isStringArray(edit.addTeams) || !isStringArray(edit.removeTeams)) {
878
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove) || !isStringArray$1(edit.addTeams) || !isStringArray$1(edit.removeTeams)) {
804
879
  return "reviewers take add, remove, addTeams and removeTeams, arrays of logins and team slugs";
805
880
  }
806
881
  return {
@@ -816,7 +891,7 @@ const parsePullRequestEdit = (value) => {
816
891
  return { kind: "draft", draft: edit.draft };
817
892
  }
818
893
  case "assignees": {
819
- if (!isStringArray(edit.add) || !isStringArray(edit.remove)) {
894
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove)) {
820
895
  return "assignees take add and remove, arrays of logins";
821
896
  }
822
897
  return { kind: "assignees", add: edit.add, remove: edit.remove };
@@ -1376,6 +1451,91 @@ const diffLines = ({
1376
1451
  ];
1377
1452
  };
1378
1453
 
1454
+ const openPullRequestsLimit = 100;
1455
+ const openPullRequestsQuery = `query OpenPullRequests($owner: String!, $name: String!, $first: Int!) {
1456
+ repository(owner: $owner, name: $name) {
1457
+ pullRequests(states: OPEN, first: $first, orderBy: { field: UPDATED_AT, direction: DESC }) {
1458
+ totalCount
1459
+ nodes {
1460
+ number
1461
+ title
1462
+ isDraft
1463
+ updatedAt
1464
+ headRefName
1465
+ headRepository { nameWithOwner }
1466
+ author { login }
1467
+ }
1468
+ }
1469
+ }
1470
+ }`;
1471
+ const parseOpenPullRequests = ({
1472
+ data,
1473
+ errors
1474
+ }) => {
1475
+ const connection = data?.repository?.pullRequests ?? null;
1476
+ if (connection === null) {
1477
+ throw new Error(errors.length > 0 ? errors.join("; ") : "not found");
1478
+ }
1479
+ const pullRequests = (connection.nodes ?? []).filter((node) => node !== null).map((node) => ({
1480
+ number: node.number,
1481
+ title: node.title,
1482
+ isDraft: node.isDraft,
1483
+ headRefName: node.headRefName,
1484
+ headOwnerRepo: node.headRepository?.nameWithOwner ?? null,
1485
+ author: node.author?.login ?? null,
1486
+ updatedAt: node.updatedAt
1487
+ }));
1488
+ return { pullRequests, totalCount: connection.totalCount };
1489
+ };
1490
+ const fetchOpenPullRequests = async (transport, ownerRepo) => {
1491
+ const split = splitOwnerRepo(ownerRepo);
1492
+ if (split === null) throw new Error(`${ownerRepo} is not owner/repo`);
1493
+ return parseOpenPullRequests(
1494
+ await transport.graphql(openPullRequestsQuery, {
1495
+ owner: split.owner,
1496
+ name: split.name,
1497
+ first: openPullRequestsLimit
1498
+ })
1499
+ );
1500
+ };
1501
+ const branchPullRequestsQuery = `query BranchPullRequests($owner: String!, $name: String!, $branch: String!) {
1502
+ repository(owner: $owner, name: $name) {
1503
+ pullRequests(headRefName: $branch, first: 20, orderBy: { field: UPDATED_AT, direction: DESC }) {
1504
+ nodes {
1505
+ number
1506
+ state
1507
+ headRefOid
1508
+ headRepository { nameWithOwner }
1509
+ }
1510
+ }
1511
+ }
1512
+ }`;
1513
+ const parseBranchPullRequestHeads = ({ data, errors }, ownerRepo) => {
1514
+ const connection = data?.repository?.pullRequests ?? null;
1515
+ if (connection === null) {
1516
+ throw new Error(errors.length > 0 ? errors.join("; ") : "not found");
1517
+ }
1518
+ return (connection.nodes ?? []).filter((node) => node !== null).filter(
1519
+ (node) => node.headRepository?.nameWithOwner.toLowerCase() === ownerRepo.toLowerCase()
1520
+ ).map(({ number, state, headRefOid }) => ({
1521
+ number,
1522
+ state,
1523
+ sha: headRefOid
1524
+ }));
1525
+ };
1526
+ const fetchBranchPullRequestHeads = async (transport, ownerRepo, branch) => {
1527
+ const split = splitOwnerRepo(ownerRepo);
1528
+ if (split === null) throw new Error(`${ownerRepo} is not owner/repo`);
1529
+ return parseBranchPullRequestHeads(
1530
+ await transport.graphql(branchPullRequestsQuery, {
1531
+ owner: split.owner,
1532
+ name: split.name,
1533
+ branch
1534
+ }),
1535
+ ownerRepo
1536
+ );
1537
+ };
1538
+
1379
1539
  const imgTag = /<img\b[^>]*>/gi;
1380
1540
  const decodeAttribute = (value) => value.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
1381
1541
  const attribute = (tag, name) => {
@@ -3488,21 +3648,49 @@ const createReviewflowFollower = ({
3488
3648
  };
3489
3649
  };
3490
3650
 
3491
- const defaultRepoPath = "~/dev/";
3651
+ const defaultRepoPaths = ["~/dev/"];
3492
3652
 
3493
3653
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3494
- const insertCheckoutEntry = (source, ownerRepo) => {
3654
+ const parseConfigText = (source) => {
3655
+ if (source.trim() === "") return {};
3495
3656
  let parsed;
3496
- if (source.trim() === "") {
3497
- parsed = {};
3498
- } else {
3499
- try {
3500
- parsed = JSON.parse(source);
3501
- } catch {
3502
- return null;
3503
- }
3657
+ try {
3658
+ parsed = JSON.parse(source);
3659
+ } catch {
3660
+ return null;
3504
3661
  }
3505
- if (!isRecord(parsed)) return null;
3662
+ return isRecord(parsed) ? parsed : null;
3663
+ };
3664
+ const stringifyConfig = (config) => `${JSON.stringify(config, null, 2)}
3665
+ `;
3666
+
3667
+ const repoPathsKeys = /* @__PURE__ */ new Set(["repoPaths", "defaultRepoPath"]);
3668
+ const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
3669
+ const readRepoPathsSetting = (source) => {
3670
+ const config = parseConfigText(source);
3671
+ if (config === null) return void 0;
3672
+ if (isStringArray(config.repoPaths)) return config.repoPaths;
3673
+ if (typeof config.defaultRepoPath === "string") {
3674
+ return [config.defaultRepoPath];
3675
+ }
3676
+ return void 0;
3677
+ };
3678
+ const writeRepoPathsSetting = (source, paths) => {
3679
+ const config = parseConfigText(source);
3680
+ if (config === null) return null;
3681
+ const repoPaths = [
3682
+ ...new Set(paths.map((path) => path.trim()).filter((path) => path !== ""))
3683
+ ];
3684
+ const entries = Object.entries(config);
3685
+ const at = entries.findIndex(([key]) => repoPathsKeys.has(key));
3686
+ const others = entries.filter(([key]) => !repoPathsKeys.has(key));
3687
+ others.splice(Math.max(at, 0), 0, ["repoPaths", repoPaths]);
3688
+ return stringifyConfig(Object.fromEntries(others));
3689
+ };
3690
+
3691
+ const insertCheckoutEntry = (source, ownerRepo) => {
3692
+ const parsed = parseConfigText(source);
3693
+ if (parsed === null) return null;
3506
3694
  const repos = parsed.repos ?? {};
3507
3695
  if (!isRecord(repos)) return null;
3508
3696
  const wanted = ownerRepo.toLowerCase();
@@ -3511,8 +3699,7 @@ const insertCheckoutEntry = (source, ownerRepo) => {
3511
3699
  );
3512
3700
  const key = existing ?? ownerRepo;
3513
3701
  const next = existing === void 0 ? { ...repos, [key]: "" } : repos;
3514
- const text = `${JSON.stringify({ ...parsed, repos: next }, null, 2)}
3515
- `;
3702
+ const text = stringifyConfig({ ...parsed, repos: next });
3516
3703
  const marker = `${JSON.stringify(key)}: "`;
3517
3704
  const reposAt = text.indexOf('\n "repos": ');
3518
3705
  const at = text.indexOf(marker, reposAt);
@@ -3521,5 +3708,5 @@ const insertCheckoutEntry = (source, ownerRepo) => {
3521
3708
  return { source: text, cursor: value };
3522
3709
  };
3523
3710
 
3524
- export { accountOfOwnerRepo, applyPullRequestEdit, applyStreamEvent, applySuggestionsToText, assembleBoard, boardReadDelayMs, buildPrQuery, changedPullRequestParts, checkOutcomes, checksFromReviewflow, coAuthorTrailers, commitSuggestions, createGitHubTransport, createReviewflowClient, createReviewflowFollower, defaultDiffFont, defaultDiffTheme, defaultRepoPath, diffFontOptions, diffLines, diffPaths, diffThemeNames, diffThemeOptions, diffThemes, editPullRequest, fail, fetchCompareDiff, fetchPullRequestCandidates, fetchPullRequestHead, fetchPullRequestPart, fetchPullRequestRevisions, formatPrChanges, formatRelativeDate, insertCheckoutEntry, interpretGraphql, isAttachablePath, isDiffFont, isDiffTheme, isDiffWhitespace, isPullRequestOnBoard, isPullRequestPart, isPullRequestReactionContent, isReviewflowFailure, lingerMs, maxExtraPagesPerBucket, maxFailedCheckNames, maxLookupTargets, maxRetryMs, parsePrQueryResponse, parsePullRequestCandidates, parsePullRequestChecks, parsePullRequestCommits, parsePullRequestDetail, parsePullRequestEdit, parsePullRequestFiles, parsePullRequestRevisions, parsePullRequestTimeline, parseSuggestions, partFingerprint, planSuggestions, pluralOf, prFields, prHasConflicts, prNumberOf, prStatus, prStatusLabels, prStatuses, prUrl, pullRequestCandidatesQuery, pullRequestChecksQuery, pullRequestCommitsQuery, pullRequestDetailQuery, pullRequestEditMovesBoard, pullRequestFilesQuery, pullRequestHeadQuery, pullRequestParts, pullRequestPartsStaleAfter, pullRequestReactionContents, pullRequestRevisionsQuery, pullRequestTimelineQuery, readAccountBoards, readPrDetail, readReviewflowStream, reconnectDelayMs, responseFailure, restErrorMessage, reviewflowErrorCode, reviewflowPrChangesRequested, reviewflowPrFailure, reviewflowPrFlowDate, reviewflowPrOwners, reviewflowPrProgress, reviewflowPrStatus, reviewflowReviewDecisions, splitOwnerRepo, streamClosedWithinDays, streamIdleTimeoutMs, subjectsOf, suggestionCommitRefusal, suggestionOriginalLines, suggestionRangesOverlap, toBoardPr, toFetchedPr, withAccountBoard, withFileViewed };
3711
+ export { accountOfOwnerRepo, applyPullRequestEdit, applyStreamEvent, applySuggestionsToText, assembleBoard, boardReadDelayMs, buildPrQuery, changedPullRequestParts, checkOutcomes, checksFromReviewflow, coAuthorTrailers, commitSuggestions, createGitHubTransport, createReviewflowClient, createReviewflowFollower, defaultDiffFont, defaultDiffTheme, defaultRepoPaths, diffFontOptions, diffLines, diffPaths, diffThemeNames, diffThemeOptions, diffThemes, dirtyRefusal, editPullRequest, fail, fetchBranchPullRequestHeads, fetchCompareDiff, fetchOpenPullRequests, fetchPullRequestCandidates, fetchPullRequestHead, fetchPullRequestPart, fetchPullRequestRevisions, formatPrChanges, formatRelativeDate, insertCheckoutEntry, interpretGraphql, isAttachablePath, isBranchDiffScope, isDiffFont, isDiffTheme, isDiffWhitespace, isPullRequestOnBoard, isPullRequestPart, isPullRequestReactionContent, isReviewflowFailure, lingerMs, lockRefusal, mainWorktreeRefusal, maxExtraPagesPerBucket, maxFailedCheckNames, maxLookupTargets, maxRetryMs, parseBranchPullRequestHeads, parseOpenPullRequests, parsePrQueryResponse, parsePullRequestCandidates, parsePullRequestChecks, parsePullRequestCommits, parsePullRequestDetail, parsePullRequestEdit, parsePullRequestFiles, parsePullRequestRevisions, parsePullRequestTimeline, parseSuggestions, partFingerprint, planSuggestions, pluralOf, prFields, prHasConflicts, prNumberOf, prStatus, prStatusLabels, prStatuses, prUrl, processesRefusal, pullRefusal, pullRequestCandidatesQuery, pullRequestChecksQuery, pullRequestCommitsQuery, pullRequestDetailQuery, pullRequestEditMovesBoard, pullRequestFilesQuery, pullRequestHeadQuery, pullRequestOfBranch, pullRequestParts, pullRequestPartsStaleAfter, pullRequestReactionContents, pullRequestRevisionsQuery, pullRequestTimelineQuery, pullRequestsWithoutBranch, pushRefusal, readAccountBoards, readPrDetail, readRepoPathsSetting, readReviewflowStream, reconnectDelayMs, responseFailure, restErrorMessage, reviewflowErrorCode, reviewflowPrChangesRequested, reviewflowPrFailure, reviewflowPrFlowDate, reviewflowPrOwners, reviewflowPrProgress, reviewflowPrStatus, reviewflowReviewDecisions, splitOwnerRepo, streamClosedWithinDays, streamIdleTimeoutMs, subjectsOf, suggestionCommitRefusal, suggestionOriginalLines, suggestionRangesOverlap, toBoardPr, toFetchedPr, unpushedRefusal, withAccountBoard, withFileViewed, worktreeRemovalRefusal, worktreeRemovalRefusals, writeRepoPathsSetting };
3525
3712
  //# sourceMappingURL=index-browser.es.js.map
@@ -1,3 +1,77 @@
1
+ const sameRepo = (left, right) => left !== null && left.toLowerCase() === right.toLowerCase();
2
+ const pullRequestOfBranch = (pullRequests, ownerRepo, branch) => pullRequests.find(
3
+ (pullRequest) => pullRequest.headRefName === branch && sameRepo(pullRequest.headOwnerRepo, ownerRepo)
4
+ ) ?? null;
5
+ const pullRequestsWithoutBranch = (pullRequests, ownerRepo, branches) => {
6
+ const names = new Set(branches.map(({ name }) => name));
7
+ return pullRequests.filter(
8
+ (pullRequest) => !names.has(pullRequest.headRefName) || !sameRepo(pullRequest.headOwnerRepo, ownerRepo)
9
+ );
10
+ };
11
+
12
+ const upstreamGone = (upstream) => `${upstream} no longer exists on the remote`;
13
+ const pullRefusal = (status) => {
14
+ if (status.branch === null) return "a detached HEAD has nothing to pull into";
15
+ if (status.upstream === null) {
16
+ return `${status.branch} tracks no remote branch`;
17
+ }
18
+ if (status.behind === null) return upstreamGone(status.upstream);
19
+ if (status.behind === 0) {
20
+ return `${status.branch} is not behind ${status.upstream}`;
21
+ }
22
+ return null;
23
+ };
24
+ const pushRefusal = (status, hasOrigin) => {
25
+ if (status.branch === null) return "a detached HEAD has no branch to push";
26
+ if (status.upstream === null) {
27
+ return hasOrigin ? null : "this repository has no origin remote";
28
+ }
29
+ if (status.ahead === null) return upstreamGone(status.upstream);
30
+ if (status.ahead === 0) {
31
+ return `${status.branch} has nothing to push to ${status.upstream}`;
32
+ }
33
+ return null;
34
+ };
35
+
36
+ const fileCount = (count, kind) => count === 0 ? null : `${String(count)} ${kind} file${count === 1 ? "" : "s"}`;
37
+ const mainWorktreeRefusal = "This is the main checkout, which holds the repository: it is never removed.";
38
+ const dirtyRefusal = ({
39
+ modified,
40
+ untracked
41
+ }) => {
42
+ const parts = [
43
+ fileCount(modified, "modified"),
44
+ fileCount(untracked, "untracked")
45
+ ].filter((part) => part !== null);
46
+ if (parts.length === 0) return null;
47
+ return `${parts.join(" and ")}: git would refuse to remove this worktree. Commit or discard them first.`;
48
+ };
49
+ const unpushedRefusal = (count) => {
50
+ if (count === 0) return null;
51
+ const subject = count === 1 ? "1 commit exists" : `${String(count)} commits exist`;
52
+ return `${subject} only in this clone, on no remote branch and in no pull request. Push them first.`;
53
+ };
54
+ const lockRefusal = (lock) => {
55
+ if (lock === null) return null;
56
+ const reason = lock === "" ? "" : ` (${lock})`;
57
+ return `git holds this worktree locked${reason}, usually by a Claude Code process still running in it. Exit it first.`;
58
+ };
59
+ const processesRefusal = (count) => {
60
+ if (count === null || count === 0) return null;
61
+ const subject = count === 1 ? "1 process runs" : `${String(count)} processes run`;
62
+ return `${subject} inside this worktree (a shell, an editor or an agent): removing it would pull the directory out from under them. Exit them first.`;
63
+ };
64
+ const worktreeRemovalRefusals = (status) => {
65
+ if (status.main) return [mainWorktreeRefusal];
66
+ return [
67
+ dirtyRefusal(status),
68
+ unpushedRefusal(status.unpushedCommits),
69
+ lockRefusal(status.lock),
70
+ processesRefusal(status.processesInside)
71
+ ].filter((refusal) => refusal !== null);
72
+ };
73
+ const worktreeRemovalRefusal = (status) => worktreeRemovalRefusals(status)[0] ?? null;
74
+
1
75
  const cEscapes = {
2
76
  a: 7,
3
77
  b: 8,
@@ -196,6 +270,7 @@ const diffFontOptions = [
196
270
  ];
197
271
 
198
272
  const isDiffWhitespace = (value) => value === "hide" || value === "show";
273
+ const isBranchDiffScope = (value) => value === "committed" || value === "uncommitted";
199
274
 
200
275
  const fenceOpen = /^( {0,3})(`{3,}(?!`)|~{3,}(?!~))(.*)$/;
201
276
  const withoutIndent = (line, indent) => {
@@ -669,7 +744,7 @@ const pullRequestReactionContents = [
669
744
  ];
670
745
  const isPullRequestReactionContent = (value) => pullRequestReactionContents.includes(value);
671
746
 
672
- const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
747
+ const isStringArray$1 = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
673
748
  const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
674
749
  const isPositiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0;
675
750
  const isSha = (value) => typeof value === "string" && /^[\da-f]{40}$/i.test(value);
@@ -794,13 +869,13 @@ const parsePullRequestEdit = (value) => {
794
869
  return { kind: "title", title: edit.title.trim() };
795
870
  }
796
871
  case "labels": {
797
- if (!isStringArray(edit.add) || !isStringArray(edit.remove)) {
872
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove)) {
798
873
  return "labels take add and remove, arrays of names";
799
874
  }
800
875
  return { kind: "labels", add: edit.add, remove: edit.remove };
801
876
  }
802
877
  case "reviewers": {
803
- if (!isStringArray(edit.add) || !isStringArray(edit.remove) || !isStringArray(edit.addTeams) || !isStringArray(edit.removeTeams)) {
878
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove) || !isStringArray$1(edit.addTeams) || !isStringArray$1(edit.removeTeams)) {
804
879
  return "reviewers take add, remove, addTeams and removeTeams, arrays of logins and team slugs";
805
880
  }
806
881
  return {
@@ -816,7 +891,7 @@ const parsePullRequestEdit = (value) => {
816
891
  return { kind: "draft", draft: edit.draft };
817
892
  }
818
893
  case "assignees": {
819
- if (!isStringArray(edit.add) || !isStringArray(edit.remove)) {
894
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove)) {
820
895
  return "assignees take add and remove, arrays of logins";
821
896
  }
822
897
  return { kind: "assignees", add: edit.add, remove: edit.remove };
@@ -1376,6 +1451,91 @@ const diffLines = ({
1376
1451
  ];
1377
1452
  };
1378
1453
 
1454
+ const openPullRequestsLimit = 100;
1455
+ const openPullRequestsQuery = `query OpenPullRequests($owner: String!, $name: String!, $first: Int!) {
1456
+ repository(owner: $owner, name: $name) {
1457
+ pullRequests(states: OPEN, first: $first, orderBy: { field: UPDATED_AT, direction: DESC }) {
1458
+ totalCount
1459
+ nodes {
1460
+ number
1461
+ title
1462
+ isDraft
1463
+ updatedAt
1464
+ headRefName
1465
+ headRepository { nameWithOwner }
1466
+ author { login }
1467
+ }
1468
+ }
1469
+ }
1470
+ }`;
1471
+ const parseOpenPullRequests = ({
1472
+ data,
1473
+ errors
1474
+ }) => {
1475
+ const connection = data?.repository?.pullRequests ?? null;
1476
+ if (connection === null) {
1477
+ throw new Error(errors.length > 0 ? errors.join("; ") : "not found");
1478
+ }
1479
+ const pullRequests = (connection.nodes ?? []).filter((node) => node !== null).map((node) => ({
1480
+ number: node.number,
1481
+ title: node.title,
1482
+ isDraft: node.isDraft,
1483
+ headRefName: node.headRefName,
1484
+ headOwnerRepo: node.headRepository?.nameWithOwner ?? null,
1485
+ author: node.author?.login ?? null,
1486
+ updatedAt: node.updatedAt
1487
+ }));
1488
+ return { pullRequests, totalCount: connection.totalCount };
1489
+ };
1490
+ const fetchOpenPullRequests = async (transport, ownerRepo) => {
1491
+ const split = splitOwnerRepo(ownerRepo);
1492
+ if (split === null) throw new Error(`${ownerRepo} is not owner/repo`);
1493
+ return parseOpenPullRequests(
1494
+ await transport.graphql(openPullRequestsQuery, {
1495
+ owner: split.owner,
1496
+ name: split.name,
1497
+ first: openPullRequestsLimit
1498
+ })
1499
+ );
1500
+ };
1501
+ const branchPullRequestsQuery = `query BranchPullRequests($owner: String!, $name: String!, $branch: String!) {
1502
+ repository(owner: $owner, name: $name) {
1503
+ pullRequests(headRefName: $branch, first: 20, orderBy: { field: UPDATED_AT, direction: DESC }) {
1504
+ nodes {
1505
+ number
1506
+ state
1507
+ headRefOid
1508
+ headRepository { nameWithOwner }
1509
+ }
1510
+ }
1511
+ }
1512
+ }`;
1513
+ const parseBranchPullRequestHeads = ({ data, errors }, ownerRepo) => {
1514
+ const connection = data?.repository?.pullRequests ?? null;
1515
+ if (connection === null) {
1516
+ throw new Error(errors.length > 0 ? errors.join("; ") : "not found");
1517
+ }
1518
+ return (connection.nodes ?? []).filter((node) => node !== null).filter(
1519
+ (node) => node.headRepository?.nameWithOwner.toLowerCase() === ownerRepo.toLowerCase()
1520
+ ).map(({ number, state, headRefOid }) => ({
1521
+ number,
1522
+ state,
1523
+ sha: headRefOid
1524
+ }));
1525
+ };
1526
+ const fetchBranchPullRequestHeads = async (transport, ownerRepo, branch) => {
1527
+ const split = splitOwnerRepo(ownerRepo);
1528
+ if (split === null) throw new Error(`${ownerRepo} is not owner/repo`);
1529
+ return parseBranchPullRequestHeads(
1530
+ await transport.graphql(branchPullRequestsQuery, {
1531
+ owner: split.owner,
1532
+ name: split.name,
1533
+ branch
1534
+ }),
1535
+ ownerRepo
1536
+ );
1537
+ };
1538
+
1379
1539
  const imgTag = /<img\b[^>]*>/gi;
1380
1540
  const decodeAttribute = (value) => value.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
1381
1541
  const attribute = (tag, name) => {
@@ -3488,21 +3648,49 @@ const createReviewflowFollower = ({
3488
3648
  };
3489
3649
  };
3490
3650
 
3491
- const defaultRepoPath = "~/dev/";
3651
+ const defaultRepoPaths = ["~/dev/"];
3492
3652
 
3493
3653
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3494
- const insertCheckoutEntry = (source, ownerRepo) => {
3654
+ const parseConfigText = (source) => {
3655
+ if (source.trim() === "") return {};
3495
3656
  let parsed;
3496
- if (source.trim() === "") {
3497
- parsed = {};
3498
- } else {
3499
- try {
3500
- parsed = JSON.parse(source);
3501
- } catch {
3502
- return null;
3503
- }
3657
+ try {
3658
+ parsed = JSON.parse(source);
3659
+ } catch {
3660
+ return null;
3504
3661
  }
3505
- if (!isRecord(parsed)) return null;
3662
+ return isRecord(parsed) ? parsed : null;
3663
+ };
3664
+ const stringifyConfig = (config) => `${JSON.stringify(config, null, 2)}
3665
+ `;
3666
+
3667
+ const repoPathsKeys = /* @__PURE__ */ new Set(["repoPaths", "defaultRepoPath"]);
3668
+ const isStringArray = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
3669
+ const readRepoPathsSetting = (source) => {
3670
+ const config = parseConfigText(source);
3671
+ if (config === null) return void 0;
3672
+ if (isStringArray(config.repoPaths)) return config.repoPaths;
3673
+ if (typeof config.defaultRepoPath === "string") {
3674
+ return [config.defaultRepoPath];
3675
+ }
3676
+ return void 0;
3677
+ };
3678
+ const writeRepoPathsSetting = (source, paths) => {
3679
+ const config = parseConfigText(source);
3680
+ if (config === null) return null;
3681
+ const repoPaths = [
3682
+ ...new Set(paths.map((path) => path.trim()).filter((path) => path !== ""))
3683
+ ];
3684
+ const entries = Object.entries(config);
3685
+ const at = entries.findIndex(([key]) => repoPathsKeys.has(key));
3686
+ const others = entries.filter(([key]) => !repoPathsKeys.has(key));
3687
+ others.splice(Math.max(at, 0), 0, ["repoPaths", repoPaths]);
3688
+ return stringifyConfig(Object.fromEntries(others));
3689
+ };
3690
+
3691
+ const insertCheckoutEntry = (source, ownerRepo) => {
3692
+ const parsed = parseConfigText(source);
3693
+ if (parsed === null) return null;
3506
3694
  const repos = parsed.repos ?? {};
3507
3695
  if (!isRecord(repos)) return null;
3508
3696
  const wanted = ownerRepo.toLowerCase();
@@ -3511,8 +3699,7 @@ const insertCheckoutEntry = (source, ownerRepo) => {
3511
3699
  );
3512
3700
  const key = existing ?? ownerRepo;
3513
3701
  const next = existing === void 0 ? { ...repos, [key]: "" } : repos;
3514
- const text = `${JSON.stringify({ ...parsed, repos: next }, null, 2)}
3515
- `;
3702
+ const text = stringifyConfig({ ...parsed, repos: next });
3516
3703
  const marker = `${JSON.stringify(key)}: "`;
3517
3704
  const reposAt = text.indexOf('\n "repos": ');
3518
3705
  const at = text.indexOf(marker, reposAt);
@@ -3521,5 +3708,5 @@ const insertCheckoutEntry = (source, ownerRepo) => {
3521
3708
  return { source: text, cursor: value };
3522
3709
  };
3523
3710
 
3524
- export { accountOfOwnerRepo, applyPullRequestEdit, applyStreamEvent, applySuggestionsToText, assembleBoard, boardReadDelayMs, buildPrQuery, changedPullRequestParts, checkOutcomes, checksFromReviewflow, coAuthorTrailers, commitSuggestions, createGitHubTransport, createReviewflowClient, createReviewflowFollower, defaultDiffFont, defaultDiffTheme, defaultRepoPath, diffFontOptions, diffLines, diffPaths, diffThemeNames, diffThemeOptions, diffThemes, editPullRequest, fail, fetchCompareDiff, fetchPullRequestCandidates, fetchPullRequestHead, fetchPullRequestPart, fetchPullRequestRevisions, formatPrChanges, formatRelativeDate, insertCheckoutEntry, interpretGraphql, isAttachablePath, isDiffFont, isDiffTheme, isDiffWhitespace, isPullRequestOnBoard, isPullRequestPart, isPullRequestReactionContent, isReviewflowFailure, lingerMs, maxExtraPagesPerBucket, maxFailedCheckNames, maxLookupTargets, maxRetryMs, parsePrQueryResponse, parsePullRequestCandidates, parsePullRequestChecks, parsePullRequestCommits, parsePullRequestDetail, parsePullRequestEdit, parsePullRequestFiles, parsePullRequestRevisions, parsePullRequestTimeline, parseSuggestions, partFingerprint, planSuggestions, pluralOf, prFields, prHasConflicts, prNumberOf, prStatus, prStatusLabels, prStatuses, prUrl, pullRequestCandidatesQuery, pullRequestChecksQuery, pullRequestCommitsQuery, pullRequestDetailQuery, pullRequestEditMovesBoard, pullRequestFilesQuery, pullRequestHeadQuery, pullRequestParts, pullRequestPartsStaleAfter, pullRequestReactionContents, pullRequestRevisionsQuery, pullRequestTimelineQuery, readAccountBoards, readPrDetail, readReviewflowStream, reconnectDelayMs, responseFailure, restErrorMessage, reviewflowErrorCode, reviewflowPrChangesRequested, reviewflowPrFailure, reviewflowPrFlowDate, reviewflowPrOwners, reviewflowPrProgress, reviewflowPrStatus, reviewflowReviewDecisions, splitOwnerRepo, streamClosedWithinDays, streamIdleTimeoutMs, subjectsOf, suggestionCommitRefusal, suggestionOriginalLines, suggestionRangesOverlap, toBoardPr, toFetchedPr, withAccountBoard, withFileViewed };
3711
+ export { accountOfOwnerRepo, applyPullRequestEdit, applyStreamEvent, applySuggestionsToText, assembleBoard, boardReadDelayMs, buildPrQuery, changedPullRequestParts, checkOutcomes, checksFromReviewflow, coAuthorTrailers, commitSuggestions, createGitHubTransport, createReviewflowClient, createReviewflowFollower, defaultDiffFont, defaultDiffTheme, defaultRepoPaths, diffFontOptions, diffLines, diffPaths, diffThemeNames, diffThemeOptions, diffThemes, dirtyRefusal, editPullRequest, fail, fetchBranchPullRequestHeads, fetchCompareDiff, fetchOpenPullRequests, fetchPullRequestCandidates, fetchPullRequestHead, fetchPullRequestPart, fetchPullRequestRevisions, formatPrChanges, formatRelativeDate, insertCheckoutEntry, interpretGraphql, isAttachablePath, isBranchDiffScope, isDiffFont, isDiffTheme, isDiffWhitespace, isPullRequestOnBoard, isPullRequestPart, isPullRequestReactionContent, isReviewflowFailure, lingerMs, lockRefusal, mainWorktreeRefusal, maxExtraPagesPerBucket, maxFailedCheckNames, maxLookupTargets, maxRetryMs, parseBranchPullRequestHeads, parseOpenPullRequests, parsePrQueryResponse, parsePullRequestCandidates, parsePullRequestChecks, parsePullRequestCommits, parsePullRequestDetail, parsePullRequestEdit, parsePullRequestFiles, parsePullRequestRevisions, parsePullRequestTimeline, parseSuggestions, partFingerprint, planSuggestions, pluralOf, prFields, prHasConflicts, prNumberOf, prStatus, prStatusLabels, prStatuses, prUrl, processesRefusal, pullRefusal, pullRequestCandidatesQuery, pullRequestChecksQuery, pullRequestCommitsQuery, pullRequestDetailQuery, pullRequestEditMovesBoard, pullRequestFilesQuery, pullRequestHeadQuery, pullRequestOfBranch, pullRequestParts, pullRequestPartsStaleAfter, pullRequestReactionContents, pullRequestRevisionsQuery, pullRequestTimelineQuery, pullRequestsWithoutBranch, pushRefusal, readAccountBoards, readPrDetail, readRepoPathsSetting, readReviewflowStream, reconnectDelayMs, responseFailure, restErrorMessage, reviewflowErrorCode, reviewflowPrChangesRequested, reviewflowPrFailure, reviewflowPrFlowDate, reviewflowPrOwners, reviewflowPrProgress, reviewflowPrStatus, reviewflowReviewDecisions, splitOwnerRepo, streamClosedWithinDays, streamIdleTimeoutMs, subjectsOf, suggestionCommitRefusal, suggestionOriginalLines, suggestionRangesOverlap, toBoardPr, toFetchedPr, unpushedRefusal, withAccountBoard, withFileViewed, worktreeRemovalRefusal, worktreeRemovalRefusals, writeRepoPathsSetting };
3525
3712
  //# sourceMappingURL=index-node.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reviewflow/sdk",
3
- "version": "6.25.0",
3
+ "version": "6.27.0",
4
4
  "description": "pull request model shared by the reviewflow app server and its ui: diff and api types, pure utils, the GitHub layer behind an injected transport, and (node only) reading a diff from a local checkout (@reviewflow/sdk/git) and editing a text file in place (@reviewflow/sdk/files)",
5
5
  "keywords": [
6
6
  "code-review",