@ondrej-svec/hog 1.23.0 → 1.24.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.
- package/dist/cli.js +1211 -796
- package/dist/cli.js.map +1 -1
- package/dist/fetch-worker.js +207 -182
- package/dist/fetch-worker.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -70,6 +70,14 @@ function saveFullConfig(config2) {
|
|
|
70
70
|
writeFileSync(CONFIG_FILE, `${JSON.stringify(config2, null, 2)}
|
|
71
71
|
`, { mode: 384 });
|
|
72
72
|
}
|
|
73
|
+
function validateConfigSchema(raw) {
|
|
74
|
+
const result = HOG_CONFIG_SCHEMA.safeParse(raw);
|
|
75
|
+
if (result.success) {
|
|
76
|
+
return { success: true, data: result.data };
|
|
77
|
+
}
|
|
78
|
+
const messages = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`);
|
|
79
|
+
return { success: false, error: messages.join("\n") };
|
|
80
|
+
}
|
|
73
81
|
function loadRawConfig() {
|
|
74
82
|
if (!existsSync(CONFIG_FILE)) return {};
|
|
75
83
|
try {
|
|
@@ -590,10 +598,13 @@ __export(github_exports, {
|
|
|
590
598
|
fetchIssueAsync: () => fetchIssueAsync,
|
|
591
599
|
fetchIssueCommentsAsync: () => fetchIssueCommentsAsync,
|
|
592
600
|
fetchProjectEnrichment: () => fetchProjectEnrichment,
|
|
601
|
+
fetchProjectEnrichmentAsync: () => fetchProjectEnrichmentAsync,
|
|
593
602
|
fetchProjectFields: () => fetchProjectFields,
|
|
594
603
|
fetchProjectStatusOptions: () => fetchProjectStatusOptions,
|
|
604
|
+
fetchProjectStatusOptionsAsync: () => fetchProjectStatusOptionsAsync,
|
|
595
605
|
fetchProjectTargetDates: () => fetchProjectTargetDates,
|
|
596
606
|
fetchRepoIssues: () => fetchRepoIssues,
|
|
607
|
+
fetchRepoIssuesAsync: () => fetchRepoIssuesAsync,
|
|
597
608
|
fetchRepoLabelsAsync: () => fetchRepoLabelsAsync,
|
|
598
609
|
removeLabelAsync: () => removeLabelAsync,
|
|
599
610
|
reopenIssueAsync: () => reopenIssueAsync,
|
|
@@ -654,6 +665,105 @@ async function runGhGraphQLAsync(args) {
|
|
|
654
665
|
throw err;
|
|
655
666
|
}
|
|
656
667
|
}
|
|
668
|
+
function findProjectItemArgs(owner, repoName, issueNumber) {
|
|
669
|
+
return [
|
|
670
|
+
"api",
|
|
671
|
+
"graphql",
|
|
672
|
+
"-f",
|
|
673
|
+
`query=${FIND_PROJECT_ITEM_QUERY}`,
|
|
674
|
+
"-F",
|
|
675
|
+
`owner=${owner}`,
|
|
676
|
+
"-F",
|
|
677
|
+
`repo=${repoName}`,
|
|
678
|
+
"-F",
|
|
679
|
+
`issueNumber=${String(issueNumber)}`
|
|
680
|
+
];
|
|
681
|
+
}
|
|
682
|
+
function findProjectItemSync(owner, repoName, issueNumber, projectNumber) {
|
|
683
|
+
const result = runGhJson(findProjectItemArgs(owner, repoName, issueNumber));
|
|
684
|
+
const items = result?.data?.repository?.issue?.projectItems?.nodes ?? [];
|
|
685
|
+
return items.find((item) => item?.project?.number === projectNumber) ?? null;
|
|
686
|
+
}
|
|
687
|
+
async function findProjectItemAsync(owner, repoName, issueNumber, projectNumber) {
|
|
688
|
+
const result = await runGhJsonAsync(
|
|
689
|
+
findProjectItemArgs(owner, repoName, issueNumber)
|
|
690
|
+
);
|
|
691
|
+
const items = result?.data?.repository?.issue?.projectItems?.nodes ?? [];
|
|
692
|
+
return items.find((item) => item?.project?.number === projectNumber) ?? null;
|
|
693
|
+
}
|
|
694
|
+
function parseFieldValues(fieldValues, statusKey) {
|
|
695
|
+
const result = {};
|
|
696
|
+
for (const fv of fieldValues) {
|
|
697
|
+
if (!fv) continue;
|
|
698
|
+
const fieldName = fv.field?.name ?? "";
|
|
699
|
+
if ("date" in fv && fv.date && DATE_FIELD_NAME_RE2.test(fieldName)) {
|
|
700
|
+
result.targetDate = fv.date;
|
|
701
|
+
} else if ("name" in fv && fieldName === "Status" && fv.name) {
|
|
702
|
+
result[statusKey] = fv.name;
|
|
703
|
+
} else if (fieldName) {
|
|
704
|
+
const value = "text" in fv && fv.text != null ? fv.text : "number" in fv && fv.number != null ? String(fv.number) : "name" in fv && fv.name != null ? fv.name : "title" in fv && fv.title != null ? fv.title : null;
|
|
705
|
+
if (value != null) {
|
|
706
|
+
if (!result.customFields) result.customFields = {};
|
|
707
|
+
result.customFields[fieldName] = value;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return result;
|
|
712
|
+
}
|
|
713
|
+
function getProjectNodeIdSync(owner, projectNumber) {
|
|
714
|
+
const key = `${owner}/${String(projectNumber)}`;
|
|
715
|
+
const cached = projectNodeIdCache.get(key);
|
|
716
|
+
if (cached !== void 0) return cached;
|
|
717
|
+
const idFragment = `projectV2(number: $projectNumber) { id }`;
|
|
718
|
+
const projectQuery = `
|
|
719
|
+
query($owner: String!, $projectNumber: Int!) {
|
|
720
|
+
organization(login: $owner) { ${idFragment} }
|
|
721
|
+
user(login: $owner) { ${idFragment} }
|
|
722
|
+
}
|
|
723
|
+
`;
|
|
724
|
+
const projectResult = runGhGraphQL([
|
|
725
|
+
"api",
|
|
726
|
+
"graphql",
|
|
727
|
+
"-f",
|
|
728
|
+
`query=${projectQuery}`,
|
|
729
|
+
"-F",
|
|
730
|
+
`owner=${owner}`,
|
|
731
|
+
"-F",
|
|
732
|
+
`projectNumber=${String(projectNumber)}`
|
|
733
|
+
]);
|
|
734
|
+
const ownerNode = projectResult?.data?.organization ?? projectResult?.data?.user;
|
|
735
|
+
const projectId = ownerNode?.projectV2?.id;
|
|
736
|
+
if (!projectId) return null;
|
|
737
|
+
projectNodeIdCache.set(key, projectId);
|
|
738
|
+
return projectId;
|
|
739
|
+
}
|
|
740
|
+
async function getProjectNodeId(owner, projectNumber) {
|
|
741
|
+
const key = `${owner}/${String(projectNumber)}`;
|
|
742
|
+
const cached = projectNodeIdCache.get(key);
|
|
743
|
+
if (cached !== void 0) return cached;
|
|
744
|
+
const idFragment = `projectV2(number: $projectNumber) { id }`;
|
|
745
|
+
const projectQuery = `
|
|
746
|
+
query($owner: String!, $projectNumber: Int!) {
|
|
747
|
+
organization(login: $owner) { ${idFragment} }
|
|
748
|
+
user(login: $owner) { ${idFragment} }
|
|
749
|
+
}
|
|
750
|
+
`;
|
|
751
|
+
const projectResult = await runGhGraphQLAsync([
|
|
752
|
+
"api",
|
|
753
|
+
"graphql",
|
|
754
|
+
"-f",
|
|
755
|
+
`query=${projectQuery}`,
|
|
756
|
+
"-F",
|
|
757
|
+
`owner=${owner}`,
|
|
758
|
+
"-F",
|
|
759
|
+
`projectNumber=${String(projectNumber)}`
|
|
760
|
+
]);
|
|
761
|
+
const ownerNode = projectResult?.data?.organization ?? projectResult?.data?.user;
|
|
762
|
+
const projectId = ownerNode?.projectV2?.id;
|
|
763
|
+
if (!projectId) return null;
|
|
764
|
+
projectNodeIdCache.set(key, projectId);
|
|
765
|
+
return projectId;
|
|
766
|
+
}
|
|
657
767
|
function fetchAssignedIssues(repo, assignee) {
|
|
658
768
|
return runGhJson([
|
|
659
769
|
"issue",
|
|
@@ -689,6 +799,25 @@ function fetchRepoIssues(repo, options = {}) {
|
|
|
689
799
|
}
|
|
690
800
|
return runGhJson(args);
|
|
691
801
|
}
|
|
802
|
+
async function fetchRepoIssuesAsync(repo, options = {}) {
|
|
803
|
+
const { state = "open", limit = 100 } = options;
|
|
804
|
+
const args = [
|
|
805
|
+
"issue",
|
|
806
|
+
"list",
|
|
807
|
+
"--repo",
|
|
808
|
+
repo,
|
|
809
|
+
"--state",
|
|
810
|
+
state,
|
|
811
|
+
"--json",
|
|
812
|
+
"number,title,url,state,updatedAt,labels,assignees,body",
|
|
813
|
+
"--limit",
|
|
814
|
+
String(limit)
|
|
815
|
+
];
|
|
816
|
+
if (options.assignee) {
|
|
817
|
+
args.push("--assignee", options.assignee);
|
|
818
|
+
}
|
|
819
|
+
return runGhJsonAsync(args);
|
|
820
|
+
}
|
|
692
821
|
function assignIssue(repo, issueNumber) {
|
|
693
822
|
runGh(["issue", "edit", String(issueNumber), "--repo", repo, "--add-assignee", "@me"]);
|
|
694
823
|
}
|
|
@@ -769,79 +898,12 @@ async function fetchIssueCommentsAsync(repo, issueNumber) {
|
|
|
769
898
|
return result.comments ?? [];
|
|
770
899
|
}
|
|
771
900
|
function fetchProjectFields(repo, issueNumber, projectNumber) {
|
|
772
|
-
const query = `
|
|
773
|
-
query($owner: String!, $repo: String!, $issueNumber: Int!) {
|
|
774
|
-
repository(owner: $owner, name: $repo) {
|
|
775
|
-
issue(number: $issueNumber) {
|
|
776
|
-
projectItems(first: 10) {
|
|
777
|
-
nodes {
|
|
778
|
-
project { number }
|
|
779
|
-
fieldValues(first: 20) {
|
|
780
|
-
nodes {
|
|
781
|
-
... on ProjectV2ItemFieldDateValue {
|
|
782
|
-
field { ... on ProjectV2Field { name } }
|
|
783
|
-
date
|
|
784
|
-
}
|
|
785
|
-
... on ProjectV2ItemFieldSingleSelectValue {
|
|
786
|
-
field { ... on ProjectV2SingleSelectField { name } }
|
|
787
|
-
name
|
|
788
|
-
}
|
|
789
|
-
... on ProjectV2ItemFieldTextValue {
|
|
790
|
-
field { ... on ProjectV2Field { name } }
|
|
791
|
-
text
|
|
792
|
-
}
|
|
793
|
-
... on ProjectV2ItemFieldNumberValue {
|
|
794
|
-
field { ... on ProjectV2Field { name } }
|
|
795
|
-
number
|
|
796
|
-
}
|
|
797
|
-
... on ProjectV2ItemFieldIterationValue {
|
|
798
|
-
field { ... on ProjectV2IterationField { name } }
|
|
799
|
-
title
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
`;
|
|
809
901
|
const [owner, repoName] = repo.split("/");
|
|
810
902
|
if (!(owner && repoName)) return {};
|
|
811
903
|
try {
|
|
812
|
-
const
|
|
813
|
-
"api",
|
|
814
|
-
"graphql",
|
|
815
|
-
"-f",
|
|
816
|
-
`query=${query}`,
|
|
817
|
-
"-F",
|
|
818
|
-
`owner=${owner}`,
|
|
819
|
-
"-F",
|
|
820
|
-
`repo=${repoName}`,
|
|
821
|
-
"-F",
|
|
822
|
-
`issueNumber=${String(issueNumber)}`
|
|
823
|
-
]);
|
|
824
|
-
const items = result?.data?.repository?.issue?.projectItems?.nodes ?? [];
|
|
825
|
-
const projectItem = items.find((item) => item?.project?.number === projectNumber);
|
|
904
|
+
const projectItem = findProjectItemSync(owner, repoName, issueNumber, projectNumber);
|
|
826
905
|
if (!projectItem) return {};
|
|
827
|
-
|
|
828
|
-
const fieldValues = projectItem.fieldValues?.nodes ?? [];
|
|
829
|
-
for (const fv of fieldValues) {
|
|
830
|
-
if (!fv) continue;
|
|
831
|
-
const fieldName = fv.field?.name ?? "";
|
|
832
|
-
if ("date" in fv && DATE_FIELD_NAME_RE2.test(fieldName)) {
|
|
833
|
-
fields.targetDate = fv.date;
|
|
834
|
-
} else if ("name" in fv && fieldName === "Status") {
|
|
835
|
-
fields.status = fv.name;
|
|
836
|
-
} else if (fieldName) {
|
|
837
|
-
const value = "text" in fv && fv.text != null ? fv.text : "number" in fv && fv.number != null ? String(fv.number) : "name" in fv && fv.name != null ? fv.name : "title" in fv && fv.title != null ? fv.title : null;
|
|
838
|
-
if (value != null) {
|
|
839
|
-
if (!fields.customFields) fields.customFields = {};
|
|
840
|
-
fields.customFields[fieldName] = value;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
return fields;
|
|
906
|
+
return parseFieldValues(projectItem.fieldValues?.nodes ?? [], "status");
|
|
845
907
|
} catch {
|
|
846
908
|
return {};
|
|
847
909
|
}
|
|
@@ -914,23 +976,86 @@ function fetchProjectEnrichment(repo, projectNumber) {
|
|
|
914
976
|
const nodes = page?.nodes ?? [];
|
|
915
977
|
for (const item of nodes) {
|
|
916
978
|
if (!item?.content?.number) continue;
|
|
917
|
-
const enrichment =
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
979
|
+
const enrichment = parseFieldValues(item.fieldValues?.nodes ?? [], "projectStatus");
|
|
980
|
+
enrichMap.set(item.content.number, enrichment);
|
|
981
|
+
}
|
|
982
|
+
if (!page?.pageInfo?.hasNextPage) break;
|
|
983
|
+
cursor = page.pageInfo.endCursor ?? null;
|
|
984
|
+
} while (cursor);
|
|
985
|
+
return enrichMap;
|
|
986
|
+
} catch {
|
|
987
|
+
return /* @__PURE__ */ new Map();
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
async function fetchProjectEnrichmentAsync(repo, projectNumber) {
|
|
991
|
+
const [owner] = repo.split("/");
|
|
992
|
+
if (!owner) return /* @__PURE__ */ new Map();
|
|
993
|
+
const projectItemsFragment = `
|
|
994
|
+
projectV2(number: $projectNumber) {
|
|
995
|
+
items(first: 100, after: $cursor) {
|
|
996
|
+
pageInfo { hasNextPage endCursor }
|
|
997
|
+
nodes {
|
|
998
|
+
content {
|
|
999
|
+
... on Issue {
|
|
1000
|
+
number
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
fieldValues(first: 20) {
|
|
1004
|
+
nodes {
|
|
1005
|
+
... on ProjectV2ItemFieldDateValue {
|
|
1006
|
+
field { ... on ProjectV2Field { name } }
|
|
1007
|
+
date
|
|
1008
|
+
}
|
|
1009
|
+
... on ProjectV2ItemFieldSingleSelectValue {
|
|
1010
|
+
field { ... on ProjectV2SingleSelectField { name } }
|
|
1011
|
+
name
|
|
1012
|
+
}
|
|
1013
|
+
... on ProjectV2ItemFieldTextValue {
|
|
1014
|
+
field { ... on ProjectV2Field { name } }
|
|
1015
|
+
text
|
|
1016
|
+
}
|
|
1017
|
+
... on ProjectV2ItemFieldNumberValue {
|
|
1018
|
+
field { ... on ProjectV2Field { name } }
|
|
1019
|
+
number
|
|
1020
|
+
}
|
|
1021
|
+
... on ProjectV2ItemFieldIterationValue {
|
|
1022
|
+
field { ... on ProjectV2IterationField { name } }
|
|
1023
|
+
title
|
|
1024
|
+
}
|
|
931
1025
|
}
|
|
932
1026
|
}
|
|
933
1027
|
}
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
`;
|
|
1031
|
+
const query = `
|
|
1032
|
+
query($owner: String!, $projectNumber: Int!, $cursor: String) {
|
|
1033
|
+
organization(login: $owner) { ${projectItemsFragment} }
|
|
1034
|
+
user(login: $owner) { ${projectItemsFragment} }
|
|
1035
|
+
}
|
|
1036
|
+
`;
|
|
1037
|
+
try {
|
|
1038
|
+
const enrichMap = /* @__PURE__ */ new Map();
|
|
1039
|
+
let cursor = null;
|
|
1040
|
+
do {
|
|
1041
|
+
const args = [
|
|
1042
|
+
"api",
|
|
1043
|
+
"graphql",
|
|
1044
|
+
"-f",
|
|
1045
|
+
`query=${query}`,
|
|
1046
|
+
"-F",
|
|
1047
|
+
`owner=${owner}`,
|
|
1048
|
+
"-F",
|
|
1049
|
+
`projectNumber=${String(projectNumber)}`
|
|
1050
|
+
];
|
|
1051
|
+
if (cursor) args.push("-f", `cursor=${cursor}`);
|
|
1052
|
+
const result = await runGhGraphQLAsync(args);
|
|
1053
|
+
const ownerNode = result?.data?.organization ?? result?.data?.user;
|
|
1054
|
+
const page = ownerNode?.projectV2?.items;
|
|
1055
|
+
const nodes = page?.nodes ?? [];
|
|
1056
|
+
for (const item of nodes) {
|
|
1057
|
+
if (!item?.content?.number) continue;
|
|
1058
|
+
const enrichment = parseFieldValues(item.fieldValues?.nodes ?? [], "projectStatus");
|
|
934
1059
|
enrichMap.set(item.content.number, enrichment);
|
|
935
1060
|
}
|
|
936
1061
|
if (!page?.pageInfo?.hasNextPage) break;
|
|
@@ -987,6 +1112,44 @@ function fetchProjectStatusOptions(repo, projectNumber, _statusFieldId) {
|
|
|
987
1112
|
return [];
|
|
988
1113
|
}
|
|
989
1114
|
}
|
|
1115
|
+
async function fetchProjectStatusOptionsAsync(repo, projectNumber, _statusFieldId) {
|
|
1116
|
+
const [owner] = repo.split("/");
|
|
1117
|
+
if (!owner) return [];
|
|
1118
|
+
const statusFragment = `
|
|
1119
|
+
projectV2(number: $projectNumber) {
|
|
1120
|
+
field(name: "Status") {
|
|
1121
|
+
... on ProjectV2SingleSelectField {
|
|
1122
|
+
options {
|
|
1123
|
+
id
|
|
1124
|
+
name
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
`;
|
|
1130
|
+
const query = `
|
|
1131
|
+
query($owner: String!, $projectNumber: Int!) {
|
|
1132
|
+
organization(login: $owner) { ${statusFragment} }
|
|
1133
|
+
user(login: $owner) { ${statusFragment} }
|
|
1134
|
+
}
|
|
1135
|
+
`;
|
|
1136
|
+
try {
|
|
1137
|
+
const result = await runGhGraphQLAsync([
|
|
1138
|
+
"api",
|
|
1139
|
+
"graphql",
|
|
1140
|
+
"-f",
|
|
1141
|
+
`query=${query}`,
|
|
1142
|
+
"-F",
|
|
1143
|
+
`owner=${owner}`,
|
|
1144
|
+
"-F",
|
|
1145
|
+
`projectNumber=${String(projectNumber)}`
|
|
1146
|
+
]);
|
|
1147
|
+
const ownerNode = result?.data?.organization ?? result?.data?.user;
|
|
1148
|
+
return ownerNode?.projectV2?.field?.options ?? [];
|
|
1149
|
+
} catch {
|
|
1150
|
+
return [];
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
990
1153
|
function addLabel(repo, issueNumber, label) {
|
|
991
1154
|
runGh(["issue", "edit", String(issueNumber), "--repo", repo, "--add-label", label]);
|
|
992
1155
|
}
|
|
@@ -1008,88 +1171,18 @@ async function fetchRepoLabelsAsync(repo) {
|
|
|
1008
1171
|
function clearProjectNodeIdCache() {
|
|
1009
1172
|
projectNodeIdCache.clear();
|
|
1010
1173
|
}
|
|
1011
|
-
async function getProjectNodeId(owner, projectNumber) {
|
|
1012
|
-
const key = `${owner}/${String(projectNumber)}`;
|
|
1013
|
-
const cached = projectNodeIdCache.get(key);
|
|
1014
|
-
if (cached !== void 0) return cached;
|
|
1015
|
-
const idFragment = `projectV2(number: $projectNumber) { id }`;
|
|
1016
|
-
const projectQuery = `
|
|
1017
|
-
query($owner: String!, $projectNumber: Int!) {
|
|
1018
|
-
organization(login: $owner) { ${idFragment} }
|
|
1019
|
-
user(login: $owner) { ${idFragment} }
|
|
1020
|
-
}
|
|
1021
|
-
`;
|
|
1022
|
-
const projectResult = await runGhGraphQLAsync([
|
|
1023
|
-
"api",
|
|
1024
|
-
"graphql",
|
|
1025
|
-
"-f",
|
|
1026
|
-
`query=${projectQuery}`,
|
|
1027
|
-
"-F",
|
|
1028
|
-
`owner=${owner}`,
|
|
1029
|
-
"-F",
|
|
1030
|
-
`projectNumber=${String(projectNumber)}`
|
|
1031
|
-
]);
|
|
1032
|
-
const ownerNode = projectResult?.data?.organization ?? projectResult?.data?.user;
|
|
1033
|
-
const projectId = ownerNode?.projectV2?.id;
|
|
1034
|
-
if (!projectId) return null;
|
|
1035
|
-
projectNodeIdCache.set(key, projectId);
|
|
1036
|
-
return projectId;
|
|
1037
|
-
}
|
|
1038
1174
|
function updateProjectItemStatus(repo, issueNumber, projectConfig) {
|
|
1039
1175
|
const [owner, repoName] = repo.split("/");
|
|
1040
1176
|
if (!(owner && repoName)) return;
|
|
1041
|
-
const
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
id
|
|
1048
|
-
project { number }
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
1054
|
-
`;
|
|
1055
|
-
const findResult = runGhJson([
|
|
1056
|
-
"api",
|
|
1057
|
-
"graphql",
|
|
1058
|
-
"-f",
|
|
1059
|
-
`query=${findItemQuery}`,
|
|
1060
|
-
"-F",
|
|
1061
|
-
`owner=${owner}`,
|
|
1062
|
-
"-F",
|
|
1063
|
-
`repo=${repoName}`,
|
|
1064
|
-
"-F",
|
|
1065
|
-
`issueNumber=${String(issueNumber)}`
|
|
1066
|
-
]);
|
|
1067
|
-
const items = findResult?.data?.repository?.issue?.projectItems?.nodes ?? [];
|
|
1068
|
-
const projectNumber = projectConfig.projectNumber;
|
|
1069
|
-
const projectItem = items.find((item) => item?.project?.number === projectNumber);
|
|
1177
|
+
const projectItem = findProjectItemSync(
|
|
1178
|
+
owner,
|
|
1179
|
+
repoName,
|
|
1180
|
+
issueNumber,
|
|
1181
|
+
projectConfig.projectNumber
|
|
1182
|
+
);
|
|
1070
1183
|
if (!projectItem?.id) return;
|
|
1071
|
-
const
|
|
1072
|
-
const projectQuery = `
|
|
1073
|
-
query($owner: String!, $projectNumber: Int!) {
|
|
1074
|
-
organization(login: $owner) { ${idFragment} }
|
|
1075
|
-
user(login: $owner) { ${idFragment} }
|
|
1076
|
-
}
|
|
1077
|
-
`;
|
|
1078
|
-
const projectResult = runGhGraphQL([
|
|
1079
|
-
"api",
|
|
1080
|
-
"graphql",
|
|
1081
|
-
"-f",
|
|
1082
|
-
`query=${projectQuery}`,
|
|
1083
|
-
"-F",
|
|
1084
|
-
`owner=${owner}`,
|
|
1085
|
-
"-F",
|
|
1086
|
-
`projectNumber=${String(projectNumber)}`
|
|
1087
|
-
]);
|
|
1088
|
-
const projectOwner = projectResult?.data?.organization ?? projectResult?.data?.user;
|
|
1089
|
-
const projectId = projectOwner?.projectV2?.id;
|
|
1184
|
+
const projectId = getProjectNodeIdSync(owner, projectConfig.projectNumber);
|
|
1090
1185
|
if (!projectId) return;
|
|
1091
|
-
const statusFieldId = projectConfig.statusFieldId;
|
|
1092
|
-
const optionId = projectConfig.optionId;
|
|
1093
1186
|
const mutation = `
|
|
1094
1187
|
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
1095
1188
|
updateProjectV2ItemFieldValue(
|
|
@@ -1114,108 +1207,61 @@ function updateProjectItemStatus(repo, issueNumber, projectConfig) {
|
|
|
1114
1207
|
"-F",
|
|
1115
1208
|
`itemId=${projectItem.id}`,
|
|
1116
1209
|
"-F",
|
|
1117
|
-
`fieldId=${statusFieldId}`,
|
|
1210
|
+
`fieldId=${projectConfig.statusFieldId}`,
|
|
1118
1211
|
"-F",
|
|
1119
|
-
`optionId=${optionId}`
|
|
1212
|
+
`optionId=${projectConfig.optionId}`
|
|
1120
1213
|
]);
|
|
1121
1214
|
}
|
|
1122
1215
|
async function updateProjectItemStatusAsync(repo, issueNumber, projectConfig) {
|
|
1123
1216
|
const [owner, repoName] = repo.split("/");
|
|
1124
1217
|
if (!(owner && repoName)) return;
|
|
1125
|
-
const
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
id
|
|
1132
|
-
project { number }
|
|
1133
|
-
}
|
|
1134
|
-
}
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
}
|
|
1138
|
-
`;
|
|
1139
|
-
const findResult = await runGhJsonAsync([
|
|
1140
|
-
"api",
|
|
1141
|
-
"graphql",
|
|
1142
|
-
"-f",
|
|
1143
|
-
`query=${findItemQuery}`,
|
|
1144
|
-
"-F",
|
|
1145
|
-
`owner=${owner}`,
|
|
1146
|
-
"-F",
|
|
1147
|
-
`repo=${repoName}`,
|
|
1148
|
-
"-F",
|
|
1149
|
-
`issueNumber=${String(issueNumber)}`
|
|
1150
|
-
]);
|
|
1151
|
-
const items = findResult?.data?.repository?.issue?.projectItems?.nodes ?? [];
|
|
1152
|
-
const projectNumber = projectConfig.projectNumber;
|
|
1153
|
-
const projectItem = items.find((item) => item?.project?.number === projectNumber);
|
|
1218
|
+
const projectItem = await findProjectItemAsync(
|
|
1219
|
+
owner,
|
|
1220
|
+
repoName,
|
|
1221
|
+
issueNumber,
|
|
1222
|
+
projectConfig.projectNumber
|
|
1223
|
+
);
|
|
1154
1224
|
if (!projectItem?.id) return;
|
|
1155
|
-
const projectId = await getProjectNodeId(owner, projectNumber);
|
|
1225
|
+
const projectId = await getProjectNodeId(owner, projectConfig.projectNumber);
|
|
1156
1226
|
if (!projectId) return;
|
|
1157
|
-
const statusFieldId = projectConfig.statusFieldId;
|
|
1158
|
-
const optionId = projectConfig.optionId;
|
|
1159
1227
|
const mutation = `
|
|
1160
1228
|
mutation($projectId: ID!, $itemId: ID!, $fieldId: ID!, $optionId: String!) {
|
|
1161
1229
|
updateProjectV2ItemFieldValue(
|
|
1162
1230
|
input: {
|
|
1163
1231
|
projectId: $projectId
|
|
1164
1232
|
itemId: $itemId
|
|
1165
|
-
fieldId: $fieldId
|
|
1166
|
-
value: { singleSelectOptionId: $optionId }
|
|
1167
|
-
}
|
|
1168
|
-
) {
|
|
1169
|
-
projectV2Item { id }
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
`;
|
|
1173
|
-
await runGhAsync([
|
|
1174
|
-
"api",
|
|
1175
|
-
"graphql",
|
|
1176
|
-
"-f",
|
|
1177
|
-
`query=${mutation}`,
|
|
1178
|
-
"-F",
|
|
1179
|
-
`projectId=${projectId}`,
|
|
1180
|
-
"-F",
|
|
1181
|
-
`itemId=${projectItem.id}`,
|
|
1182
|
-
"-F",
|
|
1183
|
-
`fieldId=${statusFieldId}`,
|
|
1184
|
-
"-F",
|
|
1185
|
-
`optionId=${optionId}`
|
|
1186
|
-
]);
|
|
1187
|
-
}
|
|
1188
|
-
async function updateProjectItemDateAsync(repo, issueNumber, projectConfig, dueDate) {
|
|
1189
|
-
const [owner, repoName] = repo.split("/");
|
|
1190
|
-
if (!(owner && repoName)) return;
|
|
1191
|
-
const findItemQuery = `
|
|
1192
|
-
query($owner: String!, $repo: String!, $issueNumber: Int!) {
|
|
1193
|
-
repository(owner: $owner, name: $repo) {
|
|
1194
|
-
issue(number: $issueNumber) {
|
|
1195
|
-
projectItems(first: 10) {
|
|
1196
|
-
nodes {
|
|
1197
|
-
id
|
|
1198
|
-
project { number }
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1233
|
+
fieldId: $fieldId
|
|
1234
|
+
value: { singleSelectOptionId: $optionId }
|
|
1201
1235
|
}
|
|
1236
|
+
) {
|
|
1237
|
+
projectV2Item { id }
|
|
1202
1238
|
}
|
|
1203
1239
|
}
|
|
1204
1240
|
`;
|
|
1205
|
-
|
|
1241
|
+
await runGhAsync([
|
|
1206
1242
|
"api",
|
|
1207
1243
|
"graphql",
|
|
1208
1244
|
"-f",
|
|
1209
|
-
`query=${
|
|
1245
|
+
`query=${mutation}`,
|
|
1210
1246
|
"-F",
|
|
1211
|
-
`
|
|
1247
|
+
`projectId=${projectId}`,
|
|
1212
1248
|
"-F",
|
|
1213
|
-
`
|
|
1249
|
+
`itemId=${projectItem.id}`,
|
|
1214
1250
|
"-F",
|
|
1215
|
-
`
|
|
1251
|
+
`fieldId=${projectConfig.statusFieldId}`,
|
|
1252
|
+
"-F",
|
|
1253
|
+
`optionId=${projectConfig.optionId}`
|
|
1216
1254
|
]);
|
|
1217
|
-
|
|
1218
|
-
|
|
1255
|
+
}
|
|
1256
|
+
async function updateProjectItemDateAsync(repo, issueNumber, projectConfig, dueDate) {
|
|
1257
|
+
const [owner, repoName] = repo.split("/");
|
|
1258
|
+
if (!(owner && repoName)) return;
|
|
1259
|
+
const projectItem = await findProjectItemAsync(
|
|
1260
|
+
owner,
|
|
1261
|
+
repoName,
|
|
1262
|
+
issueNumber,
|
|
1263
|
+
projectConfig.projectNumber
|
|
1264
|
+
);
|
|
1219
1265
|
if (!projectItem?.id) return;
|
|
1220
1266
|
const projectId = await getProjectNodeId(owner, projectConfig.projectNumber);
|
|
1221
1267
|
if (!projectId) return;
|
|
@@ -1248,12 +1294,50 @@ async function updateProjectItemDateAsync(repo, issueNumber, projectConfig, dueD
|
|
|
1248
1294
|
`date=${dueDate}`
|
|
1249
1295
|
]);
|
|
1250
1296
|
}
|
|
1251
|
-
var execFileAsync, DATE_FIELD_NAME_RE2, projectNodeIdCache;
|
|
1297
|
+
var execFileAsync, DATE_FIELD_NAME_RE2, FIND_PROJECT_ITEM_QUERY, projectNodeIdCache;
|
|
1252
1298
|
var init_github = __esm({
|
|
1253
1299
|
"src/github.ts"() {
|
|
1254
1300
|
"use strict";
|
|
1255
1301
|
execFileAsync = promisify(execFile);
|
|
1256
1302
|
DATE_FIELD_NAME_RE2 = /^(target\s*date|due\s*date|due|deadline)$/i;
|
|
1303
|
+
FIND_PROJECT_ITEM_QUERY = `
|
|
1304
|
+
query($owner: String!, $repo: String!, $issueNumber: Int!) {
|
|
1305
|
+
repository(owner: $owner, name: $repo) {
|
|
1306
|
+
issue(number: $issueNumber) {
|
|
1307
|
+
projectItems(first: 10) {
|
|
1308
|
+
nodes {
|
|
1309
|
+
id
|
|
1310
|
+
project { number }
|
|
1311
|
+
fieldValues(first: 20) {
|
|
1312
|
+
nodes {
|
|
1313
|
+
... on ProjectV2ItemFieldDateValue {
|
|
1314
|
+
field { ... on ProjectV2Field { name } }
|
|
1315
|
+
date
|
|
1316
|
+
}
|
|
1317
|
+
... on ProjectV2ItemFieldSingleSelectValue {
|
|
1318
|
+
field { ... on ProjectV2SingleSelectField { name } }
|
|
1319
|
+
name
|
|
1320
|
+
}
|
|
1321
|
+
... on ProjectV2ItemFieldTextValue {
|
|
1322
|
+
field { ... on ProjectV2Field { name } }
|
|
1323
|
+
text
|
|
1324
|
+
}
|
|
1325
|
+
... on ProjectV2ItemFieldNumberValue {
|
|
1326
|
+
field { ... on ProjectV2Field { name } }
|
|
1327
|
+
number
|
|
1328
|
+
}
|
|
1329
|
+
... on ProjectV2ItemFieldIterationValue {
|
|
1330
|
+
field { ... on ProjectV2IterationField { name } }
|
|
1331
|
+
title
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
`;
|
|
1257
1341
|
projectNodeIdCache = /* @__PURE__ */ new Map();
|
|
1258
1342
|
}
|
|
1259
1343
|
});
|
|
@@ -1360,6 +1444,182 @@ var init_constants = __esm({
|
|
|
1360
1444
|
}
|
|
1361
1445
|
});
|
|
1362
1446
|
|
|
1447
|
+
// src/board/board-tree.ts
|
|
1448
|
+
function resolveStatusGroups(statusOptions, configuredGroups) {
|
|
1449
|
+
if (configuredGroups && configuredGroups.length > 0) {
|
|
1450
|
+
return configuredGroups.map((entry) => {
|
|
1451
|
+
const statuses = entry.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1452
|
+
return { label: statuses[0] ?? entry, statuses };
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
const nonTerminal = statusOptions.map((o) => o.name).filter((s) => !isTerminalStatus(s));
|
|
1456
|
+
if (nonTerminal.length > 0 && !nonTerminal.includes("Backlog")) {
|
|
1457
|
+
nonTerminal.push("Backlog");
|
|
1458
|
+
}
|
|
1459
|
+
const order = nonTerminal.length > 0 ? nonTerminal : ["In Progress", "Backlog"];
|
|
1460
|
+
return order.map((s) => ({ label: s, statuses: [s] }));
|
|
1461
|
+
}
|
|
1462
|
+
function issuePriorityRank(issue) {
|
|
1463
|
+
for (const label of issue.labels ?? []) {
|
|
1464
|
+
const rank = PRIORITY_RANK[label.name.toLowerCase()];
|
|
1465
|
+
if (rank != null) return rank;
|
|
1466
|
+
}
|
|
1467
|
+
return 99;
|
|
1468
|
+
}
|
|
1469
|
+
function groupByStatus(issues) {
|
|
1470
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1471
|
+
for (const issue of issues) {
|
|
1472
|
+
const status = issue.projectStatus ?? "Backlog";
|
|
1473
|
+
const list = groups.get(status);
|
|
1474
|
+
if (list) {
|
|
1475
|
+
list.push(issue);
|
|
1476
|
+
} else {
|
|
1477
|
+
groups.set(status, [issue]);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
for (const [, list] of groups) {
|
|
1481
|
+
list.sort((a, b) => issuePriorityRank(a) - issuePriorityRank(b));
|
|
1482
|
+
}
|
|
1483
|
+
return groups;
|
|
1484
|
+
}
|
|
1485
|
+
function buildBoardTree(repos, activity) {
|
|
1486
|
+
const sections = repos.map((rd) => {
|
|
1487
|
+
const sectionId = rd.repo.name;
|
|
1488
|
+
if (rd.error) {
|
|
1489
|
+
return { repo: rd.repo, sectionId, groups: [], error: rd.error };
|
|
1490
|
+
}
|
|
1491
|
+
const statusGroupDefs = resolveStatusGroups(rd.statusOptions, rd.repo.statusGroups);
|
|
1492
|
+
const byStatus = groupByStatus(rd.issues);
|
|
1493
|
+
const coveredKeys = /* @__PURE__ */ new Set();
|
|
1494
|
+
const groups = [];
|
|
1495
|
+
for (const sg of statusGroupDefs) {
|
|
1496
|
+
const issues = [];
|
|
1497
|
+
for (const [status, statusIssues] of byStatus) {
|
|
1498
|
+
if (sg.statuses.some((s) => s.toLowerCase().trim() === status.toLowerCase().trim())) {
|
|
1499
|
+
issues.push(...statusIssues);
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
if (issues.length === 0) continue;
|
|
1503
|
+
issues.sort((a, b) => issuePriorityRank(a) - issuePriorityRank(b));
|
|
1504
|
+
groups.push({ label: sg.label, subId: `sub:${sectionId}:${sg.label}`, issues });
|
|
1505
|
+
for (const s of sg.statuses) coveredKeys.add(s.toLowerCase().trim());
|
|
1506
|
+
}
|
|
1507
|
+
for (const [status, statusIssues] of byStatus) {
|
|
1508
|
+
if (!(coveredKeys.has(status.toLowerCase().trim()) || isTerminalStatus(status))) {
|
|
1509
|
+
groups.push({ label: status, subId: `sub:${sectionId}:${status}`, issues: statusIssues });
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return { repo: rd.repo, sectionId, groups, error: null };
|
|
1513
|
+
});
|
|
1514
|
+
return { activity, sections };
|
|
1515
|
+
}
|
|
1516
|
+
function buildNavItemsForRepo(sections, repoName, statusGroupId) {
|
|
1517
|
+
if (!repoName) return [];
|
|
1518
|
+
const section = sections.find((s) => s.sectionId === repoName);
|
|
1519
|
+
if (!section) return [];
|
|
1520
|
+
const activeGroup = section.groups.find((g) => g.subId === statusGroupId) ?? section.groups[0];
|
|
1521
|
+
if (!activeGroup) return [];
|
|
1522
|
+
return activeGroup.issues.map((issue) => ({
|
|
1523
|
+
id: `gh:${section.repo.name}:${issue.number}`,
|
|
1524
|
+
section: repoName,
|
|
1525
|
+
type: "item"
|
|
1526
|
+
}));
|
|
1527
|
+
}
|
|
1528
|
+
function buildFlatRowsForRepo(sections, repoName, statusGroupId) {
|
|
1529
|
+
if (!repoName) {
|
|
1530
|
+
return [
|
|
1531
|
+
{
|
|
1532
|
+
type: "subHeader",
|
|
1533
|
+
key: "select-repo",
|
|
1534
|
+
navId: null,
|
|
1535
|
+
text: "Select a repo in panel [1]"
|
|
1536
|
+
}
|
|
1537
|
+
];
|
|
1538
|
+
}
|
|
1539
|
+
const section = sections.find((s) => s.sectionId === repoName);
|
|
1540
|
+
if (!section) return [];
|
|
1541
|
+
if (section.error) {
|
|
1542
|
+
return [{ type: "error", key: `error:${repoName}`, navId: null, text: section.error }];
|
|
1543
|
+
}
|
|
1544
|
+
if (section.groups.length === 0) {
|
|
1545
|
+
return [
|
|
1546
|
+
{
|
|
1547
|
+
type: "subHeader",
|
|
1548
|
+
key: `empty:${repoName}`,
|
|
1549
|
+
navId: null,
|
|
1550
|
+
text: "No open issues"
|
|
1551
|
+
}
|
|
1552
|
+
];
|
|
1553
|
+
}
|
|
1554
|
+
const activeGroup = section.groups.find((g) => g.subId === statusGroupId) ?? section.groups[0];
|
|
1555
|
+
if (!activeGroup) return [];
|
|
1556
|
+
if (activeGroup.issues.length === 0) {
|
|
1557
|
+
return [
|
|
1558
|
+
{
|
|
1559
|
+
type: "subHeader",
|
|
1560
|
+
key: `empty-group:${statusGroupId}`,
|
|
1561
|
+
navId: null,
|
|
1562
|
+
text: "No issues in this status group"
|
|
1563
|
+
}
|
|
1564
|
+
];
|
|
1565
|
+
}
|
|
1566
|
+
return activeGroup.issues.map((issue) => ({
|
|
1567
|
+
type: "issue",
|
|
1568
|
+
key: `gh:${section.repo.name}:${issue.number}`,
|
|
1569
|
+
navId: `gh:${section.repo.name}:${issue.number}`,
|
|
1570
|
+
issue,
|
|
1571
|
+
repoName: section.repo.name
|
|
1572
|
+
}));
|
|
1573
|
+
}
|
|
1574
|
+
function matchesSearch(issue, query) {
|
|
1575
|
+
if (!query.trim()) return true;
|
|
1576
|
+
const tokens = query.toLowerCase().trim().split(/\s+/);
|
|
1577
|
+
const labels = issue.labels ?? [];
|
|
1578
|
+
const assignees = issue.assignees ?? [];
|
|
1579
|
+
return tokens.every((token) => {
|
|
1580
|
+
if (token.startsWith("#")) {
|
|
1581
|
+
const num = parseInt(token.slice(1), 10);
|
|
1582
|
+
return !Number.isNaN(num) && issue.number === num;
|
|
1583
|
+
}
|
|
1584
|
+
if (token.startsWith("@")) {
|
|
1585
|
+
const login = token.slice(1);
|
|
1586
|
+
return assignees.some((a) => a.login.toLowerCase().includes(login));
|
|
1587
|
+
}
|
|
1588
|
+
if (token === "unassigned") return assignees.length === 0;
|
|
1589
|
+
if (token === "assigned") return assignees.length > 0;
|
|
1590
|
+
if (issue.title.toLowerCase().includes(token)) return true;
|
|
1591
|
+
if (labels.some((l) => l.name.toLowerCase().includes(token))) return true;
|
|
1592
|
+
if (issue.projectStatus?.toLowerCase().includes(token)) return true;
|
|
1593
|
+
if (issue.customFields && Object.values(issue.customFields).some((v) => v.toLowerCase().includes(token)))
|
|
1594
|
+
return true;
|
|
1595
|
+
if (assignees.some((a) => a.login.toLowerCase().includes(token))) return true;
|
|
1596
|
+
return false;
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
function findSelectedIssueWithRepo(repos, selectedId) {
|
|
1600
|
+
if (!selectedId?.startsWith("gh:")) return null;
|
|
1601
|
+
for (const rd of repos) {
|
|
1602
|
+
for (const issue of rd.issues) {
|
|
1603
|
+
if (`gh:${rd.repo.name}:${issue.number}` === selectedId)
|
|
1604
|
+
return { issue, repoName: rd.repo.name };
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
return null;
|
|
1608
|
+
}
|
|
1609
|
+
var PRIORITY_RANK;
|
|
1610
|
+
var init_board_tree = __esm({
|
|
1611
|
+
"src/board/board-tree.ts"() {
|
|
1612
|
+
"use strict";
|
|
1613
|
+
init_constants();
|
|
1614
|
+
PRIORITY_RANK = {
|
|
1615
|
+
"priority:critical": 0,
|
|
1616
|
+
"priority:high": 1,
|
|
1617
|
+
"priority:medium": 2,
|
|
1618
|
+
"priority:low": 3
|
|
1619
|
+
};
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
|
|
1363
1623
|
// src/board/hooks/use-action-log.ts
|
|
1364
1624
|
import { useCallback, useRef, useState } from "react";
|
|
1365
1625
|
function nextEntryId() {
|
|
@@ -1427,21 +1687,49 @@ var init_utils = __esm({
|
|
|
1427
1687
|
}
|
|
1428
1688
|
});
|
|
1429
1689
|
|
|
1430
|
-
// src/board/
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1690
|
+
// src/board/board-utils.ts
|
|
1691
|
+
function makeIssueNavId(repoName, issueNumber) {
|
|
1692
|
+
return `gh:${repoName}:${issueNumber}`;
|
|
1693
|
+
}
|
|
1694
|
+
function parseIssueNavId(navId) {
|
|
1695
|
+
if (!navId?.startsWith("gh:")) return null;
|
|
1696
|
+
const parts = navId.split(":");
|
|
1697
|
+
if (parts.length < 3) return null;
|
|
1698
|
+
const num = Number(parts[2]);
|
|
1699
|
+
return Number.isNaN(num) ? null : { repoName: parts[1], issueNumber: num };
|
|
1700
|
+
}
|
|
1701
|
+
function findIssueByNavId(repos, navId) {
|
|
1702
|
+
if (!navId?.startsWith("gh:")) return null;
|
|
1436
1703
|
for (const rd of repos) {
|
|
1437
1704
|
for (const issue of rd.issues) {
|
|
1438
|
-
if (
|
|
1439
|
-
|
|
1440
|
-
return { issue, repoName: rd.repo.name, repoConfig, statusOptions: rd.statusOptions };
|
|
1705
|
+
if (makeIssueNavId(rd.repo.name, issue.number) === navId) {
|
|
1706
|
+
return { issue, repoName: rd.repo.name };
|
|
1441
1707
|
}
|
|
1442
1708
|
}
|
|
1443
1709
|
}
|
|
1444
|
-
return
|
|
1710
|
+
return null;
|
|
1711
|
+
}
|
|
1712
|
+
var init_board_utils = __esm({
|
|
1713
|
+
"src/board/board-utils.ts"() {
|
|
1714
|
+
"use strict";
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
// src/board/hooks/use-actions.ts
|
|
1719
|
+
import { useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
1720
|
+
function findIssueContext(repos, selectedId, config2) {
|
|
1721
|
+
const found = findIssueByNavId(repos, selectedId);
|
|
1722
|
+
if (!found) {
|
|
1723
|
+
return { issue: null, repoName: null, repoConfig: null, statusOptions: [] };
|
|
1724
|
+
}
|
|
1725
|
+
const rd = repos.find((r) => r.repo.name === found.repoName);
|
|
1726
|
+
const repoConfig = config2.repos.find((r) => r.name === found.repoName) ?? null;
|
|
1727
|
+
return {
|
|
1728
|
+
issue: found.issue,
|
|
1729
|
+
repoName: found.repoName,
|
|
1730
|
+
repoConfig,
|
|
1731
|
+
statusOptions: rd?.statusOptions ?? []
|
|
1732
|
+
};
|
|
1445
1733
|
}
|
|
1446
1734
|
function checkAlreadyAssigned(issue, selfLogin, toast) {
|
|
1447
1735
|
const assignees = issue.assignees ?? [];
|
|
@@ -1863,6 +2151,7 @@ var init_use_actions = __esm({
|
|
|
1863
2151
|
init_github();
|
|
1864
2152
|
init_pick();
|
|
1865
2153
|
init_utils();
|
|
2154
|
+
init_board_utils();
|
|
1866
2155
|
init_constants();
|
|
1867
2156
|
init_use_action_log();
|
|
1868
2157
|
}
|
|
@@ -1995,17 +2284,37 @@ end tell`;
|
|
|
1995
2284
|
return { ok: true, value: void 0 };
|
|
1996
2285
|
}
|
|
1997
2286
|
case "Terminal": {
|
|
1998
|
-
const
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2287
|
+
const quotedArgs = [command, ...extraArgs, "--", prompt].map(shellQuote).join(" ");
|
|
2288
|
+
const script = `tell application "Terminal"
|
|
2289
|
+
activate
|
|
2290
|
+
do script "cd " & ${JSON.stringify(shellQuote(localPath))} & " && " & ${JSON.stringify(quotedArgs)}
|
|
2291
|
+
end tell`;
|
|
2292
|
+
const result = spawnSync("osascript", ["-e", script], { stdio: "ignore" });
|
|
2293
|
+
if (result.status !== 0) {
|
|
2294
|
+
return {
|
|
2295
|
+
ok: false,
|
|
2296
|
+
error: {
|
|
2297
|
+
kind: "terminal-failed",
|
|
2298
|
+
message: "Terminal.app launch failed."
|
|
2299
|
+
}
|
|
2300
|
+
};
|
|
2301
|
+
}
|
|
2003
2302
|
return { ok: true, value: void 0 };
|
|
2004
2303
|
}
|
|
2005
2304
|
case "Ghostty": {
|
|
2006
2305
|
const child = spawn2(
|
|
2007
2306
|
"open",
|
|
2008
|
-
[
|
|
2307
|
+
[
|
|
2308
|
+
"-na",
|
|
2309
|
+
"Ghostty",
|
|
2310
|
+
"--args",
|
|
2311
|
+
`--working-directory=${localPath}`,
|
|
2312
|
+
"-e",
|
|
2313
|
+
command,
|
|
2314
|
+
...extraArgs,
|
|
2315
|
+
"--",
|
|
2316
|
+
prompt
|
|
2317
|
+
],
|
|
2009
2318
|
{ stdio: "ignore", detached: true }
|
|
2010
2319
|
);
|
|
2011
2320
|
child.unref();
|
|
@@ -2897,7 +3206,8 @@ function useKeyboard({
|
|
|
2897
3206
|
onStatusEnter,
|
|
2898
3207
|
onActivityEnter,
|
|
2899
3208
|
showDetailPanel,
|
|
2900
|
-
leftPanelHidden
|
|
3209
|
+
leftPanelHidden,
|
|
3210
|
+
issuesPageSize
|
|
2901
3211
|
}) {
|
|
2902
3212
|
const {
|
|
2903
3213
|
exit,
|
|
@@ -2934,12 +3244,32 @@ function useKeyboard({
|
|
|
2934
3244
|
handleToggleZen();
|
|
2935
3245
|
return;
|
|
2936
3246
|
}
|
|
2937
|
-
if (input2 === "j" || key.downArrow) {
|
|
2938
|
-
nav.moveDown();
|
|
3247
|
+
if (input2 === "j" || key.downArrow) {
|
|
3248
|
+
nav.moveDown();
|
|
3249
|
+
return;
|
|
3250
|
+
}
|
|
3251
|
+
if (input2 === "k" || key.upArrow) {
|
|
3252
|
+
nav.moveUp();
|
|
3253
|
+
return;
|
|
3254
|
+
}
|
|
3255
|
+
if (key.ctrl && input2 === "d" || key.pageDown) {
|
|
3256
|
+
nav.moveDownBy(Math.max(1, Math.floor(issuesPageSize / 2)));
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
if (key.ctrl && input2 === "u" || key.pageUp) {
|
|
3260
|
+
nav.moveUpBy(Math.max(1, Math.floor(issuesPageSize / 2)));
|
|
3261
|
+
return;
|
|
3262
|
+
}
|
|
3263
|
+
if (input2 === "G") {
|
|
3264
|
+
nav.goToBottom();
|
|
3265
|
+
return;
|
|
3266
|
+
}
|
|
3267
|
+
if (key.ctrl && input2 === "f") {
|
|
3268
|
+
nav.moveDownBy(issuesPageSize);
|
|
2939
3269
|
return;
|
|
2940
3270
|
}
|
|
2941
|
-
if (input2 === "
|
|
2942
|
-
nav.
|
|
3271
|
+
if (key.ctrl && input2 === "b") {
|
|
3272
|
+
nav.moveUpBy(issuesPageSize);
|
|
2943
3273
|
return;
|
|
2944
3274
|
}
|
|
2945
3275
|
if (input2 === "q") {
|
|
@@ -3027,6 +3357,36 @@ function useKeyboard({
|
|
|
3027
3357
|
}
|
|
3028
3358
|
return;
|
|
3029
3359
|
}
|
|
3360
|
+
if (key.ctrl && input2 === "d" || key.pageDown) {
|
|
3361
|
+
if (panelFocus.activePanelId === 3) {
|
|
3362
|
+
nav.moveDownBy(Math.max(1, Math.floor(issuesPageSize / 2)));
|
|
3363
|
+
}
|
|
3364
|
+
return;
|
|
3365
|
+
}
|
|
3366
|
+
if (key.ctrl && input2 === "u" || key.pageUp) {
|
|
3367
|
+
if (panelFocus.activePanelId === 3) {
|
|
3368
|
+
nav.moveUpBy(Math.max(1, Math.floor(issuesPageSize / 2)));
|
|
3369
|
+
}
|
|
3370
|
+
return;
|
|
3371
|
+
}
|
|
3372
|
+
if (key.ctrl && input2 === "f") {
|
|
3373
|
+
if (panelFocus.activePanelId === 3) {
|
|
3374
|
+
nav.moveDownBy(issuesPageSize);
|
|
3375
|
+
}
|
|
3376
|
+
return;
|
|
3377
|
+
}
|
|
3378
|
+
if (key.ctrl && input2 === "b") {
|
|
3379
|
+
if (panelFocus.activePanelId === 3) {
|
|
3380
|
+
nav.moveUpBy(issuesPageSize);
|
|
3381
|
+
}
|
|
3382
|
+
return;
|
|
3383
|
+
}
|
|
3384
|
+
if (input2 === "G") {
|
|
3385
|
+
if (panelFocus.activePanelId === 3) {
|
|
3386
|
+
nav.goToBottom();
|
|
3387
|
+
}
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
3030
3390
|
}
|
|
3031
3391
|
if (ui.state.mode === "multiSelect") {
|
|
3032
3392
|
if (input2 === " ") {
|
|
@@ -3249,7 +3609,8 @@ function useKeyboard({
|
|
|
3249
3609
|
handleToggleLeftPanel,
|
|
3250
3610
|
handleToggleZen,
|
|
3251
3611
|
showDetailPanel,
|
|
3252
|
-
leftPanelHidden
|
|
3612
|
+
leftPanelHidden,
|
|
3613
|
+
issuesPageSize
|
|
3253
3614
|
]
|
|
3254
3615
|
);
|
|
3255
3616
|
const inputActive = ui.state.mode === "normal" || ui.state.mode === "multiSelect" || ui.state.mode === "focus" || ui.state.mode === "overlay:detail" || ui.state.mode === "zen";
|
|
@@ -3474,6 +3835,30 @@ function useNavigation(allItems) {
|
|
|
3474
3835
|
const item = visibleItems[newIdx];
|
|
3475
3836
|
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
3476
3837
|
}, [selectedIndex, visibleItems]);
|
|
3838
|
+
const moveUpBy = useCallback8(
|
|
3839
|
+
(count) => {
|
|
3840
|
+
const newIdx = Math.max(0, selectedIndex - count);
|
|
3841
|
+
const item = visibleItems[newIdx];
|
|
3842
|
+
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
3843
|
+
},
|
|
3844
|
+
[selectedIndex, visibleItems]
|
|
3845
|
+
);
|
|
3846
|
+
const moveDownBy = useCallback8(
|
|
3847
|
+
(count) => {
|
|
3848
|
+
const newIdx = Math.min(visibleItems.length - 1, selectedIndex + count);
|
|
3849
|
+
const item = visibleItems[newIdx];
|
|
3850
|
+
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
3851
|
+
},
|
|
3852
|
+
[selectedIndex, visibleItems]
|
|
3853
|
+
);
|
|
3854
|
+
const goToTop = useCallback8(() => {
|
|
3855
|
+
const item = visibleItems[0];
|
|
3856
|
+
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
3857
|
+
}, [visibleItems]);
|
|
3858
|
+
const goToBottom = useCallback8(() => {
|
|
3859
|
+
const item = visibleItems[visibleItems.length - 1];
|
|
3860
|
+
if (item) dispatch({ type: "SELECT", id: item.id, section: item.section });
|
|
3861
|
+
}, [visibleItems]);
|
|
3477
3862
|
const nextSection = useCallback8(() => {
|
|
3478
3863
|
const currentItem = visibleItems[selectedIndex];
|
|
3479
3864
|
if (!currentItem) return;
|
|
@@ -3518,6 +3903,10 @@ function useNavigation(allItems) {
|
|
|
3518
3903
|
collapsedSections: state.collapsedSections,
|
|
3519
3904
|
moveUp,
|
|
3520
3905
|
moveDown,
|
|
3906
|
+
moveUpBy,
|
|
3907
|
+
moveDownBy,
|
|
3908
|
+
goToTop,
|
|
3909
|
+
goToBottom,
|
|
3521
3910
|
nextSection,
|
|
3522
3911
|
prevSection,
|
|
3523
3912
|
toggleSection,
|
|
@@ -3839,8 +4228,8 @@ function enterStatusMode(state) {
|
|
|
3839
4228
|
function uiReducer(state, action) {
|
|
3840
4229
|
switch (action.type) {
|
|
3841
4230
|
case "ENTER_SEARCH":
|
|
3842
|
-
if (state.mode !== "normal") return state;
|
|
3843
|
-
return { ...state, mode: "search", previousMode:
|
|
4231
|
+
if (state.mode !== "normal" && state.mode !== "zen") return state;
|
|
4232
|
+
return { ...state, mode: "search", previousMode: state.mode };
|
|
3844
4233
|
case "ENTER_COMMENT":
|
|
3845
4234
|
if (state.mode !== "normal" && state.mode !== "overlay:detail") return state;
|
|
3846
4235
|
return { ...state, mode: "overlay:comment", previousMode: "normal" };
|
|
@@ -3867,8 +4256,8 @@ function uiReducer(state, action) {
|
|
|
3867
4256
|
if (state.mode !== "normal") return state;
|
|
3868
4257
|
return { ...state, mode: "focus", previousMode: "normal" };
|
|
3869
4258
|
case "ENTER_FUZZY_PICKER":
|
|
3870
|
-
if (state.mode !== "normal") return state;
|
|
3871
|
-
return { ...state, mode: "overlay:fuzzyPicker", previousMode:
|
|
4259
|
+
if (state.mode !== "normal" && state.mode !== "zen") return state;
|
|
4260
|
+
return { ...state, mode: "overlay:fuzzyPicker", previousMode: state.mode };
|
|
3872
4261
|
case "ENTER_EDIT_ISSUE":
|
|
3873
4262
|
if (state.mode !== "normal" && state.mode !== "overlay:detail") return state;
|
|
3874
4263
|
return { ...state, mode: "overlay:editIssue", previousMode: "normal" };
|
|
@@ -3961,8 +4350,75 @@ var init_use_ui_state = __esm({
|
|
|
3961
4350
|
}
|
|
3962
4351
|
});
|
|
3963
4352
|
|
|
4353
|
+
// src/board/hooks/use-viewport-scroll.ts
|
|
4354
|
+
import { useRef as useRef10 } from "react";
|
|
4355
|
+
function computeViewportScroll(totalItems, contentRowCount, cursorIndex, currentOffset) {
|
|
4356
|
+
if (totalItems === 0 || contentRowCount <= 0) {
|
|
4357
|
+
return {
|
|
4358
|
+
scrollOffset: 0,
|
|
4359
|
+
visibleCount: 0,
|
|
4360
|
+
hasMoreAbove: false,
|
|
4361
|
+
hasMoreBelow: false,
|
|
4362
|
+
aboveCount: 0,
|
|
4363
|
+
belowCount: 0
|
|
4364
|
+
};
|
|
4365
|
+
}
|
|
4366
|
+
let offset = currentOffset;
|
|
4367
|
+
const effectiveMargin = Math.min(SCROLL_MARGIN, Math.floor(contentRowCount / 4));
|
|
4368
|
+
if (cursorIndex >= 0) {
|
|
4369
|
+
if (cursorIndex < offset + effectiveMargin) {
|
|
4370
|
+
offset = Math.max(0, cursorIndex - effectiveMargin);
|
|
4371
|
+
}
|
|
4372
|
+
if (cursorIndex >= offset + contentRowCount - effectiveMargin) {
|
|
4373
|
+
offset = cursorIndex - contentRowCount + effectiveMargin + 1;
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
const needsAboveIndicator = offset > 0;
|
|
4377
|
+
const tentativeVisibleCount = contentRowCount - (needsAboveIndicator ? 1 : 0);
|
|
4378
|
+
const needsBelowIndicator = offset + tentativeVisibleCount < totalItems;
|
|
4379
|
+
const indicatorRows = (needsAboveIndicator ? 1 : 0) + (needsBelowIndicator ? 1 : 0);
|
|
4380
|
+
const visibleCount = Math.max(1, contentRowCount - indicatorRows);
|
|
4381
|
+
if (cursorIndex >= 0) {
|
|
4382
|
+
if (cursorIndex < offset) {
|
|
4383
|
+
offset = cursorIndex;
|
|
4384
|
+
} else if (cursorIndex >= offset + visibleCount) {
|
|
4385
|
+
offset = cursorIndex - visibleCount + 1;
|
|
4386
|
+
}
|
|
4387
|
+
}
|
|
4388
|
+
const maxOffset = Math.max(0, totalItems - visibleCount);
|
|
4389
|
+
offset = Math.max(0, Math.min(offset, maxOffset));
|
|
4390
|
+
const hasMoreAbove = offset > 0;
|
|
4391
|
+
const hasMoreBelow = offset + visibleCount < totalItems;
|
|
4392
|
+
return {
|
|
4393
|
+
scrollOffset: offset,
|
|
4394
|
+
visibleCount,
|
|
4395
|
+
hasMoreAbove,
|
|
4396
|
+
hasMoreBelow,
|
|
4397
|
+
aboveCount: offset,
|
|
4398
|
+
belowCount: Math.max(0, totalItems - offset - visibleCount)
|
|
4399
|
+
};
|
|
4400
|
+
}
|
|
4401
|
+
function useViewportScroll(totalItems, contentRowCount, cursorIndex, resetKey) {
|
|
4402
|
+
const scrollRef = useRef10(0);
|
|
4403
|
+
const prevResetKeyRef = useRef10(resetKey);
|
|
4404
|
+
if (resetKey !== prevResetKeyRef.current) {
|
|
4405
|
+
prevResetKeyRef.current = resetKey;
|
|
4406
|
+
scrollRef.current = 0;
|
|
4407
|
+
}
|
|
4408
|
+
const result = computeViewportScroll(totalItems, contentRowCount, cursorIndex, scrollRef.current);
|
|
4409
|
+
scrollRef.current = result.scrollOffset;
|
|
4410
|
+
return result;
|
|
4411
|
+
}
|
|
4412
|
+
var SCROLL_MARGIN;
|
|
4413
|
+
var init_use_viewport_scroll = __esm({
|
|
4414
|
+
"src/board/hooks/use-viewport-scroll.ts"() {
|
|
4415
|
+
"use strict";
|
|
4416
|
+
SCROLL_MARGIN = 2;
|
|
4417
|
+
}
|
|
4418
|
+
});
|
|
4419
|
+
|
|
3964
4420
|
// src/board/hooks/use-workflow-state.ts
|
|
3965
|
-
import { useCallback as useCallback12, useRef as
|
|
4421
|
+
import { useCallback as useCallback12, useRef as useRef11, useState as useState6 } from "react";
|
|
3966
4422
|
function resolvePhases(config2, repoConfig) {
|
|
3967
4423
|
const repoPhases = repoConfig?.workflow?.phases;
|
|
3968
4424
|
if (repoPhases && repoPhases.length > 0) return repoPhases;
|
|
@@ -3988,7 +4444,7 @@ function derivePhaseStatus(phaseName, sessions) {
|
|
|
3988
4444
|
}
|
|
3989
4445
|
function useWorkflowState(config2) {
|
|
3990
4446
|
const [enrichment, setEnrichment] = useState6(loadEnrichment);
|
|
3991
|
-
const enrichmentRef =
|
|
4447
|
+
const enrichmentRef = useRef11(enrichment);
|
|
3992
4448
|
enrichmentRef.current = enrichment;
|
|
3993
4449
|
const reload = useCallback12(() => {
|
|
3994
4450
|
const data = loadEnrichment();
|
|
@@ -4117,12 +4573,9 @@ var init_tmux_pane = __esm({
|
|
|
4117
4573
|
});
|
|
4118
4574
|
|
|
4119
4575
|
// src/board/hooks/use-zen-mode.ts
|
|
4120
|
-
import { useCallback as useCallback13, useEffect as useEffect5, useRef as
|
|
4576
|
+
import { useCallback as useCallback13, useEffect as useEffect5, useRef as useRef12, useState as useState7 } from "react";
|
|
4121
4577
|
function issueNumberFromId(selectedId) {
|
|
4122
|
-
|
|
4123
|
-
const parts = selectedId.split(":");
|
|
4124
|
-
const num = Number(parts[2]);
|
|
4125
|
-
return Number.isNaN(num) ? null : num;
|
|
4578
|
+
return parseIssueNavId(selectedId)?.issueNumber ?? null;
|
|
4126
4579
|
}
|
|
4127
4580
|
function tryJoinAgent(issueNumber) {
|
|
4128
4581
|
const winName = agentWindowName(issueNumber);
|
|
@@ -4137,7 +4590,7 @@ function useZenMode({
|
|
|
4137
4590
|
selectedId
|
|
4138
4591
|
}) {
|
|
4139
4592
|
const [zenPaneId, setZenPaneId] = useState7(null);
|
|
4140
|
-
const paneRef =
|
|
4593
|
+
const paneRef = useRef12(null);
|
|
4141
4594
|
paneRef.current = zenPaneId;
|
|
4142
4595
|
const cleanupCurrentPane = useCallback13(() => {
|
|
4143
4596
|
const id = paneRef.current;
|
|
@@ -4181,7 +4634,7 @@ function useZenMode({
|
|
|
4181
4634
|
},
|
|
4182
4635
|
[ui.state.mode, cleanupCurrentPane]
|
|
4183
4636
|
);
|
|
4184
|
-
const prevSelectedRef =
|
|
4637
|
+
const prevSelectedRef = useRef12(null);
|
|
4185
4638
|
useEffect5(() => {
|
|
4186
4639
|
if (ui.state.mode !== "zen") {
|
|
4187
4640
|
prevSelectedRef.current = selectedId;
|
|
@@ -4216,6 +4669,7 @@ var ZEN_PANE_WIDTH_PERCENT, ZEN_MIN_COLS, CURSOR_FOLLOW_DEBOUNCE_MS, DEAD_PANE_C
|
|
|
4216
4669
|
var init_use_zen_mode = __esm({
|
|
4217
4670
|
"src/board/hooks/use-zen-mode.ts"() {
|
|
4218
4671
|
"use strict";
|
|
4672
|
+
init_board_utils();
|
|
4219
4673
|
init_tmux_pane();
|
|
4220
4674
|
ZEN_PANE_WIDTH_PERCENT = 65;
|
|
4221
4675
|
ZEN_MIN_COLS = 100;
|
|
@@ -4602,7 +5056,7 @@ function HintBar({
|
|
|
4602
5056
|
0: "j/k:scroll Esc:close ? help",
|
|
4603
5057
|
1: "j/k:move Enter:filter 0-4:panel ? help",
|
|
4604
5058
|
2: "j/k:move Enter:filter Esc:clear 0-4:panel ? help",
|
|
4605
|
-
3: `j/k:move Enter:detail g:open p:pick m:status c:comment C:claude /:search n:new H:hide-panel Z:zen 0-4:panel${hasUndoable ? " u:undo" : ""} ? help q:quit`,
|
|
5059
|
+
3: `j/k:move ^d/^u:page G:bottom Enter:detail g:open p:pick m:status c:comment C:claude /:search n:new H:hide-panel Z:zen 0-4:panel${hasUndoable ? " u:undo" : ""} ? help q:quit`,
|
|
4606
5060
|
4: "j/k:scroll Enter:jump r:refresh 0-4:panel ? help"
|
|
4607
5061
|
};
|
|
4608
5062
|
return /* @__PURE__ */ jsxs6(Box6, { children: [
|
|
@@ -4625,18 +5079,15 @@ var init_hint_bar = __esm({
|
|
|
4625
5079
|
import { Box as Box7, Text as Text7, useInput as useInput2 } from "ink";
|
|
4626
5080
|
import { useState as useState9 } from "react";
|
|
4627
5081
|
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
4628
|
-
function getMenuItems(
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
|
|
4633
|
-
|
|
4634
|
-
];
|
|
4635
|
-
}
|
|
4636
|
-
return [];
|
|
5082
|
+
function getMenuItems() {
|
|
5083
|
+
return [
|
|
5084
|
+
{ label: "Assign all to me", action: { type: "assign" } },
|
|
5085
|
+
{ label: "Unassign all from me", action: { type: "unassign" } },
|
|
5086
|
+
{ label: "Move status (all)", action: { type: "statusChange" } }
|
|
5087
|
+
];
|
|
4637
5088
|
}
|
|
4638
|
-
function BulkActionMenu({ count,
|
|
4639
|
-
const items = getMenuItems(
|
|
5089
|
+
function BulkActionMenu({ count, onSelect, onCancel }) {
|
|
5090
|
+
const items = getMenuItems();
|
|
4640
5091
|
const [selectedIdx, setSelectedIdx] = useState9(0);
|
|
4641
5092
|
useInput2((input2, key) => {
|
|
4642
5093
|
if (key.escape) return onCancel();
|
|
@@ -4652,12 +5103,6 @@ function BulkActionMenu({ count, selectionType, onSelect, onCancel }) {
|
|
|
4652
5103
|
setSelectedIdx((i) => Math.max(i - 1, 0));
|
|
4653
5104
|
}
|
|
4654
5105
|
});
|
|
4655
|
-
if (items.length === 0) {
|
|
4656
|
-
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
4657
|
-
/* @__PURE__ */ jsx7(Text7, { color: "yellow", children: "No bulk actions for mixed selection types." }),
|
|
4658
|
-
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: "Esc to cancel" })
|
|
4659
|
-
] });
|
|
4660
|
-
}
|
|
4661
5106
|
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
4662
5107
|
/* @__PURE__ */ jsxs7(Text7, { color: "cyan", bold: true, children: [
|
|
4663
5108
|
"Bulk action (",
|
|
@@ -4740,7 +5185,7 @@ import { tmpdir } from "os";
|
|
|
4740
5185
|
import { join as join5 } from "path";
|
|
4741
5186
|
import { TextInput } from "@inkjs/ui";
|
|
4742
5187
|
import { Box as Box8, Text as Text8, useInput as useInput3, useStdin } from "ink";
|
|
4743
|
-
import { useEffect as useEffect8, useRef as
|
|
5188
|
+
import { useEffect as useEffect8, useRef as useRef13, useState as useState10 } from "react";
|
|
4744
5189
|
import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
4745
5190
|
function CommentInput({
|
|
4746
5191
|
issueNumber,
|
|
@@ -4752,10 +5197,10 @@ function CommentInput({
|
|
|
4752
5197
|
const [value, setValue] = useState10("");
|
|
4753
5198
|
const [editing, setEditing] = useState10(false);
|
|
4754
5199
|
const { setRawMode } = useStdin();
|
|
4755
|
-
const onSubmitRef =
|
|
4756
|
-
const onCancelRef =
|
|
4757
|
-
const onPauseRef =
|
|
4758
|
-
const onResumeRef =
|
|
5200
|
+
const onSubmitRef = useRef13(onSubmit);
|
|
5201
|
+
const onCancelRef = useRef13(onCancel);
|
|
5202
|
+
const onPauseRef = useRef13(onPauseRefresh);
|
|
5203
|
+
const onResumeRef = useRef13(onResumeRefresh);
|
|
4759
5204
|
onSubmitRef.current = onSubmit;
|
|
4760
5205
|
onCancelRef.current = onCancel;
|
|
4761
5206
|
onPauseRef.current = onPauseRefresh;
|
|
@@ -4863,7 +5308,7 @@ var init_confirm_prompt = __esm({
|
|
|
4863
5308
|
// src/board/components/label-picker.tsx
|
|
4864
5309
|
import { Spinner } from "@inkjs/ui";
|
|
4865
5310
|
import { Box as Box10, Text as Text10, useInput as useInput5 } from "ink";
|
|
4866
|
-
import { useEffect as useEffect9, useRef as
|
|
5311
|
+
import { useEffect as useEffect9, useRef as useRef14, useState as useState11 } from "react";
|
|
4867
5312
|
import { jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
4868
5313
|
function LabelPicker({
|
|
4869
5314
|
repo,
|
|
@@ -4878,7 +5323,7 @@ function LabelPicker({
|
|
|
4878
5323
|
const [fetchAttempted, setFetchAttempted] = useState11(false);
|
|
4879
5324
|
const [selected, setSelected] = useState11(new Set(currentLabels));
|
|
4880
5325
|
const [cursor, setCursor] = useState11(0);
|
|
4881
|
-
const submittedRef =
|
|
5326
|
+
const submittedRef = useRef14(false);
|
|
4882
5327
|
useEffect9(() => {
|
|
4883
5328
|
if (labels !== null || fetchAttempted) return;
|
|
4884
5329
|
setFetchAttempted(true);
|
|
@@ -5099,7 +5544,7 @@ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync7, rmSync as r
|
|
|
5099
5544
|
import { tmpdir as tmpdir2 } from "os";
|
|
5100
5545
|
import { join as join6 } from "path";
|
|
5101
5546
|
import { Box as Box12, Text as Text12, useStdin as useStdin2 } from "ink";
|
|
5102
|
-
import { useEffect as useEffect10, useRef as
|
|
5547
|
+
import { useEffect as useEffect10, useRef as useRef15, useState as useState13 } from "react";
|
|
5103
5548
|
import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
5104
5549
|
function buildEditorFile(issue, repoName, statusOptions, repoLabels) {
|
|
5105
5550
|
const statusNames = statusOptions.map((o) => o.name).join(", ");
|
|
@@ -5185,9 +5630,9 @@ function EditIssueOverlay({
|
|
|
5185
5630
|
}) {
|
|
5186
5631
|
const [editing, setEditing] = useState13(true);
|
|
5187
5632
|
const { setRawMode } = useStdin2();
|
|
5188
|
-
const onDoneRef =
|
|
5189
|
-
const onPauseRef =
|
|
5190
|
-
const onResumeRef =
|
|
5633
|
+
const onDoneRef = useRef15(onDone);
|
|
5634
|
+
const onPauseRef = useRef15(onPauseRefresh);
|
|
5635
|
+
const onResumeRef = useRef15(onResumeRefresh);
|
|
5191
5636
|
onDoneRef.current = onDone;
|
|
5192
5637
|
onPauseRef.current = onPauseRefresh;
|
|
5193
5638
|
onResumeRef.current = onResumeRefresh;
|
|
@@ -5359,7 +5804,7 @@ var init_edit_issue_overlay = __esm({
|
|
|
5359
5804
|
|
|
5360
5805
|
// src/board/components/focus-mode.tsx
|
|
5361
5806
|
import { Box as Box13, Text as Text13, useInput as useInput7 } from "ink";
|
|
5362
|
-
import { useCallback as useCallback14, useEffect as useEffect11, useRef as
|
|
5807
|
+
import { useCallback as useCallback14, useEffect as useEffect11, useRef as useRef16, useState as useState14 } from "react";
|
|
5363
5808
|
import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
5364
5809
|
function formatTime(secs) {
|
|
5365
5810
|
const m = Math.floor(secs / 60);
|
|
@@ -5369,7 +5814,7 @@ function formatTime(secs) {
|
|
|
5369
5814
|
function FocusMode({ label, durationSec, onExit, onEndAction }) {
|
|
5370
5815
|
const [remaining, setRemaining] = useState14(durationSec);
|
|
5371
5816
|
const [timerDone, setTimerDone] = useState14(false);
|
|
5372
|
-
const bellSentRef =
|
|
5817
|
+
const bellSentRef = useRef16(false);
|
|
5373
5818
|
useEffect11(() => {
|
|
5374
5819
|
if (timerDone) return;
|
|
5375
5820
|
const interval = setInterval(() => {
|
|
@@ -5467,8 +5912,8 @@ var init_focus_mode = __esm({
|
|
|
5467
5912
|
// src/board/components/fuzzy-picker.tsx
|
|
5468
5913
|
import { TextInput as TextInput3 } from "@inkjs/ui";
|
|
5469
5914
|
import { Fzf } from "fzf";
|
|
5470
|
-
import { Box as Box14, Text as Text14, useInput as useInput8 } from "ink";
|
|
5471
|
-
import { useMemo as useMemo4, useState as useState15 } from "react";
|
|
5915
|
+
import { Box as Box14, Text as Text14, useInput as useInput8, useStdout } from "ink";
|
|
5916
|
+
import { useEffect as useEffect12, useMemo as useMemo4, useState as useState15 } from "react";
|
|
5472
5917
|
import { jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
5473
5918
|
function keepCursorVisible(cursor, offset, visible) {
|
|
5474
5919
|
if (cursor < offset) return cursor;
|
|
@@ -5484,7 +5929,7 @@ function FuzzyPicker({ repos, onSelect, onClose }) {
|
|
|
5484
5929
|
for (const rd of repos) {
|
|
5485
5930
|
for (const issue of rd.issues) {
|
|
5486
5931
|
items.push({
|
|
5487
|
-
navId:
|
|
5932
|
+
navId: makeIssueNavId(rd.repo.name, issue.number),
|
|
5488
5933
|
repoShortName: rd.repo.shortName,
|
|
5489
5934
|
number: issue.number,
|
|
5490
5935
|
title: issue.title,
|
|
@@ -5534,7 +5979,17 @@ function FuzzyPicker({ repos, onSelect, onClose }) {
|
|
|
5534
5979
|
upsert(fuzzyIndex.byLabel.find(query), WEIGHTS.label);
|
|
5535
5980
|
return [...scoreMap.values()].sort((a, b) => b.score - a.score).map((e) => e.item);
|
|
5536
5981
|
}, [query, fuzzyIndex, allIssues]);
|
|
5537
|
-
const
|
|
5982
|
+
const { stdout } = useStdout();
|
|
5983
|
+
const [termRows, setTermRows] = useState15(stdout?.rows ?? 24);
|
|
5984
|
+
useEffect12(() => {
|
|
5985
|
+
if (!stdout) return;
|
|
5986
|
+
const onResize = () => setTermRows(stdout.rows);
|
|
5987
|
+
stdout.on("resize", onResize);
|
|
5988
|
+
return () => {
|
|
5989
|
+
stdout.off("resize", onResize);
|
|
5990
|
+
};
|
|
5991
|
+
}, [stdout]);
|
|
5992
|
+
const VISIBLE = Math.min(termRows - 4, 15);
|
|
5538
5993
|
useInput8((input2, key) => {
|
|
5539
5994
|
if (key.downArrow || key.ctrl && input2 === "j") {
|
|
5540
5995
|
const newCursor = Math.min(cursor + 1, results.length - 1);
|
|
@@ -5644,6 +6099,7 @@ function FuzzyPicker({ repos, onSelect, onClose }) {
|
|
|
5644
6099
|
var init_fuzzy_picker = __esm({
|
|
5645
6100
|
"src/board/components/fuzzy-picker.tsx"() {
|
|
5646
6101
|
"use strict";
|
|
6102
|
+
init_board_utils();
|
|
5647
6103
|
}
|
|
5648
6104
|
});
|
|
5649
6105
|
|
|
@@ -5683,6 +6139,11 @@ var init_help_overlay = __esm({
|
|
|
5683
6139
|
items: [
|
|
5684
6140
|
{ key: "j / Down", desc: "Move down" },
|
|
5685
6141
|
{ key: "k / Up", desc: "Move up" },
|
|
6142
|
+
{ key: "Ctrl+d / PgDn", desc: "Half-page down" },
|
|
6143
|
+
{ key: "Ctrl+u / PgUp", desc: "Half-page up" },
|
|
6144
|
+
{ key: "Ctrl+f", desc: "Full page down" },
|
|
6145
|
+
{ key: "Ctrl+b", desc: "Full page up" },
|
|
6146
|
+
{ key: "G", desc: "Jump to bottom" },
|
|
5686
6147
|
{ key: "Tab", desc: "Next repo tab" },
|
|
5687
6148
|
{ key: "Shift+Tab", desc: "Previous repo tab" },
|
|
5688
6149
|
{ key: "1-9", desc: "Jump to repo tab by number" },
|
|
@@ -5744,7 +6205,7 @@ import { tmpdir as tmpdir3 } from "os";
|
|
|
5744
6205
|
import { join as join7 } from "path";
|
|
5745
6206
|
import { Spinner as Spinner2, TextInput as TextInput4 } from "@inkjs/ui";
|
|
5746
6207
|
import { Box as Box16, Text as Text16, useInput as useInput10, useStdin as useStdin3 } from "ink";
|
|
5747
|
-
import { useCallback as useCallback15, useEffect as
|
|
6208
|
+
import { useCallback as useCallback15, useEffect as useEffect13, useRef as useRef17, useState as useState16 } from "react";
|
|
5748
6209
|
import { jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
5749
6210
|
function NlCreateOverlay({
|
|
5750
6211
|
repos,
|
|
@@ -5763,12 +6224,12 @@ function NlCreateOverlay({
|
|
|
5763
6224
|
const [step, setStep] = useState16("input");
|
|
5764
6225
|
const [body, setBody] = useState16("");
|
|
5765
6226
|
const [editingBody, setEditingBody] = useState16(false);
|
|
5766
|
-
const submittedRef =
|
|
5767
|
-
const parseParamsRef =
|
|
5768
|
-
const onSubmitRef =
|
|
5769
|
-
const onCancelRef =
|
|
5770
|
-
const onPauseRef =
|
|
5771
|
-
const onResumeRef =
|
|
6227
|
+
const submittedRef = useRef17(false);
|
|
6228
|
+
const parseParamsRef = useRef17(null);
|
|
6229
|
+
const onSubmitRef = useRef17(onSubmit);
|
|
6230
|
+
const onCancelRef = useRef17(onCancel);
|
|
6231
|
+
const onPauseRef = useRef17(onPauseRefresh);
|
|
6232
|
+
const onResumeRef = useRef17(onResumeRefresh);
|
|
5772
6233
|
onSubmitRef.current = onSubmit;
|
|
5773
6234
|
onCancelRef.current = onCancel;
|
|
5774
6235
|
onPauseRef.current = onPauseRefresh;
|
|
@@ -5805,7 +6266,7 @@ function NlCreateOverlay({
|
|
|
5805
6266
|
setEditingBody(true);
|
|
5806
6267
|
}
|
|
5807
6268
|
});
|
|
5808
|
-
|
|
6269
|
+
useEffect13(() => {
|
|
5809
6270
|
if (!editingBody) return;
|
|
5810
6271
|
const editor = resolveEditor();
|
|
5811
6272
|
if (!editor) {
|
|
@@ -5849,7 +6310,7 @@ function NlCreateOverlay({
|
|
|
5849
6310
|
},
|
|
5850
6311
|
[selectedRepo, labelCache]
|
|
5851
6312
|
);
|
|
5852
|
-
|
|
6313
|
+
useEffect13(() => {
|
|
5853
6314
|
if (!(isParsing && parseParamsRef.current)) return;
|
|
5854
6315
|
const { input: capturedInput, validLabels } = parseParamsRef.current;
|
|
5855
6316
|
extractIssueFields(capturedInput, {
|
|
@@ -6105,7 +6566,7 @@ var init_search_bar = __esm({
|
|
|
6105
6566
|
|
|
6106
6567
|
// src/board/components/status-picker.tsx
|
|
6107
6568
|
import { Box as Box19, Text as Text19, useInput as useInput12 } from "ink";
|
|
6108
|
-
import { useRef as
|
|
6569
|
+
import { useRef as useRef18, useState as useState18 } from "react";
|
|
6109
6570
|
import { jsx as jsx19, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
6110
6571
|
function isTerminal(name) {
|
|
6111
6572
|
return TERMINAL_STATUS_RE.test(name);
|
|
@@ -6158,7 +6619,7 @@ function StatusPicker({
|
|
|
6158
6619
|
return idx >= 0 ? idx : 0;
|
|
6159
6620
|
});
|
|
6160
6621
|
const [confirmingTerminal, setConfirmingTerminal] = useState18(false);
|
|
6161
|
-
const submittedRef =
|
|
6622
|
+
const submittedRef = useRef18(false);
|
|
6162
6623
|
useInput12((input2, key) => {
|
|
6163
6624
|
if (confirmingTerminal) {
|
|
6164
6625
|
handleConfirmInput(input2, key, {
|
|
@@ -6492,7 +6953,6 @@ function OverlayRenderer({
|
|
|
6492
6953
|
onConfirmPick,
|
|
6493
6954
|
onCancelPick,
|
|
6494
6955
|
multiSelectCount,
|
|
6495
|
-
multiSelectType,
|
|
6496
6956
|
onBulkAction,
|
|
6497
6957
|
focusLabel,
|
|
6498
6958
|
focusKey,
|
|
@@ -6553,15 +7013,7 @@ function OverlayRenderer({
|
|
|
6553
7013
|
onCancel: onCancelPick
|
|
6554
7014
|
}
|
|
6555
7015
|
) : null,
|
|
6556
|
-
mode === "overlay:bulkAction" ? /* @__PURE__ */ jsx22(
|
|
6557
|
-
BulkActionMenu,
|
|
6558
|
-
{
|
|
6559
|
-
count: multiSelectCount,
|
|
6560
|
-
selectionType: multiSelectType,
|
|
6561
|
-
onSelect: onBulkAction,
|
|
6562
|
-
onCancel: onExitOverlay
|
|
6563
|
-
}
|
|
6564
|
-
) : null,
|
|
7016
|
+
mode === "overlay:bulkAction" ? /* @__PURE__ */ jsx22(BulkActionMenu, { count: multiSelectCount, onSelect: onBulkAction, onCancel: onExitOverlay }) : null,
|
|
6565
7017
|
mode === "focus" && focusLabel ? /* @__PURE__ */ jsx22(
|
|
6566
7018
|
FocusMode,
|
|
6567
7019
|
{
|
|
@@ -6745,30 +7197,73 @@ var init_panel_layout = __esm({
|
|
|
6745
7197
|
|
|
6746
7198
|
// src/board/components/repos-panel.tsx
|
|
6747
7199
|
import { Box as Box23, Text as Text22 } from "ink";
|
|
6748
|
-
import { jsx as jsx24, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
7200
|
+
import { Fragment as Fragment4, jsx as jsx24, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
6749
7201
|
function shortName(fullName) {
|
|
6750
7202
|
return fullName.includes("/") ? fullName.split("/")[1] ?? fullName : fullName;
|
|
6751
7203
|
}
|
|
6752
|
-
function ReposPanel({
|
|
7204
|
+
function ReposPanel({
|
|
7205
|
+
repos,
|
|
7206
|
+
selectedIdx,
|
|
7207
|
+
isActive,
|
|
7208
|
+
width,
|
|
7209
|
+
flexGrow,
|
|
7210
|
+
height
|
|
7211
|
+
}) {
|
|
6753
7212
|
const maxLabel = Math.max(4, width - 8);
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
7213
|
+
const contentRows = height != null ? Math.max(1, height - 2) : repos.length;
|
|
7214
|
+
const needsScroll = repos.length > contentRows;
|
|
7215
|
+
let visibleRepos = repos;
|
|
7216
|
+
let hasMoreAbove = false;
|
|
7217
|
+
let hasMoreBelow = false;
|
|
7218
|
+
let aboveCount = 0;
|
|
7219
|
+
let belowCount = 0;
|
|
7220
|
+
if (needsScroll) {
|
|
7221
|
+
const scroll = computeViewportScroll(
|
|
7222
|
+
repos.length,
|
|
7223
|
+
contentRows,
|
|
7224
|
+
selectedIdx,
|
|
7225
|
+
Math.max(0, selectedIdx - Math.floor(contentRows / 2))
|
|
7226
|
+
);
|
|
7227
|
+
visibleRepos = repos.slice(scroll.scrollOffset, scroll.scrollOffset + scroll.visibleCount);
|
|
7228
|
+
hasMoreAbove = scroll.hasMoreAbove;
|
|
7229
|
+
hasMoreBelow = scroll.hasMoreBelow;
|
|
7230
|
+
aboveCount = scroll.aboveCount;
|
|
7231
|
+
belowCount = scroll.belowCount;
|
|
7232
|
+
}
|
|
7233
|
+
return /* @__PURE__ */ jsx24(Panel, { title: "[1] Repos", isActive, width, flexGrow, height, children: repos.length === 0 ? /* @__PURE__ */ jsx24(Text22, { color: "gray", children: "\u2014" }) : /* @__PURE__ */ jsxs24(Fragment4, { children: [
|
|
7234
|
+
hasMoreAbove ? /* @__PURE__ */ jsxs24(Text22, { color: "gray", dimColor: true, children: [
|
|
7235
|
+
" ",
|
|
7236
|
+
"\u25B2 ",
|
|
7237
|
+
aboveCount,
|
|
7238
|
+
" more"
|
|
7239
|
+
] }) : null,
|
|
7240
|
+
visibleRepos.map((repo) => {
|
|
7241
|
+
const actualIdx = repos.indexOf(repo);
|
|
7242
|
+
const isSel = actualIdx === selectedIdx;
|
|
7243
|
+
const label = shortName(repo.name).slice(0, maxLabel);
|
|
7244
|
+
return /* @__PURE__ */ jsxs24(Box23, { children: [
|
|
7245
|
+
/* @__PURE__ */ jsxs24(Text22, { color: isSel ? "cyan" : isActive ? "white" : "gray", bold: isSel, children: [
|
|
7246
|
+
isSel ? "\u25BA " : " ",
|
|
7247
|
+
label
|
|
7248
|
+
] }),
|
|
7249
|
+
/* @__PURE__ */ jsxs24(Text22, { color: "gray", children: [
|
|
7250
|
+
" ",
|
|
7251
|
+
repo.openCount
|
|
7252
|
+
] })
|
|
7253
|
+
] }, repo.name);
|
|
7254
|
+
}),
|
|
7255
|
+
hasMoreBelow ? /* @__PURE__ */ jsxs24(Text22, { color: "gray", dimColor: true, children: [
|
|
7256
|
+
" ",
|
|
7257
|
+
"\u25BC ",
|
|
7258
|
+
belowCount,
|
|
7259
|
+
" more"
|
|
7260
|
+
] }) : null
|
|
7261
|
+
] }) });
|
|
6768
7262
|
}
|
|
6769
7263
|
var init_repos_panel = __esm({
|
|
6770
7264
|
"src/board/components/repos-panel.tsx"() {
|
|
6771
7265
|
"use strict";
|
|
7266
|
+
init_use_viewport_scroll();
|
|
6772
7267
|
init_panel();
|
|
6773
7268
|
}
|
|
6774
7269
|
});
|
|
@@ -7033,218 +7528,139 @@ function RowRenderer({
|
|
|
7033
7528
|
var init_row_renderer = __esm({
|
|
7034
7529
|
"src/board/components/row-renderer.tsx"() {
|
|
7035
7530
|
"use strict";
|
|
7036
|
-
init_issue_row();
|
|
7037
|
-
}
|
|
7038
|
-
});
|
|
7039
|
-
|
|
7040
|
-
// src/board/components/statuses-panel.tsx
|
|
7041
|
-
import { Box as Box26, Text as Text25 } from "ink";
|
|
7042
|
-
import { jsx as jsx27, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
7043
|
-
function StatusesPanel({
|
|
7044
|
-
groups,
|
|
7045
|
-
selectedIdx,
|
|
7046
|
-
isActive,
|
|
7047
|
-
width,
|
|
7048
|
-
flexGrow
|
|
7049
|
-
}) {
|
|
7050
|
-
const maxLabel = Math.max(4, width - 8);
|
|
7051
|
-
return /* @__PURE__ */ jsx27(Panel, { title: "[2] Statuses", isActive, width, flexGrow, children: groups.length === 0 ? /* @__PURE__ */ jsx27(Text25, { color: "gray", children: "\u2014" }) : groups.map((group, i) => {
|
|
7052
|
-
const isSel = i === selectedIdx;
|
|
7053
|
-
const label = group.label.slice(0, maxLabel);
|
|
7054
|
-
return /* @__PURE__ */ jsxs27(Box26, { children: [
|
|
7055
|
-
/* @__PURE__ */ jsxs27(Text25, { color: isSel ? "cyan" : isActive ? "white" : "gray", bold: isSel, children: [
|
|
7056
|
-
isSel ? "\u25BA " : " ",
|
|
7057
|
-
label
|
|
7058
|
-
] }),
|
|
7059
|
-
/* @__PURE__ */ jsxs27(Text25, { color: "gray", children: [
|
|
7060
|
-
" ",
|
|
7061
|
-
group.count
|
|
7062
|
-
] })
|
|
7063
|
-
] }, group.id);
|
|
7064
|
-
}) });
|
|
7065
|
-
}
|
|
7066
|
-
var init_statuses_panel = __esm({
|
|
7067
|
-
"src/board/components/statuses-panel.tsx"() {
|
|
7068
|
-
"use strict";
|
|
7069
|
-
init_panel();
|
|
7070
|
-
}
|
|
7071
|
-
});
|
|
7072
|
-
|
|
7073
|
-
// src/board/components/toast-container.tsx
|
|
7074
|
-
import { Spinner as Spinner3 } from "@inkjs/ui";
|
|
7075
|
-
import { Box as Box27, Text as Text26 } from "ink";
|
|
7076
|
-
import { Fragment as Fragment4, jsx as jsx28, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
7077
|
-
function ToastContainer({ toasts }) {
|
|
7078
|
-
if (toasts.length === 0) return null;
|
|
7079
|
-
return /* @__PURE__ */ jsx28(Box27, { flexDirection: "column", children: toasts.map((t) => /* @__PURE__ */ jsx28(Box27, { children: t.type === "loading" ? /* @__PURE__ */ jsxs28(Fragment4, { children: [
|
|
7080
|
-
/* @__PURE__ */ jsx28(Spinner3, { label: "" }),
|
|
7081
|
-
/* @__PURE__ */ jsxs28(Text26, { color: "cyan", children: [
|
|
7082
|
-
" ",
|
|
7083
|
-
t.message
|
|
7084
|
-
] })
|
|
7085
|
-
] }) : /* @__PURE__ */ jsxs28(Text26, { color: TYPE_COLORS[t.type], children: [
|
|
7086
|
-
TYPE_PREFIXES[t.type],
|
|
7087
|
-
" ",
|
|
7088
|
-
t.message,
|
|
7089
|
-
t.type === "error" ? /* @__PURE__ */ jsx28(Text26, { color: "gray", children: t.retry ? " [r]etry [d]ismiss" : " [d]ismiss" }) : null
|
|
7090
|
-
] }) }, t.id)) });
|
|
7091
|
-
}
|
|
7092
|
-
var TYPE_COLORS, TYPE_PREFIXES;
|
|
7093
|
-
var init_toast_container = __esm({
|
|
7094
|
-
"src/board/components/toast-container.tsx"() {
|
|
7095
|
-
"use strict";
|
|
7096
|
-
TYPE_COLORS = {
|
|
7097
|
-
info: "cyan",
|
|
7098
|
-
success: "green",
|
|
7099
|
-
error: "red",
|
|
7100
|
-
loading: "cyan"
|
|
7101
|
-
};
|
|
7102
|
-
TYPE_PREFIXES = {
|
|
7103
|
-
info: "\u2139",
|
|
7104
|
-
success: "\u2713",
|
|
7105
|
-
error: "\u2717"
|
|
7106
|
-
};
|
|
7107
|
-
}
|
|
7108
|
-
});
|
|
7109
|
-
|
|
7110
|
-
// src/board/components/dashboard.tsx
|
|
7111
|
-
import { execFile as execFile2, spawn as spawn4 } from "child_process";
|
|
7112
|
-
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
7113
|
-
import { Box as Box28, Text as Text27, useApp, useStdout } from "ink";
|
|
7114
|
-
import { useCallback as useCallback16, useEffect as useEffect13, useMemo as useMemo5, useRef as useRef18, useState as useState21 } from "react";
|
|
7115
|
-
import { Fragment as Fragment5, jsx as jsx29, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
7116
|
-
function resolvePhaseConfig(rc, config2, issueTitle, phase) {
|
|
7117
|
-
const phasePrompts = rc.workflow?.phasePrompts ?? config2.board.workflow?.phasePrompts ?? {};
|
|
7118
|
-
const template = phasePrompts[phase] ?? DEFAULT_PHASE_PROMPTS[phase];
|
|
7119
|
-
const startCommand = rc.claudeStartCommand ?? config2.board.claudeStartCommand;
|
|
7120
|
-
const slug = issueTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
7121
|
-
return { template, startCommand, slug };
|
|
7122
|
-
}
|
|
7123
|
-
function resolveStatusGroups(statusOptions, configuredGroups) {
|
|
7124
|
-
if (configuredGroups && configuredGroups.length > 0) {
|
|
7125
|
-
return configuredGroups.map((entry) => {
|
|
7126
|
-
const statuses = entry.split(",").map((s) => s.trim()).filter(Boolean);
|
|
7127
|
-
return { label: statuses[0] ?? entry, statuses };
|
|
7128
|
-
});
|
|
7129
|
-
}
|
|
7130
|
-
const nonTerminal = statusOptions.map((o) => o.name).filter((s) => !isTerminalStatus(s));
|
|
7131
|
-
if (nonTerminal.length > 0 && !nonTerminal.includes("Backlog")) {
|
|
7132
|
-
nonTerminal.push("Backlog");
|
|
7133
|
-
}
|
|
7134
|
-
const order = nonTerminal.length > 0 ? nonTerminal : ["In Progress", "Backlog"];
|
|
7135
|
-
return order.map((s) => ({ label: s, statuses: [s] }));
|
|
7136
|
-
}
|
|
7137
|
-
function issuePriorityRank(issue) {
|
|
7138
|
-
for (const label of issue.labels ?? []) {
|
|
7139
|
-
const rank = PRIORITY_RANK[label.name.toLowerCase()];
|
|
7140
|
-
if (rank != null) return rank;
|
|
7141
|
-
}
|
|
7142
|
-
return 99;
|
|
7143
|
-
}
|
|
7144
|
-
function groupByStatus(issues) {
|
|
7145
|
-
const groups = /* @__PURE__ */ new Map();
|
|
7146
|
-
for (const issue of issues) {
|
|
7147
|
-
const status = issue.projectStatus ?? "Backlog";
|
|
7148
|
-
const list = groups.get(status);
|
|
7149
|
-
if (list) {
|
|
7150
|
-
list.push(issue);
|
|
7151
|
-
} else {
|
|
7152
|
-
groups.set(status, [issue]);
|
|
7153
|
-
}
|
|
7154
|
-
}
|
|
7155
|
-
for (const [, list] of groups) {
|
|
7156
|
-
list.sort((a, b) => issuePriorityRank(a) - issuePriorityRank(b));
|
|
7157
|
-
}
|
|
7158
|
-
return groups;
|
|
7159
|
-
}
|
|
7160
|
-
function buildBoardTree(repos, activity) {
|
|
7161
|
-
const sections = repos.map((rd) => {
|
|
7162
|
-
const sectionId = rd.repo.name;
|
|
7163
|
-
if (rd.error) {
|
|
7164
|
-
return { repo: rd.repo, sectionId, groups: [], error: rd.error };
|
|
7165
|
-
}
|
|
7166
|
-
const statusGroupDefs = resolveStatusGroups(rd.statusOptions, rd.repo.statusGroups);
|
|
7167
|
-
const byStatus = groupByStatus(rd.issues);
|
|
7168
|
-
const coveredKeys = /* @__PURE__ */ new Set();
|
|
7169
|
-
const groups = [];
|
|
7170
|
-
for (const sg of statusGroupDefs) {
|
|
7171
|
-
const issues = [];
|
|
7172
|
-
for (const [status, statusIssues] of byStatus) {
|
|
7173
|
-
if (sg.statuses.some((s) => s.toLowerCase().trim() === status.toLowerCase().trim())) {
|
|
7174
|
-
issues.push(...statusIssues);
|
|
7175
|
-
}
|
|
7176
|
-
}
|
|
7177
|
-
if (issues.length === 0) continue;
|
|
7178
|
-
issues.sort((a, b) => issuePriorityRank(a) - issuePriorityRank(b));
|
|
7179
|
-
groups.push({ label: sg.label, subId: `sub:${sectionId}:${sg.label}`, issues });
|
|
7180
|
-
for (const s of sg.statuses) coveredKeys.add(s.toLowerCase().trim());
|
|
7181
|
-
}
|
|
7182
|
-
for (const [status, statusIssues] of byStatus) {
|
|
7183
|
-
if (!(coveredKeys.has(status.toLowerCase().trim()) || isTerminalStatus(status))) {
|
|
7184
|
-
groups.push({ label: status, subId: `sub:${sectionId}:${status}`, issues: statusIssues });
|
|
7185
|
-
}
|
|
7186
|
-
}
|
|
7187
|
-
return { repo: rd.repo, sectionId, groups, error: null };
|
|
7188
|
-
});
|
|
7189
|
-
return { activity, sections };
|
|
7190
|
-
}
|
|
7191
|
-
function buildNavItemsForRepo(sections, repoName, statusGroupId) {
|
|
7192
|
-
if (!repoName) return [];
|
|
7193
|
-
const section = sections.find((s) => s.sectionId === repoName);
|
|
7194
|
-
if (!section) return [];
|
|
7195
|
-
const activeGroup = section.groups.find((g) => g.subId === statusGroupId) ?? section.groups[0];
|
|
7196
|
-
if (!activeGroup) return [];
|
|
7197
|
-
return activeGroup.issues.map((issue) => ({
|
|
7198
|
-
id: `gh:${section.repo.name}:${issue.number}`,
|
|
7199
|
-
section: repoName,
|
|
7200
|
-
type: "item"
|
|
7201
|
-
}));
|
|
7202
|
-
}
|
|
7203
|
-
function buildFlatRowsForRepo(sections, repoName, statusGroupId) {
|
|
7204
|
-
if (!repoName) {
|
|
7205
|
-
return [
|
|
7206
|
-
{
|
|
7207
|
-
type: "subHeader",
|
|
7208
|
-
key: "select-repo",
|
|
7209
|
-
navId: null,
|
|
7210
|
-
text: "Select a repo in panel [1]"
|
|
7211
|
-
}
|
|
7212
|
-
];
|
|
7531
|
+
init_issue_row();
|
|
7213
7532
|
}
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7533
|
+
});
|
|
7534
|
+
|
|
7535
|
+
// src/board/components/statuses-panel.tsx
|
|
7536
|
+
import { Box as Box26, Text as Text25 } from "ink";
|
|
7537
|
+
import { Fragment as Fragment5, jsx as jsx27, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
7538
|
+
function StatusesPanel({
|
|
7539
|
+
groups,
|
|
7540
|
+
selectedIdx,
|
|
7541
|
+
isActive,
|
|
7542
|
+
width,
|
|
7543
|
+
flexGrow,
|
|
7544
|
+
height
|
|
7545
|
+
}) {
|
|
7546
|
+
const maxLabel = Math.max(4, width - 8);
|
|
7547
|
+
const contentRows = height != null ? Math.max(1, height - 2) : groups.length;
|
|
7548
|
+
const needsScroll = groups.length > contentRows;
|
|
7549
|
+
let visibleGroups = groups;
|
|
7550
|
+
let hasMoreAbove = false;
|
|
7551
|
+
let hasMoreBelow = false;
|
|
7552
|
+
let aboveCount = 0;
|
|
7553
|
+
let belowCount = 0;
|
|
7554
|
+
if (needsScroll) {
|
|
7555
|
+
const scroll = computeViewportScroll(
|
|
7556
|
+
groups.length,
|
|
7557
|
+
contentRows,
|
|
7558
|
+
selectedIdx,
|
|
7559
|
+
Math.max(0, selectedIdx - Math.floor(contentRows / 2))
|
|
7560
|
+
);
|
|
7561
|
+
visibleGroups = groups.slice(scroll.scrollOffset, scroll.scrollOffset + scroll.visibleCount);
|
|
7562
|
+
hasMoreAbove = scroll.hasMoreAbove;
|
|
7563
|
+
hasMoreBelow = scroll.hasMoreBelow;
|
|
7564
|
+
aboveCount = scroll.aboveCount;
|
|
7565
|
+
belowCount = scroll.belowCount;
|
|
7218
7566
|
}
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7567
|
+
return /* @__PURE__ */ jsx27(
|
|
7568
|
+
Panel,
|
|
7569
|
+
{
|
|
7570
|
+
title: "[2] Statuses",
|
|
7571
|
+
isActive,
|
|
7572
|
+
width,
|
|
7573
|
+
flexGrow,
|
|
7574
|
+
height,
|
|
7575
|
+
children: groups.length === 0 ? /* @__PURE__ */ jsx27(Text25, { color: "gray", children: "\u2014" }) : /* @__PURE__ */ jsxs27(Fragment5, { children: [
|
|
7576
|
+
hasMoreAbove ? /* @__PURE__ */ jsxs27(Text25, { color: "gray", dimColor: true, children: [
|
|
7577
|
+
" ",
|
|
7578
|
+
"\u25B2 ",
|
|
7579
|
+
aboveCount,
|
|
7580
|
+
" more"
|
|
7581
|
+
] }) : null,
|
|
7582
|
+
visibleGroups.map((group) => {
|
|
7583
|
+
const actualIdx = groups.indexOf(group);
|
|
7584
|
+
const isSel = actualIdx === selectedIdx;
|
|
7585
|
+
const label = group.label.slice(0, maxLabel);
|
|
7586
|
+
return /* @__PURE__ */ jsxs27(Box26, { children: [
|
|
7587
|
+
/* @__PURE__ */ jsxs27(Text25, { color: isSel ? "cyan" : isActive ? "white" : "gray", bold: isSel, children: [
|
|
7588
|
+
isSel ? "\u25BA " : " ",
|
|
7589
|
+
label
|
|
7590
|
+
] }),
|
|
7591
|
+
/* @__PURE__ */ jsxs27(Text25, { color: "gray", children: [
|
|
7592
|
+
" ",
|
|
7593
|
+
group.count
|
|
7594
|
+
] })
|
|
7595
|
+
] }, group.id);
|
|
7596
|
+
}),
|
|
7597
|
+
hasMoreBelow ? /* @__PURE__ */ jsxs27(Text25, { color: "gray", dimColor: true, children: [
|
|
7598
|
+
" ",
|
|
7599
|
+
"\u25BC ",
|
|
7600
|
+
belowCount,
|
|
7601
|
+
" more"
|
|
7602
|
+
] }) : null
|
|
7603
|
+
] })
|
|
7604
|
+
}
|
|
7605
|
+
);
|
|
7606
|
+
}
|
|
7607
|
+
var init_statuses_panel = __esm({
|
|
7608
|
+
"src/board/components/statuses-panel.tsx"() {
|
|
7609
|
+
"use strict";
|
|
7610
|
+
init_use_viewport_scroll();
|
|
7611
|
+
init_panel();
|
|
7228
7612
|
}
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7613
|
+
});
|
|
7614
|
+
|
|
7615
|
+
// src/board/components/toast-container.tsx
|
|
7616
|
+
import { Spinner as Spinner3 } from "@inkjs/ui";
|
|
7617
|
+
import { Box as Box27, Text as Text26 } from "ink";
|
|
7618
|
+
import { Fragment as Fragment6, jsx as jsx28, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
7619
|
+
function ToastContainer({ toasts }) {
|
|
7620
|
+
if (toasts.length === 0) return null;
|
|
7621
|
+
return /* @__PURE__ */ jsx28(Box27, { flexDirection: "column", children: toasts.map((t) => /* @__PURE__ */ jsx28(Box27, { children: t.type === "loading" ? /* @__PURE__ */ jsxs28(Fragment6, { children: [
|
|
7622
|
+
/* @__PURE__ */ jsx28(Spinner3, { label: "" }),
|
|
7623
|
+
/* @__PURE__ */ jsxs28(Text26, { color: "cyan", children: [
|
|
7624
|
+
" ",
|
|
7625
|
+
t.message
|
|
7626
|
+
] })
|
|
7627
|
+
] }) : /* @__PURE__ */ jsxs28(Text26, { color: TYPE_COLORS[t.type], children: [
|
|
7628
|
+
TYPE_PREFIXES[t.type],
|
|
7629
|
+
" ",
|
|
7630
|
+
t.message,
|
|
7631
|
+
t.type === "error" ? /* @__PURE__ */ jsx28(Text26, { color: "gray", children: t.retry ? " [r]etry [d]ismiss" : " [d]ismiss" }) : null
|
|
7632
|
+
] }) }, t.id)) });
|
|
7633
|
+
}
|
|
7634
|
+
var TYPE_COLORS, TYPE_PREFIXES;
|
|
7635
|
+
var init_toast_container = __esm({
|
|
7636
|
+
"src/board/components/toast-container.tsx"() {
|
|
7637
|
+
"use strict";
|
|
7638
|
+
TYPE_COLORS = {
|
|
7639
|
+
info: "cyan",
|
|
7640
|
+
success: "green",
|
|
7641
|
+
error: "red",
|
|
7642
|
+
loading: "cyan"
|
|
7643
|
+
};
|
|
7644
|
+
TYPE_PREFIXES = {
|
|
7645
|
+
info: "\u2139",
|
|
7646
|
+
success: "\u2713",
|
|
7647
|
+
error: "\u2717"
|
|
7648
|
+
};
|
|
7240
7649
|
}
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7650
|
+
});
|
|
7651
|
+
|
|
7652
|
+
// src/board/components/dashboard.tsx
|
|
7653
|
+
import { execFile as execFile2, spawn as spawn4 } from "child_process";
|
|
7654
|
+
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
7655
|
+
import { Box as Box28, Text as Text27, useApp, useStdout as useStdout2 } from "ink";
|
|
7656
|
+
import { useCallback as useCallback16, useEffect as useEffect14, useMemo as useMemo5, useRef as useRef19, useState as useState21 } from "react";
|
|
7657
|
+
import { Fragment as Fragment7, jsx as jsx29, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
7658
|
+
function resolvePhaseConfig(rc, config2, issueTitle, phase) {
|
|
7659
|
+
const phasePrompts = rc.workflow?.phasePrompts ?? config2.board.workflow?.phasePrompts ?? {};
|
|
7660
|
+
const template = phasePrompts[phase] ?? DEFAULT_PHASE_PROMPTS[phase];
|
|
7661
|
+
const startCommand = rc.claudeStartCommand ?? config2.board.claudeStartCommand;
|
|
7662
|
+
const slug = issueTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
7663
|
+
return { template, startCommand, slug };
|
|
7248
7664
|
}
|
|
7249
7665
|
function openInBrowser(url) {
|
|
7250
7666
|
try {
|
|
@@ -7255,19 +7671,9 @@ function openInBrowser(url) {
|
|
|
7255
7671
|
} catch {
|
|
7256
7672
|
}
|
|
7257
7673
|
}
|
|
7258
|
-
function findSelectedIssueWithRepo(repos, selectedId) {
|
|
7259
|
-
if (!selectedId?.startsWith("gh:")) return null;
|
|
7260
|
-
for (const rd of repos) {
|
|
7261
|
-
for (const issue of rd.issues) {
|
|
7262
|
-
if (`gh:${rd.repo.name}:${issue.number}` === selectedId)
|
|
7263
|
-
return { issue, repoName: rd.repo.name };
|
|
7264
|
-
}
|
|
7265
|
-
}
|
|
7266
|
-
return null;
|
|
7267
|
-
}
|
|
7268
7674
|
function RefreshAge({ lastRefresh }) {
|
|
7269
7675
|
const [, setTick] = useState21(0);
|
|
7270
|
-
|
|
7676
|
+
useEffect14(() => {
|
|
7271
7677
|
const id = setInterval(() => setTick((t) => t + 1), 3e4);
|
|
7272
7678
|
return () => clearInterval(id);
|
|
7273
7679
|
}, []);
|
|
@@ -7277,31 +7683,6 @@ function RefreshAge({ lastRefresh }) {
|
|
|
7277
7683
|
timeAgo(lastRefresh)
|
|
7278
7684
|
] });
|
|
7279
7685
|
}
|
|
7280
|
-
function matchesSearch(issue, query) {
|
|
7281
|
-
if (!query.trim()) return true;
|
|
7282
|
-
const tokens = query.toLowerCase().trim().split(/\s+/);
|
|
7283
|
-
const labels = issue.labels ?? [];
|
|
7284
|
-
const assignees = issue.assignees ?? [];
|
|
7285
|
-
return tokens.every((token) => {
|
|
7286
|
-
if (token.startsWith("#")) {
|
|
7287
|
-
const num = parseInt(token.slice(1), 10);
|
|
7288
|
-
return !Number.isNaN(num) && issue.number === num;
|
|
7289
|
-
}
|
|
7290
|
-
if (token.startsWith("@")) {
|
|
7291
|
-
const login = token.slice(1);
|
|
7292
|
-
return assignees.some((a) => a.login.toLowerCase().includes(login));
|
|
7293
|
-
}
|
|
7294
|
-
if (token === "unassigned") return assignees.length === 0;
|
|
7295
|
-
if (token === "assigned") return assignees.length > 0;
|
|
7296
|
-
if (issue.title.toLowerCase().includes(token)) return true;
|
|
7297
|
-
if (labels.some((l) => l.name.toLowerCase().includes(token))) return true;
|
|
7298
|
-
if (issue.projectStatus?.toLowerCase().includes(token)) return true;
|
|
7299
|
-
if (issue.customFields && Object.values(issue.customFields).some((v) => v.toLowerCase().includes(token)))
|
|
7300
|
-
return true;
|
|
7301
|
-
if (assignees.some((a) => a.login.toLowerCase().includes(token))) return true;
|
|
7302
|
-
return false;
|
|
7303
|
-
});
|
|
7304
|
-
}
|
|
7305
7686
|
function Dashboard({ config: config2, options, activeProfile }) {
|
|
7306
7687
|
const { exit } = useApp();
|
|
7307
7688
|
const refreshMs = config2.board.refreshInterval * 1e3;
|
|
@@ -7361,7 +7742,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7361
7742
|
enrichment: workflowState.enrichment,
|
|
7362
7743
|
onEnrichmentChange: handleEnrichmentChange
|
|
7363
7744
|
});
|
|
7364
|
-
|
|
7745
|
+
useEffect14(() => {
|
|
7365
7746
|
const last = logEntries[logEntries.length - 1];
|
|
7366
7747
|
if (last?.status === "error") setLogVisible(true);
|
|
7367
7748
|
}, [logEntries]);
|
|
@@ -7449,7 +7830,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7449
7830
|
return null;
|
|
7450
7831
|
}, []);
|
|
7451
7832
|
const multiSelect = useMultiSelect(getRepoForId);
|
|
7452
|
-
|
|
7833
|
+
useEffect14(() => {
|
|
7453
7834
|
if (multiSelect.count === 0) return;
|
|
7454
7835
|
const validIds = new Set(navItems.map((i) => i.id));
|
|
7455
7836
|
multiSelect.prune(validIds);
|
|
@@ -7466,9 +7847,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7466
7847
|
registerPendingMutation,
|
|
7467
7848
|
clearPendingMutation
|
|
7468
7849
|
});
|
|
7469
|
-
const pendingPickRef =
|
|
7470
|
-
const labelCacheRef =
|
|
7471
|
-
const commentCacheRef =
|
|
7850
|
+
const pendingPickRef = useRef19(null);
|
|
7851
|
+
const labelCacheRef = useRef19({});
|
|
7852
|
+
const commentCacheRef = useRef19({});
|
|
7472
7853
|
const [commentTick, setCommentTick] = useState21(0);
|
|
7473
7854
|
const handleFetchComments = useCallback16((repo, issueNumber) => {
|
|
7474
7855
|
const key = `${repo}:${issueNumber}`;
|
|
@@ -7563,12 +7944,12 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7563
7944
|
[toast, ui]
|
|
7564
7945
|
);
|
|
7565
7946
|
const [focusKey, setFocusKey] = useState21(0);
|
|
7566
|
-
const { stdout } =
|
|
7947
|
+
const { stdout } = useStdout2();
|
|
7567
7948
|
const [termSize, setTermSize] = useState21({
|
|
7568
7949
|
cols: stdout?.columns ?? 80,
|
|
7569
7950
|
rows: stdout?.rows ?? 24
|
|
7570
7951
|
});
|
|
7571
|
-
|
|
7952
|
+
useEffect14(() => {
|
|
7572
7953
|
if (!stdout) return;
|
|
7573
7954
|
const onResize = () => setTermSize({ cols: stdout.columns, rows: stdout.rows });
|
|
7574
7955
|
stdout.on("resize", onResize);
|
|
@@ -7619,32 +8000,22 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7619
8000
|
return { ...row, phaseIndicator, statusAgeDays };
|
|
7620
8001
|
});
|
|
7621
8002
|
}, [boardTree.sections, selectedRepoName, selectedStatusGroupId, workflowState, config2.repos]);
|
|
7622
|
-
const scrollRef = useRef18(0);
|
|
7623
|
-
const prevRepoRef = useRef18(null);
|
|
7624
|
-
const prevStatusRef = useRef18(null);
|
|
7625
|
-
if (selectedRepoName !== prevRepoRef.current || selectedStatusGroupId !== prevStatusRef.current) {
|
|
7626
|
-
prevRepoRef.current = selectedRepoName;
|
|
7627
|
-
prevStatusRef.current = selectedStatusGroupId;
|
|
7628
|
-
scrollRef.current = 0;
|
|
7629
|
-
}
|
|
7630
8003
|
const selectedRowIdx = useMemo5(
|
|
7631
8004
|
() => flatRows.findIndex((r) => r.navId === nav.selectedId),
|
|
7632
8005
|
[flatRows, nav.selectedId]
|
|
7633
8006
|
);
|
|
7634
|
-
|
|
7635
|
-
|
|
7636
|
-
|
|
7637
|
-
|
|
7638
|
-
|
|
7639
|
-
|
|
7640
|
-
|
|
7641
|
-
const
|
|
7642
|
-
|
|
7643
|
-
|
|
7644
|
-
|
|
7645
|
-
|
|
7646
|
-
const aboveCount = scrollRef.current;
|
|
7647
|
-
const belowCount = flatRows.length - scrollRef.current - contentRowCount;
|
|
8007
|
+
const scrollResetKey = `${selectedRepoName ?? ""}:${selectedStatusGroupId ?? ""}`;
|
|
8008
|
+
const viewport = useViewportScroll(
|
|
8009
|
+
flatRows.length,
|
|
8010
|
+
contentRowCount,
|
|
8011
|
+
selectedRowIdx,
|
|
8012
|
+
scrollResetKey
|
|
8013
|
+
);
|
|
8014
|
+
const { hasMoreAbove, hasMoreBelow, aboveCount, belowCount } = viewport;
|
|
8015
|
+
const visibleRows = flatRows.slice(
|
|
8016
|
+
viewport.scrollOffset,
|
|
8017
|
+
viewport.scrollOffset + viewport.visibleCount
|
|
8018
|
+
);
|
|
7648
8019
|
const selectedItem = useMemo5(() => {
|
|
7649
8020
|
const id = nav.selectedId;
|
|
7650
8021
|
if (!id || isHeaderId(id)) return { issue: null, repoName: null };
|
|
@@ -7953,7 +8324,6 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7953
8324
|
}
|
|
7954
8325
|
ui.enterTriage();
|
|
7955
8326
|
}, [nudges.candidates.length, toast, ui]);
|
|
7956
|
-
const multiSelectType = "github";
|
|
7957
8327
|
const handleBulkAction = useCallback16(
|
|
7958
8328
|
(action) => {
|
|
7959
8329
|
const ids = multiSelect.selected;
|
|
@@ -7987,15 +8357,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7987
8357
|
case "statusChange":
|
|
7988
8358
|
ui.enterStatus();
|
|
7989
8359
|
return;
|
|
7990
|
-
case "complete":
|
|
7991
|
-
case "delete":
|
|
7992
|
-
toast.info(`Bulk ${action.type} not yet implemented`);
|
|
7993
|
-
ui.exitOverlay();
|
|
7994
|
-
multiSelect.clear();
|
|
7995
|
-
return;
|
|
7996
8360
|
}
|
|
7997
8361
|
},
|
|
7998
|
-
[multiSelect, actions, ui
|
|
8362
|
+
[multiSelect, actions, ui]
|
|
7999
8363
|
);
|
|
8000
8364
|
const handleBulkStatusSelect = useCallback16(
|
|
8001
8365
|
(optionId) => {
|
|
@@ -8077,7 +8441,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8077
8441
|
onStatusEnter,
|
|
8078
8442
|
onActivityEnter,
|
|
8079
8443
|
showDetailPanel,
|
|
8080
|
-
leftPanelHidden
|
|
8444
|
+
leftPanelHidden,
|
|
8445
|
+
issuesPageSize: viewport.visibleCount
|
|
8081
8446
|
});
|
|
8082
8447
|
if (status === "loading" && !data) {
|
|
8083
8448
|
return /* @__PURE__ */ jsx29(Box28, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx29(Spinner4, { label: "Loading dashboard..." }) });
|
|
@@ -8193,10 +8558,10 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8193
8558
|
dateStr
|
|
8194
8559
|
] }),
|
|
8195
8560
|
/* @__PURE__ */ jsx29(Text27, { children: " " }),
|
|
8196
|
-
isRefreshing ? /* @__PURE__ */ jsxs29(
|
|
8561
|
+
isRefreshing ? /* @__PURE__ */ jsxs29(Fragment7, { children: [
|
|
8197
8562
|
/* @__PURE__ */ jsx29(Spinner4, { label: "" }),
|
|
8198
8563
|
/* @__PURE__ */ jsx29(Text27, { color: "cyan", children: " Refreshing..." })
|
|
8199
|
-
] }) : /* @__PURE__ */ jsxs29(
|
|
8564
|
+
] }) : /* @__PURE__ */ jsxs29(Fragment7, { children: [
|
|
8200
8565
|
/* @__PURE__ */ jsx29(RefreshAge, { lastRefresh }),
|
|
8201
8566
|
consecutiveFailures > 0 ? /* @__PURE__ */ jsx29(Text27, { color: "red", children: " (!)" }) : null
|
|
8202
8567
|
] }),
|
|
@@ -8231,7 +8596,6 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8231
8596
|
onConfirmPick: handleConfirmPick,
|
|
8232
8597
|
onCancelPick: handleCancelPick,
|
|
8233
8598
|
multiSelectCount: multiSelect.count,
|
|
8234
|
-
multiSelectType,
|
|
8235
8599
|
onBulkAction: handleBulkAction,
|
|
8236
8600
|
focusLabel,
|
|
8237
8601
|
focusKey,
|
|
@@ -8343,12 +8707,13 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8343
8707
|
)
|
|
8344
8708
|
] });
|
|
8345
8709
|
}
|
|
8346
|
-
var
|
|
8710
|
+
var CHROME_ROWS;
|
|
8347
8711
|
var init_dashboard = __esm({
|
|
8348
8712
|
"src/board/components/dashboard.tsx"() {
|
|
8349
8713
|
"use strict";
|
|
8350
8714
|
init_clipboard();
|
|
8351
8715
|
init_github();
|
|
8716
|
+
init_board_tree();
|
|
8352
8717
|
init_constants();
|
|
8353
8718
|
init_use_action_log();
|
|
8354
8719
|
init_use_actions();
|
|
@@ -8361,6 +8726,7 @@ var init_dashboard = __esm({
|
|
|
8361
8726
|
init_use_nudges();
|
|
8362
8727
|
init_use_toast();
|
|
8363
8728
|
init_use_ui_state();
|
|
8729
|
+
init_use_viewport_scroll();
|
|
8364
8730
|
init_use_workflow_state();
|
|
8365
8731
|
init_use_zen_mode();
|
|
8366
8732
|
init_launch_claude();
|
|
@@ -8376,12 +8742,6 @@ var init_dashboard = __esm({
|
|
|
8376
8742
|
init_row_renderer();
|
|
8377
8743
|
init_statuses_panel();
|
|
8378
8744
|
init_toast_container();
|
|
8379
|
-
PRIORITY_RANK = {
|
|
8380
|
-
"priority:critical": 0,
|
|
8381
|
-
"priority:high": 1,
|
|
8382
|
-
"priority:medium": 2,
|
|
8383
|
-
"priority:low": 3
|
|
8384
|
-
};
|
|
8385
8745
|
CHROME_ROWS = 3;
|
|
8386
8746
|
}
|
|
8387
8747
|
});
|
|
@@ -8434,7 +8794,8 @@ __export(fetch_exports, {
|
|
|
8434
8794
|
fetchDashboard: () => fetchDashboard,
|
|
8435
8795
|
fetchRecentActivity: () => fetchRecentActivity
|
|
8436
8796
|
});
|
|
8437
|
-
import { execFileSync as execFileSync4 } from "child_process";
|
|
8797
|
+
import { execFile as execFile3, execFileSync as execFileSync4 } from "child_process";
|
|
8798
|
+
import { promisify as promisify2 } from "util";
|
|
8438
8799
|
function extractSlackUrl(body) {
|
|
8439
8800
|
if (!body) return void 0;
|
|
8440
8801
|
const match = body.match(SLACK_URL_RE2);
|
|
@@ -8451,6 +8812,116 @@ function extractLinkedIssueNumbers(title, body) {
|
|
|
8451
8812
|
if (!matches) return [];
|
|
8452
8813
|
return [...new Set(matches.map((m) => parseInt(m.slice(1), 10)).filter((n) => n > 0))];
|
|
8453
8814
|
}
|
|
8815
|
+
function parseActivityOutput(output, shortName2) {
|
|
8816
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
8817
|
+
const events = [];
|
|
8818
|
+
for (const line of output.trim().split("\n")) {
|
|
8819
|
+
if (!line.trim()) continue;
|
|
8820
|
+
try {
|
|
8821
|
+
const ev = JSON.parse(line);
|
|
8822
|
+
const timestamp = new Date(ev.created_at);
|
|
8823
|
+
if (timestamp.getTime() < cutoff) continue;
|
|
8824
|
+
if (ev.type === "CreateEvent") {
|
|
8825
|
+
if (ev.ref_type !== "branch" || !ev.ref) continue;
|
|
8826
|
+
const issueNumbers = extractIssueNumbersFromBranch(ev.ref);
|
|
8827
|
+
for (const num of issueNumbers) {
|
|
8828
|
+
events.push({
|
|
8829
|
+
type: "branch_created",
|
|
8830
|
+
repoShortName: shortName2,
|
|
8831
|
+
issueNumber: num,
|
|
8832
|
+
actor: ev.actor,
|
|
8833
|
+
summary: `created branch ${ev.ref}`,
|
|
8834
|
+
timestamp,
|
|
8835
|
+
branchName: ev.ref
|
|
8836
|
+
});
|
|
8837
|
+
}
|
|
8838
|
+
continue;
|
|
8839
|
+
}
|
|
8840
|
+
if (!ev.number) continue;
|
|
8841
|
+
let eventType;
|
|
8842
|
+
let summary;
|
|
8843
|
+
let extras = {};
|
|
8844
|
+
if (ev.type === "IssueCommentEvent") {
|
|
8845
|
+
eventType = "comment";
|
|
8846
|
+
const preview = ev.body ? ev.body.slice(0, 60).replace(/\n/g, " ") : "";
|
|
8847
|
+
summary = `commented on #${ev.number}${preview ? ` \u2014 "${preview}${(ev.body?.length ?? 0) > 60 ? "..." : ""}"` : ""}`;
|
|
8848
|
+
} else if (ev.type === "IssuesEvent") {
|
|
8849
|
+
switch (ev.action) {
|
|
8850
|
+
case "opened":
|
|
8851
|
+
eventType = "opened";
|
|
8852
|
+
summary = `opened #${ev.number}: ${ev.title ?? ""}`;
|
|
8853
|
+
break;
|
|
8854
|
+
case "closed":
|
|
8855
|
+
eventType = "closed";
|
|
8856
|
+
summary = `closed #${ev.number}`;
|
|
8857
|
+
break;
|
|
8858
|
+
case "assigned":
|
|
8859
|
+
eventType = "assignment";
|
|
8860
|
+
summary = `assigned #${ev.number}`;
|
|
8861
|
+
break;
|
|
8862
|
+
case "labeled":
|
|
8863
|
+
eventType = "labeled";
|
|
8864
|
+
summary = `labeled #${ev.number}`;
|
|
8865
|
+
break;
|
|
8866
|
+
default:
|
|
8867
|
+
continue;
|
|
8868
|
+
}
|
|
8869
|
+
} else if (ev.type === "PullRequestEvent") {
|
|
8870
|
+
const prNumber = ev.number;
|
|
8871
|
+
extras = { prNumber };
|
|
8872
|
+
if (ev.action === "opened") {
|
|
8873
|
+
eventType = "pr_opened";
|
|
8874
|
+
summary = `opened PR #${prNumber}: ${ev.title ?? ""}`;
|
|
8875
|
+
} else if (ev.action === "closed" && ev.merged) {
|
|
8876
|
+
eventType = "pr_merged";
|
|
8877
|
+
summary = `merged PR #${prNumber}: ${ev.title ?? ""}`;
|
|
8878
|
+
} else if (ev.action === "closed") {
|
|
8879
|
+
eventType = "pr_closed";
|
|
8880
|
+
summary = `closed PR #${prNumber}`;
|
|
8881
|
+
} else {
|
|
8882
|
+
continue;
|
|
8883
|
+
}
|
|
8884
|
+
const linkedIssues = extractLinkedIssueNumbers(ev.title, ev.body);
|
|
8885
|
+
for (const issueNum of linkedIssues) {
|
|
8886
|
+
events.push({
|
|
8887
|
+
type: eventType,
|
|
8888
|
+
repoShortName: shortName2,
|
|
8889
|
+
issueNumber: issueNum,
|
|
8890
|
+
actor: ev.actor,
|
|
8891
|
+
summary,
|
|
8892
|
+
timestamp,
|
|
8893
|
+
prNumber
|
|
8894
|
+
});
|
|
8895
|
+
}
|
|
8896
|
+
if (linkedIssues.length === 0) {
|
|
8897
|
+
events.push({
|
|
8898
|
+
type: eventType,
|
|
8899
|
+
repoShortName: shortName2,
|
|
8900
|
+
issueNumber: prNumber,
|
|
8901
|
+
actor: ev.actor,
|
|
8902
|
+
summary,
|
|
8903
|
+
timestamp,
|
|
8904
|
+
prNumber
|
|
8905
|
+
});
|
|
8906
|
+
}
|
|
8907
|
+
continue;
|
|
8908
|
+
} else {
|
|
8909
|
+
continue;
|
|
8910
|
+
}
|
|
8911
|
+
events.push({
|
|
8912
|
+
type: eventType,
|
|
8913
|
+
repoShortName: shortName2,
|
|
8914
|
+
issueNumber: ev.number,
|
|
8915
|
+
actor: ev.actor,
|
|
8916
|
+
summary,
|
|
8917
|
+
timestamp,
|
|
8918
|
+
...extras
|
|
8919
|
+
});
|
|
8920
|
+
} catch {
|
|
8921
|
+
}
|
|
8922
|
+
}
|
|
8923
|
+
return events.slice(0, 15);
|
|
8924
|
+
}
|
|
8454
8925
|
function fetchRecentActivity(repoName, shortName2) {
|
|
8455
8926
|
try {
|
|
8456
8927
|
const output = execFileSync4(
|
|
@@ -8465,114 +8936,61 @@ function fetchRecentActivity(repoName, shortName2) {
|
|
|
8465
8936
|
],
|
|
8466
8937
|
{ encoding: "utf-8", timeout: 15e3, stdio: "pipe" }
|
|
8467
8938
|
);
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
|
|
8472
|
-
|
|
8473
|
-
|
|
8474
|
-
|
|
8475
|
-
|
|
8476
|
-
|
|
8477
|
-
|
|
8478
|
-
|
|
8479
|
-
|
|
8480
|
-
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8486
|
-
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
|
|
8492
|
-
|
|
8493
|
-
|
|
8494
|
-
|
|
8495
|
-
|
|
8496
|
-
|
|
8497
|
-
|
|
8498
|
-
|
|
8499
|
-
|
|
8500
|
-
|
|
8501
|
-
|
|
8502
|
-
case "opened":
|
|
8503
|
-
eventType = "opened";
|
|
8504
|
-
summary = `opened #${ev.number}: ${ev.title ?? ""}`;
|
|
8505
|
-
break;
|
|
8506
|
-
case "closed":
|
|
8507
|
-
eventType = "closed";
|
|
8508
|
-
summary = `closed #${ev.number}`;
|
|
8509
|
-
break;
|
|
8510
|
-
case "assigned":
|
|
8511
|
-
eventType = "assignment";
|
|
8512
|
-
summary = `assigned #${ev.number}`;
|
|
8513
|
-
break;
|
|
8514
|
-
case "labeled":
|
|
8515
|
-
eventType = "labeled";
|
|
8516
|
-
summary = `labeled #${ev.number}`;
|
|
8517
|
-
break;
|
|
8518
|
-
default:
|
|
8519
|
-
continue;
|
|
8520
|
-
}
|
|
8521
|
-
} else if (ev.type === "PullRequestEvent") {
|
|
8522
|
-
const prNumber = ev.number;
|
|
8523
|
-
extras = { prNumber };
|
|
8524
|
-
if (ev.action === "opened") {
|
|
8525
|
-
eventType = "pr_opened";
|
|
8526
|
-
summary = `opened PR #${prNumber}: ${ev.title ?? ""}`;
|
|
8527
|
-
} else if (ev.action === "closed" && ev.merged) {
|
|
8528
|
-
eventType = "pr_merged";
|
|
8529
|
-
summary = `merged PR #${prNumber}: ${ev.title ?? ""}`;
|
|
8530
|
-
} else if (ev.action === "closed") {
|
|
8531
|
-
eventType = "pr_closed";
|
|
8532
|
-
summary = `closed PR #${prNumber}`;
|
|
8533
|
-
} else {
|
|
8534
|
-
continue;
|
|
8535
|
-
}
|
|
8536
|
-
const linkedIssues = extractLinkedIssueNumbers(ev.title, ev.body);
|
|
8537
|
-
for (const issueNum of linkedIssues) {
|
|
8538
|
-
events.push({
|
|
8539
|
-
type: eventType,
|
|
8540
|
-
repoShortName: shortName2,
|
|
8541
|
-
issueNumber: issueNum,
|
|
8542
|
-
actor: ev.actor,
|
|
8543
|
-
summary,
|
|
8544
|
-
timestamp,
|
|
8545
|
-
prNumber
|
|
8546
|
-
});
|
|
8547
|
-
}
|
|
8548
|
-
if (linkedIssues.length === 0) {
|
|
8549
|
-
events.push({
|
|
8550
|
-
type: eventType,
|
|
8551
|
-
repoShortName: shortName2,
|
|
8552
|
-
issueNumber: prNumber,
|
|
8553
|
-
actor: ev.actor,
|
|
8554
|
-
summary,
|
|
8555
|
-
timestamp,
|
|
8556
|
-
prNumber
|
|
8557
|
-
});
|
|
8558
|
-
}
|
|
8559
|
-
continue;
|
|
8560
|
-
} else {
|
|
8561
|
-
continue;
|
|
8562
|
-
}
|
|
8563
|
-
events.push({
|
|
8564
|
-
type: eventType,
|
|
8565
|
-
repoShortName: shortName2,
|
|
8566
|
-
issueNumber: ev.number,
|
|
8567
|
-
actor: ev.actor,
|
|
8568
|
-
summary,
|
|
8569
|
-
timestamp,
|
|
8570
|
-
...extras
|
|
8571
|
-
});
|
|
8572
|
-
} catch {
|
|
8573
|
-
}
|
|
8939
|
+
return parseActivityOutput(output, shortName2);
|
|
8940
|
+
} catch {
|
|
8941
|
+
return [];
|
|
8942
|
+
}
|
|
8943
|
+
}
|
|
8944
|
+
async function fetchRepoDataAsync(repo, assignee) {
|
|
8945
|
+
try {
|
|
8946
|
+
const fetchOpts = {};
|
|
8947
|
+
if (assignee) fetchOpts.assignee = assignee;
|
|
8948
|
+
const issues = await fetchRepoIssuesAsync(repo.name, fetchOpts);
|
|
8949
|
+
let enrichedIssues = issues;
|
|
8950
|
+
let statusOptions = [];
|
|
8951
|
+
try {
|
|
8952
|
+
const [enrichMap, opts] = await Promise.all([
|
|
8953
|
+
fetchProjectEnrichmentAsync(repo.name, repo.projectNumber),
|
|
8954
|
+
fetchProjectStatusOptionsAsync(repo.name, repo.projectNumber, repo.statusFieldId)
|
|
8955
|
+
]);
|
|
8956
|
+
enrichedIssues = issues.map((issue) => {
|
|
8957
|
+
const e = enrichMap.get(issue.number);
|
|
8958
|
+
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
8959
|
+
return {
|
|
8960
|
+
...issue,
|
|
8961
|
+
...e?.targetDate !== void 0 ? { targetDate: e.targetDate } : {},
|
|
8962
|
+
...e?.projectStatus !== void 0 ? { projectStatus: e.projectStatus } : {},
|
|
8963
|
+
...e?.customFields !== void 0 ? { customFields: e.customFields } : {},
|
|
8964
|
+
...slackUrl ? { slackThreadUrl: slackUrl } : {}
|
|
8965
|
+
};
|
|
8966
|
+
});
|
|
8967
|
+
statusOptions = opts;
|
|
8968
|
+
} catch {
|
|
8969
|
+
enrichedIssues = issues.map((issue) => {
|
|
8970
|
+
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
8971
|
+
return slackUrl ? { ...issue, slackThreadUrl: slackUrl } : issue;
|
|
8972
|
+
});
|
|
8574
8973
|
}
|
|
8575
|
-
return
|
|
8974
|
+
return { repo, issues: enrichedIssues, statusOptions, error: null };
|
|
8975
|
+
} catch (err) {
|
|
8976
|
+
return { repo, issues: [], statusOptions: [], error: formatError(err) };
|
|
8977
|
+
}
|
|
8978
|
+
}
|
|
8979
|
+
async function fetchRecentActivityAsync(repoName, shortName2) {
|
|
8980
|
+
try {
|
|
8981
|
+
const { stdout } = await execFileAsync2(
|
|
8982
|
+
"gh",
|
|
8983
|
+
[
|
|
8984
|
+
"api",
|
|
8985
|
+
`repos/${repoName}/events`,
|
|
8986
|
+
"-f",
|
|
8987
|
+
"per_page=30",
|
|
8988
|
+
"-q",
|
|
8989
|
+
'.[] | select(.type == "IssuesEvent" or .type == "IssueCommentEvent" or .type == "PullRequestEvent" or .type == "CreateEvent") | {type: .type, actor: .actor.login, action: .payload.action, number: (.payload.issue.number // .payload.pull_request.number), title: (.payload.issue.title // .payload.pull_request.title), body: (.payload.comment.body // .payload.pull_request.body), created_at: .created_at, ref: .payload.ref, ref_type: .payload.ref_type, merged: .payload.pull_request.merged}'
|
|
8990
|
+
],
|
|
8991
|
+
{ encoding: "utf-8", timeout: 15e3 }
|
|
8992
|
+
);
|
|
8993
|
+
return parseActivityOutput(stdout, shortName2);
|
|
8576
8994
|
} catch {
|
|
8577
8995
|
return [];
|
|
8578
8996
|
}
|
|
@@ -8581,62 +8999,25 @@ async function fetchDashboard(config2, options = {}) {
|
|
|
8581
8999
|
const repos = options.repoFilter ? config2.repos.filter(
|
|
8582
9000
|
(r) => r.shortName === options.repoFilter || r.name === options.repoFilter
|
|
8583
9001
|
) : config2.repos;
|
|
8584
|
-
const
|
|
8585
|
-
|
|
8586
|
-
|
|
8587
|
-
|
|
8588
|
-
|
|
8589
|
-
|
|
8590
|
-
const issues = fetchRepoIssues(repo.name, fetchOpts);
|
|
8591
|
-
let enrichedIssues = issues;
|
|
8592
|
-
let statusOptions = [];
|
|
8593
|
-
try {
|
|
8594
|
-
const enrichMap = fetchProjectEnrichment(repo.name, repo.projectNumber);
|
|
8595
|
-
enrichedIssues = issues.map((issue) => {
|
|
8596
|
-
const e = enrichMap.get(issue.number);
|
|
8597
|
-
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
8598
|
-
return {
|
|
8599
|
-
...issue,
|
|
8600
|
-
...e?.targetDate !== void 0 ? { targetDate: e.targetDate } : {},
|
|
8601
|
-
...e?.projectStatus !== void 0 ? { projectStatus: e.projectStatus } : {},
|
|
8602
|
-
...e?.customFields !== void 0 ? { customFields: e.customFields } : {},
|
|
8603
|
-
...slackUrl ? { slackThreadUrl: slackUrl } : {}
|
|
8604
|
-
};
|
|
8605
|
-
});
|
|
8606
|
-
statusOptions = fetchProjectStatusOptions(
|
|
8607
|
-
repo.name,
|
|
8608
|
-
repo.projectNumber,
|
|
8609
|
-
repo.statusFieldId
|
|
8610
|
-
);
|
|
8611
|
-
} catch {
|
|
8612
|
-
enrichedIssues = issues.map((issue) => {
|
|
8613
|
-
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
8614
|
-
return slackUrl ? { ...issue, slackThreadUrl: slackUrl } : issue;
|
|
8615
|
-
});
|
|
8616
|
-
}
|
|
8617
|
-
return { repo, issues: enrichedIssues, statusOptions, error: null };
|
|
8618
|
-
} catch (err) {
|
|
8619
|
-
return { repo, issues: [], statusOptions: [], error: formatError(err) };
|
|
8620
|
-
}
|
|
8621
|
-
});
|
|
8622
|
-
const activity = [];
|
|
8623
|
-
for (const repo of repos) {
|
|
8624
|
-
const events = fetchRecentActivity(repo.name, repo.shortName);
|
|
8625
|
-
activity.push(...events);
|
|
8626
|
-
}
|
|
8627
|
-
activity.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
9002
|
+
const assignee = options.mineOnly ? config2.board.assignee : void 0;
|
|
9003
|
+
const [repoData, ...activityResults] = await Promise.all([
|
|
9004
|
+
Promise.all(repos.map((repo) => fetchRepoDataAsync(repo, assignee))),
|
|
9005
|
+
...repos.map((repo) => fetchRecentActivityAsync(repo.name, repo.shortName))
|
|
9006
|
+
]);
|
|
9007
|
+
const activity = activityResults.flat().sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
8628
9008
|
return {
|
|
8629
9009
|
repos: repoData,
|
|
8630
9010
|
activity: activity.slice(0, 15),
|
|
8631
9011
|
fetchedAt: /* @__PURE__ */ new Date()
|
|
8632
9012
|
};
|
|
8633
9013
|
}
|
|
8634
|
-
var SLACK_URL_RE2;
|
|
9014
|
+
var execFileAsync2, SLACK_URL_RE2;
|
|
8635
9015
|
var init_fetch = __esm({
|
|
8636
9016
|
"src/board/fetch.ts"() {
|
|
8637
9017
|
"use strict";
|
|
8638
9018
|
init_github();
|
|
8639
9019
|
init_utils();
|
|
9020
|
+
execFileAsync2 = promisify2(execFile3);
|
|
8640
9021
|
SLACK_URL_RE2 = /https:\/\/[^/]+\.slack\.com\/archives\/[A-Z0-9]+\/p[0-9]+/i;
|
|
8641
9022
|
}
|
|
8642
9023
|
});
|
|
@@ -8795,8 +9176,8 @@ var init_format_static = __esm({
|
|
|
8795
9176
|
// src/cli.ts
|
|
8796
9177
|
init_ai();
|
|
8797
9178
|
init_config();
|
|
8798
|
-
import { execFile as
|
|
8799
|
-
import { promisify as
|
|
9179
|
+
import { execFile as execFile4, execFileSync as execFileSync5 } from "child_process";
|
|
9180
|
+
import { promisify as promisify3 } from "util";
|
|
8800
9181
|
import { Command } from "commander";
|
|
8801
9182
|
|
|
8802
9183
|
// src/init.ts
|
|
@@ -9397,7 +9778,7 @@ if (major < 22) {
|
|
|
9397
9778
|
);
|
|
9398
9779
|
process.exit(1);
|
|
9399
9780
|
}
|
|
9400
|
-
var
|
|
9781
|
+
var execFileAsync3 = promisify3(execFile4);
|
|
9401
9782
|
async function resolveRef(ref, config2) {
|
|
9402
9783
|
const { parseIssueRef: parseIssueRef2 } = await Promise.resolve().then(() => (init_pick(), pick_exports));
|
|
9403
9784
|
try {
|
|
@@ -9407,7 +9788,7 @@ async function resolveRef(ref, config2) {
|
|
|
9407
9788
|
}
|
|
9408
9789
|
}
|
|
9409
9790
|
var program = new Command();
|
|
9410
|
-
program.name("hog").description("Personal command deck \u2014 GitHub Projects dashboard with workflow orchestration").version("1.
|
|
9791
|
+
program.name("hog").description("Personal command deck \u2014 GitHub Projects dashboard with workflow orchestration").version("1.24.0").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
9411
9792
|
const opts = thisCommand.opts();
|
|
9412
9793
|
if (opts.json) setFormat("json");
|
|
9413
9794
|
if (opts.human) setFormat("human");
|
|
@@ -9534,6 +9915,40 @@ config.command("show").description("Show full configuration").action(() => {
|
|
|
9534
9915
|
}
|
|
9535
9916
|
}
|
|
9536
9917
|
});
|
|
9918
|
+
config.command("set <path> <value>").description("Set a configuration value (dot-notation path, e.g. board.assignee)").action((path, rawValue) => {
|
|
9919
|
+
const cfg = loadFullConfig();
|
|
9920
|
+
let value = rawValue;
|
|
9921
|
+
if (rawValue === "true") value = true;
|
|
9922
|
+
else if (rawValue === "false") value = false;
|
|
9923
|
+
else if (rawValue !== "" && !Number.isNaN(Number(rawValue))) value = Number(rawValue);
|
|
9924
|
+
const keys = path.split(".");
|
|
9925
|
+
if (keys.length === 0) {
|
|
9926
|
+
errorOut("Invalid path: empty path");
|
|
9927
|
+
}
|
|
9928
|
+
const updated = JSON.parse(JSON.stringify(cfg));
|
|
9929
|
+
let target = updated;
|
|
9930
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
9931
|
+
const key = keys[i];
|
|
9932
|
+
const next = target[key];
|
|
9933
|
+
if (next === void 0 || next === null || typeof next !== "object" || Array.isArray(next)) {
|
|
9934
|
+
errorOut(`Invalid path: "${keys.slice(0, i + 1).join(".")}" is not a nested object`);
|
|
9935
|
+
}
|
|
9936
|
+
target = next;
|
|
9937
|
+
}
|
|
9938
|
+
const lastKey = keys[keys.length - 1];
|
|
9939
|
+
target[lastKey] = value;
|
|
9940
|
+
const result = validateConfigSchema(updated);
|
|
9941
|
+
if (!result.success) {
|
|
9942
|
+
errorOut(`Validation failed:
|
|
9943
|
+
${result.error}`);
|
|
9944
|
+
}
|
|
9945
|
+
saveFullConfig(result.data);
|
|
9946
|
+
if (useJson()) {
|
|
9947
|
+
jsonOut({ ok: true, path, value });
|
|
9948
|
+
} else {
|
|
9949
|
+
printSuccess(`Set ${path} = ${JSON.stringify(value)}`);
|
|
9950
|
+
}
|
|
9951
|
+
});
|
|
9537
9952
|
config.command("repos").description("List configured repositories").action(() => {
|
|
9538
9953
|
const cfg = loadFullConfig();
|
|
9539
9954
|
if (useJson()) {
|
|
@@ -9794,7 +10209,7 @@ issueCommand.command("create <text>").description("Create a GitHub issue from na
|
|
|
9794
10209
|
}
|
|
9795
10210
|
try {
|
|
9796
10211
|
if (json) {
|
|
9797
|
-
const output = await
|
|
10212
|
+
const output = await execFileAsync3("gh", ghArgs, { encoding: "utf-8", timeout: 6e4 });
|
|
9798
10213
|
const url = output.stdout.trim();
|
|
9799
10214
|
const issueNumber = Number.parseInt(url.split("/").pop() ?? "0", 10);
|
|
9800
10215
|
jsonOut({ ok: true, data: { url, issueNumber, repo } });
|
|
@@ -10019,7 +10434,7 @@ issueCommand.command("edit <issueRef>").description("Edit issue fields (title, b
|
|
|
10019
10434
|
if (opts.assignee) ghArgs.push("--add-assignee", opts.assignee);
|
|
10020
10435
|
if (opts.removeAssignee) ghArgs.push("--remove-assignee", opts.removeAssignee);
|
|
10021
10436
|
if (useJson()) {
|
|
10022
|
-
await
|
|
10437
|
+
await execFileAsync3("gh", ghArgs, { encoding: "utf-8", timeout: 3e4 });
|
|
10023
10438
|
jsonOut({ ok: true, data: { issue: ref.issueNumber, changes } });
|
|
10024
10439
|
} else {
|
|
10025
10440
|
execFileSync5("gh", ghArgs, { stdio: "inherit" });
|