From 1a057a6dc64bf0c4b9b4fd05c74135e72f2bb047 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 11:35:21 -0800 Subject: [PATCH 01/18] Centralize tool versions and improve build consistency Introduce versions.toml as the single source of truth for mdBook and plugin versions, used by both CI and local development scripts. Add cross-platform install scripts (install-tools.sh, install-tools.ps1) to automate tool installation based on versions.toml. Update GitHub Actions workflow to use these scripts and bump action versions. Refactor and expand README with unified setup, build, and contribution instructions. Remove redundant better-code/README.md and minor cleanup in book.toml. --- .github/workflows/deploy.yml | 16 ++--- .tool-versions | 7 +++ README.md | 119 ++++++++++++++++++++++------------- better-code/README.md | 52 --------------- better-code/book.toml | 1 - scripts/install-tools.ps1 | 76 ++++++++++++++++++++++ scripts/install-tools.sh | 57 +++++++++++++++++ versions.toml | 36 +++++++++++ 8 files changed, 260 insertions(+), 104 deletions(-) create mode 100644 .tool-versions delete mode 100644 better-code/README.md create mode 100644 scripts/install-tools.ps1 create mode 100644 scripts/install-tools.sh create mode 100644 versions.toml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index d977ea9..18f9895 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,4 +1,6 @@ name: Deploy mdBook to GitHub Pages +# Tool versions are managed in versions.toml at the repository root. +# To update mdbook or plugin versions, edit versions.toml and the CI will automatically use them. on: # Runs on pushes targeting the default branch @@ -26,22 +28,22 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - - name: Install mdBook - run: cargo install mdbook + - name: Install mdBook and plugins + run: | + chmod +x scripts/install-tools.sh + ./scripts/install-tools.sh - name: Setup Pages id: pages uses: actions/configure-pages@v5 - name: Build with mdBook - run: | - cd better-code - mdbook build + run: mdbook build ./better-code - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@v4 with: path: ./better-code/book diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..bc75e7b --- /dev/null +++ b/.tool-versions @@ -0,0 +1,7 @@ +# Tool versions for asdf version manager +# See: https://asdf-vm.com/ +# +# Note: For mdbook and plugin versions, see versions.toml +# Use the install scripts in better-code/ to install the correct versions +rust stable + diff --git a/README.md b/README.md index 27e4924..45aa568 100644 --- a/README.md +++ b/README.md @@ -8,90 +8,121 @@ We're migrating from using Jekyll to using [mdBook](https://github.com/rust-lang/mdBook). The mdBook version is located in the `./better-code` directory and includes automated CI/CD deployment to GitHub Pages. -## Installing and updating mdBook +The published book is available at: https://stlab.github.io/better-code/ -Install [Rust and -Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html): +## Prerequisites -Linux and macOS: +Install [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html): -``` +**Linux and macOS:** +```bash curl https://sh.rustup.rs -sSf | sh ``` -On Windows, you can download the installer from -[here](https://win.rustup.rs/). +**Windows:** +Download the installer from [here](https://win.rustup.rs/). -Once you have Rust and Cargo installed, you can install or upgrade mdBook by running: +## Installing mdBook and Plugins -``` -cargo install mdbook +**Important**: To ensure consistency between local development and CI, all tool +versions are centrally managed in `versions.toml`. Use the provided installation +scripts to automatically install the correct versions: + +**Linux/macOS:** +```bash +./scripts/install-tools.sh ``` -## Building the book +**Windows (PowerShell):** +```powershell +.\scripts\install-tools.ps1 +``` -To build the book, run: +These scripts automatically install mdBook and all required plugins using the +versions specified in `versions.toml`. This is the same file used by CI, ensuring +perfect consistency. +**Manual Installation:** +If you prefer to install manually, check `versions.toml` for the current version +numbers, then run: +```bash +cargo install mdbook --version +cargo install mdbook-katex --version ``` + +## Building and Serving the Book + +To build and serve the book locally with live reload: + +```bash mdbook serve ./better-code ``` -Open the browser to http://localhost:3000 to see the book. You can use the +This will start a local server at http://localhost:3000. You can use the Simple Browser in VSCode/Cursor to view the book while editing. -## Automated Deployment +To build the book without serving: -The mdBook is automatically built and deployed to GitHub Pages using GitHub Actions. -When you push changes to the main branch: +```bash +mdbook build ./better-code +``` -1. GitHub Actions will build the book using mdBook -2. The built book will be deployed to GitHub Pages -3. The book will be available at https://stlab.github.io/better-code/ +The built book will be in the `./better-code/book/` directory. -No manual deployment steps are required! +## Adding and Editing Content -### Conventions and Guidelines +1. Edit existing chapters in `better-code/src/` +2. Add new chapters by creating new `.md` files in `better-code/src/` +3. Update `better-code/src/SUMMARY.md` to include new chapters in the table of contents +4. The book will automatically rebuild and redeploy when you push changes to the main branch + +### Content Conventions * Avoid unnecessary HTML tags; use Markdown formatting to the degree possible. * Wrap lines at 80 columns to support diff-friendly change tracking. -* Chapters are represented as individual Markdown files in the chapters/ - subdirectory. -* Each chapter begins with a 2nd-level heading, e.g. `## Chapter Name`. All +* Each chapter begins with a 2nd-level heading, e.g. `## Chapter Name`. All other headings in a chapter are 3rd-level and below. -* Each file's name starts with a 4-digit number that determines its order in the - overall document. Initial numbering is spaced by 100s. * Maintain stable file names and heading titles for linkability until another solution is in place. -### Older draft +## Automated Deployment -Please see the [latest published draft](https://stlab.github.io/better-code/) -for information about the motivation for this project. +The mdBook is automatically built and deployed to GitHub Pages using GitHub Actions. +When you push changes to the main branch: -### Infrastructure +1. GitHub Actions builds the book using mdBook with versions from `versions.toml` +2. The built book is deployed to GitHub Pages +3. The book becomes available at https://stlab.github.io/better-code/ -The book is being migrated from [Jekyll](https://jekyllrb.com) to [mdBook](https://github.com/rust-lang/mdBook). -The new mdBook version is automatically built and deployed to [GitHub Pages](https://pages.github.com) using GitHub Actions. +No manual deployment steps are required! -The legacy Jekyll files remain in the root directory for reference during the transition. +## Dependency Management -### Running a local server +All tool versions (mdBook, plugins, and future tools like Swift) are managed in +`versions.toml` at the repository root. To update a version: -If you are able to install the necessary parts for jekyll, +1. Edit the version number in `versions.toml` +2. Run the appropriate install script to update locally +3. Test your changes +4. Commit - CI will automatically use the new version -``` +## Legacy Jekyll Content + +The legacy Jekyll files remain in the `archive/` directory for reference during +the transition. You can ignore these unless you're working on the migration. + +**Running the Jekyll version (for reference only):** + +If you need to run the legacy Jekyll site: + +```bash bundle exec jekyll serve -l ``` -will start a server for the site at http://localhost:4000. +This will start a server at http://localhost:4000. -Creating a complete installation of jekyll and all the parts needed for github -pages development can be fraught. If you install -[docker-compose](https://docs.docker.com/compose/), you can start the server -by invoking +Alternatively, if you have [docker-compose](https://docs.docker.com/compose/): -``` +```bash docker-compose up ``` - -in the root directory of your working copy. diff --git a/better-code/README.md b/better-code/README.md deleted file mode 100644 index 1125d25..0000000 --- a/better-code/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# Better Code - mdBook - -This directory contains the mdBook version of the Better Code course. - -## Local Development - -### Prerequisites - -Install [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html): - -**Linux and macOS:** -```bash -curl https://sh.rustup.rs -sSf | sh -``` - -**Windows:** -Download the installer from [here](https://win.rustup.rs/). - -### Install mdBook - -```bash -cargo install mdbook -``` - -### Building and Serving the Book - -To build and serve the book locally: - -```bash -mdbook serve -``` - -This will start a local server at `http://localhost:3000`. - -To just build the book without serving: - -```bash -mdbook build -``` - -The built book will be in the `book/` directory. - -## Deployment - -The book is automatically built and deployed to GitHub Pages using GitHub Actions when changes are pushed to the main branch. - -## Adding Content - -1. Edit existing chapters in the `src/` directory -2. Add new chapters by creating new `.md` files in `src/` -3. Update `src/SUMMARY.md` to include new chapters in the table of contents -4. The book will automatically rebuild when you push changes to GitHub diff --git a/better-code/book.toml b/better-code/book.toml index 5bd576a..f626b05 100644 --- a/better-code/book.toml +++ b/better-code/book.toml @@ -1,7 +1,6 @@ [book] authors = ["Sean Parent"] language = "en" -multilingual = false src = "src" title = "Better Code" description = "A principled and rigorous approach to software development" diff --git a/scripts/install-tools.ps1 b/scripts/install-tools.ps1 new file mode 100644 index 0000000..473b328 --- /dev/null +++ b/scripts/install-tools.ps1 @@ -0,0 +1,76 @@ +# Install mdBook and plugins using versions from ../versions.toml +# This ensures consistency between local development and CI + +$ErrorActionPreference = "Stop" + +$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$VersionsFile = Join-Path (Split-Path -Parent $ScriptDir) "versions.toml" + +# Function to parse TOML and extract version +function Get-ToolVersion { + param ( + [string]$Tool, + [string]$Section = "" + ) + + $content = Get-Content $VersionsFile -Raw + + if ($Section) { + # Extract from section like [mdbook-plugins] + $pattern = "(?ms)\[$Section\].*?$Tool\s*=\s*`"([^`"]+)`"" + } else { + # Extract from top-level section like [mdbook] + $pattern = "(?ms)\[$Tool\].*?version\s*=\s*`"([^`"]+)`"" + } + + if ($content -match $pattern) { + return $Matches[1] + } + return $null +} + +# Function to get all plugins from [mdbook-plugins] section +function Get-MdbookPlugins { + $content = Get-Content $VersionsFile -Raw + $plugins = @{} + + if ($content -match '(?ms)\[mdbook-plugins\](.*?)(\[|$)') { + $section = $Matches[1] + $lines = $section -split "`n" + + foreach ($line in $lines) { + if ($line -match '^([a-zA-Z0-9_-]+)\s*=\s*"([^"]+)"') { + $plugins[$Matches[1]] = $Matches[2] + } + } + } + + return $plugins +} + +Write-Host "Reading versions from $VersionsFile..." -ForegroundColor Cyan + +# Install mdBook +$MdbookVersion = Get-ToolVersion -Tool "mdbook" +if ($MdbookVersion) { + Write-Host "`nInstalling mdBook $MdbookVersion..." -ForegroundColor Cyan + cargo install mdbook --version $MdbookVersion +} else { + Write-Host "Error: Could not find mdbook version in versions.toml" -ForegroundColor Red + exit 1 +} + +# Install mdBook plugins +Write-Host "`nInstalling mdBook plugins..." -ForegroundColor Cyan + +$plugins = Get-MdbookPlugins +foreach ($plugin in $plugins.GetEnumerator()) { + Write-Host "Installing $($plugin.Key) $($plugin.Value)..." -ForegroundColor Cyan + cargo install $plugin.Key --version $plugin.Value +} + +Write-Host "`n✓ Installation complete!" -ForegroundColor Green +Write-Host "`nInstalled versions:" -ForegroundColor Cyan +mdbook --version +cargo install --list | Select-String "mdbook" + diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh new file mode 100644 index 0000000..33037a2 --- /dev/null +++ b/scripts/install-tools.sh @@ -0,0 +1,57 @@ +#!/bin/bash +# Install mdBook and plugins using versions from ../versions.toml +# This ensures consistency between local development and CI + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +VERSIONS_FILE="${SCRIPT_DIR}/../versions.toml" + +# Function to extract version from TOML file +get_version() { + local tool=$1 + local section=$2 + + if [ -z "$section" ]; then + # Top-level tool like [mdbook] + grep -A 1 "^\[$tool\]" "$VERSIONS_FILE" | grep "version" | sed 's/.*"\(.*\)".*/\1/' + else + # Plugin in a section like [mdbook-plugins] + awk "/^\[$section\]/,/^\[/ {if (\$0 ~ /^$tool = /) print}" "$VERSIONS_FILE" | sed 's/.*"\(.*\)".*/\1/' + fi +} + +echo "Reading versions from ${VERSIONS_FILE}..." + +# Install mdBook +MDBOOK_VERSION=$(get_version "mdbook") +if [ -n "$MDBOOK_VERSION" ]; then + echo "Installing mdBook ${MDBOOK_VERSION}..." + cargo install mdbook --version "${MDBOOK_VERSION}" +else + echo "Error: Could not find mdbook version in versions.toml" + exit 1 +fi + +# Install mdBook plugins +echo "" +echo "Installing mdBook plugins..." + +# Extract all plugins from [mdbook-plugins] section +while IFS= read -r line; do + if [[ $line =~ ^([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then + plugin="${BASH_REMATCH[1]}" + version="${BASH_REMATCH[2]}" + echo "Installing ${plugin} ${version}..." + cargo install "${plugin}" --version "${version}" + fi +done < <(awk '/^\[mdbook-plugins\]/,/^\[/ {if ($0 ~ /^[a-zA-Z]/) print}' "$VERSIONS_FILE") + +echo "" +echo "✓ Installation complete!" +echo "" +echo "Installed versions:" +mdbook --version +# List installed plugins +cargo install --list | grep mdbook + diff --git a/versions.toml b/versions.toml new file mode 100644 index 0000000..3f07262 --- /dev/null +++ b/versions.toml @@ -0,0 +1,36 @@ +# Tool versions - single source of truth for all build dependencies +# Used by CI and local development scripts +# +# Usage: +# Linux/macOS: ./scripts/install-tools.sh +# Windows: .\scripts\install-tools.ps1 +# +# To update a version: +# 1. Change the version number here +# 2. Run the install script from the repository root +# 3. Test locally: mdbook serve ./better-code +# 4. Commit the change - CI will automatically use the new version +# +# Note: After adding mdbook plugins here, you may also need to configure +# them in better-code/book.toml. See mdbook documentation for plugin setup. + +[mdbook] +version = "0.5.2" + +[mdbook-plugins] +mdbook-katex = "0.10.0-alpha" +# mdbook-mermaid = "x.y.z" # Uncomment when diagram support is needed +# Add more mdbook plugins here as needed + +# [swift] +# Uncomment and configure when Swift tooling is added +# version = "5.9" + +# Add other tool dependencies here as the project grows +# Examples: +# [python] +# version = "3.11" +# +# [node] +# version = "20.10.0" + From 1b2cc2c5a6d9b8a5fbc028d9423fd2e69be30b62 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 23 Dec 2025 22:09:15 +0000 Subject: [PATCH 02/18] Refactor version parsing for mdbook plugins Co-authored-by: sparent --- scripts/install-tools.ps1 | 4 +++- scripts/install-tools.sh | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/scripts/install-tools.ps1 b/scripts/install-tools.ps1 index 473b328..9c109eb 100644 --- a/scripts/install-tools.ps1 +++ b/scripts/install-tools.ps1 @@ -34,7 +34,9 @@ function Get-MdbookPlugins { $content = Get-Content $VersionsFile -Raw $plugins = @{} - if ($content -match '(?ms)\[mdbook-plugins\](.*?)(\[|$)') { + # Capture everything *after* the header line up to (but not including) the next section header. + # Use \z (end-of-string) instead of $ (end-of-line in multiline mode). + if ($content -match '(?ms)^\[mdbook-plugins\]\s*\r?\n(.*?)(?=^\[|\z)') { $section = $Matches[1] $lines = $section -split "`n" diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index 33037a2..6e51c88 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -17,7 +17,12 @@ get_version() { grep -A 1 "^\[$tool\]" "$VERSIONS_FILE" | grep "version" | sed 's/.*"\(.*\)".*/\1/' else # Plugin in a section like [mdbook-plugins] - awk "/^\[$section\]/,/^\[/ {if (\$0 ~ /^$tool = /) print}" "$VERSIONS_FILE" | sed 's/.*"\(.*\)".*/\1/' + awk -v section="$section" -v tool="$tool" ' + BEGIN { in_section = 0 } + $0 ~ ("^\\[" section "\\]$") { in_section = 1; next } + in_section && $0 ~ "^\\[" { exit } + in_section && $1 == tool && $2 == "=" { print; exit } + ' "$VERSIONS_FILE" | sed 's/.*"\(.*\)".*/\1/' fi } @@ -45,7 +50,14 @@ while IFS= read -r line; do echo "Installing ${plugin} ${version}..." cargo install "${plugin}" --version "${version}" fi -done < <(awk '/^\[mdbook-plugins\]/,/^\[/ {if ($0 ~ /^[a-zA-Z]/) print}' "$VERSIONS_FILE") +done < <( + awk ' + BEGIN { in_section = 0 } + /^\[mdbook-plugins\]$/ { in_section = 1; next } + in_section && /^\[/ { exit } + in_section && $0 ~ /^[a-zA-Z0-9_-]+[[:space:]]*=/ { print } + ' "$VERSIONS_FILE" +) echo "" echo "✓ Installation complete!" From ac5697bd1bb7287271368a24d7e3d1e84bf0b612 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 14:19:59 -0800 Subject: [PATCH 03/18] Add PR preview deployment and cleanup workflows Introduces a PR preview system using GitHub Pages, including a new workflow for automatic cleanup of preview deployments when PRs are closed. Updates the deploy workflow to support PR previews, adds documentation for the preview system, and enhances the README with details about PR preview deployments. --- .github/PR-PREVIEW.md | 101 ++++++++++++++ .github/workflows/cleanup-preview.yml | 31 +++++ .github/workflows/deploy.yml | 192 +++++++++++++++++++++++--- README.md | 18 +++ 4 files changed, 323 insertions(+), 19 deletions(-) create mode 100644 .github/PR-PREVIEW.md create mode 100644 .github/workflows/cleanup-preview.yml diff --git a/.github/PR-PREVIEW.md b/.github/PR-PREVIEW.md new file mode 100644 index 0000000..b73f076 --- /dev/null +++ b/.github/PR-PREVIEW.md @@ -0,0 +1,101 @@ +# PR Preview System + +This repository uses GitHub Pages for automated PR preview deployments without any third-party dependencies. + +## How It Works + +### Architecture + +- **Production**: `gh-pages` branch root → https://stlab.github.io/better-code/ +- **PR Previews**: `gh-pages` branch under `pr-preview/[NUMBER]/` → https://stlab.github.io/better-code/pr-preview/123/ +- **Preview Index**: https://stlab.github.io/better-code/pr-preview/ (lists all active previews) + +### Workflow + +1. **Open PR** → Builds and deploys to `gh-pages:pr-preview/[PR-NUMBER]/` +2. **Push to PR** → Updates the preview deployment +3. **Close/Merge PR** → Automatically removes the preview directory +4. **Merge to main** → Deploys to production (root of gh-pages) + +## Features + +✅ **Automatic deployment** - No manual steps required +✅ **PR comments** - Bot posts preview URL on every PR +✅ **Automatic cleanup** - Previews removed when PR closes +✅ **No third-party services** - Pure GitHub Pages +✅ **Fast builds** - Only builds changed content +✅ **Isolated environments** - Each PR gets its own subdirectory + +## File Structure on gh-pages Branch + +``` +gh-pages/ +├── index.html # Production site +├── chapter-1-intro.html +├── ... +└── pr-preview/ + ├── index.html # List of all PR previews + ├── 123/ # Preview for PR #123 + │ ├── index.html + │ └── ... + ├── 124/ # Preview for PR #124 + │ └── ... + └── ... +``` + +## Configuration + +### Required Permissions + +The workflow requires these permissions (already configured): +- `contents: write` - To push to gh-pages branch +- `pages: write` - To deploy to GitHub Pages +- `pull-requests: write` - To comment on PRs + +### GitHub Pages Settings + +Ensure GitHub Pages is configured to deploy from the `gh-pages` branch: +1. Go to Settings → Pages +2. Source: Deploy from a branch +3. Branch: `gh-pages` / `root` + +## Troubleshooting + +### Preview not deploying + +Check: +1. Workflow ran successfully in Actions tab +2. `gh-pages` branch exists and has the preview directory +3. GitHub Pages is enabled and set to deploy from `gh-pages` branch + +### Preview URL 404 + +- Wait 1-2 minutes after deployment for GitHub Pages to update +- Check if the directory exists in the `gh-pages` branch +- Verify the PR number in the URL matches the directory name + +### Old previews not cleaning up + +- Check if the cleanup workflow ran when PR closed +- Manually remove with: `git checkout gh-pages && git rm -rf pr-preview/[NUMBER] && git commit && git push` + +## Manual Cleanup + +To manually remove all PR previews: + +```bash +git checkout gh-pages +git rm -rf pr-preview/ +git commit -m "Clean up all PR previews" +git push +``` + +To remove a specific preview: + +```bash +git checkout gh-pages +git rm -rf pr-preview/123 +git commit -m "Remove preview for PR #123" +git push +``` + diff --git a/.github/workflows/cleanup-preview.yml b/.github/workflows/cleanup-preview.yml new file mode 100644 index 0000000..ce2c2f8 --- /dev/null +++ b/.github/workflows/cleanup-preview.yml @@ -0,0 +1,31 @@ +name: Cleanup PR Preview + +on: + pull_request: + types: [closed] + +permissions: + contents: write + +jobs: + cleanup: + runs-on: ubuntu-latest + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v6 + with: + ref: gh-pages + + - name: Remove PR preview directory + run: | + if [ -d "pr-preview/${{ github.event.number }}" ]; then + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git rm -rf pr-preview/${{ github.event.number }} + git commit -m "Remove preview for closed PR #${{ github.event.number }}" + git push + echo "Removed preview for PR #${{ github.event.number }}" + else + echo "No preview directory found for PR #${{ github.event.number }}" + fi + diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 18f9895..61f44bc 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,4 +1,4 @@ -name: Deploy mdBook to GitHub Pages +name: Build and Deploy mdBook # Tool versions are managed in versions.toml at the repository root. # To update mdbook or plugin versions, edit versions.toml and the CI will automatically use them. @@ -6,24 +6,28 @@ on: # Runs on pushes targeting the default branch push: branches: ["main"] + + # Runs on pull requests for validation and preview deployment + pull_request: + branches: ["main"] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: - contents: read + contents: write # Needed for PR preview deployments pages: write id-token: write + pull-requests: write # Needed to comment on PRs -# 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. +# Allow only one concurrent deployment per PR or main concurrency: - group: "pages" - cancel-in-progress: false + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.number || 'main' }} + cancel-in-progress: true jobs: - # Build job + # Build job - runs on both PRs and main branch build: runs-on: ubuntu-latest steps: @@ -34,27 +38,177 @@ jobs: run: | chmod +x scripts/install-tools.sh ./scripts/install-tools.sh - - - name: Setup Pages - id: pages - uses: actions/configure-pages@v5 - - - name: Build with mdBook + + # For PRs: Build with base path for preview subdirectory + - name: Build with mdBook (PR Preview) + if: github.event_name == 'pull_request' + run: | + # Update book.toml to use PR-specific path + sed -i 's|site-url = "/better-code/"|site-url = "/better-code/pr-preview/${{ github.event.number }}/"|' better-code/book.toml + mdbook build ./better-code + + # For main: Build with production path + - name: Build with mdBook (Production) + if: github.event_name == 'push' && github.ref == 'refs/heads/main' run: mdbook build ./better-code - - - name: Upload artifact - uses: actions/upload-pages-artifact@v4 + + # Upload build artifact for inspection/debugging + - name: Upload build artifact + uses: actions/upload-artifact@v4 with: + name: book-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || 'production' }} path: ./better-code/book + retention-days: ${{ github.event_name == 'pull_request' && 7 || 30 }} + + # PR Preview Deployment - deploys to gh-pages branch under pr-preview/NUMBER/ + deploy-preview: + if: github.event_name == 'pull_request' + needs: build + runs-on: ubuntu-latest + steps: + - name: Checkout gh-pages branch + uses: actions/checkout@v6 + with: + ref: gh-pages + + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: book-pr-${{ github.event.number }} + path: ./pr-preview/${{ github.event.number }} + + - name: Create preview index + run: | + # Create an index of all PR previews if it doesn't exist + if [ ! -f pr-preview/index.html ]; then + cat > pr-preview/index.html << 'EOF' + + + + PR Previews - Better Code + + + +

Pull Request Previews

+

← Back to main documentation

+

Preview deployments for pull requests:

+
    + + + + EOF + fi + + - name: Commit and push preview + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add pr-preview/${{ github.event.number }} + git add pr-preview/index.html + git commit -m "Deploy preview for PR #${{ github.event.number }}" || echo "No changes to commit" + git push + + - name: Comment PR with preview URL + uses: actions/github-script@v7 + with: + script: | + const prNumber = context.issue.number; + const previewUrl = `https://stlab.github.io/better-code/pr-preview/${prNumber}/`; + const comment = `## 📚 Documentation Preview + + Your changes have been deployed to a preview environment: + + 🔗 **Preview URL:** ${previewUrl} + + This preview will be automatically updated with new commits and removed when the PR is closed. + + Built with commit ${context.sha.substring(0, 7)}`; + + // Find existing preview comment + const { data: comments } = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + }); + + const botComment = comments.find(comment => + comment.user.type === 'Bot' && + comment.body.includes('Documentation Preview') + ); + + if (botComment) { + // Update existing comment + await github.rest.issues.updateComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: botComment.id, + body: comment + }); + } else { + // Create new comment + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: comment + }); + } - # Deployment job - deploy: + # Production Deployment - deploys to root of GitHub Pages + deploy-production: + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + needs: build environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest - needs: build steps: + - name: Download build artifact + uses: actions/download-artifact@v4 + with: + name: book-production + path: ./book + + - name: Setup Pages + id: pages + uses: actions/configure-pages@v5 + + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v4 + with: + path: ./book + - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 45aa568..238cc54 100644 --- a/README.md +++ b/README.md @@ -88,12 +88,30 @@ The built book will be in the `./better-code/book/` directory. ## Automated Deployment The mdBook is automatically built and deployed to GitHub Pages using GitHub Actions. + +### Production Deployment (Main Branch) + When you push changes to the main branch: 1. GitHub Actions builds the book using mdBook with versions from `versions.toml` 2. The built book is deployed to GitHub Pages 3. The book becomes available at https://stlab.github.io/better-code/ +### Pull Request Previews + +When you open a pull request: + +1. GitHub Actions builds the book to validate your changes +2. A preview deployment is created at `https://stlab.github.io/better-code/pr-preview/[PR-NUMBER]/` +3. A comment is posted on the PR with the preview URL +4. The preview is automatically updated with new commits +5. The preview is automatically removed when the PR is closed or merged + +**Benefits:** +- Reviewers can see live previews of documentation changes +- Catch rendering issues before merging +- No third-party services required - uses GitHub Pages directly + No manual deployment steps are required! ## Dependency Management From 6074e3ba92541c34d866f7688101bab2362bad2f Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 14:27:26 -0800 Subject: [PATCH 04/18] Improve gh-pages branch setup in deploy workflow Enhances the deployment workflow to check for the existence of the gh-pages branch and create it as an orphan branch if it does not exist. This ensures smoother deployments and initializes the branch with a README if needed. --- .github/workflows/deploy.yml | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 61f44bc..94bb1cb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -66,10 +66,28 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - name: Checkout gh-pages branch + - name: Checkout repository uses: actions/checkout@v6 with: - ref: gh-pages + fetch-depth: 0 + + - name: Setup gh-pages branch + run: | + # Check if gh-pages branch exists + if git ls-remote --heads origin gh-pages | grep gh-pages; then + echo "gh-pages branch exists, checking out..." + git checkout gh-pages + else + echo "gh-pages branch doesn't exist, creating orphan branch..." + git checkout --orphan gh-pages + git rm -rf . + echo "# Better Code - PR Previews" > README.md + git add README.md + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git commit -m "Initialize gh-pages branch for PR previews" + git push origin gh-pages + fi - name: Download build artifact uses: actions/download-artifact@v4 From 4b39647e0d7a9e04deae8cc9a47471f10f1c5055 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 14:34:05 -0800 Subject: [PATCH 05/18] Set upstream when pushing gh-pages branch Adds the --set-upstream flag to the git push command for the gh-pages branch, ensuring the local branch tracks the remote. This helps with future pushes and branch management in the deployment workflow. --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 94bb1cb..2a4e664 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -86,7 +86,7 @@ jobs: git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git commit -m "Initialize gh-pages branch for PR previews" - git push origin gh-pages + git push --set-upstream origin gh-pages fi - name: Download build artifact From dc0b5be7a0f30f5b910a31b9ac692c538aba5120 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 14:37:27 -0800 Subject: [PATCH 06/18] Add Rust toolchain and cargo cache to CI workflow Introduces caching for the Rust toolchain and cargo registry in the deploy GitHub Actions workflow to speed up builds and reduce redundant downloads. --- .github/workflows/deploy.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2a4e664..35121bb 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -33,6 +33,19 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + + # Cache Rust toolchain and cargo registry + - name: Cache Rust + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + key: ${{ runner.os }}-cargo-${{ hashFiles('versions.toml') }} + restore-keys: | + ${{ runner.os }}-cargo- - name: Install mdBook and plugins run: | From 302512906639e3571ba5c346d6c39c1f5b46de04 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 14:46:02 -0800 Subject: [PATCH 07/18] Simplify deploy workflow and remove PR preview Streamlined the GitHub Actions deploy workflow by removing the pull request preview deployment and related steps. Now, the workflow only builds and deploys the book from the main branch, and pull requests are validated by building the book without deploying. Updated the README to reflect these changes and clarify the new process. --- .github/workflows/deploy.yml | 206 +++-------------------------------- README.md | 13 +-- 2 files changed, 20 insertions(+), 199 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 35121bb..82e9dd1 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -3,31 +3,24 @@ name: Build and Deploy mdBook # To update mdbook or plugin versions, edit versions.toml and the CI will automatically use them. on: - # Runs on pushes targeting the default branch push: branches: ["main"] - # Runs on pull requests for validation and preview deployment pull_request: branches: ["main"] - # Allows you to run this workflow manually from the Actions tab workflow_dispatch: -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages permissions: - contents: write # Needed for PR preview deployments + contents: read pages: write id-token: write - pull-requests: write # Needed to comment on PRs -# Allow only one concurrent deployment per PR or main concurrency: - group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.number || 'main' }} - cancel-in-progress: true + group: "pages" + cancel-in-progress: false jobs: - # Build job - runs on both PRs and main branch build: runs-on: ubuntu-latest steps: @@ -52,194 +45,29 @@ jobs: chmod +x scripts/install-tools.sh ./scripts/install-tools.sh - # For PRs: Build with base path for preview subdirectory - - name: Build with mdBook (PR Preview) - if: github.event_name == 'pull_request' - run: | - # Update book.toml to use PR-specific path - sed -i 's|site-url = "/better-code/"|site-url = "/better-code/pr-preview/${{ github.event.number }}/"|' better-code/book.toml - mdbook build ./better-code - - # For main: Build with production path - - name: Build with mdBook (Production) - if: github.event_name == 'push' && github.ref == 'refs/heads/main' + - name: Build with mdBook run: mdbook build ./better-code - - # Upload build artifact for inspection/debugging - - name: Upload build artifact - uses: actions/upload-artifact@v4 + + # Only deploy from main branch + - name: Setup Pages + if: github.ref == 'refs/heads/main' + id: pages + uses: actions/configure-pages@v5 + + - name: Upload artifact + if: github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v4 with: - name: book-${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || 'production' }} path: ./better-code/book - retention-days: ${{ github.event_name == 'pull_request' && 7 || 30 }} - - # PR Preview Deployment - deploys to gh-pages branch under pr-preview/NUMBER/ - deploy-preview: - if: github.event_name == 'pull_request' - needs: build - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Setup gh-pages branch - run: | - # Check if gh-pages branch exists - if git ls-remote --heads origin gh-pages | grep gh-pages; then - echo "gh-pages branch exists, checking out..." - git checkout gh-pages - else - echo "gh-pages branch doesn't exist, creating orphan branch..." - git checkout --orphan gh-pages - git rm -rf . - echo "# Better Code - PR Previews" > README.md - git add README.md - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git commit -m "Initialize gh-pages branch for PR previews" - git push --set-upstream origin gh-pages - fi - - - name: Download build artifact - uses: actions/download-artifact@v4 - with: - name: book-pr-${{ github.event.number }} - path: ./pr-preview/${{ github.event.number }} - - - name: Create preview index - run: | - # Create an index of all PR previews if it doesn't exist - if [ ! -f pr-preview/index.html ]; then - cat > pr-preview/index.html << 'EOF' - - - - PR Previews - Better Code - - - -

    Pull Request Previews

    -

    ← Back to main documentation

    -

    Preview deployments for pull requests:

    -
      - - - - EOF - fi - - - name: Commit and push preview - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add pr-preview/${{ github.event.number }} - git add pr-preview/index.html - git commit -m "Deploy preview for PR #${{ github.event.number }}" || echo "No changes to commit" - git push - - - name: Comment PR with preview URL - uses: actions/github-script@v7 - with: - script: | - const prNumber = context.issue.number; - const previewUrl = `https://stlab.github.io/better-code/pr-preview/${prNumber}/`; - const comment = `## 📚 Documentation Preview - - Your changes have been deployed to a preview environment: - - 🔗 **Preview URL:** ${previewUrl} - - This preview will be automatically updated with new commits and removed when the PR is closed. - - Built with commit ${context.sha.substring(0, 7)}`; - - // Find existing preview comment - const { data: comments } = await github.rest.issues.listComments({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - }); - - const botComment = comments.find(comment => - comment.user.type === 'Bot' && - comment.body.includes('Documentation Preview') - ); - - if (botComment) { - // Update existing comment - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: botComment.id, - body: comment - }); - } else { - // Create new comment - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: prNumber, - body: comment - }); - } - # Production Deployment - deploys to root of GitHub Pages - deploy-production: - if: github.event_name == 'push' && github.ref == 'refs/heads/main' - needs: build + deploy: + if: github.ref == 'refs/heads/main' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest + needs: build steps: - - name: Download build artifact - uses: actions/download-artifact@v4 - with: - name: book-production - path: ./book - - - name: Setup Pages - id: pages - uses: actions/configure-pages@v5 - - - name: Upload Pages artifact - uses: actions/upload-pages-artifact@v4 - with: - path: ./book - - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 238cc54..82b8ce2 100644 --- a/README.md +++ b/README.md @@ -97,20 +97,13 @@ When you push changes to the main branch: 2. The built book is deployed to GitHub Pages 3. The book becomes available at https://stlab.github.io/better-code/ -### Pull Request Previews +### Pull Request Validation When you open a pull request: 1. GitHub Actions builds the book to validate your changes -2. A preview deployment is created at `https://stlab.github.io/better-code/pr-preview/[PR-NUMBER]/` -3. A comment is posted on the PR with the preview URL -4. The preview is automatically updated with new commits -5. The preview is automatically removed when the PR is closed or merged - -**Benefits:** -- Reviewers can see live previews of documentation changes -- Catch rendering issues before merging -- No third-party services required - uses GitHub Pages directly +2. The PR status check shows whether the build succeeded +3. No deployment occurs - this just validates the book builds correctly No manual deployment steps are required! From 38d37334b7138c52b22fdbf325350a22c54fc9b9 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 15:06:32 -0800 Subject: [PATCH 08/18] Improve mdBook install scripts and deploy workflow Enhances PowerShell and Bash install scripts to handle 'already installed' errors from cargo install, preventing unnecessary failures. Updates deploy workflow to only run deployment steps on main branch for push or manual dispatch events. --- .github/workflows/deploy.yml | 8 ++++---- scripts/install-tools.ps1 | 25 +++++++++++++++++++++++-- scripts/install-tools.sh | 23 +++++++++++++++++++++-- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 82e9dd1..2d1987c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -48,20 +48,20 @@ jobs: - name: Build with mdBook run: mdbook build ./better-code - # Only deploy from main branch + # Only deploy from main branch (push or manual dispatch) - name: Setup Pages - if: github.ref == 'refs/heads/main' + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' id: pages uses: actions/configure-pages@v5 - name: Upload artifact - if: github.ref == 'refs/heads/main' + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' uses: actions/upload-pages-artifact@v4 with: path: ./better-code/book deploy: - if: github.ref == 'refs/heads/main' + if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} diff --git a/scripts/install-tools.ps1 b/scripts/install-tools.ps1 index 9c109eb..750b417 100644 --- a/scripts/install-tools.ps1 +++ b/scripts/install-tools.ps1 @@ -53,10 +53,22 @@ function Get-MdbookPlugins { Write-Host "Reading versions from $VersionsFile..." -ForegroundColor Cyan # Install mdBook +# Note: cargo install doesn't provide separate exit codes for "already installed" vs other errors +# See: https://github.com/rust-lang/cargo/issues/11513 +# We check the error message as a workaround until cargo provides a better solution. $MdbookVersion = Get-ToolVersion -Tool "mdbook" if ($MdbookVersion) { Write-Host "`nInstalling mdBook $MdbookVersion..." -ForegroundColor Cyan - cargo install mdbook --version $MdbookVersion + $output = cargo install mdbook --version $MdbookVersion 2>&1 + if ($LASTEXITCODE -ne 0) { + # Check for "already installed" error (case-insensitive, package name may vary) + if ($output -match "(?i)is already installed") { + Write-Host " (already installed)" -ForegroundColor Gray + } else { + Write-Host $output -ForegroundColor Red + exit 1 + } + } } else { Write-Host "Error: Could not find mdbook version in versions.toml" -ForegroundColor Red exit 1 @@ -68,7 +80,16 @@ Write-Host "`nInstalling mdBook plugins..." -ForegroundColor Cyan $plugins = Get-MdbookPlugins foreach ($plugin in $plugins.GetEnumerator()) { Write-Host "Installing $($plugin.Key) $($plugin.Value)..." -ForegroundColor Cyan - cargo install $plugin.Key --version $plugin.Value + $output = cargo install $plugin.Key --version $plugin.Value 2>&1 + if ($LASTEXITCODE -ne 0) { + # Check for "already installed" error (case-insensitive, package name may vary) + if ($output -match "(?i)is already installed") { + Write-Host " (already installed)" -ForegroundColor Gray + } else { + Write-Host $output -ForegroundColor Red + exit 1 + } + } } Write-Host "`n✓ Installation complete!" -ForegroundColor Green diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index 6e51c88..81f5eb2 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -29,10 +29,21 @@ get_version() { echo "Reading versions from ${VERSIONS_FILE}..." # Install mdBook +# Note: cargo install doesn't provide separate exit codes for "already installed" vs other errors +# See: https://github.com/rust-lang/cargo/issues/11513 +# We check the error message as a workaround until cargo provides a better solution. MDBOOK_VERSION=$(get_version "mdbook") if [ -n "$MDBOOK_VERSION" ]; then echo "Installing mdBook ${MDBOOK_VERSION}..." - cargo install mdbook --version "${MDBOOK_VERSION}" + if ! output=$(cargo install mdbook --version "${MDBOOK_VERSION}" 2>&1); then + # Check for "already installed" error (package name may vary) + if echo "$output" | grep -qi "is already installed"; then + echo " (already installed)" + else + echo "$output" >&2 + exit 1 + fi + fi else echo "Error: Could not find mdbook version in versions.toml" exit 1 @@ -48,7 +59,15 @@ while IFS= read -r line; do plugin="${BASH_REMATCH[1]}" version="${BASH_REMATCH[2]}" echo "Installing ${plugin} ${version}..." - cargo install "${plugin}" --version "${version}" + if ! output=$(cargo install "${plugin}" --version "${version}" 2>&1); then + # Check for "already installed" error (package name may vary) + if echo "$output" | grep -qi "is already installed"; then + echo " (already installed)" + else + echo "$output" >&2 + exit 1 + fi + fi fi done < <( awk ' From fb64c634f04bca473d0c9c5c40e8e09949568761 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 15:09:03 -0800 Subject: [PATCH 09/18] Improve cargo install error handling for existing packages Update install-tools.ps1 and install-tools.sh to recognize both 'already exists in destination' and 'is already installed' messages from cargo. This ensures the scripts correctly detect when a package is already installed, accommodating variations in cargo's output. --- scripts/install-tools.ps1 | 8 ++++---- scripts/install-tools.sh | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/install-tools.ps1 b/scripts/install-tools.ps1 index 750b417..be1a5e2 100644 --- a/scripts/install-tools.ps1 +++ b/scripts/install-tools.ps1 @@ -61,8 +61,8 @@ if ($MdbookVersion) { Write-Host "`nInstalling mdBook $MdbookVersion..." -ForegroundColor Cyan $output = cargo install mdbook --version $MdbookVersion 2>&1 if ($LASTEXITCODE -ne 0) { - # Check for "already installed" error (case-insensitive, package name may vary) - if ($output -match "(?i)is already installed") { + # Check for "already installed" error - cargo uses different messages + if ($output -match "(?i)(already exists in destination|is already installed)") { Write-Host " (already installed)" -ForegroundColor Gray } else { Write-Host $output -ForegroundColor Red @@ -82,8 +82,8 @@ foreach ($plugin in $plugins.GetEnumerator()) { Write-Host "Installing $($plugin.Key) $($plugin.Value)..." -ForegroundColor Cyan $output = cargo install $plugin.Key --version $plugin.Value 2>&1 if ($LASTEXITCODE -ne 0) { - # Check for "already installed" error (case-insensitive, package name may vary) - if ($output -match "(?i)is already installed") { + # Check for "already installed" error - cargo uses different messages + if ($output -match "(?i)(already exists in destination|is already installed)") { Write-Host " (already installed)" -ForegroundColor Gray } else { Write-Host $output -ForegroundColor Red diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index 81f5eb2..0cdd7e1 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -36,8 +36,8 @@ MDBOOK_VERSION=$(get_version "mdbook") if [ -n "$MDBOOK_VERSION" ]; then echo "Installing mdBook ${MDBOOK_VERSION}..." if ! output=$(cargo install mdbook --version "${MDBOOK_VERSION}" 2>&1); then - # Check for "already installed" error (package name may vary) - if echo "$output" | grep -qi "is already installed"; then + # Check for "already installed" error - cargo uses different messages + if echo "$output" | grep -qiE "(already exists in destination|is already installed)"; then echo " (already installed)" else echo "$output" >&2 @@ -60,8 +60,8 @@ while IFS= read -r line; do version="${BASH_REMATCH[2]}" echo "Installing ${plugin} ${version}..." if ! output=$(cargo install "${plugin}" --version "${version}" 2>&1); then - # Check for "already installed" error (package name may vary) - if echo "$output" | grep -qi "is already installed"; then + # Check for "already installed" error - cargo uses different messages + if echo "$output" | grep -qiE "(already exists in destination|is already installed)"; then echo " (already installed)" else echo "$output" >&2 From 881d4154ae9bbc8985dbc967d4eb3334017fb583 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 15:15:45 -0800 Subject: [PATCH 10/18] Simplify cargo install error handling in tool scripts Removed custom checks for 'already installed' errors in install-tools.ps1 and install-tools.sh. Now, any non-zero exit code from cargo install will cause the script to exit with an error, streamlining error handling. Updated deploy.yml to cache additional Cargo files for improved build performance. --- .github/workflows/deploy.yml | 2 ++ scripts/install-tools.ps1 | 23 ++++------------------- scripts/install-tools.sh | 23 ++--------------------- 3 files changed, 8 insertions(+), 40 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 2d1987c..06c9e5b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -36,6 +36,8 @@ jobs: ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ + ~/.cargo/.crates.toml + ~/.cargo/.crates2.json key: ${{ runner.os }}-cargo-${{ hashFiles('versions.toml') }} restore-keys: | ${{ runner.os }}-cargo- diff --git a/scripts/install-tools.ps1 b/scripts/install-tools.ps1 index be1a5e2..59075a0 100644 --- a/scripts/install-tools.ps1 +++ b/scripts/install-tools.ps1 @@ -53,21 +53,12 @@ function Get-MdbookPlugins { Write-Host "Reading versions from $VersionsFile..." -ForegroundColor Cyan # Install mdBook -# Note: cargo install doesn't provide separate exit codes for "already installed" vs other errors -# See: https://github.com/rust-lang/cargo/issues/11513 -# We check the error message as a workaround until cargo provides a better solution. $MdbookVersion = Get-ToolVersion -Tool "mdbook" if ($MdbookVersion) { Write-Host "`nInstalling mdBook $MdbookVersion..." -ForegroundColor Cyan - $output = cargo install mdbook --version $MdbookVersion 2>&1 + cargo install mdbook --version $MdbookVersion if ($LASTEXITCODE -ne 0) { - # Check for "already installed" error - cargo uses different messages - if ($output -match "(?i)(already exists in destination|is already installed)") { - Write-Host " (already installed)" -ForegroundColor Gray - } else { - Write-Host $output -ForegroundColor Red - exit 1 - } + exit 1 } } else { Write-Host "Error: Could not find mdbook version in versions.toml" -ForegroundColor Red @@ -80,15 +71,9 @@ Write-Host "`nInstalling mdBook plugins..." -ForegroundColor Cyan $plugins = Get-MdbookPlugins foreach ($plugin in $plugins.GetEnumerator()) { Write-Host "Installing $($plugin.Key) $($plugin.Value)..." -ForegroundColor Cyan - $output = cargo install $plugin.Key --version $plugin.Value 2>&1 + cargo install $plugin.Key --version $plugin.Value if ($LASTEXITCODE -ne 0) { - # Check for "already installed" error - cargo uses different messages - if ($output -match "(?i)(already exists in destination|is already installed)") { - Write-Host " (already installed)" -ForegroundColor Gray - } else { - Write-Host $output -ForegroundColor Red - exit 1 - } + exit 1 } } diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index 0cdd7e1..6e51c88 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -29,21 +29,10 @@ get_version() { echo "Reading versions from ${VERSIONS_FILE}..." # Install mdBook -# Note: cargo install doesn't provide separate exit codes for "already installed" vs other errors -# See: https://github.com/rust-lang/cargo/issues/11513 -# We check the error message as a workaround until cargo provides a better solution. MDBOOK_VERSION=$(get_version "mdbook") if [ -n "$MDBOOK_VERSION" ]; then echo "Installing mdBook ${MDBOOK_VERSION}..." - if ! output=$(cargo install mdbook --version "${MDBOOK_VERSION}" 2>&1); then - # Check for "already installed" error - cargo uses different messages - if echo "$output" | grep -qiE "(already exists in destination|is already installed)"; then - echo " (already installed)" - else - echo "$output" >&2 - exit 1 - fi - fi + cargo install mdbook --version "${MDBOOK_VERSION}" else echo "Error: Could not find mdbook version in versions.toml" exit 1 @@ -59,15 +48,7 @@ while IFS= read -r line; do plugin="${BASH_REMATCH[1]}" version="${BASH_REMATCH[2]}" echo "Installing ${plugin} ${version}..." - if ! output=$(cargo install "${plugin}" --version "${version}" 2>&1); then - # Check for "already installed" error - cargo uses different messages - if echo "$output" | grep -qiE "(already exists in destination|is already installed)"; then - echo " (already installed)" - else - echo "$output" >&2 - exit 1 - fi - fi + cargo install "${plugin}" --version "${version}" fi done < <( awk ' From 33da196a16a186e214af96551d33b2bb5994d73b Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 19:36:56 -0800 Subject: [PATCH 11/18] Improve version extraction in install-tools.sh Replaces grep and sed with awk for extracting tool versions from the versions file, allowing for comments and blank lines between section headers and version keys. This makes the script more robust and reliable. --- scripts/install-tools.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index 6e51c88..acc8678 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -14,7 +14,17 @@ get_version() { if [ -z "$section" ]; then # Top-level tool like [mdbook] - grep -A 1 "^\[$tool\]" "$VERSIONS_FILE" | grep "version" | sed 's/.*"\(.*\)".*/\1/' + # Use awk to handle comments/blank lines between section header and version key + awk -v tool="$tool" ' + BEGIN { in_section = 0 } + $0 ~ ("^\\[" tool "\\]$") { in_section = 1; next } + in_section && /^\[/ { exit } + in_section && /^version[[:space:]]*=/ { + match($0, /"([^"]+)"/, arr) + print arr[1] + exit + } + ' "$VERSIONS_FILE" else # Plugin in a section like [mdbook-plugins] awk -v section="$section" -v tool="$tool" ' From c587f4751afff33692fd4fb206a4b3369f714d88 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 19:53:08 -0800 Subject: [PATCH 12/18] Update README.md Co-authored-by: Dave Abrahams --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 82b8ce2..ce65c8f 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,7 @@ scripts to automatically install the correct versions: ``` These scripts automatically install mdBook and all required plugins using the -versions specified in `versions.toml`. This is the same file used by CI, ensuring -perfect consistency. +versions specified in `versions.toml`. **Manual Installation:** If you prefer to install manually, check `versions.toml` for the current version From f5fdda31e35085fc7a50a9a7bbc31f5b56d9df47 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 20:17:51 -0800 Subject: [PATCH 13/18] Refactor CI: simplify build, remove PR preview system Removes the PR preview and cleanup workflows, consolidates CI to validate PR builds and deploy only from main. Updates README with streamlined instructions and clarifies the new CI/CD process. Improves documentation in install scripts for clarity and maintainability. --- .github/PR-PREVIEW.md | 101 ------------------ .github/workflows/build-pr.yml | 13 +++ .github/workflows/build.yml | 59 +++++++++++ .github/workflows/cleanup-preview.yml | 31 ------ .github/workflows/deploy.yml | 63 ++++------- README.md | 146 +++++++++++--------------- scripts/install-tools.ps1 | 51 ++++++++- scripts/install-tools.sh | 37 ++++++- 8 files changed, 235 insertions(+), 266 deletions(-) delete mode 100644 .github/PR-PREVIEW.md create mode 100644 .github/workflows/build-pr.yml create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/cleanup-preview.yml diff --git a/.github/PR-PREVIEW.md b/.github/PR-PREVIEW.md deleted file mode 100644 index b73f076..0000000 --- a/.github/PR-PREVIEW.md +++ /dev/null @@ -1,101 +0,0 @@ -# PR Preview System - -This repository uses GitHub Pages for automated PR preview deployments without any third-party dependencies. - -## How It Works - -### Architecture - -- **Production**: `gh-pages` branch root → https://stlab.github.io/better-code/ -- **PR Previews**: `gh-pages` branch under `pr-preview/[NUMBER]/` → https://stlab.github.io/better-code/pr-preview/123/ -- **Preview Index**: https://stlab.github.io/better-code/pr-preview/ (lists all active previews) - -### Workflow - -1. **Open PR** → Builds and deploys to `gh-pages:pr-preview/[PR-NUMBER]/` -2. **Push to PR** → Updates the preview deployment -3. **Close/Merge PR** → Automatically removes the preview directory -4. **Merge to main** → Deploys to production (root of gh-pages) - -## Features - -✅ **Automatic deployment** - No manual steps required -✅ **PR comments** - Bot posts preview URL on every PR -✅ **Automatic cleanup** - Previews removed when PR closes -✅ **No third-party services** - Pure GitHub Pages -✅ **Fast builds** - Only builds changed content -✅ **Isolated environments** - Each PR gets its own subdirectory - -## File Structure on gh-pages Branch - -``` -gh-pages/ -├── index.html # Production site -├── chapter-1-intro.html -├── ... -└── pr-preview/ - ├── index.html # List of all PR previews - ├── 123/ # Preview for PR #123 - │ ├── index.html - │ └── ... - ├── 124/ # Preview for PR #124 - │ └── ... - └── ... -``` - -## Configuration - -### Required Permissions - -The workflow requires these permissions (already configured): -- `contents: write` - To push to gh-pages branch -- `pages: write` - To deploy to GitHub Pages -- `pull-requests: write` - To comment on PRs - -### GitHub Pages Settings - -Ensure GitHub Pages is configured to deploy from the `gh-pages` branch: -1. Go to Settings → Pages -2. Source: Deploy from a branch -3. Branch: `gh-pages` / `root` - -## Troubleshooting - -### Preview not deploying - -Check: -1. Workflow ran successfully in Actions tab -2. `gh-pages` branch exists and has the preview directory -3. GitHub Pages is enabled and set to deploy from `gh-pages` branch - -### Preview URL 404 - -- Wait 1-2 minutes after deployment for GitHub Pages to update -- Check if the directory exists in the `gh-pages` branch -- Verify the PR number in the URL matches the directory name - -### Old previews not cleaning up - -- Check if the cleanup workflow ran when PR closed -- Manually remove with: `git checkout gh-pages && git rm -rf pr-preview/[NUMBER] && git commit && git push` - -## Manual Cleanup - -To manually remove all PR previews: - -```bash -git checkout gh-pages -git rm -rf pr-preview/ -git commit -m "Clean up all PR previews" -git push -``` - -To remove a specific preview: - -```bash -git checkout gh-pages -git rm -rf pr-preview/123 -git commit -m "Remove preview for PR #123" -git push -``` - diff --git a/.github/workflows/build-pr.yml b/.github/workflows/build-pr.yml new file mode 100644 index 0000000..bdc038a --- /dev/null +++ b/.github/workflows/build-pr.yml @@ -0,0 +1,13 @@ +name: Validate PR Build + +on: + pull_request: + branches: ["main"] + +permissions: + contents: read + +jobs: + validate: + uses: ./.github/workflows/build.yml + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..6d9e145 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,59 @@ +name: Build mdBook + +on: + workflow_call: + outputs: + artifact-path: + description: "Path to the built book artifact" + value: ${{ jobs.build.outputs.artifact-path }} + +permissions: + contents: read + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + outputs: + artifact-path: ./better-code/book + steps: + - name: Checkout + uses: actions/checkout@v6 + + # Cache Rust toolchain and cargo registry + - name: Cache Rust + uses: actions/cache@v4 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + ~/.cargo/.crates.toml + ~/.cargo/.crates2.json + key: ${{ runner.os }}-cargo-${{ hashFiles('versions.toml') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Install mdBook and plugins (Linux) + if: runner.os != 'Windows' + run: | + chmod +x scripts/install-tools.sh + ./scripts/install-tools.sh + + - name: Install mdBook and plugins (Windows) + if: runner.os == 'Windows' + run: .\scripts\install-tools.ps1 + + - name: Build with mdBook + run: mdbook build ./better-code + + - name: Upload build artifact + uses: actions/upload-artifact@v4 + with: + name: mdbook-build-${{ matrix.os }} + path: ./better-code/book + retention-days: 1 + diff --git a/.github/workflows/cleanup-preview.yml b/.github/workflows/cleanup-preview.yml deleted file mode 100644 index ce2c2f8..0000000 --- a/.github/workflows/cleanup-preview.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Cleanup PR Preview - -on: - pull_request: - types: [closed] - -permissions: - contents: write - -jobs: - cleanup: - runs-on: ubuntu-latest - steps: - - name: Checkout gh-pages branch - uses: actions/checkout@v6 - with: - ref: gh-pages - - - name: Remove PR preview directory - run: | - if [ -d "pr-preview/${{ github.event.number }}" ]; then - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git rm -rf pr-preview/${{ github.event.number }} - git commit -m "Remove preview for closed PR #${{ github.event.number }}" - git push - echo "Removed preview for PR #${{ github.event.number }}" - else - echo "No preview directory found for PR #${{ github.event.number }}" - fi - diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 06c9e5b..ceae2e2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,14 +1,10 @@ -name: Build and Deploy mdBook +name: Deploy to GitHub Pages # Tool versions are managed in versions.toml at the repository root. # To update mdbook or plugin versions, edit versions.toml and the CI will automatically use them. on: push: branches: ["main"] - - pull_request: - branches: ["main"] - workflow_dispatch: permissions: @@ -22,54 +18,35 @@ concurrency: jobs: build: + uses: ./.github/workflows/build.yml + permissions: + contents: read + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest + needs: build + permissions: + pages: write + id-token: write steps: - - name: Checkout - uses: actions/checkout@v6 - - # Cache Rust toolchain and cargo registry - - name: Cache Rust - uses: actions/cache@v4 + - name: Download build artifact + uses: actions/download-artifact@v4 with: - path: | - ~/.cargo/bin/ - ~/.cargo/registry/index/ - ~/.cargo/registry/cache/ - ~/.cargo/git/db/ - ~/.cargo/.crates.toml - ~/.cargo/.crates2.json - key: ${{ runner.os }}-cargo-${{ hashFiles('versions.toml') }} - restore-keys: | - ${{ runner.os }}-cargo- - - - name: Install mdBook and plugins - run: | - chmod +x scripts/install-tools.sh - ./scripts/install-tools.sh + name: mdbook-build-ubuntu-latest + path: ./book - - name: Build with mdBook - run: mdbook build ./better-code - - # Only deploy from main branch (push or manual dispatch) - name: Setup Pages - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' id: pages uses: actions/configure-pages@v5 - - name: Upload artifact - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' + - name: Upload Pages artifact uses: actions/upload-pages-artifact@v4 with: - path: ./better-code/book - - deploy: - if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && github.ref == 'refs/heads/main' - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - needs: build - steps: + path: ./book + - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 diff --git a/README.md b/README.md index 82b8ce2..dd2a981 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,17 @@ -# Welcome to Better Code +# Better Code -This is the development home of the STLab Better Code course. +Development repository for the STLab Better Code course. -## Working with the Book +**Read the book:** https://stlab.github.io/better-code/ -We're migrating from using Jekyll to using -[mdBook](https://github.com/rust-lang/mdBook). The mdBook version is located in -the `./better-code` directory and includes automated CI/CD deployment to GitHub Pages. +## Quick Start -The published book is available at: https://stlab.github.io/better-code/ +### 1. Install Prerequisites -## Prerequisites +Install [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html). -Install [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html): -**Linux and macOS:** -```bash -curl https://sh.rustup.rs -sSf | sh -``` - -**Windows:** -Download the installer from [here](https://win.rustup.rs/). - -## Installing mdBook and Plugins - -**Important**: To ensure consistency between local development and CI, all tool -versions are centrally managed in `versions.toml`. Use the provided installation -scripts to automatically install the correct versions: +### 2. Install mdBook and Plugins **Linux/macOS:** ```bash @@ -38,101 +23,94 @@ scripts to automatically install the correct versions: .\scripts\install-tools.ps1 ``` -These scripts automatically install mdBook and all required plugins using the -versions specified in `versions.toml`. This is the same file used by CI, ensuring -perfect consistency. +These scripts install mdBook and all required plugins using versions from `versions.toml`. -**Manual Installation:** -If you prefer to install manually, check `versions.toml` for the current version -numbers, then run: -```bash -cargo install mdbook --version -cargo install mdbook-katex --version -``` - -## Building and Serving the Book - -To build and serve the book locally with live reload: +### 3. Build and Serve ```bash mdbook serve ./better-code ``` -This will start a local server at http://localhost:3000. You can use the -Simple Browser in VSCode/Cursor to view the book while editing. +Open http://localhost:3000 in your browser. -To build the book without serving: +## Contributing -```bash -mdbook build ./better-code -``` +### Editing Content -The built book will be in the `./better-code/book/` directory. +1. Edit markdown files in `better-code/src/` +2. Add new chapters by creating `.md` files and updating `better-code/src/SUMMARY.md` +3. Changes automatically rebuild when you push to the main branch -## Adding and Editing Content +### Content Conventions -1. Edit existing chapters in `better-code/src/` -2. Add new chapters by creating new `.md` files in `better-code/src/` -3. Update `better-code/src/SUMMARY.md` to include new chapters in the table of contents -4. The book will automatically rebuild and redeploy when you push changes to the main branch +* Use Markdown formatting; avoid unnecessary HTML +* Wrap lines at 80 columns for diff-friendly change tracking +* Start each chapter with a level-2 heading (`## Chapter Name`) +* Use level-3+ headings within chapters +* Keep file names and heading titles stable for linkability -### Content Conventions +## Deployment -* Avoid unnecessary HTML tags; use Markdown formatting to the degree possible. -* Wrap lines at 80 columns to support diff-friendly change tracking. -* Each chapter begins with a 2nd-level heading, e.g. `## Chapter Name`. All - other headings in a chapter are 3rd-level and below. -* Maintain stable file names and heading titles for linkability until another - solution is in place. +### CI/CD Pipeline -## Automated Deployment +**Pull Requests:** +- Validates build on Ubuntu and Windows +- No deployment; PR check shows build status -The mdBook is automatically built and deployed to GitHub Pages using GitHub Actions. +**Main Branch:** +- Builds book using mdBook with versions from `versions.toml` +- Deploys to GitHub Pages +- Available at https://stlab.github.io/better-code/ -### Production Deployment (Main Branch) +### Managing Dependencies -When you push changes to the main branch: +All tool versions are centrally managed in `versions.toml`. To update: -1. GitHub Actions builds the book using mdBook with versions from `versions.toml` -2. The built book is deployed to GitHub Pages -3. The book becomes available at https://stlab.github.io/better-code/ +1. Edit version number in `versions.toml` +2. Run the appropriate install script locally +3. Test with `mdbook serve ./better-code` +4. Commit - CI automatically uses the new version -### Pull Request Validation +## Project Structure -When you open a pull request: +``` +better-code/ # mdBook source and configuration +├── src/ # Markdown chapter files +├── book.toml # mdBook configuration +└── book/ # Generated HTML (gitignored) -1. GitHub Actions builds the book to validate your changes -2. The PR status check shows whether the build succeeded -3. No deployment occurs - this just validates the book builds correctly +scripts/ # Installation scripts +├── install-tools.sh # Linux/macOS +└── install-tools.ps1 # Windows -No manual deployment steps are required! +versions.toml # Single source of truth for tool versions +archive/ # Legacy Jekyll site (for reference only) +``` -## Dependency Management +## Advanced Usage -All tool versions (mdBook, plugins, and future tools like Swift) are managed in -`versions.toml` at the repository root. To update a version: -1. Edit the version number in `versions.toml` -2. Run the appropriate install script to update locally -3. Test your changes -4. Commit - CI will automatically use the new version +### Building Without Serving -## Legacy Jekyll Content +```bash +mdbook build ./better-code +``` + +Output will be in `./better-code/book/`. -The legacy Jekyll files remain in the `archive/` directory for reference during -the transition. You can ignore these unless you're working on the migration. -**Running the Jekyll version (for reference only):** +## Legacy Content -If you need to run the legacy Jekyll site: +The `archive/` directory contains the legacy Jekyll site for reference during migration. +You can safely ignore it unless working on migration tasks. + +To run the Jekyll version (not recommended for regular development): ```bash -bundle exec jekyll serve -l +bundle exec jekyll serve -l # Starts server at http://localhost:4000 ``` -This will start a server at http://localhost:4000. - -Alternatively, if you have [docker-compose](https://docs.docker.com/compose/): +Or with Docker: ```bash docker-compose up diff --git a/scripts/install-tools.ps1 b/scripts/install-tools.ps1 index 59075a0..d231ae2 100644 --- a/scripts/install-tools.ps1 +++ b/scripts/install-tools.ps1 @@ -1,12 +1,43 @@ -# Install mdBook and plugins using versions from ../versions.toml -# This ensures consistency between local development and CI +# Installs mdBook and plugins using versions from versions.toml. +# +# Reads tool versions from the repository root's versions.toml file and +# installs them via cargo. This ensures consistency between local development +# and CI environments. +# +# Usage: .\install-tools.ps1 +# +# Preconditions: +# - Rust and Cargo are installed and in PATH +# - versions.toml exists in the repository root +# - versions.toml contains [mdbook] section with version key +# +# Complexity: O(N) where N is the number of plugins to install $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $VersionsFile = Join-Path (Split-Path -Parent $ScriptDir) "versions.toml" -# Function to parse TOML and extract version +# Extracts a version string from versions.toml. +# +# Parameters: +# Tool - Tool name matching a TOML section header (e.g., "mdbook") +# Section - Optional. Parent section name for nested keys (e.g., "mdbook-plugins") +# +# Returns: The version string, or $null if not found +# +# When Section is empty: +# - Searches for [Tool] section header +# - Returns value of version key within that section +# - Handles comments and blank lines between section header and version key +# +# When Section is provided: +# - Searches for [Section] header +# - Returns value of "Tool = version" within that section +# +# Example: +# Get-ToolVersion -Tool "mdbook" +# Get-ToolVersion -Tool "mdbook-katex" -Section "mdbook-plugins" function Get-ToolVersion { param ( [string]$Tool, @@ -29,7 +60,19 @@ function Get-ToolVersion { return $null } -# Function to get all plugins from [mdbook-plugins] section +# Extracts all plugin name-version pairs from [mdbook-plugins] section. +# +# Returns: A hashtable mapping plugin names to version strings +# +# The hashtable keys are plugin names (e.g., "mdbook-katex") and values +# are version strings (e.g., "0.10.0-alpha"). Returns an empty hashtable +# if the [mdbook-plugins] section is not found or contains no plugins. +# +# Example: +# $plugins = Get-MdbookPlugins +# foreach ($plugin in $plugins.GetEnumerator()) { +# Write-Host "$($plugin.Key) version $($plugin.Value)" +# } function Get-MdbookPlugins { $content = Get-Content $VersionsFile -Raw $plugins = @{} diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index acc8678..50f92fa 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -1,13 +1,44 @@ #!/bin/bash -# Install mdBook and plugins using versions from ../versions.toml -# This ensures consistency between local development and CI +# Installs mdBook and plugins using versions from versions.toml. +# +# Reads tool versions from the repository root's versions.toml file and +# installs them via cargo. This ensures consistency between local development +# and CI environments. +# +# Usage: ./install-tools.sh +# +# Preconditions: +# - Rust and Cargo are installed and in PATH +# - versions.toml exists in the repository root +# - versions.toml contains [mdbook] section with version key +# +# Complexity: O(N) where N is the number of plugins to install set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VERSIONS_FILE="${SCRIPT_DIR}/../versions.toml" -# Function to extract version from TOML file +# Extracts a version string from versions.toml. +# +# Parameters: +# $1 (tool) - Tool name matching a TOML section header (e.g., "mdbook") +# $2 (section) - Optional. Parent section name for nested keys (e.g., "mdbook-plugins") +# +# Returns: The version string on stdout, or empty if not found +# +# When section is empty: +# - Searches for [tool] section header +# - Returns value of version key within that section +# - Handles comments and blank lines between section header and version key +# +# When section is provided: +# - Searches for [section] header +# - Returns value of "tool = version" within that section +# +# Example: +# get_version "mdbook" # Returns version from [mdbook] +# get_version "mdbook-katex" "mdbook-plugins" # Returns version from [mdbook-plugins] get_version() { local tool=$1 local section=$2 From 79b706f44aee66491e54198f3abe4102661d1cb3 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 20:29:45 -0800 Subject: [PATCH 14/18] Improve POSIX compliance in version extraction Replaces the use of awk's match function with sub to extract the version string in a more POSIX-compliant way. This enhances compatibility across different awk implementations. --- scripts/install-tools.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index 50f92fa..2ccebdd 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -51,8 +51,11 @@ get_version() { $0 ~ ("^\\[" tool "\\]$") { in_section = 1; next } in_section && /^\[/ { exit } in_section && /^version[[:space:]]*=/ { - match($0, /"([^"]+)"/, arr) - print arr[1] + # Extract version string between quotes using sub (POSIX-compliant) + line = $0 + sub(/^[^"]*"/, "", line) + sub(/".*$/, "", line) + print line exit } ' "$VERSIONS_FILE" From 959a88df503e4016e841c8c619a349e942af0da4 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 20:36:34 -0800 Subject: [PATCH 15/18] Handle mdbook-katex install on Windows with duktape backend Updated install-tools.ps1 to install mdbook-katex with the duktape backend on Windows, providing user guidance about limited functionality and alternative installation. Added explanatory comments to versions.toml regarding mdbook-katex installation on Windows. --- scripts/install-tools.ps1 | 25 +++++++++++++++++++++---- versions.toml | 2 ++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/scripts/install-tools.ps1 b/scripts/install-tools.ps1 index d231ae2..8ef7fa9 100644 --- a/scripts/install-tools.ps1 +++ b/scripts/install-tools.ps1 @@ -113,10 +113,27 @@ Write-Host "`nInstalling mdBook plugins..." -ForegroundColor Cyan $plugins = Get-MdbookPlugins foreach ($plugin in $plugins.GetEnumerator()) { - Write-Host "Installing $($plugin.Key) $($plugin.Value)..." -ForegroundColor Cyan - cargo install $plugin.Key --version $plugin.Value - if ($LASTEXITCODE -ne 0) { - exit 1 + $pluginName = $plugin.Key + $pluginVersion = $plugin.Value + + # mdbook-katex requires special handling on Windows + # See: https://github.com/lzanini/mdbook-katex#windows-users + if ($pluginName -eq "mdbook-katex") { + Write-Host "Installing $pluginName $pluginVersion with duktape backend (Windows)..." -ForegroundColor Cyan + Write-Host " Note: Using duktape backend. Some features like matrices may not work." -ForegroundColor Yellow + Write-Host " For full functionality, download pre-built binary from:" -ForegroundColor Yellow + Write-Host " https://github.com/lzanini/mdbook-katex/releases" -ForegroundColor Yellow + cargo install $pluginName --version $pluginVersion --no-default-features --features duktape + if ($LASTEXITCODE -ne 0) { + Write-Host "Failed to install $pluginName. Continuing without it..." -ForegroundColor Yellow + continue + } + } else { + Write-Host "Installing $pluginName $pluginVersion..." -ForegroundColor Cyan + cargo install $pluginName --version $pluginVersion + if ($LASTEXITCODE -ne 0) { + exit 1 + } } } diff --git a/versions.toml b/versions.toml index 3f07262..2cb790c 100644 --- a/versions.toml +++ b/versions.toml @@ -18,6 +18,8 @@ version = "0.5.2" [mdbook-plugins] +# Note: On Windows, mdbook-katex installs with the duktape backend (limited functionality). +# For full features, download pre-built binaries: https://github.com/lzanini/mdbook-katex/releases mdbook-katex = "0.10.0-alpha" # mdbook-mermaid = "x.y.z" # Uncomment when diagram support is needed # Add more mdbook plugins here as needed From 09869f62fc3d01d484c8d61b87ba9601190089d0 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 20:54:03 -0800 Subject: [PATCH 16/18] Remove complexity comment from install scripts Deleted the O(N) complexity comment from both install-tools.ps1 and install-tools.sh for clarity, as it was unnecessary for script documentation. --- scripts/install-tools.ps1 | 2 -- scripts/install-tools.sh | 2 -- 2 files changed, 4 deletions(-) diff --git a/scripts/install-tools.ps1 b/scripts/install-tools.ps1 index 8ef7fa9..2dbc4e0 100644 --- a/scripts/install-tools.ps1 +++ b/scripts/install-tools.ps1 @@ -10,8 +10,6 @@ # - Rust and Cargo are installed and in PATH # - versions.toml exists in the repository root # - versions.toml contains [mdbook] section with version key -# -# Complexity: O(N) where N is the number of plugins to install $ErrorActionPreference = "Stop" diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index 2ccebdd..7a3c187 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -11,8 +11,6 @@ # - Rust and Cargo are installed and in PATH # - versions.toml exists in the repository root # - versions.toml contains [mdbook] section with version key -# -# Complexity: O(N) where N is the number of plugins to install set -e From 5ce64424a5d3c4822c9117bc0ed69cd18ea57de7 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 20:59:17 -0800 Subject: [PATCH 17/18] Migrate tool version management to versions.txt Replaces versions.toml with versions.txt for managing tool versions. Updates CI workflows, install scripts, and documentation to reference versions.txt and simplify version parsing logic. This change streamlines version management and improves cross-platform compatibility. --- .github/workflows/build.yml | 2 +- .github/workflows/deploy.yml | 4 +- .tool-versions | 4 +- README.md | 10 ++-- scripts/install-tools.ps1 | 104 +++++----------------------------- scripts/install-tools.sh | 85 ++++----------------------- versions.toml => versions.txt | 30 +++++----- 7 files changed, 49 insertions(+), 190 deletions(-) rename versions.toml => versions.txt (59%) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6d9e145..3d5059f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: ~/.cargo/git/db/ ~/.cargo/.crates.toml ~/.cargo/.crates2.json - key: ${{ runner.os }}-cargo-${{ hashFiles('versions.toml') }} + key: ${{ runner.os }}-cargo-${{ hashFiles('versions.txt') }} restore-keys: | ${{ runner.os }}-cargo- diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ceae2e2..3536ff5 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,6 +1,6 @@ name: Deploy to GitHub Pages -# Tool versions are managed in versions.toml at the repository root. -# To update mdbook or plugin versions, edit versions.toml and the CI will automatically use them. +# Tool versions are managed in versions.txt at the repository root. +# To update mdbook or plugin versions, edit versions.txt and the CI will automatically use them. on: push: diff --git a/.tool-versions b/.tool-versions index bc75e7b..7e21b6d 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,7 +1,7 @@ # Tool versions for asdf version manager # See: https://asdf-vm.com/ # -# Note: For mdbook and plugin versions, see versions.toml -# Use the install scripts in better-code/ to install the correct versions +# Note: For mdbook and plugin versions, see versions.txt +# Use the install scripts to install the correct versions: ./scripts/install-tools.sh rust stable diff --git a/README.md b/README.md index dd2a981..3c17924 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Install [Rust and Cargo](https://doc.rust-lang.org/cargo/getting-started/install .\scripts\install-tools.ps1 ``` -These scripts install mdBook and all required plugins using versions from `versions.toml`. +These scripts install mdBook and all required plugins using versions from `versions.txt`. ### 3. Build and Serve @@ -58,15 +58,15 @@ Open http://localhost:3000 in your browser. - No deployment; PR check shows build status **Main Branch:** -- Builds book using mdBook with versions from `versions.toml` +- Builds book using mdBook with versions from `versions.txt` - Deploys to GitHub Pages - Available at https://stlab.github.io/better-code/ ### Managing Dependencies -All tool versions are centrally managed in `versions.toml`. To update: +All tool versions are centrally managed in `versions.txt`. To update: -1. Edit version number in `versions.toml` +1. Edit version number in `versions.txt` 2. Run the appropriate install script locally 3. Test with `mdbook serve ./better-code` 4. Commit - CI automatically uses the new version @@ -83,7 +83,7 @@ scripts/ # Installation scripts ├── install-tools.sh # Linux/macOS └── install-tools.ps1 # Windows -versions.toml # Single source of truth for tool versions +versions.txt # Single source of truth for tool versions archive/ # Legacy Jekyll site (for reference only) ``` diff --git a/scripts/install-tools.ps1 b/scripts/install-tools.ps1 index 2dbc4e0..59fb2b6 100644 --- a/scripts/install-tools.ps1 +++ b/scripts/install-tools.ps1 @@ -1,6 +1,6 @@ -# Installs mdBook and plugins using versions from versions.toml. +# Installs mdBook and plugins using versions from versions.txt. # -# Reads tool versions from the repository root's versions.toml file and +# Reads tool versions from the repository root's versions.txt file and # installs them via cargo. This ensures consistency between local development # and CI environments. # @@ -8,111 +8,35 @@ # # Preconditions: # - Rust and Cargo are installed and in PATH -# - versions.toml exists in the repository root -# - versions.toml contains [mdbook] section with version key +# - versions.txt exists in the repository root $ErrorActionPreference = "Stop" $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path -$VersionsFile = Join-Path (Split-Path -Parent $ScriptDir) "versions.toml" - -# Extracts a version string from versions.toml. -# -# Parameters: -# Tool - Tool name matching a TOML section header (e.g., "mdbook") -# Section - Optional. Parent section name for nested keys (e.g., "mdbook-plugins") -# -# Returns: The version string, or $null if not found -# -# When Section is empty: -# - Searches for [Tool] section header -# - Returns value of version key within that section -# - Handles comments and blank lines between section header and version key -# -# When Section is provided: -# - Searches for [Section] header -# - Returns value of "Tool = version" within that section -# -# Example: -# Get-ToolVersion -Tool "mdbook" -# Get-ToolVersion -Tool "mdbook-katex" -Section "mdbook-plugins" -function Get-ToolVersion { - param ( - [string]$Tool, - [string]$Section = "" - ) - - $content = Get-Content $VersionsFile -Raw - - if ($Section) { - # Extract from section like [mdbook-plugins] - $pattern = "(?ms)\[$Section\].*?$Tool\s*=\s*`"([^`"]+)`"" - } else { - # Extract from top-level section like [mdbook] - $pattern = "(?ms)\[$Tool\].*?version\s*=\s*`"([^`"]+)`"" - } - - if ($content -match $pattern) { - return $Matches[1] - } - return $null -} - -# Extracts all plugin name-version pairs from [mdbook-plugins] section. -# -# Returns: A hashtable mapping plugin names to version strings -# -# The hashtable keys are plugin names (e.g., "mdbook-katex") and values -# are version strings (e.g., "0.10.0-alpha"). Returns an empty hashtable -# if the [mdbook-plugins] section is not found or contains no plugins. -# -# Example: -# $plugins = Get-MdbookPlugins -# foreach ($plugin in $plugins.GetEnumerator()) { -# Write-Host "$($plugin.Key) version $($plugin.Value)" -# } -function Get-MdbookPlugins { - $content = Get-Content $VersionsFile -Raw - $plugins = @{} - - # Capture everything *after* the header line up to (but not including) the next section header. - # Use \z (end-of-string) instead of $ (end-of-line in multiline mode). - if ($content -match '(?ms)^\[mdbook-plugins\]\s*\r?\n(.*?)(?=^\[|\z)') { - $section = $Matches[1] - $lines = $section -split "`n" - - foreach ($line in $lines) { - if ($line -match '^([a-zA-Z0-9_-]+)\s*=\s*"([^"]+)"') { - $plugins[$Matches[1]] = $Matches[2] - } - } - } - - return $plugins -} +$VersionsFile = Join-Path (Split-Path -Parent $ScriptDir) "versions.txt" Write-Host "Reading versions from $VersionsFile..." -ForegroundColor Cyan # Install mdBook -$MdbookVersion = Get-ToolVersion -Tool "mdbook" -if ($MdbookVersion) { - Write-Host "`nInstalling mdBook $MdbookVersion..." -ForegroundColor Cyan - cargo install mdbook --version $MdbookVersion +$mdbookLine = Get-Content $VersionsFile | Where-Object { $_ -match '^mdbook=' } +if ($mdbookLine) { + $mdbookVersion = $mdbookLine.Split('=')[1] + Write-Host "`nInstalling mdBook $mdbookVersion..." -ForegroundColor Cyan + cargo install mdbook --version $mdbookVersion if ($LASTEXITCODE -ne 0) { exit 1 } } else { - Write-Host "Error: Could not find mdbook version in versions.toml" -ForegroundColor Red + Write-Host "Error: Could not find mdbook version in versions.txt" -ForegroundColor Red exit 1 } # Install mdBook plugins Write-Host "`nInstalling mdBook plugins..." -ForegroundColor Cyan -$plugins = Get-MdbookPlugins -foreach ($plugin in $plugins.GetEnumerator()) { - $pluginName = $plugin.Key - $pluginVersion = $plugin.Value +# Process all lines starting with "mdbook-" (plugins) +Get-Content $VersionsFile | Where-Object { $_ -match '^mdbook-' } | ForEach-Object { + $pluginName, $pluginVersion = $_ -split '=', 2 # mdbook-katex requires special handling on Windows # See: https://github.com/lzanini/mdbook-katex#windows-users @@ -124,7 +48,7 @@ foreach ($plugin in $plugins.GetEnumerator()) { cargo install $pluginName --version $pluginVersion --no-default-features --features duktape if ($LASTEXITCODE -ne 0) { Write-Host "Failed to install $pluginName. Continuing without it..." -ForegroundColor Yellow - continue + return } } else { Write-Host "Installing $pluginName $pluginVersion..." -ForegroundColor Cyan diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index 7a3c187..112608c 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -1,7 +1,7 @@ #!/bin/bash -# Installs mdBook and plugins using versions from versions.toml. +# Installs mdBook and plugins using versions from versions.txt. # -# Reads tool versions from the repository root's versions.toml file and +# Reads tool versions from the repository root's versions.txt file and # installs them via cargo. This ensures consistency between local development # and CI environments. # @@ -9,74 +9,22 @@ # # Preconditions: # - Rust and Cargo are installed and in PATH -# - versions.toml exists in the repository root -# - versions.toml contains [mdbook] section with version key +# - versions.txt exists in the repository root set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -VERSIONS_FILE="${SCRIPT_DIR}/../versions.toml" - -# Extracts a version string from versions.toml. -# -# Parameters: -# $1 (tool) - Tool name matching a TOML section header (e.g., "mdbook") -# $2 (section) - Optional. Parent section name for nested keys (e.g., "mdbook-plugins") -# -# Returns: The version string on stdout, or empty if not found -# -# When section is empty: -# - Searches for [tool] section header -# - Returns value of version key within that section -# - Handles comments and blank lines between section header and version key -# -# When section is provided: -# - Searches for [section] header -# - Returns value of "tool = version" within that section -# -# Example: -# get_version "mdbook" # Returns version from [mdbook] -# get_version "mdbook-katex" "mdbook-plugins" # Returns version from [mdbook-plugins] -get_version() { - local tool=$1 - local section=$2 - - if [ -z "$section" ]; then - # Top-level tool like [mdbook] - # Use awk to handle comments/blank lines between section header and version key - awk -v tool="$tool" ' - BEGIN { in_section = 0 } - $0 ~ ("^\\[" tool "\\]$") { in_section = 1; next } - in_section && /^\[/ { exit } - in_section && /^version[[:space:]]*=/ { - # Extract version string between quotes using sub (POSIX-compliant) - line = $0 - sub(/^[^"]*"/, "", line) - sub(/".*$/, "", line) - print line - exit - } - ' "$VERSIONS_FILE" - else - # Plugin in a section like [mdbook-plugins] - awk -v section="$section" -v tool="$tool" ' - BEGIN { in_section = 0 } - $0 ~ ("^\\[" section "\\]$") { in_section = 1; next } - in_section && $0 ~ "^\\[" { exit } - in_section && $1 == tool && $2 == "=" { print; exit } - ' "$VERSIONS_FILE" | sed 's/.*"\(.*\)".*/\1/' - fi -} +VERSIONS_FILE="${SCRIPT_DIR}/../versions.txt" echo "Reading versions from ${VERSIONS_FILE}..." # Install mdBook -MDBOOK_VERSION=$(get_version "mdbook") +MDBOOK_VERSION=$(grep "^mdbook=" "$VERSIONS_FILE" | cut -d'=' -f2) if [ -n "$MDBOOK_VERSION" ]; then echo "Installing mdBook ${MDBOOK_VERSION}..." cargo install mdbook --version "${MDBOOK_VERSION}" else - echo "Error: Could not find mdbook version in versions.toml" + echo "Error: Could not find mdbook version in versions.txt" exit 1 fi @@ -84,22 +32,11 @@ fi echo "" echo "Installing mdBook plugins..." -# Extract all plugins from [mdbook-plugins] section -while IFS= read -r line; do - if [[ $line =~ ^([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\"([^\"]+)\" ]]; then - plugin="${BASH_REMATCH[1]}" - version="${BASH_REMATCH[2]}" - echo "Installing ${plugin} ${version}..." - cargo install "${plugin}" --version "${version}" - fi -done < <( - awk ' - BEGIN { in_section = 0 } - /^\[mdbook-plugins\]$/ { in_section = 1; next } - in_section && /^\[/ { exit } - in_section && $0 ~ /^[a-zA-Z0-9_-]+[[:space:]]*=/ { print } - ' "$VERSIONS_FILE" -) +# Process all lines starting with "mdbook-" (plugins) +grep "^mdbook-" "$VERSIONS_FILE" | while IFS='=' read -r plugin version; do + echo "Installing ${plugin} ${version}..." + cargo install "${plugin}" --version "${version}" +done echo "" echo "✓ Installation complete!" diff --git a/versions.toml b/versions.txt similarity index 59% rename from versions.toml rename to versions.txt index 2cb790c..121f5c2 100644 --- a/versions.toml +++ b/versions.txt @@ -1,6 +1,9 @@ # Tool versions - single source of truth for all build dependencies # Used by CI and local development scripts # +# Format: tool=version (one per line, no quotes needed) +# Lines starting with # are comments +# # Usage: # Linux/macOS: ./scripts/install-tools.sh # Windows: .\scripts\install-tools.ps1 @@ -11,28 +14,23 @@ # 3. Test locally: mdbook serve ./better-code # 4. Commit the change - CI will automatically use the new version # -# Note: After adding mdbook plugins here, you may also need to configure -# them in better-code/book.toml. See mdbook documentation for plugin setup. +# Note: After adding mdbook plugins, you may also need to configure +# them in better-code/book.toml. See mdbook documentation for details. -[mdbook] -version = "0.5.2" +# Core mdBook +mdbook=0.5.2 -[mdbook-plugins] +# mdBook plugins # Note: On Windows, mdbook-katex installs with the duktape backend (limited functionality). # For full features, download pre-built binaries: https://github.com/lzanini/mdbook-katex/releases -mdbook-katex = "0.10.0-alpha" -# mdbook-mermaid = "x.y.z" # Uncomment when diagram support is needed -# Add more mdbook plugins here as needed +mdbook-katex=0.10.0-alpha -# [swift] -# Uncomment and configure when Swift tooling is added -# version = "5.9" +# Uncomment to add more plugins: +# mdbook-mermaid=x.y.z # Add other tool dependencies here as the project grows # Examples: -# [python] -# version = "3.11" -# -# [node] -# version = "20.10.0" +# swift=5.9 +# python=3.11 +# node=20.10.0 From 39cfff98a5e8d27b38f39dff9d75ee4848ed05c6 Mon Sep 17 00:00:00 2001 From: Sean Parent Date: Tue, 23 Dec 2025 21:08:46 -0800 Subject: [PATCH 18/18] Fix mdBook plugin installation loop in script Refactors the plugin installation loop to use process substitution, ensuring the while loop reads from the filtered list of plugins. This improves compatibility and prevents issues with subshell variable scope. --- scripts/install-tools.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/install-tools.sh b/scripts/install-tools.sh index 112608c..d05c5b6 100644 --- a/scripts/install-tools.sh +++ b/scripts/install-tools.sh @@ -33,10 +33,10 @@ echo "" echo "Installing mdBook plugins..." # Process all lines starting with "mdbook-" (plugins) -grep "^mdbook-" "$VERSIONS_FILE" | while IFS='=' read -r plugin version; do +while IFS='=' read -r plugin version; do echo "Installing ${plugin} ${version}..." cargo install "${plugin}" --version "${version}" -done +done < <(grep "^mdbook-" "$VERSIONS_FILE") echo "" echo "✓ Installation complete!"