Writing Files to Github with Octokit and Typescript

Cameron Steele
Cameron Steele
Blog Image
GitHub is a popular version control system that allows developers to share and manage their code. Here at LibLab we wanted to store the code that our SDK generator wrote, automatically and in a natural way. In this case, we ended up using Octokit, GitHub’s official Javascript and Typescript SDK.
To get started we are going to install Octokit.
npm install @octokit/rest
Then we can create our typescript entry point. In this case src/index.ts
import { Octokit } from '@octokit/rest'; const client = new Octokit({ auth: '<AUTH TOKEN>' });
We instantiate the Octokit constructor and create a new client. We will need to replace the <AUTH TOKEN> with a personal access token from GitHub. Checkout the guide to getting yourself a personal access token from GitHub.
Now that we have our client setup we are going to look at how we can create files and commit them to a repository. In this tutorial I am going to be writing to an existing repo. This will allow you to write to any repo public or private that you have write access to.
Just like using git or the GitHub desktop application we need to do a couple of things to add a file to a repository.
  1. Generate a tree
  1. Commit files to the tree
  1. Push the files
 
To create a tree we need to get the latest commits. We will use the repos.listCommits method and we will pass an owner and repo argument. owner is the username or name of the organization the repository belongs to and repo is the name of the repository.
const commits = await client.repos.listCommits({ owner: "<USER OR ORGANIZATION NAME>", repo: "<REPOSITORY NAME>", });
We now want to take that list of commits and get the first item from it and retrieve its SHA hash. This will be used to tell the tree where in the history our commits should go. To get that we can make a variable to store the commit hash.
const commitSHA = commits.data[0].sha;
Now that we have our latest commit hash we can begin constructing our tree. We are going to pass the files we want to update or create to the tree construction method. In this case I will be representing the files I want to add as an Array of Objects. In my example I will be adding 2 files. test.md which will hold the string Hello World and time.txt which will store the latest timestamp.
const files = [ { name: "test.md", contents: "Hello World" }, { name: "time.txt", contents: new Date().toString() } ];
Octokit will want the files in a specific format:
interface File { path: string; mode: '100644' | '100755' | '040000' | '160000' | '120000'; type: 'commit' | 'tree' | 'blob'; sha?: string | null; content: string; }
There are a couple of properties in this interface.
  • path - Where in the repository the file should be stored.
  • mode - This is a code that represents what kind of file we are adding. Here is a quick run down:
    • File = '100644'
    • ExecutableFile = '100755'
    • Directory = '040000'
    • Submodule = '160000'
    • Symlink = '120000'
  • type - The type of action you are performing on the tree. In this case we are making a file commit
  • sha - The last known hash of the file if you plan on overwriting it. (This is not needed)
  • content - Whatever should be in the file
We can map to transform our file array into this proper format:
const commitableFiles: File[] = files.map(({name, contents}) => { return { path: name, mode: '100644', type: 'commit', content: contents } })
Now that we have an array of all the files we want to commit we will pass them to the createTree() method. You can think of this as adding your files in git.
const { data: { sha: currentTreeSHA }, } = await client.git.createTree({ owner: "<USER OR ORGANIZATION NAME>", repo: "<REPOSITORY NAME>", tree: commitableFiles, base_tree: CommitSHA, message: 'Updated programatically with Octokit', parents: [CommitSHA], });
Afterwards we have the variable currentTreeSHA . We will need this when we actually commit the files.
Next we go to actually make a commit on the tree
const { data: { sha: newCommitSHA }, } = await client.git.createCommit({ owner: "<USER OR ORGANIZATION NAME>", repo: "<REPOSITORY NAME>", tree: currentTreeSHA, message: `Updated programatically with Octokit`, parents: [latestCommitSHA], });
Then we push the commit
await client.git.updateRef({ owner: "<USER OR ORGANIZATION NAME>", repo: "<REPOSITORY NAME>", sha: newCommitSHA, ref: "heads/main", // Whatever branch you want to push to });
That is all you need to do to push files to a GitHub repository. We have found this functionality to be really useful when we need to push files that are automatically generated or often change.
If you find yourself needing to manage SDKs in multiple languages from an API, checkout LibLab. Our tools make generating SDKs dead simple with the ability to connect to the CI/CD tools you are probably already using.
LibLab!