Mandi Wise

Mandi Wise

// Menu: Open a GitHub Repo
// Description: Launch a GitHub repo in your browser
// Author: Mandi Wise
// Twitter: @mandiwise
// Learn how to create a personal access token for GitHub here:
// https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token
let { Octokit } = await npm("octokit");
const GH_PERSONAL_ACCESS_TOKEN = await env("GH_PERSONAL_ACCESS_TOKEN");
const octokit = new Octokit({ auth: GH_PERSONAL_ACCESS_TOKEN });
const ORGS_PER_PAGE = 20;
const REPOS_PER_PAGE = 100;
// GraphQL operations
const GetAccounts = `query GetAccounts($first: Int) {
viewer {
organizations(first: $first) {
edges {
node {
login
name
url
}
}
}
login
name
url
}
}`;
const ReposPage = `fragment ReposPage on RepositoryConnection {
edges {
node {
name
description
url
}
}
pageInfo {
endCursor
hasNextPage
}
}`;
const GetOrgRepos = `query GetOrgRepos($first: Int, $after: String, $login: String!) {
viewer {
organization(login: $login) {
repositories(
first: $first
after: $after
orderBy: { field: UPDATED_AT, direction: DESC }
) {
...ReposPage
}
}
}
}
${ReposPage}
`;
const GetUserRepos = `query GetUserRepos($first: Int, $after: String) {
viewer {
repositories(
first: $first
after: $after
orderBy: {field: UPDATED_AT, direction: DESC}
affiliations: OWNER
) {
...ReposPage
}
}
}
${ReposPage}
`;
// Get user and their organizations in a list
let dots = 0;
const accountsPlaceholderIntervalId = setInterval(() => {
setPlaceholder(`Loading GitHub accounts`.padEnd(++dots, "."));
}, 100);
const { viewer } = await octokit.graphql(GetAccounts, { first: ORGS_PER_PAGE });
if (!viewer) {
exit(1);
}
const { login, name, url, organizations } = viewer;
const accounts = [
{ name, value: login, description: url, type: "user" },
...organizations.edges.map(({ node: { login, name, url } }) => ({
name,
value: login,
description: url,
type: "org"
}))
].sort((a, b) => (a.name > b.name ? 1 : -1));
clearInterval(accountsPlaceholderIntervalId);
dots = 0;
const accountChoice = await arg("Which account?", accounts);
const { type: accountType } = accounts.find(
account => accountChoice === account.value
);
// Get repo list for the user or organization
let repositoriesAndLoadMore = [];
async function fetchRepositories(variables) {
const reposPlaceholderIntervalId = setInterval(() => {
setPlaceholder(`Loading repositories`.padEnd(++dots, "."));
}, 100);
let edges, endCursor, hasNextPage;
const oldLoadMore = repositoriesAndLoadMore.find(({ value }) =>
value.startsWith("load-more-after-")
);
if (oldLoadMore) {
repositoriesAndLoadMore.pop();
}
if (accountType === "org") {
({
viewer: {
organization: {
repositories: {
edges,
pageInfo: { endCursor, hasNextPage }
}
}
}
} = await octokit.graphql(GetOrgRepos, {
login: accountChoice,
...variables
}));
} else {
({
viewer: {
repositories: {
edges,
pageInfo: { endCursor, hasNextPage }
}
}
} = await octokit.graphql(GetUserRepos, variables));
}
repositoriesAndLoadMore = [
...repositoriesAndLoadMore,
...edges.map(({ node: { description, name, url } }) => ({
name,
description,
value: url
}))
];
if (hasNextPage) {
repositoriesAndLoadMore.push({
name: "Load more...",
value: `load-more-after-${endCursor}`
});
}
clearInterval(reposPlaceholderIntervalId);
dots = 0;
if (!repositoriesAndLoadMore.length) {
exit(1);
}
let repoChoice = await arg("Which project?", repositoriesAndLoadMore);
if (repoChoice.startsWith("load-more-after-")) {
await fetchRepositories({
first: REPOS_PER_PAGE,
after: repoChoice.split("-").pop()
});
} else {
exec(`open ${repoChoice}`);
}
}
await fetchRepositories({ first: REPOS_PER_PAGE });