Created
September 16, 2022 16:42
-
-
Save vogelino/82f586856e5b165144066827e6af5f96 to your computer and use it in GitHub Desktop.
Script Kit - GitHub Repos
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Name: GitHub Repos | |
// Description: List GitHub repositories and perform actions on them | |
// Author: Vogelino | |
// Twitter: @soyvogelino | |
import "@johnlindquist/kit"; | |
const { Octokit } = await npm("octokit"); | |
const shodwon = await npm("showdown"); | |
const converter = new shodwon.Converter({ | |
ghMentions: true, | |
emoji: true, | |
}); | |
converter.setFlavor("github"); | |
interface RawRepositoryType { | |
id: string; | |
name: string; | |
html_url: string; | |
full_name: string; | |
visibility: string[]; | |
description: string; | |
homepage?: string; | |
owner: { | |
login: string; | |
}; | |
open_issues_count: number; | |
} | |
interface RawIssueType { | |
id: string; | |
title: string; | |
body: string; | |
labels: { | |
name: string; | |
}[]; | |
number: string; | |
user: { | |
login: string; | |
}; | |
html_url: string; | |
} | |
interface RawBranchName { | |
id: string; | |
name: string; | |
protected: boolean; | |
} | |
interface OptionType<ValueType = unknown> { | |
name: string; | |
descritpion?: string; | |
preview?: string; | |
value: ValueType; | |
} | |
const auth = await env(`GITHUB_ACCESS_TOKEN`, "Enter your GitHub access token"); | |
const octokit = new Octokit({ auth }); | |
const { | |
data: { login }, | |
} = await octokit.rest.users.getAuthenticated(); | |
const universalOptions = [ | |
{ | |
name: "Browse", | |
description: "Open the url in your default browser", | |
value: "browse", | |
}, | |
{ | |
name: "Copy URL", | |
description: "Copy the URL to the clipboard", | |
value: "copy", | |
}, | |
]; | |
const mapRawRepo = (repo: RawRepositoryType) => ({ | |
name: repo.full_name, | |
description: [ | |
repo.visibility[0].toUpperCase() + repo.visibility.slice(1), | |
repo.description, | |
repo.homepage, | |
] | |
.filter(Boolean) | |
.join(" · "), | |
value: repo, | |
}); | |
const mapReposResponse = (response: { data: RawRepositoryType[] }) => | |
(response.data || []).map(mapRawRepo); | |
async function fetchAllRepos() { | |
return await octokit.paginate( | |
octokit.rest.repos.listForAuthenticatedUser, | |
{ sort: "updated", per_page: 100 }, | |
mapReposResponse | |
); | |
} | |
async function fetchRecentRepos() { | |
const res = await octokit.request("GET /user/repos", { | |
sort: "updated", | |
per_page: 50, | |
}); | |
return res.data; | |
} | |
async function fetchOwnerRepos() { | |
const res = await octokit.request("GET /user/repos", { | |
sort: "updated", | |
per_page: 50, | |
affiliation: "owner", | |
}); | |
return res.data; | |
} | |
function createIssuePullHandler(key: "pull" | "issue") { | |
return async (repo: RawRepositoryType) => { | |
const res = await octokit.request(`GET /repos/{owner}/{repo}/${key}s`, { | |
owner: repo.owner.login, | |
repo: repo.name, | |
pulls: key === "pull", | |
}); | |
let { data: items } = res as { data: RawIssueType[] }; | |
if (key === "issue") { | |
const pullsRes = (await octokit.request( | |
`GET /repos/{owner}/{repo}/pulls`, | |
{ | |
owner: repo.owner.login, | |
repo: repo.name, | |
} | |
)) as { data: RawIssueType[] }; | |
items = items.filter( | |
({ number }) => !pullsRes.data.find((p) => p.number === number) | |
); | |
} | |
if (items.length === 0) { | |
await div(`<div class="p-4 bg-white">No ${key}s</div>`); | |
return; | |
} | |
const itemSelected = await arg( | |
`Search for ${key}s`, | |
items.map((i) => ({ | |
name: i.title, | |
description: [ | |
`#${i.number}`, | |
i.user.login, | |
i.labels.map((l) => l.name).join(", "), | |
] | |
.filter(Boolean) | |
.join(" · "), | |
preview: ` | |
<style>p,h1,h2,h3 { margin-bottom: 8px; }</style> | |
<div class="p-4 bg-white"> | |
<h1>${i.title}</h1> | |
<small>By @${i.user.login} · ${i.labels | |
.map((l) => l.name) | |
.join(", ")}</small> | |
<hr/><br /> | |
${i?.body ? converter.makeHtml(i.body) : "–"} | |
</div> | |
`, | |
value: i, | |
})) | |
); | |
const action = await arg("Select an action to perform", [ | |
...universalOptions, | |
{ | |
name: `Copy ${key} number`, | |
description: `Copy ${key} number to clipboard for reference elswhere (eg. branch-name)`, | |
value: "number", | |
}, | |
{ | |
name: `Close ${key}`, | |
description: `Close ${key}`, | |
value: "close", | |
}, | |
]); | |
switch (action) { | |
case "browse": | |
await browse(itemSelected.html_url); | |
exit(); | |
case "copy": | |
await copy(itemSelected.html_url); | |
exit(); | |
case "number": | |
await copy(`${itemSelected.number}`); | |
exit(); | |
case "close": | |
await await octokit.request( | |
`PATCH /repos/{owner}/{repo}/${key}s/{${key}_number}`, | |
{ | |
owner: repo.owner.login, | |
repo: repo.name, | |
[`${key}_number`]: itemSelected.number, | |
state: "closed", | |
} | |
); | |
break; | |
} | |
notify(`${action} successful`); | |
const nextHanlder = createIssuePullHandler(key); | |
await nextHanlder(repo); | |
}; | |
} | |
async function getAllBranches(repo: RawRepositoryType) { | |
const { data } = await octokit.request(`GET /repos/{owner}/{repo}/branches`, { | |
owner: repo.owner.login, | |
repo: repo.name, | |
}); | |
const items = data as RawBranchName[]; | |
if (items.length === 0) { | |
await div(`<div class="p-4 bg-white">No branches</div>`); | |
return; | |
} | |
const branchSelected = await arg( | |
`Search for branches`, | |
items.map((b) => ({ | |
name: b.name, | |
description: b.protected ? "Protected" : undefined, | |
value: b, | |
})) | |
); | |
const action = await arg("Select an action to perform", [ | |
{ | |
name: `Copy name`, | |
description: `Copy name to clipboard for reference elswhere (eg. in issue)`, | |
value: "name", | |
}, | |
{ | |
name: `Rename`, | |
value: "rename", | |
}, | |
]); | |
switch (action) { | |
case "name": | |
await copy(branchSelected.name); | |
exit(); | |
case "rename": | |
const newName = await arg( | |
`What should the new name be? (was ${branchSelected.name})` | |
); | |
await octokit.request( | |
`POST /repos/{owner}/{repo}/branches/{branch}/rename`, | |
{ | |
owner: repo.owner.login, | |
repo: repo.name, | |
branch: branchSelected.name, | |
new_name: newName, | |
} | |
); | |
break; | |
} | |
notify(`${action} successful`); | |
await getAllBranches(repo); | |
} | |
const issueHandler = createIssuePullHandler(`issue`); | |
const pullHandler = createIssuePullHandler(`pull`); | |
function getTabHandler(getter: () => Promise<OptionType<RawRepositoryType>[]>) { | |
return async function handler() { | |
const repos = await getter(); | |
const repoSelected = await arg(`Hello ${login}. Search for a repo`, repos); | |
if (repos.length === 0) { | |
await div(`<div class="p-4 bg-white">No repos</div>`); | |
await handler(); | |
} | |
const action = await arg( | |
"Select an action to perform", | |
[ | |
...universalOptions, | |
repoSelected.open_issues_count > 0 && { | |
name: "Recent issues", | |
description: "List top 10 most recent open issues", | |
value: "issues", | |
}, | |
{ | |
name: "Pull Requests", | |
description: "List top 10 most recent PRs", | |
value: "prs", | |
}, | |
{ | |
name: "Branches", | |
description: "List branches", | |
value: "branches", | |
}, | |
].filter(Boolean) | |
); | |
switch (action) { | |
case "browse": | |
await browse(repoSelected.html_url); | |
exit(); | |
case "copy": | |
await copy(repoSelected.html_url); | |
exit(); | |
case "issues": | |
await issueHandler(repoSelected); | |
break; | |
case "prs": | |
await pullHandler(repoSelected); | |
break; | |
case "branches": | |
await getAllBranches(repoSelected); | |
break; | |
} | |
notify(`${action} successful`); | |
await handler(); | |
}; | |
} | |
const recentTab = getTabHandler(fetchRecentRepos); | |
onTab("Recent", recentTab); | |
onTab("Owner", getTabHandler(fetchOwnerRepos)); | |
onTab("All", getTabHandler(fetchAllRepos)); | |
await recentTab(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment