gh-sweep 1.0.3 → 1.0.4

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.
Files changed (2) hide show
  1. package/dist/index.js +100 -88
  2. package/package.json +8 -3
package/dist/index.js CHANGED
@@ -122,9 +122,9 @@ var require_fast_content_type_parse = __commonJS({
122
122
  });
123
123
 
124
124
  // src/index.tsx
125
- import { useState, useEffect, useMemo, useCallback } from "react";
126
- import { render, Box, Text, useInput, useApp, useStdout } from "ink";
127
125
  import { execSync } from "child_process";
126
+ import { Box, render, Text, useApp, useInput, useStdout } from "ink";
127
+ import { useCallback, useEffect, useMemo, useState } from "react";
128
128
 
129
129
  // node_modules/universal-user-agent/index.js
130
130
  function getUserAgent() {
@@ -3792,35 +3792,21 @@ async function deleteRepo(octokit, owner, repo) {
3792
3792
  await octokit.rest.repos.delete({ owner, repo });
3793
3793
  }
3794
3794
 
3795
- // src/index.tsx
3796
- import { jsx, jsxs } from "react/jsx-runtime";
3797
- function getToken() {
3798
- try {
3799
- return execSync("gh auth token", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
3800
- } catch {
3801
- }
3802
- if (process.env.GITHUB_TOKEN) {
3803
- return process.env.GITHUB_TOKEN;
3804
- }
3805
- console.error(
3806
- "No GitHub token found. Install the gh CLI and run `gh auth login`, or set GITHUB_TOKEN."
3807
- );
3808
- process.exit(1);
3809
- }
3810
- function openUrl(url) {
3811
- try {
3812
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
3813
- execSync(`${cmd} "${url}"`);
3814
- } catch {
3815
- }
3816
- }
3795
+ // src/utils.ts
3796
+ var FILTERS = [
3797
+ "all",
3798
+ "public",
3799
+ "private",
3800
+ "sources",
3801
+ "forks",
3802
+ "archived"
3803
+ ];
3817
3804
  function formatDate(iso) {
3818
3805
  return new Date(iso).toLocaleDateString("en-US", {
3819
3806
  month: "short",
3820
3807
  year: "numeric"
3821
3808
  });
3822
3809
  }
3823
- var FILTERS = ["all", "public", "private", "sources", "forks", "archived"];
3824
3810
  function applyFilter(repos, filter) {
3825
3811
  switch (filter) {
3826
3812
  case "all":
@@ -3837,6 +3823,69 @@ function applyFilter(repos, filter) {
3837
3823
  return repos.filter((r) => r.isArchived);
3838
3824
  }
3839
3825
  }
3826
+ function getVisibleActions(status, isArchivedRepo) {
3827
+ const pending = status?.endsWith("...");
3828
+ const deleted = status === "deleted";
3829
+ const errored = status === "error";
3830
+ const archived = isArchivedRepo || status === "archived";
3831
+ const untouched = !status || errored;
3832
+ return [
3833
+ { key: "v", label: "view", show: true },
3834
+ {
3835
+ key: "a",
3836
+ label: "archive",
3837
+ show: (untouched || status === "unarchived") && !archived
3838
+ },
3839
+ { key: "u", label: "unarchive", show: archived && !pending && !deleted },
3840
+ { key: "d", label: "delete", show: !pending && !deleted },
3841
+ { key: "s", label: "skip", show: untouched },
3842
+ { key: "f", label: "filter", show: true },
3843
+ { key: "r", label: "reload", show: true },
3844
+ { key: "q", label: "quit", show: true }
3845
+ ].filter((a) => a.show).map(({ key, label }) => ({ key, label }));
3846
+ }
3847
+ function findNextUnprocessed(from, statuses, repoNames) {
3848
+ let idx = from + 1;
3849
+ while (idx < repoNames.length && statuses.has(repoNames[idx])) {
3850
+ idx++;
3851
+ }
3852
+ return Math.min(idx, repoNames.length - 1);
3853
+ }
3854
+ function calcWidths(repos) {
3855
+ if (repos.length === 0) {
3856
+ return { name: 0, vis: 0, stars: 0, date: 0, lang: 0 };
3857
+ }
3858
+ return {
3859
+ name: Math.max(...repos.map((r) => r.nameWithOwner.length)),
3860
+ vis: Math.max(...repos.map((r) => r.visibility.length)),
3861
+ stars: Math.max(...repos.map((r) => String(r.stars).length)),
3862
+ date: Math.max(...repos.map((r) => formatDate(r.updatedAt).length)),
3863
+ lang: Math.max(...repos.map((r) => (r.language ?? "").length))
3864
+ };
3865
+ }
3866
+
3867
+ // src/index.tsx
3868
+ import { jsx, jsxs } from "react/jsx-runtime";
3869
+ function getToken() {
3870
+ try {
3871
+ return execSync("gh auth token", { stdio: ["pipe", "pipe", "pipe"] }).toString().trim();
3872
+ } catch {
3873
+ }
3874
+ if (process.env.GITHUB_TOKEN) {
3875
+ return process.env.GITHUB_TOKEN;
3876
+ }
3877
+ console.error(
3878
+ "No GitHub token found. Install the gh CLI and run `gh auth login`, or set GITHUB_TOKEN."
3879
+ );
3880
+ process.exit(1);
3881
+ }
3882
+ function openUrl(url) {
3883
+ try {
3884
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
3885
+ execSync(`${cmd} "${url}"`);
3886
+ } catch {
3887
+ }
3888
+ }
3840
3889
  function Header({
3841
3890
  current,
3842
3891
  total,
@@ -3858,18 +3907,6 @@ function Header({
3858
3907
  ] })
3859
3908
  ] });
3860
3909
  }
3861
- function calcWidths(repos) {
3862
- if (repos.length === 0) {
3863
- return { name: 0, vis: 0, stars: 0, date: 0, lang: 0 };
3864
- }
3865
- return {
3866
- name: Math.max(...repos.map((r) => r.nameWithOwner.length)),
3867
- vis: Math.max(...repos.map((r) => r.visibility.length)),
3868
- stars: Math.max(...repos.map((r) => String(r.stars).length)),
3869
- date: Math.max(...repos.map((r) => formatDate(r.updatedAt).length)),
3870
- lang: Math.max(...repos.map((r) => (r.language ?? "").length))
3871
- };
3872
- }
3873
3910
  function RepoListItem({
3874
3911
  repo,
3875
3912
  status,
@@ -3880,7 +3917,7 @@ function RepoListItem({
3880
3917
  repo.nameWithOwner.padEnd(widths.name),
3881
3918
  repo.visibility.toUpperCase().padEnd(widths.vis),
3882
3919
  (repo.isFork ? "FORK" : "").padEnd(4),
3883
- ("\u2605" + String(repo.stars)).padStart(widths.stars + 1),
3920
+ `\u2605${String(repo.stars)}`.padStart(widths.stars + 1),
3884
3921
  formatDate(repo.updatedAt).padEnd(widths.date),
3885
3922
  (repo.language ?? "").padEnd(widths.lang)
3886
3923
  ].join(" ");
@@ -3922,45 +3959,26 @@ function RepoDetail({ repo }) {
3922
3959
  }
3923
3960
  );
3924
3961
  }
3925
- function FilterPicker({
3926
- current,
3927
- selected
3928
- }) {
3929
- return /* @__PURE__ */ jsxs(
3930
- Box,
3931
- {
3932
- borderStyle: "round",
3933
- flexDirection: "column",
3934
- paddingX: 2,
3935
- paddingY: 1,
3936
- children: [
3937
- /* @__PURE__ */ jsx(Text, { bold: true, children: " Select type" }),
3938
- /* @__PURE__ */ jsx(Text, { children: " " }),
3939
- FILTERS.map((f, i) => /* @__PURE__ */ jsx(Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: i === selected ? " \u276F " + f : " " + f }, f))
3940
- ]
3941
- }
3942
- );
3962
+ function FilterPicker({ selected }) {
3963
+ return /* @__PURE__ */ jsxs(Box, { borderStyle: "round", flexDirection: "column", paddingX: 2, paddingY: 1, children: [
3964
+ /* @__PURE__ */ jsx(Text, { bold: true, children: " Select type" }),
3965
+ /* @__PURE__ */ jsx(Text, { children: " " }),
3966
+ FILTERS.map((f, i) => /* @__PURE__ */ jsx(
3967
+ Text,
3968
+ {
3969
+ color: i === selected ? "cyan" : void 0,
3970
+ bold: i === selected,
3971
+ children: i === selected ? ` \u276F ${f}` : ` ${f}`
3972
+ },
3973
+ f
3974
+ ))
3975
+ ] });
3943
3976
  }
3944
3977
  function ActionBar({
3945
3978
  status,
3946
- filter,
3947
3979
  isArchivedRepo
3948
3980
  }) {
3949
- const pending = status?.endsWith("...");
3950
- const deleted = status === "deleted";
3951
- const errored = status === "error";
3952
- const archived = isArchivedRepo || status === "archived";
3953
- const untouched = !status || errored;
3954
- const actions = [
3955
- { key: "v", label: "view", show: true },
3956
- { key: "a", label: "archive", show: (untouched || status === "unarchived") && !archived },
3957
- { key: "u", label: "unarchive", show: archived && !pending && !deleted },
3958
- { key: "d", label: "delete", show: !pending && !deleted },
3959
- { key: "s", label: "skip", show: untouched },
3960
- { key: "f", label: "filter", show: true },
3961
- { key: "r", label: "reload", show: true },
3962
- { key: "q", label: "quit", show: true }
3963
- ].filter((a) => a.show);
3981
+ const actions = getVisibleActions(status, isArchivedRepo);
3964
3982
  return /* @__PURE__ */ jsx(Box, { gap: 2, children: actions.map((action) => /* @__PURE__ */ jsxs(Text, { children: [
3965
3983
  /* @__PURE__ */ jsxs(Text, { bold: true, children: [
3966
3984
  "(",
@@ -3991,23 +4009,17 @@ function App() {
3991
4009
  }, [octokit]);
3992
4010
  useEffect(() => {
3993
4011
  loadRepos();
3994
- }, []);
3995
- const repos = useMemo(() => applyFilter(allRepos, filter), [allRepos, filter]);
4012
+ }, [loadRepos]);
4013
+ const repos = useMemo(
4014
+ () => applyFilter(allRepos, filter),
4015
+ [allRepos, filter]
4016
+ );
3996
4017
  const widths = useMemo(() => calcWidths(repos), [repos]);
3997
4018
  const repo = repos[current] ?? null;
3998
4019
  const setStatus = useCallback((repoName, status) => {
3999
4020
  setStatuses((prev) => new Map(prev).set(repoName, status));
4000
4021
  }, []);
4001
- const findNextUnprocessed = useCallback(
4002
- (from, currentStatuses, repoList) => {
4003
- let idx = from + 1;
4004
- while (idx < repoList.length && currentStatuses.has(repoList[idx].nameWithOwner)) {
4005
- idx++;
4006
- }
4007
- return Math.min(idx, repoList.length - 1);
4008
- },
4009
- []
4010
- );
4022
+ const repoNames = useMemo(() => repos.map((r) => r.nameWithOwner), [repos]);
4011
4023
  useInput((input, key) => {
4012
4024
  if (loading) return;
4013
4025
  if (filterOpen) {
@@ -4072,7 +4084,7 @@ function App() {
4072
4084
  if (k === "s" && (!currentStatus || currentStatus === "error")) {
4073
4085
  setStatus(repoName, "skipped");
4074
4086
  const next = new Map(statuses).set(repoName, "skipped");
4075
- setCurrent(findNextUnprocessed(current, next, repos));
4087
+ setCurrent(findNextUnprocessed(current, next, repoNames));
4076
4088
  return;
4077
4089
  }
4078
4090
  if (k === "a" && !isArchived && (!currentStatus || currentStatus === "unarchived" || currentStatus === "error")) {
@@ -4108,17 +4120,17 @@ function App() {
4108
4120
  r.nameWithOwner
4109
4121
  );
4110
4122
  }) }),
4111
- /* @__PURE__ */ jsx(Box, { flexGrow: 1, flexDirection: "column", justifyContent: "center", children: filterOpen ? /* @__PURE__ */ jsx(FilterPicker, { current: filter, selected: filterSelected }) : repo && /* @__PURE__ */ jsx(RepoDetail, { repo }) }),
4112
- /* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 1, children: filterOpen ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select enter confirm esc cancel" }) : /* @__PURE__ */ jsx(
4123
+ /* @__PURE__ */ jsx(Box, { flexGrow: 1, flexDirection: "column", justifyContent: "center", children: filterOpen ? /* @__PURE__ */ jsx(FilterPicker, { selected: filterSelected }) : repo && /* @__PURE__ */ jsx(RepoDetail, { repo }) }),
4124
+ /* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 1, children: filterOpen ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select enter confirm esc cancel" }) : /* @__PURE__ */ jsx(
4113
4125
  ActionBar,
4114
4126
  {
4115
4127
  status: repo ? statuses.get(repo.nameWithOwner) : void 0,
4116
- filter,
4117
4128
  isArchivedRepo: repo?.isArchived ?? false
4118
4129
  }
4119
4130
  ) })
4120
4131
  ] });
4121
4132
  }
4133
+ process.stdout.write("\x1B]0;gh-sweep\x07");
4122
4134
  render(/* @__PURE__ */ jsx(App, {}));
4123
4135
  /*! Bundled license information:
4124
4136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gh-sweep",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Interactive CLI to review and clean up your GitHub repos",
5
5
  "repository": {
6
6
  "type": "git",
@@ -15,17 +15,22 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "build": "tsup",
18
- "start": "tsup && node dist/index.js"
18
+ "start": "tsup && node dist/index.js",
19
+ "test": "vitest run",
20
+ "lint": "biome check",
21
+ "format": "biome check --write"
19
22
  },
20
23
  "dependencies": {
21
24
  "ink": "^6.8.0",
22
25
  "react": "^19.2.4"
23
26
  },
24
27
  "devDependencies": {
28
+ "@biomejs/biome": "^2.4.10",
25
29
  "@octokit/rest": "^22.0.1",
26
30
  "@types/node": "^22.0.0",
27
31
  "@types/react": "^19.2.14",
28
32
  "tsup": "^8.5.1",
29
- "typescript": "^5.8.0"
33
+ "typescript": "^5.8.0",
34
+ "vitest": "^4.1.2"
30
35
  }
31
36
  }