Deploy Jekyll site to Pages #82
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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: Run Lychee against the online tree | |
| uses: lycheeverse/lychee-action@v2 | |
| with: | |
| # --remap matches the fully-resolved file URI (not the raw href), so the pattern | |
| # must include the file:// scheme and --root-dir prefix. The (/|$) tail handles | |
| # both `/twinBASIC-docs/page` and bare `/twinBASIC-docs` — lychee strips trailing | |
| # slashes before remap, so we can't require one in the pattern. | |
| # | |
| # `--fallback-extensions html` mirrors what GitHub Pages does at request time: | |
| # an extensionless URL like `/FAQ` is served as `/FAQ.html`. Without the flag | |
| # lychee would flag every pretty permalink on the site. | |
| args: >- | |
| --offline --include-fragments | |
| --fallback-extensions html | |
| --index-files 'index.html,.' | |
| --remap '^file://${{ github.workspace }}/docs/_site${{ steps.pages.outputs.base_path }}(/|$) file://${{ github.workspace }}/docs/_site/' | |
| --root-dir ${{ github.workspace }}/docs/_site | |
| ./_site | |
| workingDirectory: ./docs | |
| fail: true | |
| - name: Run Lychee against the offline tree | |
| uses: lycheeverse/lychee-action@v2 | |
| with: | |
| # 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`. | |
| args: >- | |
| --offline --include-fragments | |
| --index-files 'index.html' | |
| --root-dir ${{ github.workspace }}/docs/_site-offline | |
| ./_site-offline | |
| workingDirectory: ./docs | |
| fail: true | |
| - 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 | |
| # 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: 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://`. | |
| Source commit: ${{ github.sha }}. | |
| files: twinbasic-docs-offline.zip | |
| fail_on_unmatched_files: true | |
| make_latest: 'true' |