Skip to content

Deploy Jekyll site to Pages #89

Deploy Jekyll site to Pages

Deploy Jekyll site to Pages #89

name: Deploy Jekyll site to Pages
# Starting point: https://github.com/actions/starter-workflows/blob/main/pages/jekyll.yml
on:
push:
branches: ["staging"]
# Allows you to run this workflow manually from the Actions tab.
# The manual path additionally cuts a GitHub release with the
# offline-browsable site copy attached as a zip; pushes to
# `staging` only deploy to Pages.
workflow_dispatch:
inputs:
release_tag:
description: 'Release tag (leave blank for auto: docs-YYYY-MM-DD-HHMM in UTC)'
required: false
type: string
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false
jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Set up Ruby and install gems
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
bundler-cache: true
working-directory: ./docs
cache-version: 0 # Increment this number if you need to re-download cached gems
- name: Setup Pages
id: pages
uses: actions/configure-pages@v6
- name: Write secondary config if available
run: |
echo "${{ vars.JEKYLL_SITE_CONFIG }}" > _site_config.yml
echo "SITE_CONFIG=_site_config.yml" >> "$GITHUB_ENV"
if: ${{ vars.JEKYLL_SITE_CONFIG != '' }}
working-directory: ./docs
- name: Build with Jekyll
run: bundle exec jekyll build --config _config.yml,$SITE_CONFIG --baseurl "${{ steps.pages.outputs.base_path }}"
working-directory: ./docs
env:
JEKYLL_ENV: production
PAGES_REPO_NWO: "${{ github.repository }}"
- name: Set up Python for link checks
uses: actions/setup-python@v5
with:
python-version: '3.14'
cache: 'pip'
- name: Install Python deps
run: pip install -r requirements.txt
- name: Check online links (check_links.py)
# `--fallback-extensions html` mirrors what GitHub Pages does at request time:
# an extensionless URL like `/FAQ` is served as `/FAQ.html`. Without the flag
# every pretty permalink on the site would look broken.
#
# `--base-path` strips the Pages baseurl (e.g. `/twinBASIC-docs`) from absolute
# URLs before resolving against `--root-dir`. Equivalent to the `--remap` regex
# that lychee used in earlier iterations of this step.
run: >-
python scripts/check_links.py
--offline --include-fragments
--fallback-extensions html
--index-files 'index.html,.'
--base-path '${{ steps.pages.outputs.base_path }}'
--root-dir docs/_site
docs/_site
- name: Check offline links (check_links.py)
# Strict check on `_site-offline/`: every link must resolve to an actual file
# under `file://`, with no extension fallback. Catches relative links in
# markdown sources that point at a permalink that doesn't match the rendered
# filename (e.g. `[Foo](Foo/)` when Jekyll wrote `Foo.html`, not
# `Foo/index.html`) -- the kind of breakage the online check above hides
# behind `--fallback-extensions html`.
run: >-
python scripts/check_links.py
--offline --include-fragments
--index-files index.html
--root-dir docs/_site-offline
docs/_site-offline
- name: Check for surviving live-site links in offline tree
# Flags any https://docs.twinbasic.com/<path> reference left in
# _site-offline/ HTML outside <code>/<pre> blocks. After offlinify
# strips the jekyll-seo-tag block, anything surviving is a source
# link that points at the live site instead of using a relative or
# /tB/... permalink that resolves locally. The bare root URL
# (https://docs.twinbasic.com[/]) is exempt -- intentional "go to
# the live site" links are allowed.
run: python scripts/check_offline_live_links.py
- name: Check book links (informational)
# Failures do not block the build. The book still has absolute
# intra-site URLs that the chapter transform has not yet rewritten
# and some fragment anchors that are not yet generated. Tracked here
# for visibility until those are fixed.
continue-on-error: true
run: >-
python scripts/check_links.py
--offline --include-fragments
--root-dir docs/_site-pdf
docs/_site-pdf/book.html
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install Node.js dependencies and Chromium
run: |
npm ci
sudo npx puppeteer browsers install chrome --install-deps
- name: Render book PDF
run: |
mkdir -p _pdf
node render-book.mjs _site-pdf/book.html -o _pdf/book.pdf --outline-tags h1,h2,h3,h4 --additional-script ../perf/detach-pages.js
working-directory: ./docs
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v5
with:
path: ./docs/_site
# The next two steps run only on manual dispatch -- they
# package the offline copy of the site (`docs/_site-offline/`,
# produced by the always-on `also_build_offline: true` flag in
# `_config.yml` via `_plugins/offlinify.rb`) and ship it as a
# workflow artifact for the `release` job to attach to a new
# GitHub release. Pushes to `staging` skip this and only
# deploy to Pages.
- name: Package offline site
if: github.event_name == 'workflow_dispatch'
run: |
if [ ! -d ./docs/_site-offline ]; then
echo "::error::./docs/_site-offline not found -- ensure also_build_offline is true in _config.yml (or in the JEKYLL_SITE_CONFIG override)"
exit 1
fi
(cd ./docs/_site-offline && zip -rq "${{ runner.temp }}/twinbasic-docs-offline.zip" .)
- name: Upload offline-site workflow artifact
if: github.event_name == 'workflow_dispatch'
uses: actions/upload-artifact@v4
with:
name: twinbasic-docs-offline-zip
path: ${{ runner.temp }}/twinbasic-docs-offline.zip
# Workflow-internal hand-off to the release job; the
# release itself carries the long-lived copy of the zip.
retention-days: 7
- name: Upload book PDF workflow artifact
uses: actions/upload-artifact@v4
with:
name: twinbasic-docs-book-pdf
path: ./docs/_pdf/book.pdf
# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
# Release job -- runs only on manual dispatch, after a successful
# Pages deploy. Each manual deploy cuts a new GitHub release whose
# tag tracks a publicly visible documentation snapshot at
# https://docs.twinbasic.com, with the offline-browsable copy of
# the site attached as twinbasic-docs-offline.zip.
release:
if: github.event_name == 'workflow_dispatch'
needs: deploy
runs-on: ubuntu-latest
permissions:
# softprops/action-gh-release needs contents:write to create
# tags and publish releases. The top-level permissions block
# only grants contents:read for the build/deploy path.
contents: write
steps:
- name: Download offline-site workflow artifact
uses: actions/download-artifact@v4
with:
name: twinbasic-docs-offline-zip
- name: Download book PDF workflow artifact
uses: actions/download-artifact@v4
with:
name: twinbasic-docs-book-pdf
- name: Compute release tag and name
id: tag
env:
INPUT_TAG: ${{ inputs.release_tag }}
run: |
if [ -n "$INPUT_TAG" ]; then
TAG="$INPUT_TAG"
NAME="$INPUT_TAG"
else
TAG="docs-$(date -u +'%Y-%m-%d-%H%M')"
NAME="Documentation $(date -u +'%Y-%m-%d %H:%M UTC')"
fi
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "name=$NAME" >> "$GITHUB_OUTPUT"
- name: Create release with offline-site zip
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.tag.outputs.tag }}
name: ${{ steps.tag.outputs.name }}
target_commitish: ${{ github.sha }}
body: |
Snapshot of the documentation deployed to <https://docs.twinbasic.com>.
**Offline copy:** download `twinbasic-docs-offline.zip`, extract anywhere, and open `index.html` in any browser — no server required. URLs, navigation, dark mode, and search all work over `file://`.
**PDF book:** download `book.pdf` for an offline-readable, printable copy of the full reference.
Source commit: ${{ github.sha }}.
files: |
twinbasic-docs-offline.zip
book.pdf
fail_on_unmatched_files: true
make_latest: 'true'