Skip to content

[ci] Migrate from nwtgck/actions-netlify@v3 to netlify-cli for better PR preview control #1546

[ci] Migrate from nwtgck/actions-netlify@v3 to netlify-cli for better PR preview control

[ci] Migrate from nwtgck/actions-netlify@v3 to netlify-cli for better PR preview control #1546

Workflow file for this run

name: Build Project [using jupyter-book]
on:
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
inputs:
preview_page:
description: 'Specific page to preview (e.g., aiyagari.html)'
required: false
type: string
jobs:
preview:
runs-on: "runs-on=${{ github.run_id }}/family=g4dn.2xlarge/image=quantecon_ubuntu2404/disk=large"
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
- name: Setup Anaconda
uses: conda-incubator/setup-miniconda@v3
with:
auto-update-conda: true
auto-activate-base: true
miniconda-version: 'latest'
python-version: "3.13"
environment-file: environment.yml
activate-environment: quantecon
- name: Install JAX, Numpyro, PyTorch
shell: bash -l {0}
run: |
pip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cu128
pip install pyro-ppl
pip install --upgrade "jax[cuda12-local]==0.6.2"
pip install numpyro pyro-ppl
python scripts/test-jax-install.py
- name: Check nvidia Drivers
shell: bash -l {0}
run: nvidia-smi
- name: Display Conda Environment Versions
shell: bash -l {0}
run: conda list
- name: Display Pip Versions
shell: bash -l {0}
run: pip list
- name: Download "build" folder (cache)
uses: dawidd6/action-download-artifact@v11
with:
workflow: cache.yml
branch: main
name: build-cache
path: _build
# Build Assets (Download Notebooks and PDF via LaTeX)
- name: Build Download Notebooks (sphinx-tojupyter)
shell: bash -l {0}
run: |
jb build lectures -n -W --keep-going --path-output ./ --builder=custom --custom-builder=jupyter
mkdir -p _build/html/_notebooks
cp -u _build/jupyter/*.ipynb _build/html/_notebooks
- name: Upload Execution Reports (Download Notebooks)
uses: actions/upload-artifact@v4
if: failure()
with:
name: execution-reports-notebooks
path: _build/jupyter/reports
- name: Build PDF from LaTeX
shell: bash -l {0}
run: |
jb build lectures --builder pdflatex --path-output ./ -W --keep-going
mkdir -p _build/html/_pdf
cp -u _build/latex/*.pdf _build/html/_pdf
- name: Upload Execution Reports (LaTeX)
uses: actions/upload-artifact@v4
if: failure()
with:
name: execution-reports
path: _build/latex/reports
# Final Build of HTML
- name: Build HTML
shell: bash -l {0}
run: |
jb build lectures --path-output ./ -n -W --keep-going
- name: Upload Execution Reports (HTML)
uses: actions/upload-artifact@v4
if: failure()
with:
name: execution-reports
path: _build/html/reports
- name: Install Node.js and Netlify CLI
shell: bash -l {0}
run: |
# Install Node.js via system package manager since conda-forge doesn't have npm
sudo apt-get update
sudo apt-get install -y nodejs npm
sudo npm install -g netlify-cli
- name: Detect Changed Lecture Files
id: detect-changes
shell: bash -l {0}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "Detecting changed lecture files..."
echo "Base SHA: ${{ github.event.pull_request.base.sha }}"
echo "Head SHA: ${{ github.event.pull_request.head.sha }}"
# Get changed files in the lectures directory with better validation
all_changed=$(git diff --name-only ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} || true)
echo "All changed files:"
echo "$all_changed"
changed_files=$(echo "$all_changed" | grep '^lectures/.*\.md$' | grep -v '^lectures/_' | grep -v '^lectures/intro\.md$' || true)
if [ ! -z "$changed_files" ]; then
echo "Filtered lecture files:"
echo "$changed_files"
# Validate that these files actually exist and contain changes
validated_files=""
while IFS= read -r file; do
if [ ! -z "$file" ] && [ -f "$file" ]; then
# Check if the file actually has changes
if git diff --quiet ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} -- "$file"; then
echo "Warning: $file shows no actual changes, skipping"
else
echo "Confirmed changes in: $file"
if [ -z "$validated_files" ]; then
validated_files="$file"
else
validated_files="$validated_files"$'\n'"$file"
fi
fi
fi
done <<< "$changed_files"
if [ ! -z "$validated_files" ]; then
echo "Final validated changed files:"
echo "$validated_files"
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
echo "$validated_files" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
else
echo "No lecture files with actual changes found"
echo "changed_files=" >> $GITHUB_OUTPUT
fi
else
echo "No lecture files changed"
echo "changed_files=" >> $GITHUB_OUTPUT
fi
else
echo "Not a PR, skipping change detection"
echo "changed_files=" >> $GITHUB_OUTPUT
fi
- name: Preview Deploy to Netlify
id: netlify-deploy
shell: bash -l {0}
run: |
if [ "${{ github.event_name }}" = "pull_request" ]; then
# Deploy to Netlify and capture the response
deploy_message="Preview Deploy from GitHub Actions PR #${{ github.event.pull_request.number }} (commit: ${{ github.sha }})"
netlify_output=$(netlify deploy \
--dir _build/html/ \
--site ${{ secrets.NETLIFY_SITE_ID }} \
--auth ${{ secrets.NETLIFY_AUTH_TOKEN }} \
--context deploy-preview \
--alias deploy-preview-${{ github.event.pull_request.number }} \
--message "${deploy_message}" \
--json)
echo "Netlify deployment output:"
echo "$netlify_output"
# Extract the actual deploy URL from the JSON response
deploy_url=$(echo "$netlify_output" | jq -r '.deploy_url')
echo "deploy_url=$deploy_url" >> $GITHUB_OUTPUT
echo "✅ Deployment completed!"
echo "🌐 Actual Deploy URL: $deploy_url"
# Generate preview URLs for changed files using the actual deploy URL
if [ ! -z "${{ steps.detect-changes.outputs.changed_files }}" ]; then
echo ""
echo "📚 Direct links to changed lecture pages:"
while read -r file; do
if [ ! -z "$file" ]; then
basename=$(basename "$file" .md)
html_file="${basename}.html"
echo "- ${basename}: ${deploy_url}/${html_file}"
fi
done <<< "${{ steps.detect-changes.outputs.changed_files }}"
fi
# Display manual preview page if specified
if [ ! -z "${{ github.event.inputs.preview_page }}" ]; then
echo ""
echo "🎯 Manual preview page: ${deploy_url}/${{ github.event.inputs.preview_page }}"
fi
else
# Handle manual deployment
deploy_message="Manual Deploy from GitHub Actions (commit: ${{ github.sha }})"
netlify_output=$(netlify deploy \
--dir _build/html/ \
--site ${{ secrets.NETLIFY_SITE_ID }} \
--auth ${{ secrets.NETLIFY_AUTH_TOKEN }} \
--alias manual-${{ github.run_id }} \
--context dev \
--message "${deploy_message}" \
--json)
echo "Netlify deployment output:"
echo "$netlify_output"
# Extract the actual deploy URL from the JSON response
deploy_url=$(echo "$netlify_output" | jq -r '.deploy_url')
echo "deploy_url=$deploy_url" >> $GITHUB_OUTPUT
echo "✅ Manual deployment completed!"
echo "🌐 Actual Deploy URL: $deploy_url"
if [ ! -z "${{ github.event.inputs.preview_page }}" ]; then
echo "🎯 Preview page: ${deploy_url}/${{ github.event.inputs.preview_page }}"
fi
fi
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
- name: Post PR Comment with Preview Links
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const changedFiles = `${{ steps.detect-changes.outputs.changed_files }}`;
const manualPage = `${{ github.event.inputs.preview_page }}`;
const deployUrl = `${{ steps.netlify-deploy.outputs.deploy_url }}`;
const prNumber = ${{ github.event.pull_request.number }};
const commitSha = `${{ github.sha }}`;
// Check if we already posted a comment for this commit
const comments = await github.rest.issues.listComments({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
});
const existingCommentForCommit = comments.data.find(c =>
c.body.includes('**📖 Netlify Preview Ready!**') &&
c.body.includes(`([${commitSha.substring(0, 7)}]`)
);
if (existingCommentForCommit) {
console.log(`Comment already exists for commit ${commitSha}, skipping...`);
return;
}
const shortSha = commitSha.substring(0, 7);
const commitUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${commitSha}`;
let comment = `**📖 Netlify Preview Ready!**\n\n`;
comment += `**Preview URL:** ${deployUrl} ([${shortSha}](${commitUrl}))\n\n`;
// Add manual preview page if specified
if (manualPage) {
comment += `🎯 **Manual Preview:** [${manualPage}](${deployUrl}/${manualPage})\n\n`;
}
// Add direct links to changed lecture pages
if (changedFiles && changedFiles.trim()) {
console.log('Raw changedFiles:', JSON.stringify(changedFiles));
const files = changedFiles.split('\n').filter(f => f.trim() && f.includes('lectures/') && f.endsWith('.md'));
console.log('Filtered files:', files);
if (files.length > 0) {
comment += `📚 **Changed Lecture Pages:** `;
const pageLinks = [];
for (const file of files) {
const cleanFile = file.trim();
if (cleanFile && cleanFile.startsWith('lectures/') && cleanFile.endsWith('.md')) {
const fileName = cleanFile.replace('lectures/', '').replace('.md', '');
console.log(`Processing file: ${cleanFile} -> ${fileName}`);
const pageUrl = `${deployUrl}/${fileName}.html`;
pageLinks.push(`[${fileName}](${pageUrl})`);
}
}
if (pageLinks.length > 0) {
comment += pageLinks.join(', ') + '\n\n';
}
}
}
// Post the comment
await github.rest.issues.createComment({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});