Using GitHub Actions to Publish Astro Drafts
I love it when I manage to see a problem as an opportunity. A chance learn and challenge my skills. This story is a simple recount of such instance. Follow along and you’ll see how I solved my publishing problem with a few tools from my kit.
I’ve used Astro for a few months now. And so far I have enjoyed the experience.
But I have an issue with the way Astro handles draft pages. I can add a draft: true
flag to the blog post front matter and Astro will exclude the post when building.
Fine.
But what about when I want to publish a post?
As far as I can tell, the only way right now is to remove the flag again. Manually. Which means I can’t schedule a post for the future using Astro and simple markdown pages. Which is a problem. Especially as my AI powered blog experiment is supposed to publish blog posts daily.
How I publish Astro blog posts using GitHub Actions
To solve this problem I decided to write a small script.
The process is straightforward:
- Find all drafts
- Read the front matter of each post
- If
pubDate
is earlier thannow
, remove the draft flag
It isn’t pretty, but here is the full script:
import matter from "gray-matter";
import { getPathToBlog } from "./configuration";
import { readDir, readFromFile, writeToFile } from "./fileUtil";
function publishDraft(path: string) {
readDir(path).forEach((file) => {
const filePath = path + file;
const { data: frontMatter, content } = matter(readFromFile(filePath));
if (frontMatter["draft"]) {
const pubDate = frontMatter["pubDate"];
const parsedDate = new Date(pubDate);
if (parsedDate < now) {
console.log(`PUBLISH: ${file}. Publish date: ${pubDate}.`);
delete frontMatter["draft"];
writeToFile(filePath, matter.stringify(content, frontMatter));
} else {
console.log(`DRAFT: ${file}. Publish date: ${pubDate}.`);
}
}
});
}
const now = new Date();
const blogs = [getPathToBlog(), "path/to/other/blog"];
blogs.forEach(publishDraft)
To run the script I added the following line to the scripts
section of my package.json
file:
"publish-drafts": "ts-node src/publishDrafts.ts",
The command uses ts-node
to execute TypeScript. So I added that to devDependencies:
"ts-node": "^10.9.1",
And with that I can publish my Asto blog posts with a single command:
npm run publish-drafts
Almost.
The script will remove draft flags on all blog posts that should be published. But I still have to:
- Run the script
- Commit the changes
- Push the commit to GitHub
And I have to remember to run this whenever I have something to publish.
Still not great.
Let’s automate those 3 steps using GitHub actions.
I’ll explain the code in details, but here is the full code for a better overview:
name: Publish Blog Posts
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
defaults:
run:
working-directory: scripts
jobs:
run:
name: Remove Draft Tags to Publish Posts
runs-on: ubuntu-latest
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
- name: Install dependencies
run: npm install
- name: Remove draft tags
run: npm run publish-drafts
- name: Commit changes
uses: EndBug/add-and-commit@v9
with:
message: 'Publish Blog Posts'
committer_name: GitHub Actions
committer_email: actions@github.com
add: "['blog/src/pages/ai-overlord/blog/*.md']"
First up we need the action to run on a schedule. The schedule
configuration lets us do that using a cron expression. "0 0 * * *"
is every day at midnight.
on:
schedule:
- cron: "0 0 * * *"
workflow_dispatch:
The workflow_dispatch
configuration allows me to also run the action via the GitHub UI. This was useful for testing and also if I want to publish a draft right away.
The GitHub action has one job. To publish blog posts. The first few steps set up the environment: checkout code, set up Node.js, and install npm dependencies.
- name: Checkout repo
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
- name: Install dependencies
run: npm install
The next step runs my script:
- name: Remove draft tags
run: npm run publish-drafts
After this the GitHub action has a checked out version of my repository with changes to some files — the blog posts that are to be published. To get these changes to the live blog we need to commit them. And luckily there is an action for that: Add & Commit.
Using this action I can specify the files I want to commit and have the action do it:
- name: Commit changes
uses: EndBug/add-and-commit@v9
with:
message: 'Publish Blog Posts'
committer_name: GitHub Actions
committer_email: actions@github.com
add: "['blog/src/pages/ai-overlord/blog/*.md']"
And that’s it.
Once a day, at 00:00, the bots over at GitHub will run my code and commit any changes they have made to markdown files inside the blog/src/pages/ai-overlord/blog/ directory. Other bots will then detect these changes and rebuild my website.
Beautiful.
…or, well it works.
Epilogue
I have previously used Jekyll as my static site generator. It treats posts with a publish date in the future as drafts. This means rebuilding is enough to publish scheduled posts.
I could have implemented something similar for my Astro site, but the draft flag seems like the default supported option at the moment (e.g., the rss package also supports drafts).
With Astro 2.0 introducing content collections I may have to rethink my approach. Content collections seem more complex to use but they will also open up more options — like the ability to not publish posts with a date in the future.