@ondrej-svec/hog 1.23.1 → 1.24.1
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 +1203 -785
- 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,48 +1207,23 @@ 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(
|
|
@@ -1180,42 +1248,20 @@ async function updateProjectItemStatusAsync(repo, issueNumber, projectConfig) {
|
|
|
1180
1248
|
"-F",
|
|
1181
1249
|
`itemId=${projectItem.id}`,
|
|
1182
1250
|
"-F",
|
|
1183
|
-
`fieldId=${statusFieldId}`,
|
|
1251
|
+
`fieldId=${projectConfig.statusFieldId}`,
|
|
1184
1252
|
"-F",
|
|
1185
|
-
`optionId=${optionId}`
|
|
1253
|
+
`optionId=${projectConfig.optionId}`
|
|
1186
1254
|
]);
|
|
1187
1255
|
}
|
|
1188
1256
|
async function updateProjectItemDateAsync(repo, issueNumber, projectConfig, dueDate) {
|
|
1189
1257
|
const [owner, repoName] = repo.split("/");
|
|
1190
1258
|
if (!(owner && repoName)) return;
|
|
1191
|
-
const
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
id
|
|
1198
|
-
project { number }
|
|
1199
|
-
}
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
`;
|
|
1205
|
-
const findResult = await runGhJsonAsync([
|
|
1206
|
-
"api",
|
|
1207
|
-
"graphql",
|
|
1208
|
-
"-f",
|
|
1209
|
-
`query=${findItemQuery}`,
|
|
1210
|
-
"-F",
|
|
1211
|
-
`owner=${owner}`,
|
|
1212
|
-
"-F",
|
|
1213
|
-
`repo=${repoName}`,
|
|
1214
|
-
"-F",
|
|
1215
|
-
`issueNumber=${String(issueNumber)}`
|
|
1216
|
-
]);
|
|
1217
|
-
const items = findResult?.data?.repository?.issue?.projectItems?.nodes ?? [];
|
|
1218
|
-
const projectItem = items.find((item) => item?.project?.number === projectConfig.projectNumber);
|
|
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();
|
|
2939
3265
|
return;
|
|
2940
3266
|
}
|
|
2941
|
-
if (input2 === "
|
|
2942
|
-
nav.
|
|
3267
|
+
if (key.ctrl && input2 === "f") {
|
|
3268
|
+
nav.moveDownBy(issuesPageSize);
|
|
3269
|
+
return;
|
|
3270
|
+
}
|
|
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,
|
|
@@ -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
|
{
|
|
@@ -6689,6 +7141,7 @@ function getDetailWidth(cols) {
|
|
|
6689
7141
|
function PanelLayout({
|
|
6690
7142
|
cols,
|
|
6691
7143
|
issuesPanelHeight,
|
|
7144
|
+
totalHeight,
|
|
6692
7145
|
reposPanel,
|
|
6693
7146
|
statusesPanel,
|
|
6694
7147
|
issuesPanel,
|
|
@@ -6699,7 +7152,7 @@ function PanelLayout({
|
|
|
6699
7152
|
const mode = getLayoutMode(cols);
|
|
6700
7153
|
if (mode === "wide") {
|
|
6701
7154
|
const detailWidth = getDetailWidth(cols);
|
|
6702
|
-
return /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", children: [
|
|
7155
|
+
return /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", height: totalHeight, overflow: "hidden", children: [
|
|
6703
7156
|
/* @__PURE__ */ jsxs23(Box22, { height: issuesPanelHeight, children: [
|
|
6704
7157
|
!hideLeftPanel ? /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", width: LEFT_COL_WIDTH, children: [
|
|
6705
7158
|
reposPanel,
|
|
@@ -6712,7 +7165,7 @@ function PanelLayout({
|
|
|
6712
7165
|
] });
|
|
6713
7166
|
}
|
|
6714
7167
|
if (mode === "medium") {
|
|
6715
|
-
return /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", children: [
|
|
7168
|
+
return /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", height: totalHeight, overflow: "hidden", children: [
|
|
6716
7169
|
/* @__PURE__ */ jsxs23(Box22, { height: issuesPanelHeight, children: [
|
|
6717
7170
|
!hideLeftPanel ? /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", width: LEFT_COL_WIDTH, children: [
|
|
6718
7171
|
reposPanel,
|
|
@@ -6723,10 +7176,11 @@ function PanelLayout({
|
|
|
6723
7176
|
/* @__PURE__ */ jsx23(Box22, { height: ACTIVITY_HEIGHT, children: activityPanel })
|
|
6724
7177
|
] });
|
|
6725
7178
|
}
|
|
6726
|
-
|
|
7179
|
+
const STACKED_LEFT_HEIGHT = 6;
|
|
7180
|
+
return /* @__PURE__ */ jsxs23(Box22, { flexDirection: "column", height: totalHeight, overflow: "hidden", children: [
|
|
6727
7181
|
!hideLeftPanel ? /* @__PURE__ */ jsxs23(Fragment3, { children: [
|
|
6728
|
-
reposPanel,
|
|
6729
|
-
statusesPanel
|
|
7182
|
+
/* @__PURE__ */ jsx23(Box22, { height: STACKED_LEFT_HEIGHT, overflow: "hidden", children: reposPanel }),
|
|
7183
|
+
/* @__PURE__ */ jsx23(Box22, { height: STACKED_LEFT_HEIGHT, overflow: "hidden", children: statusesPanel })
|
|
6730
7184
|
] }) : null,
|
|
6731
7185
|
/* @__PURE__ */ jsx23(Box22, { flexGrow: 1, flexDirection: "column", children: issuesPanel }),
|
|
6732
7186
|
/* @__PURE__ */ jsx23(Box22, { height: ACTIVITY_HEIGHT, children: activityPanel })
|
|
@@ -6745,30 +7199,73 @@ var init_panel_layout = __esm({
|
|
|
6745
7199
|
|
|
6746
7200
|
// src/board/components/repos-panel.tsx
|
|
6747
7201
|
import { Box as Box23, Text as Text22 } from "ink";
|
|
6748
|
-
import { jsx as jsx24, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
7202
|
+
import { Fragment as Fragment4, jsx as jsx24, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
6749
7203
|
function shortName(fullName) {
|
|
6750
7204
|
return fullName.includes("/") ? fullName.split("/")[1] ?? fullName : fullName;
|
|
6751
7205
|
}
|
|
6752
|
-
function ReposPanel({
|
|
7206
|
+
function ReposPanel({
|
|
7207
|
+
repos,
|
|
7208
|
+
selectedIdx,
|
|
7209
|
+
isActive,
|
|
7210
|
+
width,
|
|
7211
|
+
flexGrow,
|
|
7212
|
+
height
|
|
7213
|
+
}) {
|
|
6753
7214
|
const maxLabel = Math.max(4, width - 8);
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
7215
|
+
const contentRows = height != null ? Math.max(1, height - 2) : repos.length;
|
|
7216
|
+
const needsScroll = repos.length > contentRows;
|
|
7217
|
+
let visibleRepos = repos;
|
|
7218
|
+
let hasMoreAbove = false;
|
|
7219
|
+
let hasMoreBelow = false;
|
|
7220
|
+
let aboveCount = 0;
|
|
7221
|
+
let belowCount = 0;
|
|
7222
|
+
if (needsScroll) {
|
|
7223
|
+
const scroll = computeViewportScroll(
|
|
7224
|
+
repos.length,
|
|
7225
|
+
contentRows,
|
|
7226
|
+
selectedIdx,
|
|
7227
|
+
Math.max(0, selectedIdx - Math.floor(contentRows / 2))
|
|
7228
|
+
);
|
|
7229
|
+
visibleRepos = repos.slice(scroll.scrollOffset, scroll.scrollOffset + scroll.visibleCount);
|
|
7230
|
+
hasMoreAbove = scroll.hasMoreAbove;
|
|
7231
|
+
hasMoreBelow = scroll.hasMoreBelow;
|
|
7232
|
+
aboveCount = scroll.aboveCount;
|
|
7233
|
+
belowCount = scroll.belowCount;
|
|
7234
|
+
}
|
|
7235
|
+
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: [
|
|
7236
|
+
hasMoreAbove ? /* @__PURE__ */ jsxs24(Text22, { color: "gray", dimColor: true, children: [
|
|
7237
|
+
" ",
|
|
7238
|
+
"\u25B2 ",
|
|
7239
|
+
aboveCount,
|
|
7240
|
+
" more"
|
|
7241
|
+
] }) : null,
|
|
7242
|
+
visibleRepos.map((repo) => {
|
|
7243
|
+
const actualIdx = repos.indexOf(repo);
|
|
7244
|
+
const isSel = actualIdx === selectedIdx;
|
|
7245
|
+
const label = shortName(repo.name).slice(0, maxLabel);
|
|
7246
|
+
return /* @__PURE__ */ jsxs24(Box23, { children: [
|
|
7247
|
+
/* @__PURE__ */ jsxs24(Text22, { color: isSel ? "cyan" : isActive ? "white" : "gray", bold: isSel, children: [
|
|
7248
|
+
isSel ? "\u25BA " : " ",
|
|
7249
|
+
label
|
|
7250
|
+
] }),
|
|
7251
|
+
/* @__PURE__ */ jsxs24(Text22, { color: "gray", children: [
|
|
7252
|
+
" ",
|
|
7253
|
+
repo.openCount
|
|
7254
|
+
] })
|
|
7255
|
+
] }, repo.name);
|
|
7256
|
+
}),
|
|
7257
|
+
hasMoreBelow ? /* @__PURE__ */ jsxs24(Text22, { color: "gray", dimColor: true, children: [
|
|
7258
|
+
" ",
|
|
7259
|
+
"\u25BC ",
|
|
7260
|
+
belowCount,
|
|
7261
|
+
" more"
|
|
7262
|
+
] }) : null
|
|
7263
|
+
] }) });
|
|
6768
7264
|
}
|
|
6769
7265
|
var init_repos_panel = __esm({
|
|
6770
7266
|
"src/board/components/repos-panel.tsx"() {
|
|
6771
7267
|
"use strict";
|
|
7268
|
+
init_use_viewport_scroll();
|
|
6772
7269
|
init_panel();
|
|
6773
7270
|
}
|
|
6774
7271
|
});
|
|
@@ -7033,218 +7530,139 @@ function RowRenderer({
|
|
|
7033
7530
|
var init_row_renderer = __esm({
|
|
7034
7531
|
"src/board/components/row-renderer.tsx"() {
|
|
7035
7532
|
"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
|
-
];
|
|
7533
|
+
init_issue_row();
|
|
7213
7534
|
}
|
|
7214
|
-
|
|
7215
|
-
|
|
7216
|
-
|
|
7217
|
-
|
|
7535
|
+
});
|
|
7536
|
+
|
|
7537
|
+
// src/board/components/statuses-panel.tsx
|
|
7538
|
+
import { Box as Box26, Text as Text25 } from "ink";
|
|
7539
|
+
import { Fragment as Fragment5, jsx as jsx27, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
7540
|
+
function StatusesPanel({
|
|
7541
|
+
groups,
|
|
7542
|
+
selectedIdx,
|
|
7543
|
+
isActive,
|
|
7544
|
+
width,
|
|
7545
|
+
flexGrow,
|
|
7546
|
+
height
|
|
7547
|
+
}) {
|
|
7548
|
+
const maxLabel = Math.max(4, width - 8);
|
|
7549
|
+
const contentRows = height != null ? Math.max(1, height - 2) : groups.length;
|
|
7550
|
+
const needsScroll = groups.length > contentRows;
|
|
7551
|
+
let visibleGroups = groups;
|
|
7552
|
+
let hasMoreAbove = false;
|
|
7553
|
+
let hasMoreBelow = false;
|
|
7554
|
+
let aboveCount = 0;
|
|
7555
|
+
let belowCount = 0;
|
|
7556
|
+
if (needsScroll) {
|
|
7557
|
+
const scroll = computeViewportScroll(
|
|
7558
|
+
groups.length,
|
|
7559
|
+
contentRows,
|
|
7560
|
+
selectedIdx,
|
|
7561
|
+
Math.max(0, selectedIdx - Math.floor(contentRows / 2))
|
|
7562
|
+
);
|
|
7563
|
+
visibleGroups = groups.slice(scroll.scrollOffset, scroll.scrollOffset + scroll.visibleCount);
|
|
7564
|
+
hasMoreAbove = scroll.hasMoreAbove;
|
|
7565
|
+
hasMoreBelow = scroll.hasMoreBelow;
|
|
7566
|
+
aboveCount = scroll.aboveCount;
|
|
7567
|
+
belowCount = scroll.belowCount;
|
|
7218
7568
|
}
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
|
|
7227
|
-
|
|
7569
|
+
return /* @__PURE__ */ jsx27(
|
|
7570
|
+
Panel,
|
|
7571
|
+
{
|
|
7572
|
+
title: "[2] Statuses",
|
|
7573
|
+
isActive,
|
|
7574
|
+
width,
|
|
7575
|
+
flexGrow,
|
|
7576
|
+
height,
|
|
7577
|
+
children: groups.length === 0 ? /* @__PURE__ */ jsx27(Text25, { color: "gray", children: "\u2014" }) : /* @__PURE__ */ jsxs27(Fragment5, { children: [
|
|
7578
|
+
hasMoreAbove ? /* @__PURE__ */ jsxs27(Text25, { color: "gray", dimColor: true, children: [
|
|
7579
|
+
" ",
|
|
7580
|
+
"\u25B2 ",
|
|
7581
|
+
aboveCount,
|
|
7582
|
+
" more"
|
|
7583
|
+
] }) : null,
|
|
7584
|
+
visibleGroups.map((group) => {
|
|
7585
|
+
const actualIdx = groups.indexOf(group);
|
|
7586
|
+
const isSel = actualIdx === selectedIdx;
|
|
7587
|
+
const label = group.label.slice(0, maxLabel);
|
|
7588
|
+
return /* @__PURE__ */ jsxs27(Box26, { children: [
|
|
7589
|
+
/* @__PURE__ */ jsxs27(Text25, { color: isSel ? "cyan" : isActive ? "white" : "gray", bold: isSel, children: [
|
|
7590
|
+
isSel ? "\u25BA " : " ",
|
|
7591
|
+
label
|
|
7592
|
+
] }),
|
|
7593
|
+
/* @__PURE__ */ jsxs27(Text25, { color: "gray", children: [
|
|
7594
|
+
" ",
|
|
7595
|
+
group.count
|
|
7596
|
+
] })
|
|
7597
|
+
] }, group.id);
|
|
7598
|
+
}),
|
|
7599
|
+
hasMoreBelow ? /* @__PURE__ */ jsxs27(Text25, { color: "gray", dimColor: true, children: [
|
|
7600
|
+
" ",
|
|
7601
|
+
"\u25BC ",
|
|
7602
|
+
belowCount,
|
|
7603
|
+
" more"
|
|
7604
|
+
] }) : null
|
|
7605
|
+
] })
|
|
7606
|
+
}
|
|
7607
|
+
);
|
|
7608
|
+
}
|
|
7609
|
+
var init_statuses_panel = __esm({
|
|
7610
|
+
"src/board/components/statuses-panel.tsx"() {
|
|
7611
|
+
"use strict";
|
|
7612
|
+
init_use_viewport_scroll();
|
|
7613
|
+
init_panel();
|
|
7228
7614
|
}
|
|
7229
|
-
|
|
7230
|
-
|
|
7231
|
-
|
|
7232
|
-
|
|
7233
|
-
|
|
7234
|
-
|
|
7235
|
-
|
|
7236
|
-
|
|
7237
|
-
|
|
7238
|
-
|
|
7239
|
-
|
|
7615
|
+
});
|
|
7616
|
+
|
|
7617
|
+
// src/board/components/toast-container.tsx
|
|
7618
|
+
import { Spinner as Spinner3 } from "@inkjs/ui";
|
|
7619
|
+
import { Box as Box27, Text as Text26 } from "ink";
|
|
7620
|
+
import { Fragment as Fragment6, jsx as jsx28, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
7621
|
+
function ToastContainer({ toasts }) {
|
|
7622
|
+
if (toasts.length === 0) return null;
|
|
7623
|
+
return /* @__PURE__ */ jsx28(Box27, { flexDirection: "column", children: toasts.map((t) => /* @__PURE__ */ jsx28(Box27, { children: t.type === "loading" ? /* @__PURE__ */ jsxs28(Fragment6, { children: [
|
|
7624
|
+
/* @__PURE__ */ jsx28(Spinner3, { label: "" }),
|
|
7625
|
+
/* @__PURE__ */ jsxs28(Text26, { color: "cyan", children: [
|
|
7626
|
+
" ",
|
|
7627
|
+
t.message
|
|
7628
|
+
] })
|
|
7629
|
+
] }) : /* @__PURE__ */ jsxs28(Text26, { color: TYPE_COLORS[t.type], children: [
|
|
7630
|
+
TYPE_PREFIXES[t.type],
|
|
7631
|
+
" ",
|
|
7632
|
+
t.message,
|
|
7633
|
+
t.type === "error" ? /* @__PURE__ */ jsx28(Text26, { color: "gray", children: t.retry ? " [r]etry [d]ismiss" : " [d]ismiss" }) : null
|
|
7634
|
+
] }) }, t.id)) });
|
|
7635
|
+
}
|
|
7636
|
+
var TYPE_COLORS, TYPE_PREFIXES;
|
|
7637
|
+
var init_toast_container = __esm({
|
|
7638
|
+
"src/board/components/toast-container.tsx"() {
|
|
7639
|
+
"use strict";
|
|
7640
|
+
TYPE_COLORS = {
|
|
7641
|
+
info: "cyan",
|
|
7642
|
+
success: "green",
|
|
7643
|
+
error: "red",
|
|
7644
|
+
loading: "cyan"
|
|
7645
|
+
};
|
|
7646
|
+
TYPE_PREFIXES = {
|
|
7647
|
+
info: "\u2139",
|
|
7648
|
+
success: "\u2713",
|
|
7649
|
+
error: "\u2717"
|
|
7650
|
+
};
|
|
7240
7651
|
}
|
|
7241
|
-
|
|
7242
|
-
|
|
7243
|
-
|
|
7244
|
-
|
|
7245
|
-
|
|
7246
|
-
|
|
7247
|
-
|
|
7652
|
+
});
|
|
7653
|
+
|
|
7654
|
+
// src/board/components/dashboard.tsx
|
|
7655
|
+
import { execFile as execFile2, spawn as spawn4 } from "child_process";
|
|
7656
|
+
import { Spinner as Spinner4 } from "@inkjs/ui";
|
|
7657
|
+
import { Box as Box28, Text as Text27, useApp, useStdout as useStdout2 } from "ink";
|
|
7658
|
+
import { useCallback as useCallback16, useEffect as useEffect14, useMemo as useMemo5, useRef as useRef19, useState as useState21 } from "react";
|
|
7659
|
+
import { Fragment as Fragment7, jsx as jsx29, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
7660
|
+
function resolvePhaseConfig(rc, config2, issueTitle, phase) {
|
|
7661
|
+
const phasePrompts = rc.workflow?.phasePrompts ?? config2.board.workflow?.phasePrompts ?? {};
|
|
7662
|
+
const template = phasePrompts[phase] ?? DEFAULT_PHASE_PROMPTS[phase];
|
|
7663
|
+
const startCommand = rc.claudeStartCommand ?? config2.board.claudeStartCommand;
|
|
7664
|
+
const slug = issueTitle.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
7665
|
+
return { template, startCommand, slug };
|
|
7248
7666
|
}
|
|
7249
7667
|
function openInBrowser(url) {
|
|
7250
7668
|
try {
|
|
@@ -7255,19 +7673,9 @@ function openInBrowser(url) {
|
|
|
7255
7673
|
} catch {
|
|
7256
7674
|
}
|
|
7257
7675
|
}
|
|
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
7676
|
function RefreshAge({ lastRefresh }) {
|
|
7269
7677
|
const [, setTick] = useState21(0);
|
|
7270
|
-
|
|
7678
|
+
useEffect14(() => {
|
|
7271
7679
|
const id = setInterval(() => setTick((t) => t + 1), 3e4);
|
|
7272
7680
|
return () => clearInterval(id);
|
|
7273
7681
|
}, []);
|
|
@@ -7277,31 +7685,6 @@ function RefreshAge({ lastRefresh }) {
|
|
|
7277
7685
|
timeAgo(lastRefresh)
|
|
7278
7686
|
] });
|
|
7279
7687
|
}
|
|
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
7688
|
function Dashboard({ config: config2, options, activeProfile }) {
|
|
7306
7689
|
const { exit } = useApp();
|
|
7307
7690
|
const refreshMs = config2.board.refreshInterval * 1e3;
|
|
@@ -7361,7 +7744,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7361
7744
|
enrichment: workflowState.enrichment,
|
|
7362
7745
|
onEnrichmentChange: handleEnrichmentChange
|
|
7363
7746
|
});
|
|
7364
|
-
|
|
7747
|
+
useEffect14(() => {
|
|
7365
7748
|
const last = logEntries[logEntries.length - 1];
|
|
7366
7749
|
if (last?.status === "error") setLogVisible(true);
|
|
7367
7750
|
}, [logEntries]);
|
|
@@ -7449,7 +7832,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7449
7832
|
return null;
|
|
7450
7833
|
}, []);
|
|
7451
7834
|
const multiSelect = useMultiSelect(getRepoForId);
|
|
7452
|
-
|
|
7835
|
+
useEffect14(() => {
|
|
7453
7836
|
if (multiSelect.count === 0) return;
|
|
7454
7837
|
const validIds = new Set(navItems.map((i) => i.id));
|
|
7455
7838
|
multiSelect.prune(validIds);
|
|
@@ -7466,9 +7849,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7466
7849
|
registerPendingMutation,
|
|
7467
7850
|
clearPendingMutation
|
|
7468
7851
|
});
|
|
7469
|
-
const pendingPickRef =
|
|
7470
|
-
const labelCacheRef =
|
|
7471
|
-
const commentCacheRef =
|
|
7852
|
+
const pendingPickRef = useRef19(null);
|
|
7853
|
+
const labelCacheRef = useRef19({});
|
|
7854
|
+
const commentCacheRef = useRef19({});
|
|
7472
7855
|
const [commentTick, setCommentTick] = useState21(0);
|
|
7473
7856
|
const handleFetchComments = useCallback16((repo, issueNumber) => {
|
|
7474
7857
|
const key = `${repo}:${issueNumber}`;
|
|
@@ -7563,12 +7946,12 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7563
7946
|
[toast, ui]
|
|
7564
7947
|
);
|
|
7565
7948
|
const [focusKey, setFocusKey] = useState21(0);
|
|
7566
|
-
const { stdout } =
|
|
7949
|
+
const { stdout } = useStdout2();
|
|
7567
7950
|
const [termSize, setTermSize] = useState21({
|
|
7568
7951
|
cols: stdout?.columns ?? 80,
|
|
7569
7952
|
rows: stdout?.rows ?? 24
|
|
7570
7953
|
});
|
|
7571
|
-
|
|
7954
|
+
useEffect14(() => {
|
|
7572
7955
|
if (!stdout) return;
|
|
7573
7956
|
const onResize = () => setTermSize({ cols: stdout.columns, rows: stdout.rows });
|
|
7574
7957
|
stdout.on("resize", onResize);
|
|
@@ -7619,32 +8002,22 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7619
8002
|
return { ...row, phaseIndicator, statusAgeDays };
|
|
7620
8003
|
});
|
|
7621
8004
|
}, [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
8005
|
const selectedRowIdx = useMemo5(
|
|
7631
8006
|
() => flatRows.findIndex((r) => r.navId === nav.selectedId),
|
|
7632
8007
|
[flatRows, nav.selectedId]
|
|
7633
8008
|
);
|
|
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;
|
|
8009
|
+
const scrollResetKey = `${selectedRepoName ?? ""}:${selectedStatusGroupId ?? ""}`;
|
|
8010
|
+
const viewport = useViewportScroll(
|
|
8011
|
+
flatRows.length,
|
|
8012
|
+
contentRowCount,
|
|
8013
|
+
selectedRowIdx,
|
|
8014
|
+
scrollResetKey
|
|
8015
|
+
);
|
|
8016
|
+
const { hasMoreAbove, hasMoreBelow, aboveCount, belowCount } = viewport;
|
|
8017
|
+
const visibleRows = flatRows.slice(
|
|
8018
|
+
viewport.scrollOffset,
|
|
8019
|
+
viewport.scrollOffset + viewport.visibleCount
|
|
8020
|
+
);
|
|
7648
8021
|
const selectedItem = useMemo5(() => {
|
|
7649
8022
|
const id = nav.selectedId;
|
|
7650
8023
|
if (!id || isHeaderId(id)) return { issue: null, repoName: null };
|
|
@@ -7953,7 +8326,6 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7953
8326
|
}
|
|
7954
8327
|
ui.enterTriage();
|
|
7955
8328
|
}, [nudges.candidates.length, toast, ui]);
|
|
7956
|
-
const multiSelectType = "github";
|
|
7957
8329
|
const handleBulkAction = useCallback16(
|
|
7958
8330
|
(action) => {
|
|
7959
8331
|
const ids = multiSelect.selected;
|
|
@@ -7987,15 +8359,9 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
7987
8359
|
case "statusChange":
|
|
7988
8360
|
ui.enterStatus();
|
|
7989
8361
|
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
8362
|
}
|
|
7997
8363
|
},
|
|
7998
|
-
[multiSelect, actions, ui
|
|
8364
|
+
[multiSelect, actions, ui]
|
|
7999
8365
|
);
|
|
8000
8366
|
const handleBulkStatusSelect = useCallback16(
|
|
8001
8367
|
(optionId) => {
|
|
@@ -8077,7 +8443,8 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8077
8443
|
onStatusEnter,
|
|
8078
8444
|
onActivityEnter,
|
|
8079
8445
|
showDetailPanel,
|
|
8080
|
-
leftPanelHidden
|
|
8446
|
+
leftPanelHidden,
|
|
8447
|
+
issuesPageSize: viewport.visibleCount
|
|
8081
8448
|
});
|
|
8082
8449
|
if (status === "loading" && !data) {
|
|
8083
8450
|
return /* @__PURE__ */ jsx29(Box28, { flexDirection: "column", padding: 1, children: /* @__PURE__ */ jsx29(Spinner4, { label: "Loading dashboard..." }) });
|
|
@@ -8178,7 +8545,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8178
8545
|
}
|
|
8179
8546
|
)
|
|
8180
8547
|
] });
|
|
8181
|
-
return /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", paddingX: 1, children: [
|
|
8548
|
+
return /* @__PURE__ */ jsxs29(Box28, { flexDirection: "column", paddingX: 1, height: termSize.rows, overflow: "hidden", children: [
|
|
8182
8549
|
/* @__PURE__ */ jsxs29(Box28, { children: [
|
|
8183
8550
|
/* @__PURE__ */ jsx29(Text27, { color: "cyan", bold: true, children: "HOG BOARD" }),
|
|
8184
8551
|
activeProfile ? /* @__PURE__ */ jsxs29(Text27, { color: "yellow", children: [
|
|
@@ -8193,10 +8560,10 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8193
8560
|
dateStr
|
|
8194
8561
|
] }),
|
|
8195
8562
|
/* @__PURE__ */ jsx29(Text27, { children: " " }),
|
|
8196
|
-
isRefreshing ? /* @__PURE__ */ jsxs29(
|
|
8563
|
+
isRefreshing ? /* @__PURE__ */ jsxs29(Fragment7, { children: [
|
|
8197
8564
|
/* @__PURE__ */ jsx29(Spinner4, { label: "" }),
|
|
8198
8565
|
/* @__PURE__ */ jsx29(Text27, { color: "cyan", children: " Refreshing..." })
|
|
8199
|
-
] }) : /* @__PURE__ */ jsxs29(
|
|
8566
|
+
] }) : /* @__PURE__ */ jsxs29(Fragment7, { children: [
|
|
8200
8567
|
/* @__PURE__ */ jsx29(RefreshAge, { lastRefresh }),
|
|
8201
8568
|
consecutiveFailures > 0 ? /* @__PURE__ */ jsx29(Text27, { color: "red", children: " (!)" }) : null
|
|
8202
8569
|
] }),
|
|
@@ -8231,7 +8598,6 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8231
8598
|
onConfirmPick: handleConfirmPick,
|
|
8232
8599
|
onCancelPick: handleCancelPick,
|
|
8233
8600
|
multiSelectCount: multiSelect.count,
|
|
8234
|
-
multiSelectType,
|
|
8235
8601
|
onBulkAction: handleBulkAction,
|
|
8236
8602
|
focusLabel,
|
|
8237
8603
|
focusKey,
|
|
@@ -8320,6 +8686,7 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8320
8686
|
{
|
|
8321
8687
|
cols: termSize.cols,
|
|
8322
8688
|
issuesPanelHeight,
|
|
8689
|
+
totalHeight: totalPanelHeight,
|
|
8323
8690
|
reposPanel,
|
|
8324
8691
|
statusesPanel,
|
|
8325
8692
|
issuesPanel,
|
|
@@ -8343,12 +8710,13 @@ function Dashboard({ config: config2, options, activeProfile }) {
|
|
|
8343
8710
|
)
|
|
8344
8711
|
] });
|
|
8345
8712
|
}
|
|
8346
|
-
var
|
|
8713
|
+
var CHROME_ROWS;
|
|
8347
8714
|
var init_dashboard = __esm({
|
|
8348
8715
|
"src/board/components/dashboard.tsx"() {
|
|
8349
8716
|
"use strict";
|
|
8350
8717
|
init_clipboard();
|
|
8351
8718
|
init_github();
|
|
8719
|
+
init_board_tree();
|
|
8352
8720
|
init_constants();
|
|
8353
8721
|
init_use_action_log();
|
|
8354
8722
|
init_use_actions();
|
|
@@ -8361,6 +8729,7 @@ var init_dashboard = __esm({
|
|
|
8361
8729
|
init_use_nudges();
|
|
8362
8730
|
init_use_toast();
|
|
8363
8731
|
init_use_ui_state();
|
|
8732
|
+
init_use_viewport_scroll();
|
|
8364
8733
|
init_use_workflow_state();
|
|
8365
8734
|
init_use_zen_mode();
|
|
8366
8735
|
init_launch_claude();
|
|
@@ -8376,12 +8745,6 @@ var init_dashboard = __esm({
|
|
|
8376
8745
|
init_row_renderer();
|
|
8377
8746
|
init_statuses_panel();
|
|
8378
8747
|
init_toast_container();
|
|
8379
|
-
PRIORITY_RANK = {
|
|
8380
|
-
"priority:critical": 0,
|
|
8381
|
-
"priority:high": 1,
|
|
8382
|
-
"priority:medium": 2,
|
|
8383
|
-
"priority:low": 3
|
|
8384
|
-
};
|
|
8385
8748
|
CHROME_ROWS = 3;
|
|
8386
8749
|
}
|
|
8387
8750
|
});
|
|
@@ -8434,7 +8797,8 @@ __export(fetch_exports, {
|
|
|
8434
8797
|
fetchDashboard: () => fetchDashboard,
|
|
8435
8798
|
fetchRecentActivity: () => fetchRecentActivity
|
|
8436
8799
|
});
|
|
8437
|
-
import { execFileSync as execFileSync4 } from "child_process";
|
|
8800
|
+
import { execFile as execFile3, execFileSync as execFileSync4 } from "child_process";
|
|
8801
|
+
import { promisify as promisify2 } from "util";
|
|
8438
8802
|
function extractSlackUrl(body) {
|
|
8439
8803
|
if (!body) return void 0;
|
|
8440
8804
|
const match = body.match(SLACK_URL_RE2);
|
|
@@ -8451,6 +8815,116 @@ function extractLinkedIssueNumbers(title, body) {
|
|
|
8451
8815
|
if (!matches) return [];
|
|
8452
8816
|
return [...new Set(matches.map((m) => parseInt(m.slice(1), 10)).filter((n) => n > 0))];
|
|
8453
8817
|
}
|
|
8818
|
+
function parseActivityOutput(output, shortName2) {
|
|
8819
|
+
const cutoff = Date.now() - 24 * 60 * 60 * 1e3;
|
|
8820
|
+
const events = [];
|
|
8821
|
+
for (const line of output.trim().split("\n")) {
|
|
8822
|
+
if (!line.trim()) continue;
|
|
8823
|
+
try {
|
|
8824
|
+
const ev = JSON.parse(line);
|
|
8825
|
+
const timestamp = new Date(ev.created_at);
|
|
8826
|
+
if (timestamp.getTime() < cutoff) continue;
|
|
8827
|
+
if (ev.type === "CreateEvent") {
|
|
8828
|
+
if (ev.ref_type !== "branch" || !ev.ref) continue;
|
|
8829
|
+
const issueNumbers = extractIssueNumbersFromBranch(ev.ref);
|
|
8830
|
+
for (const num of issueNumbers) {
|
|
8831
|
+
events.push({
|
|
8832
|
+
type: "branch_created",
|
|
8833
|
+
repoShortName: shortName2,
|
|
8834
|
+
issueNumber: num,
|
|
8835
|
+
actor: ev.actor,
|
|
8836
|
+
summary: `created branch ${ev.ref}`,
|
|
8837
|
+
timestamp,
|
|
8838
|
+
branchName: ev.ref
|
|
8839
|
+
});
|
|
8840
|
+
}
|
|
8841
|
+
continue;
|
|
8842
|
+
}
|
|
8843
|
+
if (!ev.number) continue;
|
|
8844
|
+
let eventType;
|
|
8845
|
+
let summary;
|
|
8846
|
+
let extras = {};
|
|
8847
|
+
if (ev.type === "IssueCommentEvent") {
|
|
8848
|
+
eventType = "comment";
|
|
8849
|
+
const preview = ev.body ? ev.body.slice(0, 60).replace(/\n/g, " ") : "";
|
|
8850
|
+
summary = `commented on #${ev.number}${preview ? ` \u2014 "${preview}${(ev.body?.length ?? 0) > 60 ? "..." : ""}"` : ""}`;
|
|
8851
|
+
} else if (ev.type === "IssuesEvent") {
|
|
8852
|
+
switch (ev.action) {
|
|
8853
|
+
case "opened":
|
|
8854
|
+
eventType = "opened";
|
|
8855
|
+
summary = `opened #${ev.number}: ${ev.title ?? ""}`;
|
|
8856
|
+
break;
|
|
8857
|
+
case "closed":
|
|
8858
|
+
eventType = "closed";
|
|
8859
|
+
summary = `closed #${ev.number}`;
|
|
8860
|
+
break;
|
|
8861
|
+
case "assigned":
|
|
8862
|
+
eventType = "assignment";
|
|
8863
|
+
summary = `assigned #${ev.number}`;
|
|
8864
|
+
break;
|
|
8865
|
+
case "labeled":
|
|
8866
|
+
eventType = "labeled";
|
|
8867
|
+
summary = `labeled #${ev.number}`;
|
|
8868
|
+
break;
|
|
8869
|
+
default:
|
|
8870
|
+
continue;
|
|
8871
|
+
}
|
|
8872
|
+
} else if (ev.type === "PullRequestEvent") {
|
|
8873
|
+
const prNumber = ev.number;
|
|
8874
|
+
extras = { prNumber };
|
|
8875
|
+
if (ev.action === "opened") {
|
|
8876
|
+
eventType = "pr_opened";
|
|
8877
|
+
summary = `opened PR #${prNumber}: ${ev.title ?? ""}`;
|
|
8878
|
+
} else if (ev.action === "closed" && ev.merged) {
|
|
8879
|
+
eventType = "pr_merged";
|
|
8880
|
+
summary = `merged PR #${prNumber}: ${ev.title ?? ""}`;
|
|
8881
|
+
} else if (ev.action === "closed") {
|
|
8882
|
+
eventType = "pr_closed";
|
|
8883
|
+
summary = `closed PR #${prNumber}`;
|
|
8884
|
+
} else {
|
|
8885
|
+
continue;
|
|
8886
|
+
}
|
|
8887
|
+
const linkedIssues = extractLinkedIssueNumbers(ev.title, ev.body);
|
|
8888
|
+
for (const issueNum of linkedIssues) {
|
|
8889
|
+
events.push({
|
|
8890
|
+
type: eventType,
|
|
8891
|
+
repoShortName: shortName2,
|
|
8892
|
+
issueNumber: issueNum,
|
|
8893
|
+
actor: ev.actor,
|
|
8894
|
+
summary,
|
|
8895
|
+
timestamp,
|
|
8896
|
+
prNumber
|
|
8897
|
+
});
|
|
8898
|
+
}
|
|
8899
|
+
if (linkedIssues.length === 0) {
|
|
8900
|
+
events.push({
|
|
8901
|
+
type: eventType,
|
|
8902
|
+
repoShortName: shortName2,
|
|
8903
|
+
issueNumber: prNumber,
|
|
8904
|
+
actor: ev.actor,
|
|
8905
|
+
summary,
|
|
8906
|
+
timestamp,
|
|
8907
|
+
prNumber
|
|
8908
|
+
});
|
|
8909
|
+
}
|
|
8910
|
+
continue;
|
|
8911
|
+
} else {
|
|
8912
|
+
continue;
|
|
8913
|
+
}
|
|
8914
|
+
events.push({
|
|
8915
|
+
type: eventType,
|
|
8916
|
+
repoShortName: shortName2,
|
|
8917
|
+
issueNumber: ev.number,
|
|
8918
|
+
actor: ev.actor,
|
|
8919
|
+
summary,
|
|
8920
|
+
timestamp,
|
|
8921
|
+
...extras
|
|
8922
|
+
});
|
|
8923
|
+
} catch {
|
|
8924
|
+
}
|
|
8925
|
+
}
|
|
8926
|
+
return events.slice(0, 15);
|
|
8927
|
+
}
|
|
8454
8928
|
function fetchRecentActivity(repoName, shortName2) {
|
|
8455
8929
|
try {
|
|
8456
8930
|
const output = execFileSync4(
|
|
@@ -8465,114 +8939,61 @@ function fetchRecentActivity(repoName, shortName2) {
|
|
|
8465
8939
|
],
|
|
8466
8940
|
{ encoding: "utf-8", timeout: 15e3, stdio: "pipe" }
|
|
8467
8941
|
);
|
|
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
|
-
}
|
|
8942
|
+
return parseActivityOutput(output, shortName2);
|
|
8943
|
+
} catch {
|
|
8944
|
+
return [];
|
|
8945
|
+
}
|
|
8946
|
+
}
|
|
8947
|
+
async function fetchRepoDataAsync(repo, assignee) {
|
|
8948
|
+
try {
|
|
8949
|
+
const fetchOpts = {};
|
|
8950
|
+
if (assignee) fetchOpts.assignee = assignee;
|
|
8951
|
+
const issues = await fetchRepoIssuesAsync(repo.name, fetchOpts);
|
|
8952
|
+
let enrichedIssues = issues;
|
|
8953
|
+
let statusOptions = [];
|
|
8954
|
+
try {
|
|
8955
|
+
const [enrichMap, opts] = await Promise.all([
|
|
8956
|
+
fetchProjectEnrichmentAsync(repo.name, repo.projectNumber),
|
|
8957
|
+
fetchProjectStatusOptionsAsync(repo.name, repo.projectNumber, repo.statusFieldId)
|
|
8958
|
+
]);
|
|
8959
|
+
enrichedIssues = issues.map((issue) => {
|
|
8960
|
+
const e = enrichMap.get(issue.number);
|
|
8961
|
+
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
8962
|
+
return {
|
|
8963
|
+
...issue,
|
|
8964
|
+
...e?.targetDate !== void 0 ? { targetDate: e.targetDate } : {},
|
|
8965
|
+
...e?.projectStatus !== void 0 ? { projectStatus: e.projectStatus } : {},
|
|
8966
|
+
...e?.customFields !== void 0 ? { customFields: e.customFields } : {},
|
|
8967
|
+
...slackUrl ? { slackThreadUrl: slackUrl } : {}
|
|
8968
|
+
};
|
|
8969
|
+
});
|
|
8970
|
+
statusOptions = opts;
|
|
8971
|
+
} catch {
|
|
8972
|
+
enrichedIssues = issues.map((issue) => {
|
|
8973
|
+
const slackUrl = extractSlackUrl(issue.body ?? "");
|
|
8974
|
+
return slackUrl ? { ...issue, slackThreadUrl: slackUrl } : issue;
|
|
8975
|
+
});
|
|
8574
8976
|
}
|
|
8575
|
-
return
|
|
8977
|
+
return { repo, issues: enrichedIssues, statusOptions, error: null };
|
|
8978
|
+
} catch (err) {
|
|
8979
|
+
return { repo, issues: [], statusOptions: [], error: formatError(err) };
|
|
8980
|
+
}
|
|
8981
|
+
}
|
|
8982
|
+
async function fetchRecentActivityAsync(repoName, shortName2) {
|
|
8983
|
+
try {
|
|
8984
|
+
const { stdout } = await execFileAsync2(
|
|
8985
|
+
"gh",
|
|
8986
|
+
[
|
|
8987
|
+
"api",
|
|
8988
|
+
`repos/${repoName}/events`,
|
|
8989
|
+
"-f",
|
|
8990
|
+
"per_page=30",
|
|
8991
|
+
"-q",
|
|
8992
|
+
'.[] | 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}'
|
|
8993
|
+
],
|
|
8994
|
+
{ encoding: "utf-8", timeout: 15e3 }
|
|
8995
|
+
);
|
|
8996
|
+
return parseActivityOutput(stdout, shortName2);
|
|
8576
8997
|
} catch {
|
|
8577
8998
|
return [];
|
|
8578
8999
|
}
|
|
@@ -8581,62 +9002,25 @@ async function fetchDashboard(config2, options = {}) {
|
|
|
8581
9002
|
const repos = options.repoFilter ? config2.repos.filter(
|
|
8582
9003
|
(r) => r.shortName === options.repoFilter || r.name === options.repoFilter
|
|
8583
9004
|
) : 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());
|
|
9005
|
+
const assignee = options.mineOnly ? config2.board.assignee : void 0;
|
|
9006
|
+
const [repoData, ...activityResults] = await Promise.all([
|
|
9007
|
+
Promise.all(repos.map((repo) => fetchRepoDataAsync(repo, assignee))),
|
|
9008
|
+
...repos.map((repo) => fetchRecentActivityAsync(repo.name, repo.shortName))
|
|
9009
|
+
]);
|
|
9010
|
+
const activity = activityResults.flat().sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
8628
9011
|
return {
|
|
8629
9012
|
repos: repoData,
|
|
8630
9013
|
activity: activity.slice(0, 15),
|
|
8631
9014
|
fetchedAt: /* @__PURE__ */ new Date()
|
|
8632
9015
|
};
|
|
8633
9016
|
}
|
|
8634
|
-
var SLACK_URL_RE2;
|
|
9017
|
+
var execFileAsync2, SLACK_URL_RE2;
|
|
8635
9018
|
var init_fetch = __esm({
|
|
8636
9019
|
"src/board/fetch.ts"() {
|
|
8637
9020
|
"use strict";
|
|
8638
9021
|
init_github();
|
|
8639
9022
|
init_utils();
|
|
9023
|
+
execFileAsync2 = promisify2(execFile3);
|
|
8640
9024
|
SLACK_URL_RE2 = /https:\/\/[^/]+\.slack\.com\/archives\/[A-Z0-9]+\/p[0-9]+/i;
|
|
8641
9025
|
}
|
|
8642
9026
|
});
|
|
@@ -8795,8 +9179,8 @@ var init_format_static = __esm({
|
|
|
8795
9179
|
// src/cli.ts
|
|
8796
9180
|
init_ai();
|
|
8797
9181
|
init_config();
|
|
8798
|
-
import { execFile as
|
|
8799
|
-
import { promisify as
|
|
9182
|
+
import { execFile as execFile4, execFileSync as execFileSync5 } from "child_process";
|
|
9183
|
+
import { promisify as promisify3 } from "util";
|
|
8800
9184
|
import { Command } from "commander";
|
|
8801
9185
|
|
|
8802
9186
|
// src/init.ts
|
|
@@ -9397,7 +9781,7 @@ if (major < 22) {
|
|
|
9397
9781
|
);
|
|
9398
9782
|
process.exit(1);
|
|
9399
9783
|
}
|
|
9400
|
-
var
|
|
9784
|
+
var execFileAsync3 = promisify3(execFile4);
|
|
9401
9785
|
async function resolveRef(ref, config2) {
|
|
9402
9786
|
const { parseIssueRef: parseIssueRef2 } = await Promise.resolve().then(() => (init_pick(), pick_exports));
|
|
9403
9787
|
try {
|
|
@@ -9407,7 +9791,7 @@ async function resolveRef(ref, config2) {
|
|
|
9407
9791
|
}
|
|
9408
9792
|
}
|
|
9409
9793
|
var program = new Command();
|
|
9410
|
-
program.name("hog").description("Personal command deck \u2014 GitHub Projects dashboard with workflow orchestration").version("1.
|
|
9794
|
+
program.name("hog").description("Personal command deck \u2014 GitHub Projects dashboard with workflow orchestration").version("1.24.1").option("--json", "Force JSON output").option("--human", "Force human-readable output").hook("preAction", (thisCommand) => {
|
|
9411
9795
|
const opts = thisCommand.opts();
|
|
9412
9796
|
if (opts.json) setFormat("json");
|
|
9413
9797
|
if (opts.human) setFormat("human");
|
|
@@ -9534,6 +9918,40 @@ config.command("show").description("Show full configuration").action(() => {
|
|
|
9534
9918
|
}
|
|
9535
9919
|
}
|
|
9536
9920
|
});
|
|
9921
|
+
config.command("set <path> <value>").description("Set a configuration value (dot-notation path, e.g. board.assignee)").action((path, rawValue) => {
|
|
9922
|
+
const cfg = loadFullConfig();
|
|
9923
|
+
let value = rawValue;
|
|
9924
|
+
if (rawValue === "true") value = true;
|
|
9925
|
+
else if (rawValue === "false") value = false;
|
|
9926
|
+
else if (rawValue !== "" && !Number.isNaN(Number(rawValue))) value = Number(rawValue);
|
|
9927
|
+
const keys = path.split(".");
|
|
9928
|
+
if (keys.length === 0) {
|
|
9929
|
+
errorOut("Invalid path: empty path");
|
|
9930
|
+
}
|
|
9931
|
+
const updated = JSON.parse(JSON.stringify(cfg));
|
|
9932
|
+
let target = updated;
|
|
9933
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
9934
|
+
const key = keys[i];
|
|
9935
|
+
const next = target[key];
|
|
9936
|
+
if (next === void 0 || next === null || typeof next !== "object" || Array.isArray(next)) {
|
|
9937
|
+
errorOut(`Invalid path: "${keys.slice(0, i + 1).join(".")}" is not a nested object`);
|
|
9938
|
+
}
|
|
9939
|
+
target = next;
|
|
9940
|
+
}
|
|
9941
|
+
const lastKey = keys[keys.length - 1];
|
|
9942
|
+
target[lastKey] = value;
|
|
9943
|
+
const result = validateConfigSchema(updated);
|
|
9944
|
+
if (!result.success) {
|
|
9945
|
+
errorOut(`Validation failed:
|
|
9946
|
+
${result.error}`);
|
|
9947
|
+
}
|
|
9948
|
+
saveFullConfig(result.data);
|
|
9949
|
+
if (useJson()) {
|
|
9950
|
+
jsonOut({ ok: true, path, value });
|
|
9951
|
+
} else {
|
|
9952
|
+
printSuccess(`Set ${path} = ${JSON.stringify(value)}`);
|
|
9953
|
+
}
|
|
9954
|
+
});
|
|
9537
9955
|
config.command("repos").description("List configured repositories").action(() => {
|
|
9538
9956
|
const cfg = loadFullConfig();
|
|
9539
9957
|
if (useJson()) {
|
|
@@ -9794,7 +10212,7 @@ issueCommand.command("create <text>").description("Create a GitHub issue from na
|
|
|
9794
10212
|
}
|
|
9795
10213
|
try {
|
|
9796
10214
|
if (json) {
|
|
9797
|
-
const output = await
|
|
10215
|
+
const output = await execFileAsync3("gh", ghArgs, { encoding: "utf-8", timeout: 6e4 });
|
|
9798
10216
|
const url = output.stdout.trim();
|
|
9799
10217
|
const issueNumber = Number.parseInt(url.split("/").pop() ?? "0", 10);
|
|
9800
10218
|
jsonOut({ ok: true, data: { url, issueNumber, repo } });
|
|
@@ -10019,7 +10437,7 @@ issueCommand.command("edit <issueRef>").description("Edit issue fields (title, b
|
|
|
10019
10437
|
if (opts.assignee) ghArgs.push("--add-assignee", opts.assignee);
|
|
10020
10438
|
if (opts.removeAssignee) ghArgs.push("--remove-assignee", opts.removeAssignee);
|
|
10021
10439
|
if (useJson()) {
|
|
10022
|
-
await
|
|
10440
|
+
await execFileAsync3("gh", ghArgs, { encoding: "utf-8", timeout: 3e4 });
|
|
10023
10441
|
jsonOut({ ok: true, data: { issue: ref.issueNumber, changes } });
|
|
10024
10442
|
} else {
|
|
10025
10443
|
execFileSync5("gh", ghArgs, { stdio: "inherit" });
|