Listing all GitHub repositories in a GitHub Organisation
In my post Analysing our dependency trees to determine where we should send Open Source contributions for Hacktoberfest, I mentioned that it can be handy to list all the repositories in a given GitHub organisation, to perform queries against them.
The previous solution I had was pretty awkward, and wasteful as it queried a lot of data via the RESTful API which it didn't then use. Fortunately, I've since dug into the GraphQL endpoint which allows us to query exactly what we need, which means we can write the following query, using the very handy auto-paginating GraphQL Octokit plugin:
const fs = require("fs");
const { Octokit } = require("@octokit/core");
const { paginateGraphql } = require("@octokit/plugin-paginate-graphql");
const MyOctokit = Octokit.plugin(paginateGraphql);
const octokit = new MyOctokit({ auth: process.env.GITHUB_TOKEN });
(async () => {
const resp = await octokit.graphql.paginate(
`query paginate($cursor: String) {
organization(login: "deliveroo") {
repositories(first: 100, orderBy: {field: NAME, direction: ASC},
after: $cursor) {
nodes {
name
}
pageInfo {
hasNextPage
endCursor
}
}
}
}`
);
/*
resp = {
organization: {
repositories: {
nodes: [
{
name: "deliveroo.engineering"
},
{
name: "determinator"
}
],
pageInfo: {
hasNextPage: false,
endCursor: '...'
}
}
}
}
*/
fs.writeFileSync('repos.txt', resp.organization.repositories.nodes.map((o) => o.name).join("\n"));
})();
When running this like so:
env GITHUB_TOKEN=... node get-repos.js
This then produces a file with one repo name per line, which is example what we wanted in the previous article.
Alternatively, in Go:
package main
import (
"context"
"fmt"
"log"
"os"
"github.com/shurcooL/githubv4"
"golang.org/x/oauth2"
)
func main() {
client, err := newGitHubClient(context.Background(), os.Getenv("GITHUB_TOKEN"))
if err != nil {
log.Fatal(err)
}
repos, err := GetReposForOrg(context.Background(), client, "deliveroo")
if err != nil {
log.Fatal(err)
}
fmt.Printf("repos: %v\n", repos)
}
func newGitHubClient(ctx context.Context, githubToken string) (*githubv4.Client, error) {
if githubToken == "" {
return nil, nil, fmt.Errorf("no GITHUB_TOKEN environment variable was set")
}
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: githubToken},
)
httpClient := oauth2.NewClient(ctx, src)
gqlClient := githubv4.NewClient(httpClient)
return gqlClient, nil
}
func GetReposForOrg(ctx context.Context, client *githubv4.Client, org string) ([]string, error) {
var q struct {
Organization struct {
Repositories struct {
Nodes []struct {
Name string
}
PageInfo struct {
EndCursor githubv4.String
HasNextPage bool
}
} `graphql:"repositories(first: 100, orderBy: {field: NAME, direction: ASC}, after: $cursor)"`
} `graphql:"organization(login: $organization)"`
}
variables := map[string]interface{}{
"organization": githubv4.String(org),
"cursor": (*githubv4.String)(nil),
}
var allRepos []string
for {
err := client.Query(ctx, &q, variables)
if err != nil {
return nil, err
}
for _, n := range q.Organization.Repositories.Nodes {
allRepos = append(allRepos, fmt.Sprintf("%s/%s", org, n.Name))
}
if !q.Organization.Repositories.PageInfo.HasNextPage {
break
}
variables["cursor"] = githubv4.NewString(q.Organization.Repositories.PageInfo.EndCursor)
}
return allRepos, nil
}
With the following go.mod
:
module foo
go 1.22.1
require (
github.com/shurcooL/githubv4 v0.0.0-20240120211514-18a1ae0e79dc
golang.org/x/oauth2 v0.19.0
)
require github.com/shurcooL/graphql v0.0.0-20230722043721-ed46e5a46466 // indirect
Which can then be run like so:
env GITHUB_TOKEN=... go run .