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.
- package/dist/index.js +100 -88
- 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/
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3927
|
-
|
|
3928
|
-
})
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
3936
|
-
|
|
3937
|
-
|
|
3938
|
-
|
|
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
|
|
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(
|
|
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
|
|
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,
|
|
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, {
|
|
4112
|
-
/* @__PURE__ */ jsx(Box, { flexDirection: "column", gap: 1, children: filterOpen ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2191\u2193 select
|
|
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
|
+
"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
|
}
|