Files
cs249r_book/.github/workflows/tinytorch-publish-live.yml
Vijay Janapa Reddi 7994f91e0e Merge pull request #1178 from salmanmkc/upgrade-github-actions-node24
Upgrade GitHub Actions for Node 24 compatibility
2026-03-01 11:36:51 -05:00

590 lines
22 KiB
YAML

name: '🔥 TinyTorch · 🚀 Publish (Live)'
# =============================================================================
# TinyTorch Publish Workflow
# =============================================================================
#
# Builds and deploys TinyTorch site to mlsysbook.ai/tinytorch/
# with automatic semantic versioning and release tagging.
#
# WORKFLOW FLOW:
# ==============
#
# 1. validate-inputs
# └─ Calculate next version from tinytorch-v* tags (e.g., v0.1.1 → v0.1.2)
#
# 2. preflight (validation checks)
# └─ Run tinytorch-validate-dev.yml tests
#
# 3. update-version (if preflight passes) [SKIPPED if site_only=true]
# └─ Bump version in 6 files on dev branch:
# - pyproject.toml (single source of truth - __init__.py reads from this)
# - site/_static/version-badge.js
# - site/_static/wip-banner.js (navbar version badge)
# - site/extra/install.sh
# - site/_static/announcement.json
# - README.md
#
# 4. sync-from-dev
# └─ Merge dev → main
#
# 5. build-pdfs (parallel) [SKIPPED if site_only=true]
# └─ Generate PDF guide and paper
#
# 6. build-and-deploy (if all previous succeed)
# └─ Build Jupyter Book, deploy to gh-pages/tinytorch/
#
# 7. create-tag-and-release (if deploy succeeds) [SKIPPED if site_only=true]
# └─ Create tinytorch-vX.Y.Z tag and GitHub Release (draft)
#
# 8. summary
# └─ Show results in GitHub Actions UI
#
# MODES:
# ======
# - Full Release: Bumps version, creates tag/release, builds PDFs (default)
# - Site Only: Deploys website changes without version bump, tag, or PDFs
# Use for: typo fixes, team page updates, CSS tweaks, docs
#
# SAFETY FEATURES:
# ================
# - Requires typing "PUBLISH" to confirm
# - Each step checks previous step success
# - Tag created only after successful deploy
# - Preflight must pass before any changes
#
# VERSIONING:
# ===========
# - Tags use format: tinytorch-v{major}.{minor}.{patch}
# - Separate from book versions (which use v{major}.{minor}.{patch})
# - Release type (patch/minor/major) determines version bump
#
# =============================================================================
on:
workflow_dispatch:
inputs:
description:
description: 'What are you publishing? [Content updates and improvements]'
required: false
default: 'Content updates and improvements'
site_only:
description: 'Website-only deploy? (no version bump, no tag, no PDFs)'
required: false
type: boolean
default: false
release_type:
description: 'Release type (ignored if site_only) [patch]'
required: true
type: choice
options:
- 'patch'
- 'minor'
- 'major'
default: 'patch'
confirm:
description: 'Type "PUBLISH" to confirm (safety check) [required]'
required: true
default: ''
permissions:
contents: write
actions: read
concurrency:
group: gh-pages-deploy
cancel-in-progress: false
jobs:
validate-inputs:
name: '🔍 Validate Inputs'
runs-on: ubuntu-latest
timeout-minutes: 5
if: github.event.inputs.confirm == 'PUBLISH'
outputs:
new_version: ${{ steps.version.outputs.new_version }}
previous_version: ${{ steps.version.outputs.previous_version }}
site_only: ${{ steps.mode.outputs.site_only }}
steps:
- name: 📥 Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: 🔀 Determine deploy mode
id: mode
run: |
SITE_ONLY="${{ github.event.inputs.site_only }}"
echo "site_only=$SITE_ONLY" >> $GITHUB_OUTPUT
if [ "$SITE_ONLY" = "true" ]; then
echo "🌐 Mode: SITE-ONLY deploy (no version bump, no tag, no PDFs)"
else
echo "🚀 Mode: FULL RELEASE (version bump + tag + PDFs)"
fi
- name: 🏷️ Calculate Next Version
id: version
run: |
# In site-only mode, just output the current version (no bump)
if [ "${{ github.event.inputs.site_only }}" = "true" ]; then
LATEST_VERSION=$(git tag -l "tinytorch-v*" | sort -V | tail -n1)
LATEST_VERSION=${LATEST_VERSION:-"tinytorch-v0.0.0"}
echo "🌐 Site-only mode — keeping current version: $LATEST_VERSION"
echo "new_version=$LATEST_VERSION" >> $GITHUB_OUTPUT
echo "previous_version=$LATEST_VERSION" >> $GITHUB_OUTPUT
exit 0
fi
echo "🔄 Getting latest TinyTorch version..."
# Get latest tinytorch-v* tag, default to v0.0.0 if none exist
LATEST_VERSION=$(git tag -l "tinytorch-v*" | sort -V | tail -n1)
if [ -z "$LATEST_VERSION" ]; then
LATEST_VERSION="tinytorch-v0.0.0"
echo "📊 No TinyTorch tags found, using default: $LATEST_VERSION"
else
echo "📊 Latest TinyTorch tag: $LATEST_VERSION"
fi
# Remove 'tinytorch-v' prefix for calculation
VERSION_NUM=${LATEST_VERSION#tinytorch-v}
# Split version into components
IFS='.' read -r MAJOR MINOR PATCH <<< "$VERSION_NUM"
MAJOR=${MAJOR:-0}
MINOR=${MINOR:-0}
PATCH=${PATCH:-0}
echo "📊 Previous version: $MAJOR.$MINOR.$PATCH"
# Calculate new version based on release type
case "${{ github.event.inputs.release_type }}" in
"major")
NEW_MAJOR=$((MAJOR + 1))
NEW_MINOR=0
NEW_PATCH=0
;;
"minor")
NEW_MAJOR=$MAJOR
NEW_MINOR=$((MINOR + 1))
NEW_PATCH=0
;;
"patch")
NEW_MAJOR=$MAJOR
NEW_MINOR=$MINOR
NEW_PATCH=$((PATCH + 1))
;;
esac
NEW_VERSION="tinytorch-v$NEW_MAJOR.$NEW_MINOR.$NEW_PATCH"
echo "🎯 New version: $NEW_VERSION (${{ github.event.inputs.release_type }} release)"
# Export for other steps
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
echo "previous_version=$LATEST_VERSION" >> $GITHUB_OUTPUT
preflight:
name: '✈️ Preflight Checks'
needs: validate-inputs
uses: ./.github/workflows/tinytorch-validate-dev.yml
with:
test_type: 'all'
update-version:
name: '📝 Update Version in Code'
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [validate-inputs, preflight]
if: needs.preflight.result == 'success' && needs.validate-inputs.outputs.site_only != 'true'
steps:
- name: 📥 Checkout dev branch
uses: actions/checkout@v6
with:
ref: dev
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: 📝 Update version in pyproject.toml (single source of truth)
run: |
echo "📝 Updating version in tinytorch/pyproject.toml..."
VERSION_NUM="${{ needs.validate-inputs.outputs.new_version }}"
VERSION_NUM=${VERSION_NUM#tinytorch-v}
# Update version in pyproject.toml
sed -i "s/^version = \".*\"/version = \"$VERSION_NUM\"/" tinytorch/pyproject.toml
echo "✅ pyproject.toml version updated"
cat tinytorch/pyproject.toml | grep "^version"
- name: 📝 Update version in website badge
run: |
echo "📝 Updating version in tinytorch/site/_static/version-badge.js..."
VERSION_NUM="${{ needs.validate-inputs.outputs.new_version }}"
VERSION_NUM=${VERSION_NUM#tinytorch-v}
# Update version in version-badge.js
sed -i "s/const version = versionMeta ? versionMeta.content : '[0-9.]*'/const version = versionMeta ? versionMeta.content : '$VERSION_NUM'/" tinytorch/site/_static/version-badge.js
echo "✅ version-badge.js updated"
cat tinytorch/site/_static/version-badge.js | grep "const version"
- name: 📝 Update version in navbar badge
run: |
echo "📝 Updating version in tinytorch/site/_static/wip-banner.js..."
VERSION_NUM="${{ needs.validate-inputs.outputs.new_version }}"
VERSION_NUM=${VERSION_NUM#tinytorch-v}
RELEASE_DATE=$(date +'%b %-d, %Y')
# Update the top-of-file constants (easy to find, easy to sed)
sed -i "s/const TINYTORCH_VERSION = '.*'/const TINYTORCH_VERSION = '$VERSION_NUM'/" tinytorch/site/_static/wip-banner.js
sed -i "s/const TINYTORCH_RELEASE_DATE = '.*'/const TINYTORCH_RELEASE_DATE = '$RELEASE_DATE'/" tinytorch/site/_static/wip-banner.js
echo "✅ wip-banner.js updated"
head -10 tinytorch/site/_static/wip-banner.js
- name: 📝 Update version in installer
run: |
echo "📝 Updating version in tinytorch/site/extra/install.sh..."
VERSION_NUM="${{ needs.validate-inputs.outputs.new_version }}"
VERSION_NUM=${VERSION_NUM#tinytorch-v}
# Update version in install.sh
sed -i "s/TINYTORCH_VERSION=\"[0-9.]*\"/TINYTORCH_VERSION=\"$VERSION_NUM\"/" tinytorch/site/extra/install.sh
echo "✅ install.sh updated"
cat tinytorch/site/extra/install.sh | grep "TINYTORCH_VERSION"
- name: 📢 Update announcement bar
run: |
echo "📢 Updating announcement in tinytorch/site/_static/announcement.json..."
VERSION_NUM="${{ needs.validate-inputs.outputs.new_version }}"
VERSION_NUM=${VERSION_NUM#tinytorch-v}
# Update announcement.json with new version
# Link directly to the TinyTorch release tag
cat > tinytorch/site/_static/announcement.json << EOF
{
"enabled": true,
"dismissId": "v$VERSION_NUM",
"items": [
{
"icon": "🎉",
"text": "v$VERSION_NUM released — ${{ github.event.inputs.description }}",
"link": "https://github.com/harvard-edge/cs249r_book/releases/tag/tinytorch-v$VERSION_NUM",
"linkText": "See →"
}
]
}
EOF
echo "✅ announcement.json updated"
cat tinytorch/site/_static/announcement.json
- name: 📝 Update README badge
run: |
echo "📝 Updating version badge in tinytorch/README.md..."
VERSION_NUM="${{ needs.validate-inputs.outputs.new_version }}"
VERSION_NUM=${VERSION_NUM#tinytorch-v}
# Update version badge in README.md
sed -i "s/version-[0-9.]*-D4740C/version-$VERSION_NUM-D4740C/" tinytorch/README.md
echo "✅ README.md badge updated"
grep "version-" tinytorch/README.md | head -1
- name: 💾 Commit version update
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add tinytorch/pyproject.toml tinytorch/site/_static/version-badge.js tinytorch/site/_static/wip-banner.js tinytorch/site/extra/install.sh tinytorch/site/_static/announcement.json tinytorch/README.md
git commit -m "chore(tinytorch): bump version to ${{ needs.validate-inputs.outputs.new_version }}" || echo "No changes to commit"
git push origin dev
sync-from-dev:
name: '🔄 Sync dev → main'
runs-on: ubuntu-latest
needs: [validate-inputs, preflight, update-version]
if: always() && needs.preflight.result == 'success' && (needs.update-version.result == 'success' || needs.update-version.result == 'skipped')
steps:
- name: 📥 Checkout main
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: 🔄 Merge dev into main
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
echo "📥 Fetching dev branch..."
git fetch origin dev
SITE_ONLY="${{ needs.validate-inputs.outputs.site_only }}"
if [ "$SITE_ONLY" = "true" ]; then
MERGE_MSG="🌐 TinyTorch website update: ${{ github.event.inputs.description }}"
else
MERGE_MSG="🔥 TinyTorch ${{ needs.validate-inputs.outputs.new_version }}: ${{ github.event.inputs.description }}"
fi
echo "🔄 Merging dev into main..."
git merge origin/dev --no-edit -m "$MERGE_MSG"
echo "🚀 Pushing updated main..."
git push origin main
build-pdfs:
name: '📚 Build PDFs'
needs: [validate-inputs, preflight, sync-from-dev]
if: needs.validate-inputs.outputs.site_only != 'true'
uses: ./.github/workflows/tinytorch-build-pdfs.yml
with:
ref: main
build-and-deploy:
name: '🔥 Build & Deploy'
runs-on: ubuntu-latest
needs: [validate-inputs, preflight, update-version, sync-from-dev, build-pdfs]
if: >-
always() &&
needs.preflight.result == 'success' &&
needs.sync-from-dev.result == 'success' &&
(needs.update-version.result == 'success' || needs.update-version.result == 'skipped') &&
(needs.build-pdfs.result == 'success' || needs.build-pdfs.result == 'skipped')
steps:
- name: 📥 Checkout
uses: actions/checkout@v6
with:
ref: main # Use freshly synced main
- name: 🐍 Setup Python
uses: actions/setup-python@v6
with:
python-version: '3.11'
cache: 'pip'
cache-dependency-path: 'tinytorch/site/requirements.txt'
- name: 📦 Install dependencies
working-directory: tinytorch
run: |
pip install --upgrade pip
pip install -r site/requirements.txt
- name: 👥 Generate team page from contributors
working-directory: tinytorch/site
run: |
echo "👥 Generating team page from .all-contributorsrc..."
python3 scripts/generate_team.py
echo "✅ Team page generated"
- name: 🔨 Build Jupyter Book
working-directory: tinytorch/site
run: |
jupyter-book build . --all
touch _build/html/.nojekyll
- name: 📥 Download PDF Guide
uses: actions/download-artifact@v7
continue-on-error: true
with:
name: TinyTorch-Guide
path: ./pdf-artifacts/guide
- name: 📥 Download PDF Paper
uses: actions/download-artifact@v7
continue-on-error: true
with:
name: TinyTorch-Paper
path: ./pdf-artifacts/paper
- name: 📁 Inject PDFs into site
run: |
echo "📁 Injecting PDFs into built site..."
mkdir -p tinytorch/site/_build/html/_static/downloads
if [ -f ./pdf-artifacts/guide/tinytorch-course.pdf ]; then
cp ./pdf-artifacts/guide/tinytorch-course.pdf tinytorch/site/_build/html/_static/downloads/TinyTorch-Guide.pdf
echo "✅ Injected TinyTorch-Guide.pdf"
else
echo "⚠️ Guide PDF not found"
fi
if [ -f ./pdf-artifacts/paper/paper.pdf ]; then
cp ./pdf-artifacts/paper/paper.pdf tinytorch/site/_build/html/_static/downloads/TinyTorch-Paper.pdf
echo "✅ Injected TinyTorch-Paper.pdf"
else
echo "⚠️ Paper PDF not found"
fi
echo ""
echo "📦 Downloads folder contents:"
ls -la tinytorch/site/_build/html/_static/downloads/ || echo "No downloads folder"
- name: 📊 Download slide decks from release
env:
GH_TOKEN: ${{ github.token }}
run: |
echo "📊 Downloading slide decks from GitHub release..."
mkdir -p tinytorch/site/_build/html/_static/slides
# Download all PDFs from the slides release
gh release download tinytorch-slides-v0.1.0 \
--repo harvard-edge/cs249r_book \
--pattern "*.pdf" \
--dir tinytorch/site/_build/html/_static/slides
echo ""
echo "📦 Slides folder contents:"
ls -la tinytorch/site/_build/html/_static/slides/ || echo "No slides folder"
echo "✅ Downloaded $(ls tinytorch/site/_build/html/_static/slides/*.pdf 2>/dev/null | wc -l) slide decks"
- name: 🚀 Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./tinytorch/site/_build/html
destination_dir: tinytorch
publish_branch: gh-pages
keep_files: true
commit_message: '${{ needs.validate-inputs.outputs.site_only == ''true'' && ''🌐 TinyTorch website update'' || format(''🔥 Deploy TinyTorch {0}'', needs.validate-inputs.outputs.new_version) }} - ${{ github.sha }}'
create-tag:
name: '🏷️ Create Tag & Release'
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [validate-inputs, build-and-deploy]
if: needs.build-and-deploy.result == 'success' && needs.validate-inputs.outputs.site_only != 'true'
steps:
- name: 📥 Checkout main
uses: actions/checkout@v6
with:
ref: main
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: 🏷️ Create and push tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Fetch latest main (includes the merge commit)
git fetch origin main
git checkout main
git pull origin main
TAG="${{ needs.validate-inputs.outputs.new_version }}"
echo "🏷️ Creating tag: $TAG on commit $(git rev-parse HEAD)"
# Delete existing tag if it exists
git tag -d "$TAG" 2>/dev/null || true
git push origin --delete "$TAG" 2>/dev/null || true
# Create annotated tag on current HEAD (latest main)
git tag -a "$TAG" -m "TinyTorch $TAG: ${{ github.event.inputs.description }}"
git push origin "$TAG"
echo "✅ Tag $TAG created and pushed"
- name: 📦 Create GitHub Release (Draft)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ needs.validate-inputs.outputs.new_version }}"
PREV_TAG="${{ needs.validate-inputs.outputs.previous_version }}"
VERSION_NUM=${TAG#tinytorch-v}
echo "📦 Creating GitHub Release for $TAG..."
# Generate auto release notes from GitHub (includes contributors automatically)
AUTO_NOTES=$(gh api repos/${{ github.repository }}/releases/generate-notes \
-f tag_name="$TAG" \
-f previous_tag_name="$PREV_TAG" \
--jq '.body')
# Build release notes with custom header + auto-generated content
cat > /tmp/release-notes.md << NOTES_EOF
## TinyTorch v${VERSION_NUM}
${{ github.event.inputs.description }}
${AUTO_NOTES}
---
**Website**: https://mlsysbook.ai/tinytorch/
NOTES_EOF
# Create the release as draft
gh release create "$TAG" \
--title "TinyTorch v${VERSION_NUM} - ${{ github.event.inputs.description }}" \
--notes-file /tmp/release-notes.md \
--draft
echo "✅ Draft release created"
echo "📝 Review and publish at: https://github.com/${{ github.repository }}/releases"
summary:
name: '📋 Publication Summary'
runs-on: ubuntu-latest
timeout-minutes: 5
needs: [validate-inputs, build-and-deploy, create-tag]
if: always()
steps:
- name: 📋 Summary
run: |
SITE_ONLY="${{ needs.validate-inputs.outputs.site_only }}"
if [ "$SITE_ONLY" = "true" ]; then
echo "## 🌐 TinyTorch Website Deploy Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Mode:** Website-only (no version bump)" >> $GITHUB_STEP_SUMMARY
echo "**Description:** ${{ github.event.inputs.description }}" >> $GITHUB_STEP_SUMMARY
echo "**Deployed by:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🌐 Access:" >> $GITHUB_STEP_SUMMARY
echo "- 📖 [TinyTorch Website](https://mlsysbook.ai/tinytorch/)" >> $GITHUB_STEP_SUMMARY
else
echo "## 🔥 TinyTorch Publication Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ needs.validate-inputs.outputs.new_version }}" >> $GITHUB_STEP_SUMMARY
echo "**Previous:** ${{ needs.validate-inputs.outputs.previous_version }}" >> $GITHUB_STEP_SUMMARY
echo "**Release Type:** ${{ github.event.inputs.release_type }}" >> $GITHUB_STEP_SUMMARY
echo "**Description:** ${{ github.event.inputs.description }}" >> $GITHUB_STEP_SUMMARY
echo "**Published by:** ${{ github.actor }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🌐 Access:" >> $GITHUB_STEP_SUMMARY
echo "- 📖 [TinyTorch Website](https://mlsysbook.ai/tinytorch/)" >> $GITHUB_STEP_SUMMARY
echo "- 🏷️ [Release](https://github.com/${{ github.repository }}/releases/tag/${{ needs.validate-inputs.outputs.new_version }})" >> $GITHUB_STEP_SUMMARY
fi
fail-validation:
name: '❌ Validation Failed'
runs-on: ubuntu-latest
timeout-minutes: 5
if: github.event.inputs.confirm != 'PUBLISH'
steps:
- name: ❌ Invalid confirmation
run: |
echo "## ❌ Publication Cancelled" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Reason:** You must type exactly 'PUBLISH' to confirm" >> $GITHUB_STEP_SUMMARY
echo "**Received:** ${{ github.event.inputs.confirm }}" >> $GITHUB_STEP_SUMMARY
exit 1