@reviewflow/sdk 6.24.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,
@@ -98,6 +172,7 @@ const subjectsOf = (path) => {
98
172
  };
99
173
 
100
174
  const diffThemes = [
175
+ "reviewflow",
101
176
  "github",
102
177
  "github-high-contrast",
103
178
  "pierre",
@@ -116,7 +191,12 @@ const diffThemes = [
116
191
  "min"
117
192
  ];
118
193
  const isDiffTheme = (value) => diffThemes.includes(value);
194
+ const defaultDiffTheme = "reviewflow";
119
195
  const families = {
196
+ reviewflow: {
197
+ label: "Reviewflow",
198
+ themes: { light: "reviewflow-light", dark: "reviewflow-dark" }
199
+ },
120
200
  github: {
121
201
  label: "GitHub",
122
202
  themes: { light: "github-light-default", dark: "github-dark-default" }
@@ -181,8 +261,16 @@ const diffThemeOptions = diffThemes.map((theme) => ({
181
261
  value: theme,
182
262
  label: families[theme].label
183
263
  }));
264
+ const isDiffFont = (value) => value === "alouette" || value === "pierre" || value === "custom";
265
+ const defaultDiffFont = "alouette";
266
+ const diffFontOptions = [
267
+ { value: "alouette", label: "Alouette (Chivo Mono)" },
268
+ { value: "pierre", label: "Pierre (system mono)" },
269
+ { value: "custom", label: "Custom" }
270
+ ];
184
271
 
185
272
  const isDiffWhitespace = (value) => value === "hide" || value === "show";
273
+ const isBranchDiffScope = (value) => value === "committed" || value === "uncommitted";
186
274
 
187
275
  const fenceOpen = /^( {0,3})(`{3,}(?!`)|~{3,}(?!~))(.*)$/;
188
276
  const withoutIndent = (line, indent) => {
@@ -656,7 +744,7 @@ const pullRequestReactionContents = [
656
744
  ];
657
745
  const isPullRequestReactionContent = (value) => pullRequestReactionContents.includes(value);
658
746
 
659
- 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");
660
748
  const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
661
749
  const isPositiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0;
662
750
  const isSha = (value) => typeof value === "string" && /^[\da-f]{40}$/i.test(value);
@@ -781,13 +869,13 @@ const parsePullRequestEdit = (value) => {
781
869
  return { kind: "title", title: edit.title.trim() };
782
870
  }
783
871
  case "labels": {
784
- if (!isStringArray(edit.add) || !isStringArray(edit.remove)) {
872
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove)) {
785
873
  return "labels take add and remove, arrays of names";
786
874
  }
787
875
  return { kind: "labels", add: edit.add, remove: edit.remove };
788
876
  }
789
877
  case "reviewers": {
790
- 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)) {
791
879
  return "reviewers take add, remove, addTeams and removeTeams, arrays of logins and team slugs";
792
880
  }
793
881
  return {
@@ -803,7 +891,7 @@ const parsePullRequestEdit = (value) => {
803
891
  return { kind: "draft", draft: edit.draft };
804
892
  }
805
893
  case "assignees": {
806
- if (!isStringArray(edit.add) || !isStringArray(edit.remove)) {
894
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove)) {
807
895
  return "assignees take add and remove, arrays of logins";
808
896
  }
809
897
  return { kind: "assignees", add: edit.add, remove: edit.remove };
@@ -1363,6 +1451,91 @@ const diffLines = ({
1363
1451
  ];
1364
1452
  };
1365
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
+
1366
1539
  const imgTag = /<img\b[^>]*>/gi;
1367
1540
  const decodeAttribute = (value) => value.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
1368
1541
  const attribute = (tag, name) => {
@@ -3475,19 +3648,49 @@ const createReviewflowFollower = ({
3475
3648
  };
3476
3649
  };
3477
3650
 
3651
+ const defaultRepoPaths = ["~/dev/"];
3652
+
3478
3653
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3479
- const insertCheckoutEntry = (source, ownerRepo) => {
3654
+ const parseConfigText = (source) => {
3655
+ if (source.trim() === "") return {};
3480
3656
  let parsed;
3481
- if (source.trim() === "") {
3482
- parsed = {};
3483
- } else {
3484
- try {
3485
- parsed = JSON.parse(source);
3486
- } catch {
3487
- return null;
3488
- }
3657
+ try {
3658
+ parsed = JSON.parse(source);
3659
+ } catch {
3660
+ return null;
3489
3661
  }
3490
- 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;
3491
3694
  const repos = parsed.repos ?? {};
3492
3695
  if (!isRecord(repos)) return null;
3493
3696
  const wanted = ownerRepo.toLowerCase();
@@ -3496,8 +3699,7 @@ const insertCheckoutEntry = (source, ownerRepo) => {
3496
3699
  );
3497
3700
  const key = existing ?? ownerRepo;
3498
3701
  const next = existing === void 0 ? { ...repos, [key]: "" } : repos;
3499
- const text = `${JSON.stringify({ ...parsed, repos: next }, null, 2)}
3500
- `;
3702
+ const text = stringifyConfig({ ...parsed, repos: next });
3501
3703
  const marker = `${JSON.stringify(key)}: "`;
3502
3704
  const reposAt = text.indexOf('\n "repos": ');
3503
3705
  const at = text.indexOf(marker, reposAt);
@@ -3506,5 +3708,5 @@ const insertCheckoutEntry = (source, ownerRepo) => {
3506
3708
  return { source: text, cursor: value };
3507
3709
  };
3508
3710
 
3509
- export { accountOfOwnerRepo, applyPullRequestEdit, applyStreamEvent, applySuggestionsToText, assembleBoard, boardReadDelayMs, buildPrQuery, changedPullRequestParts, checkOutcomes, checksFromReviewflow, coAuthorTrailers, commitSuggestions, createGitHubTransport, createReviewflowClient, createReviewflowFollower, diffLines, diffPaths, diffThemeNames, diffThemeOptions, diffThemes, editPullRequest, fail, fetchCompareDiff, fetchPullRequestCandidates, fetchPullRequestHead, fetchPullRequestPart, fetchPullRequestRevisions, formatPrChanges, formatRelativeDate, insertCheckoutEntry, interpretGraphql, isAttachablePath, 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 };
3510
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,
@@ -98,6 +172,7 @@ const subjectsOf = (path) => {
98
172
  };
99
173
 
100
174
  const diffThemes = [
175
+ "reviewflow",
101
176
  "github",
102
177
  "github-high-contrast",
103
178
  "pierre",
@@ -116,7 +191,12 @@ const diffThemes = [
116
191
  "min"
117
192
  ];
118
193
  const isDiffTheme = (value) => diffThemes.includes(value);
194
+ const defaultDiffTheme = "reviewflow";
119
195
  const families = {
196
+ reviewflow: {
197
+ label: "Reviewflow",
198
+ themes: { light: "reviewflow-light", dark: "reviewflow-dark" }
199
+ },
120
200
  github: {
121
201
  label: "GitHub",
122
202
  themes: { light: "github-light-default", dark: "github-dark-default" }
@@ -181,8 +261,16 @@ const diffThemeOptions = diffThemes.map((theme) => ({
181
261
  value: theme,
182
262
  label: families[theme].label
183
263
  }));
264
+ const isDiffFont = (value) => value === "alouette" || value === "pierre" || value === "custom";
265
+ const defaultDiffFont = "alouette";
266
+ const diffFontOptions = [
267
+ { value: "alouette", label: "Alouette (Chivo Mono)" },
268
+ { value: "pierre", label: "Pierre (system mono)" },
269
+ { value: "custom", label: "Custom" }
270
+ ];
184
271
 
185
272
  const isDiffWhitespace = (value) => value === "hide" || value === "show";
273
+ const isBranchDiffScope = (value) => value === "committed" || value === "uncommitted";
186
274
 
187
275
  const fenceOpen = /^( {0,3})(`{3,}(?!`)|~{3,}(?!~))(.*)$/;
188
276
  const withoutIndent = (line, indent) => {
@@ -656,7 +744,7 @@ const pullRequestReactionContents = [
656
744
  ];
657
745
  const isPullRequestReactionContent = (value) => pullRequestReactionContents.includes(value);
658
746
 
659
- 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");
660
748
  const isNonEmptyString = (value) => typeof value === "string" && value.trim() !== "";
661
749
  const isPositiveInteger = (value) => typeof value === "number" && Number.isInteger(value) && value > 0;
662
750
  const isSha = (value) => typeof value === "string" && /^[\da-f]{40}$/i.test(value);
@@ -781,13 +869,13 @@ const parsePullRequestEdit = (value) => {
781
869
  return { kind: "title", title: edit.title.trim() };
782
870
  }
783
871
  case "labels": {
784
- if (!isStringArray(edit.add) || !isStringArray(edit.remove)) {
872
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove)) {
785
873
  return "labels take add and remove, arrays of names";
786
874
  }
787
875
  return { kind: "labels", add: edit.add, remove: edit.remove };
788
876
  }
789
877
  case "reviewers": {
790
- 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)) {
791
879
  return "reviewers take add, remove, addTeams and removeTeams, arrays of logins and team slugs";
792
880
  }
793
881
  return {
@@ -803,7 +891,7 @@ const parsePullRequestEdit = (value) => {
803
891
  return { kind: "draft", draft: edit.draft };
804
892
  }
805
893
  case "assignees": {
806
- if (!isStringArray(edit.add) || !isStringArray(edit.remove)) {
894
+ if (!isStringArray$1(edit.add) || !isStringArray$1(edit.remove)) {
807
895
  return "assignees take add and remove, arrays of logins";
808
896
  }
809
897
  return { kind: "assignees", add: edit.add, remove: edit.remove };
@@ -1363,6 +1451,91 @@ const diffLines = ({
1363
1451
  ];
1364
1452
  };
1365
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
+
1366
1539
  const imgTag = /<img\b[^>]*>/gi;
1367
1540
  const decodeAttribute = (value) => value.replaceAll("&quot;", '"').replaceAll("&#39;", "'").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&amp;", "&");
1368
1541
  const attribute = (tag, name) => {
@@ -3475,19 +3648,49 @@ const createReviewflowFollower = ({
3475
3648
  };
3476
3649
  };
3477
3650
 
3651
+ const defaultRepoPaths = ["~/dev/"];
3652
+
3478
3653
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3479
- const insertCheckoutEntry = (source, ownerRepo) => {
3654
+ const parseConfigText = (source) => {
3655
+ if (source.trim() === "") return {};
3480
3656
  let parsed;
3481
- if (source.trim() === "") {
3482
- parsed = {};
3483
- } else {
3484
- try {
3485
- parsed = JSON.parse(source);
3486
- } catch {
3487
- return null;
3488
- }
3657
+ try {
3658
+ parsed = JSON.parse(source);
3659
+ } catch {
3660
+ return null;
3489
3661
  }
3490
- 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;
3491
3694
  const repos = parsed.repos ?? {};
3492
3695
  if (!isRecord(repos)) return null;
3493
3696
  const wanted = ownerRepo.toLowerCase();
@@ -3496,8 +3699,7 @@ const insertCheckoutEntry = (source, ownerRepo) => {
3496
3699
  );
3497
3700
  const key = existing ?? ownerRepo;
3498
3701
  const next = existing === void 0 ? { ...repos, [key]: "" } : repos;
3499
- const text = `${JSON.stringify({ ...parsed, repos: next }, null, 2)}
3500
- `;
3702
+ const text = stringifyConfig({ ...parsed, repos: next });
3501
3703
  const marker = `${JSON.stringify(key)}: "`;
3502
3704
  const reposAt = text.indexOf('\n "repos": ');
3503
3705
  const at = text.indexOf(marker, reposAt);
@@ -3506,5 +3708,5 @@ const insertCheckoutEntry = (source, ownerRepo) => {
3506
3708
  return { source: text, cursor: value };
3507
3709
  };
3508
3710
 
3509
- export { accountOfOwnerRepo, applyPullRequestEdit, applyStreamEvent, applySuggestionsToText, assembleBoard, boardReadDelayMs, buildPrQuery, changedPullRequestParts, checkOutcomes, checksFromReviewflow, coAuthorTrailers, commitSuggestions, createGitHubTransport, createReviewflowClient, createReviewflowFollower, diffLines, diffPaths, diffThemeNames, diffThemeOptions, diffThemes, editPullRequest, fail, fetchCompareDiff, fetchPullRequestCandidates, fetchPullRequestHead, fetchPullRequestPart, fetchPullRequestRevisions, formatPrChanges, formatRelativeDate, insertCheckoutEntry, interpretGraphql, isAttachablePath, 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 };
3510
3712
  //# sourceMappingURL=index-node.mjs.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reviewflow/sdk",
3
- "version": "6.24.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",