How To Set Up a GitHub Action to Update the README File in All Your GitHub Repositories
Tags: actionshackathon21, javascript, tutorial, github
Published: 2021-12-07 14:06:03

[Instructions]: # (To submit to the GitHub Actions x DEV Hackathon 2021, please fill out all sections.)

<center><h2>Submission Info</h2></center>

---

### My Workflow
[Note]: # (Please make sure the project links to the appropriate GitHub Actions repository, and includes [an open source license](https://choosealicense.com/) and README.)

A workflow for updating the README file in all your repositories. This is especially helpful if you use a template and want to update a specific section with new information, e.g. an "Other Projects" section that needs to be updated with more recent projects.


### Submission Category
[Note]: # (Maintainer Must-Haves, DIY Deployments, Interesting IoT, Phone Friendly, or Wacky Wildcards)

Wacky Wildcards

### Yaml File or Link to Code

[Note]: # (Our markdown editor supports pretty embeds. Try this syntax: `{% github link_to_your_repo %}` to share a GitHub repository)

{% github geraldiner/projects-readme-tutorial no-readme %}


### Additional Resources / Info
[Note]: # (Be sure to link to any open source projects that are using your workflow!)

[Note]: # (Be sure to include the DEV usernames of your collaborators, if any.)

[Reminder]: # (Submissions are due on December 8th, 2021 (11:59 PM PT or 2 AM ET/6 AM UTC on December 9th.)

See below for a full tutorial!

---

## README Files in Brief and How to Set One Up

### Why You Should Have a README File in Your GitHub Repositories

As you continue to build out your portfolio with projects on GitHub, it's a good idea to include a README at the root of each project to describe what the project is about and how it was built. This is useful for anyone who comes across your project—other developers, recruiters, or even your future self—to get a quick understanding of the project and your process. Personally, it is also a great reflection tool, since I usually write them after a project has been completed.

![README file template by Alec Ortega (@alecortega)](https://cdn.hashnode.com/res/hashnode/image/upload/v1637519491653/tjH7fm8tr.png)

### How to Write a README File for Your Project

But of course, you don't always have to do this from scratch. Resources, such as [GitHub README Templates](https://www.readme-templates.com/), allows you to view README layouts for different sites, which can give you an idea of how to format your own README. [A simple Google search for "github readme template"](https://www.google.com/search?q=github+readme+template) will also bring up a number of templates in repositories that you can just fork for yourself.

As the title of this post suggests, a full breakdown of a README for your project is beyond the scope of this post. To help you get started, I recommend reading [Tracy Nuwagaba](https://hashnode.com/@tracycod3r)'s article, [How to write a readme for your GitHub project](https://tracy.hashnode.dev/how-to-write-a-readme-for-your-github-project). If you're like me, you can pick your favorite template and tweak it to fit your needs.

### Bonus Tip: Load the template as a snippet in VS Code

If you use VS Code as your main code/text editor, you can save your own snippets to reuse chunks of code in different places.

![Using code snippets in VS Code](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qvzd07gobrkmuhbcrft.gif)

Follow [these directions](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_create-your-own-snippets) to save the template as a custom snippet in VS Code. You can also use [this snippet generator](https://snippet-generator.app/) to easily format the code snippet.

## Problem: Updating Older README Files with Newer Content

As previously mentioned, I use the same README template for my projects, which includes a section for "Other Projects" and links to the repos of my other projects. (This is a not-so-secret technique to keep the user clicking through my GitHub portfolio 😏)

![Other Projects section of the README template I use for my projects](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tndgn6znrkkuzahohxi5.png)

Naturally, in my older projects, there were links to even-older projects. As I continue learning, I've built more projects and want to make sure the most recent and polished projects are always on display. But who wants to go through the other ~20+ repositories to update EVERY. SINGLE. README. FILE. 😵 Definitely not me.

Using the tried-and-true method of Googling to see if anyone else has encountered this issue, I came across Eugene Yan's article, [How to Update a GitHub Profile README Automatically](https://eugeneyan.com/writing/how-to-update-github-profile-readme-automatically/). He also cites Simon Willison, who achieved a similar task of [building a self-updating profile README for GitHub](https://simonwillison.net/2020/Jul/10/self-updating-profile-readme/).

Both of these were done with GitHub Actions, which I'd never heard of until then. And while this sounds like a really neat integration for the GitHub Profile README, it wasn't quite what I needed. But I thought maybe I could still use GitHub Actions to solve my problem.

## Solution: GitHub Actions to Run a Script that Interacts with the GitHub API

In short, [GitHub Actions](https://docs.github.com/en/actions) are automated workflows that run based on triggers to perform a set of jobs. (For JavaScript folks, it's like `addEventListener` for GitHub 😅) From pushes, to pull requests, to issues, a GitHub Action can be attached to those events, which will trigger the action and the job(s) will run. 

Again, I'd never heard of GitHub Actions before, so I had to spend a lot of time reading the documentation and other people's code. 

![https://3.bp.blogspot.com/-pH1pCb3OqtQ/W9EagJJc50I/AAAAAAAMQOg/ew-YHjCU4E4hw7ortL3ZDscpc-fXur4kwCLcBGAs/s1600/AS0004540_16.gif](https://3.bp.blogspot.com/-pH1pCb3OqtQ/W9EagJJc50I/AAAAAAAMQOg/ew-YHjCU4E4hw7ortL3ZDscpc-fXur4kwCLcBGAs/s1600/AS0004540_16.gif)

It turned out to be simpler than I thought, especially since I figured out you could write a script that you set up the action to run.

## Step-by-Step Instructions to Setting Up the GitHub Action

GitHub Actions uses YAML, which this tutorial doesn't require you to know a lot of. (Personally, I relied on the documentation and Eugene and Simon's code for this part.) But the main script is written in JavaScript and uses Node.js, so some knowledge of both is needed.

### Step 1: Set up your project in your local environment

First, set up your project on your computer or other work environment.

> 💡 If you're not particularly tied to one editor or don't have a local environment, you can still code in the browser! Check out my article on browser-based code editors: [Three Levels of Code Editors in the Browser](https://blog.geraldiner.com/three-levels-of-code-editors-in-the-browser).

For this tutorial, I have a directory called `projects-readme-tutorial` and I'll be using VS Code as my code editor.

![VS Code editor with a project initialized](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p4qjgi6x6v3nr6r4ggf5.png) 

### Step 2: Initiate a Node.js project with `npm init` and install dependencies

Next you have to initiate a Node.js project and install dependencies via a Terminal or Command Prompt.

In VS Code, you can open the terminal by going to Terminal > New Terminal. You can use `npm init`, which will take you through a series of questions to initiate your project, or use `npm init -y` to use all the default configuration settings.

Here, I used `npm init -y` and a `package.json` file was created in the directory.

![In the terminal, run `npm init -y` to initialized a NodeJS project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uqgqvtwyxclj3rs7no68.png)

Now you can install some dependencies for this project. 

For this project, you'll need the following dependencies:

- [@octokit/core](https://www.npmjs.com/package/@octokit/core) - For accessing the GitHub API
- [dotenv](https://www.npmjs.com/package/dotenv) - For loading environment variables (such as a GitHub Personal Access Token) and keeping them secret when you push to the repository
- [nodemon](https://www.npmjs.com/package/nodemon) (optional) - For automatically restarting the node application when files are changed. I like to use this for quick and easy testing/debugging so I don't have to stop and start the server every time I change a file.

Run the following line in your terminal to install the dependencies:

```jsx
npm i @octokit/core dotenv nodemon
```

Your `package.json` file should be updated with the dependencies you just installed. We'll come back to this in a bit to update the `scripts`.

![In package.json, you can see the dependencies added](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oloq6wx4y6obxgumh2mx.png) 

We'll also be using `fs` for file system access, but that's already integrated with Node.js.

### Step 3: Set up the file that holds the text to update all the README files

Now you'll create the file that holds the text that will replace all the old text in the README files. This is the dynamic content, such as my "Other Projects" section of my README template. 

When creating this file, make sure there is some part that is easily distinguishable from the rest. This is so that in the main script, you can easily look for where the new content should be placed. You could add comments, such as `<!-- start dynamic content here -->`, but I chose to go by the heading instead. There is no other "Other Projects" heading in the file.

I've created a new file named `projects.md` and pasted in the content that will replace the old content in the README files. 

![Side by side comparison of the projects.md and README template file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/61nurrmh44u54aoa7nue.png)

The `projects.md` file on the left is just the "Other Projects" section from my README template file on the right.

### Step 4: Write a script to access the GitHub API and update the README file in all repos

Now comes the meat and potatoes of the project. This is the script that will do all the work in finding the README file in all your repositories, updating the README file with the content from the `projects.md` file, and pushing the new file and its changes to the repository. If you're looking to do something else with this project, be sure to read the [GitHub API documentation](https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api).

For anything interesting, we need a Personal Access Token when making requests to the GitHub API. Follow these instructions to get your own [Personal Access Token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token).

Once you've got the Personal Access Token, save it in a `.env` file in the root of your project folder.

![Add your GitHub Personal Access Token to a .env file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7dan3k4st6qsu2ltery2.png)

Now, create a file named `index.js`. We'll need the `fs`, `dotenv`, and `octokit` packages.

![Working on index.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8t22g3hv414an7u5via2.png)

Now, we have to set up the `octokit` client with our Personal Access Token to access our repos and files. For more information about setting up `octokit`, take a look at [their documentation](https://github.com/octokit/octokit.js).

![Adding octokit client to index.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/313zp4u5952v7q96iyl2.png) 

The rest of the file is made up of asynchronous functions that will

1. Find all your repos
2. Access the current README file (or create one if it doesn't exist)
3. Commit the updated/new README file

**Find All Your Repos**

This part of the script uses the ["List repositories for a user" endpoint](https://docs.github.com/en/rest/reference/repos#list-repositories-for-a-user) of the GitHub API. 

![Asynchronous function to find all repositories and update the README file in each](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x6hlw6y40bq9ywvloukl.png)

On `line 5`, note that if you have more than 100 repositories, you'll have to modify the code a bit to cover all your repositories, since the max per page is 100.

On `line 7`, I take the extra step of filtering out certain repos that I don't want to be updated, including the README file for my GitHub profile and the project itself.

Then, on `lines 8-10` a simple for loop goes through each repo to find and access the README file.

**Access the Current README file**

This part of the script uses the ["Get repository content" endpoint](https://docs.github.com/en/rest/reference/repos#get-repository-content) of the GitHub API. There are two other endpoints for specifically getting the README file of a directory, but for some reason, I couldn't get those to work. This one was the only one that would successfully retrieve the file (if it existed).

![Asynchronous function to handle updating the README in the given repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uojtu7gi0f010hahkt5i.png)

On `line 3` is where the actual request is being made.

The data looks something like this 🤯:

![Using the GitHub API, we can get the data for the README file in a repo as shown here](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hxscxiso9mr72ks4zkc2.png)

But what we're most interested in are the following:

- `path` - the file name
- `sha` - the id for the latest commit
- `content` - the actual content of the README file in `base64`
- `encoding` - the encoding for the file content

So on `line 4`, there is some destructuring to isolate those pieces of information.

The lines that follow are for converting the file contents from `base64` to a String. Then, it finds the index for the "Other Projects" section heading (remember when I said to make it something easily distinguishable?). With this index, we can use `.slice()` to only take the first part of the content and concatenate the new content from the `projects.md` file.

There is a helper function to easily get the new content using the `fs` package:

![Helper function to get the contents of the projects.md file, which is used to update each README file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7mq2fyi01knimcp0kqnd.png)

If the README file doesn't exist, it will just create a new one with the content from `projects.md`.

**Commit the updated/new README file**

Finally, the updated/new README file will be committed to their respective repos. This is where the `sha` in the previous code segment comes in handy, so that it can replace the newest commit.

This part of the script uses the ["Create or update file contents" endpoint](https://docs.github.com/en/rest/reference/repos#create-or-update-file-contents) of the GitHub API. Using information generated in the previous code segment, it will make a `PUT` request.

![Asynchronous function to handle committing the updated README file to the repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/prvrqqzfyjl7xpy9iovm.png)

**The final script**

The final script should look like the code below. Remember that we've only defined functions right now. We need to actually call the main function, `updateAllRepos()`, so all that work will actually run.

![Final version of index.js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/75kfryo38l2kkbxna8nf.png)

**Running/Testing the script**

Now that we've got the script set up, you can test it yourself by running `index.js` with `node` (or `nodemon`, if it was installed).

Back in the `package.json` file, add the following to the `scripts` section:

![Adding scripts to package.json](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r97ev7m7q9i7toxmody0.png)

### Step 5: Set up the GitHub Action in a YAML file

Now that we've got the main script set up, we can work on setting up the actual GitHub Action. GitHub Actions are **workflows** made of **event(s)** that trigger **job(s)**. For this GitHub Action, we ultimately want to run the script we just created whenever the `projects.md` file is updated and pushed to the repository.

![Basic GitHub Action workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6f7fkfk787jdc358ifns.png)

In the root of your project directory, create a folder called `.github`, and in that folder, create another folder called `workflows`. In the `workflows` folder, create a file called `action.yaml`.

![Directory structure for the GitHub Action](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/60psf1ksy0irrejsee1t.png)

This action can be broken down to the following:

- The **event** will be a `push` to the repository, specifically for the `projects.md` file
- The main **job** is to run the `index.js` script, which is further broken down to the following steps:
    - Check out the repo
    - Set up a Node.js environment
    - Install the dependencies
    - Run the script

In the `action.yaml` file, this translates to the following:

![GitHub Action code for running index.js when the projects.md file is pushed to the repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/h98ylfars1mr0iryrkco.png)

You'll notice on `lines 20-21` that we're setting an environment variable called `GH_TOKEN`, like we did in the `.env` file. In order for the script to have access to the `GH_TOKEN` while being run by the action, you need to set a repository secret. [Follow these instructions](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) to set a secret for your repository. 

You can set any number of environment variables and secrets, and access them the same way.

### Step 6: Set up a GitHub repository for where the GitHub Action should live

Now that we've got all our files set up, we can push all our code to GitHub and get the GitHub Action up and running.

First, set up a GitHub repository to house this project and the GitHub Action.

![Setting up a repo on GitHub for the project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ajg2rgraakbmyh75ja3h.png)

Then, follow the instructions for "...or push an existing repository from the command line" to connect to your local work environment.

![Follow instructions for connecting your local environment to the repo just created](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bu9o3b1mu89ie7gizjxg.png) 

Don't forget to set up a `.gitignore` file to ignore the `node_modules` folder and `.env` file that contains your Personal Access Token.

![Set up .gitignore file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s9fszo7kjwguw25uco2b.png)

If everything worked out smoothly, you should see your code in the repository.

![Commit all the code we've done to the repo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gbsxdqp8x67yx9pikcyt.png) 

If you go to the "Actions" tab, you might not see anything yet since it's the initial commit.

![Initially, the Actions tab in the repo might be empty because of the initial commit](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dbhsfod2tc29w673ba73.png)

However, once you make some changes to `projects.md` and push the changes to the repository, it should run the action.

![Actions tab seeing some action after projects.md is committed with changes](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l42l2m53x8q763odx68k.png) 

And once the action is completed, you should see your README files updated, like so:

![Changes seen in the README file for one of my older repos](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3f7qhe5g54bbxgemr5uc.png)

## Wrapping Up

And that's it! Now you have a single file that can be used to update a bajillion other files! 

![Silly Ton Ton gif](https://media1.tenor.com/images/527fbaf54fd5e4a1df0431dc0dbdd3fb/tenor.gif)

I hope you learned as much as I did about GitHub Actions. It makes me curious about a whole bunch of other automations I could create, like the one that autogenerates the GitHub Profile README. 

If you have any questions or need some help, please feel free to reach out. 

Happy coding~ ✨

---

Thanks for reading!

💖 Geraldine | Say hi on Twitter 👋🏾 [@geraldinedesu](https://twitter.com/geraldinedesu)

---

![It's feng shui](https://media2.giphy.com/media/gM0piFxf9AMVy/giphy.gif)

This post was put together as if rearranging furniture. I had to move around the different pieces to make sure the step-by-step instructions were logical and easy to follow.

Source: [Giphy](https://media2.giphy.com/media/gM0piFxf9AMVy/giphy.gif)