Astrojs GitHub Tooling
Before going digital, you might scribbling down some ideas in a sketchbook.
Astrojs GitHub Tooling
Even through Vite is probably more than enough for most of my client side tools, one of the advantages of Astrojs is the ability to snowball tools. Where any tooling I find works in one project can be applied to another project. This is a quick overview of the few simple tricks I’ve found so far to be able to copy and paste them into any other project.
GitHub Page
First one is to deploy to GitHub pages. Which is included in the AstroJs Documentation.
First step is to create a workflow file at the location .github/workflows/cd.yml
.
Fill it with the following content:
name: Deploy to GitHub Pages
on:
# Trigger the workflow every time you push to the `main` branch
# Using a different branch name? Replace `main` with your branch’s name
push:
branches: [ main ]
# Allows you to run this workflow manually from the Actions tab on GitHub.
workflow_dispatch:
# Allow this job to clone the repo and create a page deployment
permissions:
contents: read
pages: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout your repository using git
uses: actions/checkout@v4
- name: Install, build, and upload your site
uses: withastro/action@v2
# with:
# path: . # The root location of your Astro project inside the repository. (optional)
# node-version: 20 # The specific version of Node that should be used to build your site. Defaults to 20. (optional)
# package-manager: pnpm@latest # The Node package manager that should be used to install dependencies and build your site. Automatically detected based on your lockfile. (optional)
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
With GitHub pages, it’s likely going to be deployed to a page under your namespace,
to develop there before adding a custom domain name. This will be need to be
updated in the astro.config.mjs
file.
export default defineConfig({
site: 'https://dashgl.github.io',
base: '/rabbit-hole',
integrations: [tailwind(), icon(), sitemap(), robotsTxt(), mdx()]
});
And then it also makes sense to make a Link
component that will Automatically
wrap links with the correct href
. I don’t know if Astro provides their own
component for this, but hopefully they will if they don’t already.
---
export interface Props {
href: string;
}
const { href } = Astro.props;
---
<a href={`${import.meta.env.BASE_URL}${href}`}><slot /></a>
Status Checks
Once a site is being deployed to GitHub, it’s a good idea to make
sure that deployment is going to keep working on every Pull Request.
The two obvious checks are npx astro check
and npx astro build
to make sure that deployment is going to work. But I also found out
that Knip is a thing to look for ununsed references
to keep your code base from getting overrun with junk.
You can install it with npm i -D knip
, and run it with npx knip
. The
defaults should work, but you can put a knip.json
file in the base of the repository
if you need to adjust anything.
{
"roots": [
"src"
],
"extensions": [
".astro",
".js",
".ts",
".jsx",
".tsx"
],
"ignoreDependencies": [
"@iconify-json/mdi"
],
"ignorePatterns": [
"**/node_modules/**"
],
"entryPoints": [
"src/pages/**/*.astro"
],
"alias": {
"@components": "./src/components",
"@layouts": "./src/layouts"
}
}
A workflow file can be created at .github/workflows/cd.yml
to check each all of these on a pull request to main. Though
admittedly I haven’t figured out how to force a Pull Request to
pass these checks before being merged. The Action doesn’t show
up in the list of Status Checks when creating a ruleset.
name: Astro CI
on:
pull_request:
branches: [main]
jobs:
build-and-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '20' # Node.js version updated to 20
- name: Install dependencies
run: npm install
- name: Check with Astro
run: npx astro check
- name: Build with Astro
run: npx astro build
- name: Check for unused dependencies
run: npx knip
I would love it if AstroJs had something pre-defined in their documentation.
Create a New Blog Post
This one is pretty hacky, but then again what isn’t. I’ve been finding that writing blog posts is a good way to save scrum though random side projects (kind of like saving at a typewriter in Resident Evil). One of the hard parts of side projects is working on it, and then coming back to it the next week end completely forgetting what you wrote before.
With AstroJs, while the main content of the applicant might be single page, it doesn’t hurt to add notes, and integrating those notes into the deployment in more intuitive that writing a bunch of readmes or using the wiki. As you can use the stream of consciouness approach to write, where you’re at, what can be done next, and what possible problems might occur. This way your brain can sort through them and you can be more productive the next chance you have to sit down.
I’ve found that making a script to quickly create a new post makes it a lot easier to make a new post, then having to go back and copy and chnage a previous post.
Disclaimer
Note there are possible approaches for this, which are going to be more supported alternatives to this post.
astro-md-generator
frontmatter-cms
If you’re continuing to read this, it’s assuming you’ve either tried these and want a custom approach for how to do this, or you’re me.
New Post script
When creating a new post, we generally want to choose from a fixed list of categories
so we’re not mispelling a category, or randomly creating one-off new categories without
thinking about it. To enforce that we’re consciously managing categories, we want to
create a file with an enum of the categoes we want. We can put this in /src/content/blogTypes.ts
.
export const blogTypes = ['Hardware', 'Development'] as const
Then in our /src/content/config.ts
file we can reference this when defining our shcema.
// 1. Import utilities from `astro:content`
import { z, defineCollection } from 'astro:content';
import { blogTypes } from '@content/blogTypes';
// 2. Define a `type` and `schema` for each collection
const blogCollection = defineCollection({
type: 'content', // v2.5.0 and later
schema: ({ image }) => z.object({
title: z.string(),
description: z.string(),
publishDate: z.date(),
category: z.enum(blogTypes),
thumbnail: image(),
}),
});
// 3. Export a single `collections` object to register your collection(s)
export const collections = {
'blog': blogCollection,
};
Next we want to create a simple CLI that will create a new post from a prompt. We want the prompt to ask for stuff like, the title of the post, a description, and then ask for us to select an existing category or to create a new category. Once all three of these are complete, we want the script to create the new file with the front matter already populated so we can start writing.
I’m named this script createPost.ts
, and I added an entry into package.json
in order to be able to call it with npm run post
, which will call npx tsx createPost.ts
.
Note: This script WILL write files to your system, so make sure you check to make sure the file paths work with your repository before using it.
import inquirer from 'inquirer';
import fs from 'fs';
import { blogTypes } from './src/content/blogTypes';
type Answers = {
name: string;
description: string;
category: string;
newCategory?: string;
};
const questions = [
{
type: 'input',
name: 'name',
message: "What's the name of the post?",
},
{
type: 'input',
name: 'description',
message: "What's the description of the post?",
},
{
type: 'list',
name: 'category',
message: 'Select the category of the post:',
choices: [...blogTypes, 'Add New'],
},
{
type: 'input',
name: 'newCategory',
message: "Enter the new category:",
when: (answers: Answers) => answers.category === 'Add New',
},
];
console.log("Hello, let's make a new post ^_^");
inquirer.prompt(questions).then((answers: Answers) => {
const { name, description, category, newCategory } = answers;
const selectedCategory = category === 'Add New' ? newCategory! : category;
const publishDate = new Date().toISOString();
const content = `---
title: ${name}
description: ${description}
category: ${selectedCategory}
publishDate: ${publishDate}
thumbnail: /src/assets/thumbnails/placeholder.png
---
# ${name}
> A funny or inspirational quote goes here.`;
if (newCategory) {
const updatedBlogTypes = [...blogTypes, newCategory];
fs.writeFileSync(
'src/content/blogTypes.ts',
`export const blogTypes = ${JSON.stringify(updatedBlogTypes)} as const;`
);
}
fs.writeFileSync(
`src/content/blog/${name.toLowerCase().replace(/\s+/g, '-')}.mdx`,
content
);
console.log('Post created successfully!');
});