Open a GitHub repo (including org and private repos, as well as pagination)

This script allows you to authenticate with a GitHub personal access token and open repos from your user account or any organizations you belong to. It also displays private repos and handles pagination if the repo count exceeds 100 (just scroll to the bottom of the list and you'll see a "Load more..." option, when applicable).

Opening an org repo

// 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 });