Neocities Deploy from GitHub Actions

A CI workflow to lint and build, a CD workflow to deploy to Neocities, and a brief defense of why I build the site twice.

I redesigned my Neocities site to lean harder into a GeoCities look. While I was in there, I wanted a deploy process simple enough to run from my iPad.

Neocities is dead-simple hosting for static sites. Bret Comnes already built a GitHub Action for deploying to it, so the hard part was done. I just wanted a clean test run before pointing it at the live site.

ci.yml runs Prettier over the code and builds the site. It fires on any push to main, any pull request, or a manual trigger.

name: CI

# I wanted the CI to run on any push to main, pull request, or
# manually triggered.
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  workflow_dispatch:

jobs:
  ci:
    runs-on: ubuntu-latest

    steps:
      - id: checkout
        name: πŸ“₯ Checkout code
        uses: actions/checkout@v4
        with:
          lfs: true

      - id: setup-node
        name: βš™οΈ Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "npm"

      - id: install-dependencies
        name: πŸ“¦ Install dependencies
        run: npm ci

      - id: cache-dependencies
        name: πŸ“‚ Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-npm-

      - id: prettier-check
        name: πŸ’… Run Prettier check
        run: npm run prettier

      # In the future I'll probably add `astro check` support
      # here: <https://docs.astro.build/en/guides/typescript/#type-checking>

      - id: build
        name: πŸ—οΈ Build website
        run: npm run build

cd.yml does the actual deploy. It hooks into the workflow_run event, which fires whenever CI finishes, and only proceeds if that run succeeded on main.

name: CD

# I want the CD to only run if CI succeeded on main.
on:
  workflow_run:
    branches: ["main"]
    types: ["completed"]
    workflows: ["CI"]
  # It would be nice if we can only allow this to be deployed
  # from `main`, but GitHub doesn't support that right now.
  workflow_dispatch:

concurrency:
  group: cd
  cancel-in-progress: true

jobs:
  cd:
    runs-on: ubuntu-latest
    if: ${{ github.event.workflow_run.conclusion == 'success' }}

    steps:
      - id: checkout
        name: πŸ“₯ Checkout code
        uses: actions/checkout@v4
        with:
          lfs: true

      - id: setup-node
        name: βš™οΈ Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version-file: ".nvmrc"
          cache: "npm"

      - id: install-dependencies
        name: πŸ“¦ Install dependencies
        run: npm ci

      - id: cache-dependencies
        name: πŸ“‚ Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-npm-

      - id: build
        name: πŸ—οΈ Build website
        run: npm run build

      - id: deploy
        name: πŸš€ Deploy to Neocities
        uses: bcomnes/deploy-to-neocities@v3
        with:
          api_key: ${{ secrets.NEOCITIES_API_TOKEN }}
          cleanup: false
          neocities_supporter: true
          preview_before_deploy: true
          dist_dir: dist/

Yes, that’s two builds. CD rebuilds instead of pulling CI’s output so I can run the deploy on its own when I need to, without depending on a CI run existing.

If you want to see the full code, it’s all open source on the GitHub repository.