gh-manager-cli 1.8.2 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.10.0](https://github.com/wiiiimm/gh-manager-cli/compare/v1.9.0...v1.10.0) (2025-09-01)
2
+
3
+
4
+ ### Features
5
+
6
+ * sync repository actions with Apollo cache and improve info view ([563d04a](https://github.com/wiiiimm/gh-manager-cli/commit/563d04a4ec931c27dff204d3b5e6e770e4b482e6))
7
+
8
+ # [1.9.0](https://github.com/wiiiimm/gh-manager-cli/compare/v1.8.2...v1.9.0) (2025-09-01)
9
+
10
+
11
+ ### Features
12
+
13
+ * refetch repository data after sync to update timestamps ([b26468d](https://github.com/wiiiimm/gh-manager-cli/commit/b26468de5c3c12e04199f13de1958acd725fd239))
14
+
1
15
  ## [1.8.2](https://github.com/wiiiimm/gh-manager-cli/compare/v1.8.1...v1.8.2) (2025-09-01)
2
16
 
3
17
 
@@ -16,7 +16,11 @@ function makeClient(token) {
16
16
  headers: { authorization: `token ${token}` }
17
17
  });
18
18
  }
19
+ var apolloClientInstance = null;
19
20
  async function makeApolloClient(token) {
21
+ if (apolloClientInstance) {
22
+ return apolloClientInstance;
23
+ }
20
24
  try {
21
25
  if (typeof globalThis.fetch === "undefined") {
22
26
  throw new Error("Fetch API not available. Node 18+ is required.");
@@ -63,7 +67,8 @@ async function makeApolloClient(token) {
63
67
  headers: { authorization: `Bearer ${token}` }
64
68
  });
65
69
  const client = new ApolloClient({ cache, link });
66
- return { client, gql };
70
+ apolloClientInstance = { client, gql };
71
+ return apolloClientInstance;
67
72
  } catch (error) {
68
73
  const debug = process.env.GH_MANAGER_DEBUG === "1";
69
74
  if (debug) {
@@ -533,6 +538,118 @@ async function unarchiveRepositoryById(client, repositoryId) {
533
538
  );
534
539
  await client(mutation, { repositoryId });
535
540
  }
541
+ async function getRepositoryFromCache(token, repositoryId) {
542
+ try {
543
+ const ap = await makeApolloClient(token);
544
+ if (!ap || !ap.client) return null;
545
+ const cached = ap.client.cache.readFragment({
546
+ id: `Repository:${repositoryId}`,
547
+ fragment: gql`
548
+ fragment CachedRepository on Repository {
549
+ id
550
+ name
551
+ nameWithOwner
552
+ description
553
+ url
554
+ pushedAt
555
+ updatedAt
556
+ isPrivate
557
+ isArchived
558
+ isFork
559
+ stargazerCount
560
+ forkCount
561
+ diskUsage
562
+ primaryLanguage {
563
+ name
564
+ color
565
+ }
566
+ parent {
567
+ nameWithOwner
568
+ defaultBranchRef {
569
+ target {
570
+ ... on Commit {
571
+ history(first: 0) {
572
+ totalCount
573
+ }
574
+ }
575
+ }
576
+ }
577
+ }
578
+ defaultBranchRef {
579
+ name
580
+ target {
581
+ ... on Commit {
582
+ history(first: 0) {
583
+ totalCount
584
+ }
585
+ }
586
+ }
587
+ }
588
+ }
589
+ `
590
+ });
591
+ return cached;
592
+ } catch {
593
+ return null;
594
+ }
595
+ }
596
+ async function fetchRepositoryById(client, repositoryId, includeForkTracking = true) {
597
+ const query = (
598
+ /* GraphQL */
599
+ `
600
+ query GetRepository($id: ID!, $includeForkTracking: Boolean!) {
601
+ node(id: $id) {
602
+ ... on Repository {
603
+ id
604
+ name
605
+ nameWithOwner
606
+ description
607
+ url
608
+ pushedAt
609
+ updatedAt
610
+ isPrivate
611
+ isArchived
612
+ isFork
613
+ stargazerCount
614
+ forkCount
615
+ diskUsage
616
+ primaryLanguage {
617
+ name
618
+ color
619
+ }
620
+ parent @include(if: $includeForkTracking) {
621
+ nameWithOwner
622
+ defaultBranchRef {
623
+ target {
624
+ ... on Commit {
625
+ history(first: 0) {
626
+ totalCount
627
+ }
628
+ }
629
+ }
630
+ }
631
+ }
632
+ defaultBranchRef @include(if: $includeForkTracking) {
633
+ name
634
+ target {
635
+ ... on Commit {
636
+ history(first: 0) {
637
+ totalCount
638
+ }
639
+ }
640
+ }
641
+ }
642
+ }
643
+ }
644
+ }
645
+ `
646
+ );
647
+ const result = await client(query, {
648
+ id: repositoryId,
649
+ includeForkTracking
650
+ });
651
+ return result.node;
652
+ }
536
653
  async function syncForkWithUpstream(token, owner, repo, branch = "main") {
537
654
  const url = `https://api.github.com/repos/${owner}/${repo}/merge-upstream`;
538
655
  const res = await fetch(url, {
@@ -586,6 +703,82 @@ async function purgeApolloCacheFiles() {
586
703
  } catch {
587
704
  }
588
705
  }
706
+ async function updateCacheAfterDelete(token, repositoryId) {
707
+ try {
708
+ const ap = await makeApolloClient(token);
709
+ if (!ap || !ap.client) return;
710
+ ap.client.cache.evict({ id: `Repository:${repositoryId}` });
711
+ ap.client.cache.gc();
712
+ } catch {
713
+ }
714
+ }
715
+ async function updateCacheAfterArchive(token, repositoryId, isArchived) {
716
+ try {
717
+ const ap = await makeApolloClient(token);
718
+ if (!ap || !ap.client) return;
719
+ ap.client.cache.modify({
720
+ id: `Repository:${repositoryId}`,
721
+ fields: {
722
+ isArchived: () => isArchived
723
+ }
724
+ });
725
+ } catch {
726
+ }
727
+ }
728
+ async function updateCacheWithRepository(token, repository) {
729
+ try {
730
+ const ap = await makeApolloClient(token);
731
+ if (!ap || !ap.client) return;
732
+ ap.client.cache.writeFragment({
733
+ id: `Repository:${repository.id}`,
734
+ fragment: gql`
735
+ fragment UpdatedRepository on Repository {
736
+ id
737
+ name
738
+ nameWithOwner
739
+ description
740
+ url
741
+ pushedAt
742
+ updatedAt
743
+ isPrivate
744
+ isArchived
745
+ isFork
746
+ stargazerCount
747
+ forkCount
748
+ diskUsage
749
+ primaryLanguage {
750
+ name
751
+ color
752
+ }
753
+ parent {
754
+ nameWithOwner
755
+ defaultBranchRef {
756
+ target {
757
+ ... on Commit {
758
+ history(first: 0) {
759
+ totalCount
760
+ }
761
+ }
762
+ }
763
+ }
764
+ }
765
+ defaultBranchRef {
766
+ name
767
+ target {
768
+ ... on Commit {
769
+ history(first: 0) {
770
+ totalCount
771
+ }
772
+ }
773
+ }
774
+ }
775
+ }
776
+ `,
777
+ data: repository
778
+ });
779
+ } catch {
780
+ }
781
+ }
589
782
  async function inspectCacheStatus() {
590
783
  try {
591
784
  const fs2 = await import("fs");
@@ -642,7 +835,12 @@ export {
642
835
  deleteRepositoryRest,
643
836
  archiveRepositoryById,
644
837
  unarchiveRepositoryById,
838
+ getRepositoryFromCache,
839
+ fetchRepositoryById,
645
840
  syncForkWithUpstream,
646
841
  purgeApolloCacheFiles,
842
+ updateCacheAfterDelete,
843
+ updateCacheAfterArchive,
844
+ updateCacheWithRepository,
647
845
  inspectCacheStatus
648
846
  };
@@ -2,28 +2,38 @@
2
2
  import {
3
3
  archiveRepositoryById,
4
4
  deleteRepositoryRest,
5
+ fetchRepositoryById,
5
6
  fetchViewerOrganizations,
6
7
  fetchViewerReposPage,
7
8
  fetchViewerReposPageUnified,
9
+ getRepositoryFromCache,
8
10
  getViewerLogin,
9
11
  inspectCacheStatus,
10
12
  makeClient,
11
13
  purgeApolloCacheFiles,
12
14
  searchRepositoriesUnified,
13
15
  syncForkWithUpstream,
14
- unarchiveRepositoryById
15
- } from "./chunk-OQGG2X5P.js";
16
+ unarchiveRepositoryById,
17
+ updateCacheAfterArchive,
18
+ updateCacheAfterDelete,
19
+ updateCacheWithRepository
20
+ } from "./chunk-X5PZ5T27.js";
16
21
  export {
17
22
  archiveRepositoryById,
18
23
  deleteRepositoryRest,
24
+ fetchRepositoryById,
19
25
  fetchViewerOrganizations,
20
26
  fetchViewerReposPage,
21
27
  fetchViewerReposPageUnified,
28
+ getRepositoryFromCache,
22
29
  getViewerLogin,
23
30
  inspectCacheStatus,
24
31
  makeClient,
25
32
  purgeApolloCacheFiles,
26
33
  searchRepositoriesUnified,
27
34
  syncForkWithUpstream,
28
- unarchiveRepositoryById
35
+ unarchiveRepositoryById,
36
+ updateCacheAfterArchive,
37
+ updateCacheAfterDelete,
38
+ updateCacheWithRepository
29
39
  };
package/dist/index.js CHANGED
@@ -3,23 +3,28 @@ import {
3
3
  __commonJS,
4
4
  archiveRepositoryById,
5
5
  deleteRepositoryRest,
6
+ fetchRepositoryById,
6
7
  fetchViewerOrganizations,
7
8
  fetchViewerReposPageUnified,
9
+ getRepositoryFromCache,
8
10
  getViewerLogin,
9
11
  inspectCacheStatus,
10
12
  makeClient,
11
13
  purgeApolloCacheFiles,
12
14
  searchRepositoriesUnified,
13
15
  syncForkWithUpstream,
14
- unarchiveRepositoryById
15
- } from "./chunk-OQGG2X5P.js";
16
+ unarchiveRepositoryById,
17
+ updateCacheAfterArchive,
18
+ updateCacheAfterDelete,
19
+ updateCacheWithRepository
20
+ } from "./chunk-X5PZ5T27.js";
16
21
 
17
22
  // package.json
18
23
  var require_package = __commonJS({
19
24
  "package.json"(exports, module) {
20
25
  module.exports = {
21
26
  name: "gh-manager-cli",
22
- version: "1.8.2",
27
+ version: "1.10.0",
23
28
  private: false,
24
29
  description: "Interactive CLI to manage your GitHub repos (personal) with Ink",
25
30
  license: "MIT",
@@ -275,7 +280,7 @@ function OrgSwitcher({ token, currentContext, onSelect, onClose }) {
275
280
  const loadOrgs = async () => {
276
281
  try {
277
282
  setLoading(true);
278
- const client = await import("./github-TWXF5AWM.js").then((m) => m.makeClient(token));
283
+ const client = await import("./github-OM6QOCRV.js").then((m) => m.makeClient(token));
279
284
  const orgs = await fetchViewerOrganizations(client);
280
285
  setOrganizations(orgs);
281
286
  if (!isPersonalContext) {
@@ -483,6 +488,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
483
488
  const [syncError, setSyncError] = useState3(null);
484
489
  const [syncFocus, setSyncFocus] = useState3("confirm");
485
490
  const [infoMode, setInfoMode] = useState3(false);
491
+ const [infoRepo, setInfoRepo] = useState3(null);
486
492
  const [logoutMode, setLogoutMode] = useState3(false);
487
493
  const [logoutFocus, setLogoutFocus] = useState3("confirm");
488
494
  const [logoutError, setLogoutError] = useState3(null);
@@ -528,6 +534,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
528
534
  const [owner, repo] = (deleteTarget.nameWithOwner || "").split("/");
529
535
  await deleteRepositoryRest(token, owner, repo);
530
536
  const targetId = deleteTarget.id;
537
+ await updateCacheAfterDelete(token, targetId);
531
538
  setItems((prev) => prev.filter((r) => r.id !== targetId));
532
539
  setSearchItems((prev) => prev.filter((r) => r.id !== targetId));
533
540
  setTotalCount((c) => Math.max(0, c - 1));
@@ -827,6 +834,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
827
834
  const id = archiveTarget.id;
828
835
  if (isArchived) await unarchiveRepositoryById(client, id);
829
836
  else await archiveRepositoryById(client, id);
837
+ await updateCacheAfterArchive(token, id, !isArchived);
830
838
  const updateRepo = (r) => r.id === id ? { ...r, isArchived: !isArchived } : r;
831
839
  setItems((prev) => prev.map(updateRepo));
832
840
  setSearchItems((prev) => prev.map(updateRepo));
@@ -865,25 +873,38 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
865
873
  const [owner, repo] = syncTarget.nameWithOwner.split("/");
866
874
  const branchName = syncTarget.defaultBranchRef?.name || "main";
867
875
  const result = await syncForkWithUpstream(token, owner, repo, branchName);
868
- const updateSyncedRepo = (r) => {
869
- if (r.id === syncTarget.id && r.parent && r.defaultBranchRef?.target?.history && r.parent.defaultBranchRef?.target?.history) {
870
- return {
871
- ...r,
872
- defaultBranchRef: {
873
- ...r.defaultBranchRef,
874
- target: {
875
- ...r.defaultBranchRef.target,
876
- history: {
877
- totalCount: r.parent.defaultBranchRef.target.history.totalCount
876
+ const updatedRepo = await fetchRepositoryById(client, syncTarget.id, forkTracking);
877
+ if (updatedRepo) {
878
+ await updateCacheWithRepository(token, updatedRepo);
879
+ const updateSyncedRepo = (r) => {
880
+ if (r.id === syncTarget.id) {
881
+ return updatedRepo;
882
+ }
883
+ return r;
884
+ };
885
+ setItems((prev) => prev.map(updateSyncedRepo));
886
+ setSearchItems((prev) => prev.map(updateSyncedRepo));
887
+ } else {
888
+ const updateSyncedRepo = (r) => {
889
+ if (r.id === syncTarget.id && r.parent && r.defaultBranchRef?.target?.history && r.parent.defaultBranchRef?.target?.history) {
890
+ return {
891
+ ...r,
892
+ defaultBranchRef: {
893
+ ...r.defaultBranchRef,
894
+ target: {
895
+ ...r.defaultBranchRef.target,
896
+ history: {
897
+ totalCount: r.parent.defaultBranchRef.target.history.totalCount
898
+ }
878
899
  }
879
900
  }
880
- }
881
- };
882
- }
883
- return r;
884
- };
885
- setItems((prev) => prev.map(updateSyncedRepo));
886
- setSearchItems((prev) => prev.map(updateSyncedRepo));
901
+ };
902
+ }
903
+ return r;
904
+ };
905
+ setItems((prev) => prev.map(updateSyncedRepo));
906
+ setSearchItems((prev) => prev.map(updateSyncedRepo));
907
+ }
887
908
  closeSyncModal();
888
909
  } catch (e) {
889
910
  setSyncing(false);
@@ -926,6 +947,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
926
947
  if (infoMode) {
927
948
  if (key.escape || input && input.toUpperCase() === "I") {
928
949
  setInfoMode(false);
950
+ setInfoRepo(null);
929
951
  return;
930
952
  }
931
953
  return;
@@ -1064,6 +1086,17 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
1064
1086
  return;
1065
1087
  }
1066
1088
  if (input && input.toUpperCase() === "I") {
1089
+ const repo = visibleItems[cursor];
1090
+ if (repo) {
1091
+ (async () => {
1092
+ const cachedRepo = await getRepositoryFromCache(token, repo.id);
1093
+ if (cachedRepo) {
1094
+ setInfoRepo(cachedRepo);
1095
+ } else {
1096
+ setInfoRepo(repo);
1097
+ }
1098
+ })();
1099
+ }
1067
1100
  setInfoMode(true);
1068
1101
  return;
1069
1102
  }
@@ -1408,6 +1441,7 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
1408
1441
  const id = archiveTarget.id;
1409
1442
  if (isArchived) await unarchiveRepositoryById(client, id);
1410
1443
  else await archiveRepositoryById(client, id);
1444
+ await updateCacheAfterArchive(token, id, !isArchived);
1411
1445
  const updateRepo = (r) => r.id === id ? { ...r, isArchived: !isArchived } : r;
1412
1446
  setItems((prev) => prev.map(updateRepo));
1413
1447
  setSearchItems((prev) => prev.map(updateRepo));
@@ -1480,26 +1514,40 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
1480
1514
  try {
1481
1515
  setSyncing(true);
1482
1516
  const [owner, repo] = syncTarget.nameWithOwner.split("/");
1483
- const result = await syncForkWithUpstream(token, owner, repo);
1484
- const updateSyncedRepo = (r) => {
1485
- if (r.id === syncTarget.id && r.parent && r.defaultBranchRef?.target?.history && r.parent.defaultBranchRef?.target?.history) {
1486
- return {
1487
- ...r,
1488
- defaultBranchRef: {
1489
- ...r.defaultBranchRef,
1490
- target: {
1491
- ...r.defaultBranchRef.target,
1492
- history: {
1493
- totalCount: r.parent.defaultBranchRef.target.history.totalCount
1517
+ const branchName = syncTarget.defaultBranchRef?.name || "main";
1518
+ const result = await syncForkWithUpstream(token, owner, repo, branchName);
1519
+ const updatedRepo = await fetchRepositoryById(client, syncTarget.id, forkTracking);
1520
+ if (updatedRepo) {
1521
+ await updateCacheWithRepository(token, updatedRepo);
1522
+ const updateSyncedRepo = (r) => {
1523
+ if (r.id === syncTarget.id) {
1524
+ return updatedRepo;
1525
+ }
1526
+ return r;
1527
+ };
1528
+ setItems((prev) => prev.map(updateSyncedRepo));
1529
+ setSearchItems((prev) => prev.map(updateSyncedRepo));
1530
+ } else {
1531
+ const updateSyncedRepo = (r) => {
1532
+ if (r.id === syncTarget.id && r.parent && r.defaultBranchRef?.target?.history && r.parent.defaultBranchRef?.target?.history) {
1533
+ return {
1534
+ ...r,
1535
+ defaultBranchRef: {
1536
+ ...r.defaultBranchRef,
1537
+ target: {
1538
+ ...r.defaultBranchRef.target,
1539
+ history: {
1540
+ totalCount: r.parent.defaultBranchRef.target.history.totalCount
1541
+ }
1494
1542
  }
1495
1543
  }
1496
- }
1497
- };
1498
- }
1499
- return r;
1500
- };
1501
- setItems((prev) => prev.map(updateSyncedRepo));
1502
- setSearchItems((prev) => prev.map(updateSyncedRepo));
1544
+ };
1545
+ }
1546
+ return r;
1547
+ };
1548
+ setItems((prev) => prev.map(updateSyncedRepo));
1549
+ setSearchItems((prev) => prev.map(updateSyncedRepo));
1550
+ }
1503
1551
  closeSyncModal();
1504
1552
  } catch (e) {
1505
1553
  setSyncing(false);
@@ -1559,12 +1607,15 @@ function RepoList({ token, maxVisibleRows, onLogout, viewerLogin, onOrgContextCh
1559
1607
  onClose: () => setOrgSwitcherOpen(false)
1560
1608
  }
1561
1609
  ) }) : infoMode ? /* @__PURE__ */ jsx6(Box5, { height: contentHeight, alignItems: "center", justifyContent: "center", children: (() => {
1562
- const repo = visibleItems[cursor];
1610
+ const repo = infoRepo || visibleItems[cursor];
1563
1611
  if (!repo) return /* @__PURE__ */ jsx6(Text6, { color: "red", children: "No repository selected." });
1564
1612
  const langName = repo.primaryLanguage?.name || "N/A";
1565
1613
  const langColor = repo.primaryLanguage?.color || "#666666";
1566
1614
  return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "magenta", paddingX: 3, paddingY: 2, width: Math.min(terminalWidth - 8, 90), children: [
1567
- /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Repository Info" }),
1615
+ /* @__PURE__ */ jsxs5(Text6, { bold: true, children: [
1616
+ "Repository Info ",
1617
+ infoRepo ? chalk3.dim("(cached)") : ""
1618
+ ] }),
1568
1619
  /* @__PURE__ */ jsx6(Box5, { height: 1, children: /* @__PURE__ */ jsx6(Text6, { children: " " }) }),
1569
1620
  /* @__PURE__ */ jsx6(Text6, { children: chalk3.bold(repo.nameWithOwner) }),
1570
1621
  repo.description && /* @__PURE__ */ jsx6(Text6, { color: "gray", children: repo.description }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gh-manager-cli",
3
- "version": "1.8.2",
3
+ "version": "1.10.0",
4
4
  "private": false,
5
5
  "description": "Interactive CLI to manage your GitHub repos (personal) with Ink",
6
6
  "license": "MIT",