Auto update readme on GitHub with Deno
Tags: deno, github, script
Published: 2023-04-24 12:16:55

---
title: Auto update readme on GitHub with Deno
published: true
date: 2023-04-23 13:58:34 UTC
tags: deno, github, script
canonical_url: https://krzysztofzuraw.com/blog/2023/auto-update-readme-github-deno/
---

GitHub has this nice feature where you can have a [readme](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-github-profile/customizing-your-profile/managing-your-profile-readme) file in the repository named after your GitHub handle and it will be displayed on your profile page. I used to have this file automatically generated by a script that would fetch my latest blog posts and display them in the readme. I used [Paweł](https://pawelgrzybek.com/fetch-most-recent-posts-to-your-github-profile-page-using-github-workflow-and-node-js/) solution and it worked fine.

I used to run this workflow every week on Sundays with a GitHub Action [schedule](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule)](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#schedule). Unfortunately recently it started failing - it turns out that switching to [Astro](https://krzysztofzuraw.com/blog/2023/migrating-blog-to-astro/) broke the RSS structure. As I was fixing the script I decided to completely rewrite it with [Deno](https://deno.land/).

I choose Deno because I wanted out-of-the-box support for TypeScript. One of the objectives of the rewrite was to have better handling of errors when iterating through the RSS feed. To accomplish that I've used [Zod](https://github.com/colinhacks/zod).

You can see the result in the snippet below:

```ts
import { z } from "https://deno.land/x/zod@v3.21.4/mod.ts";

const ResponseSchema = z
  .array(
    z.object({
      title: z.string(),
      url: z.string().url(),
    })
  )
  .nonempty();

const response = await fetch("https://krzysztofzuraw.com/blog.json");
const blogPosts = ResponseSchema.parse(await response.json());

const markdownPosts = blogPosts
  .map(({ title, url }) => `- [${title}](${url})`)
  .join("\n");

const openTag = `<!-- FEED-START -->`;
const closeTag = `<!-- FEED-END -->`;
const filePath = "./README.md";

const readme = await Deno.readTextFile(filePath);
const indexBefore = readme.indexOf(openTag) + openTag.length;
const indexAfter = readme.indexOf(closeTag);
const readmeContentChunkBreakBefore = readme.substring(0, indexBefore);
const readmeContentChunkBreakAfter = readme.substring(indexAfter);

const readmeNew = `${readmeContentChunkBreakBefore}\n${markdownPosts}\n${readmeContentChunkBreakAfter}`;

await Deno.writeTextFile(filePath, readmeNew);

console.log("Readme updated! 🎉");
```

I did a couple of changes to the script. I decided to add a new JSON page to Astro - it contains the last 3 blog posts - you can see the code [here](https://github.com/krzysztofzuraw/blog/blob/main/src/pages/blog/index.json.ts). I fetch `blog.json` and validate it using Zod. To be honest I'm impressed by this library - I previously used it only for form field validation but it works great for validating API responses as well.

I also found out that there is nifty [Deno GitHub Action](https://github.com/denoland/setup-deno) that I can use to set up the Deno environment in CI and that Deno has something like `package.json` called [deno.json](https://deno.land/manual@v1.32.5/getting_started/configuration_file) - it allows you to specify dependencies and scripts. I've used it to set up [deno task](https://github.com/krzysztofzuraw/krzysztofzuraw/blob/main/deno.json#L3).

The last change was to run this script not on the schedule but to trigger it when I push any changes to the blog repository. It works well but there is one caveat though - Netlify deployment runs on its own (via the GitHub app) and GitHub action runs too early to catch the changes. I poke around the internet to see if there was a way to trigger GitHub Action after Netlify deployed my blog but I didn't find anything officially supported by Netlify. I decided for now that I can live with that and I will just run this script when I or [Renovate](https://github.com/renovatebot/renovate) merge changes to the master. Maybe I will find a better solution in the future.