Skip to content

Commit 71764d3

Browse files
authored
FIX: Enable Netlify preview deployments for Dependabot PRs (#804)
* Enable Netlify preview deployments for Dependabot PRs - Remove github.actor != 'dependabot[bot]' restriction from Netlify deploy step - Allow visual inspection of dependency updates (e.g., theme changes) before merging - Update skip message to clarify only fork PRs are blocked for security - Dependabot PRs are safe as they come from within the same repository * refactor: Replace custom Netlify deployment with quantecon/actions/preview-netlify@v0.6.0 - Removes ~260 lines of custom Netlify deployment code - Uses shared action that provides same functionality: - Automatic changed lecture file detection - Smart PR comments with direct links - Duplicate comment prevention - Built-in security handling (skips forks/Dependabot) - Improves maintainability across QuantEcon repos - Enables Dependabot to auto-update the action version * fix: Add workflow permissions for PR comments - Add contents:read and pull-requests:write permissions - Required for preview-netlify action to post PR comments - Addresses Copilot code review feedback * fix: Add Node.js setup for Netlify CLI - preview-netlify action requires npm to install netlify-cli - RunsOn custom AMI doesn't have Node.js pre-installed - Add actions/setup-node@v4 before preview deploy step
1 parent 513cd3d commit 71764d3

1 file changed

Lines changed: 11 additions & 263 deletions

File tree

.github/workflows/ci.yml

Lines changed: 11 additions & 263 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ on:
1111
jobs:
1212
preview:
1313
runs-on: "runs-on=${{ github.run_id }}/family=g4dn.2xlarge/image=quantecon_ubuntu2404/disk=large"
14-
env:
15-
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
16-
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
14+
permissions:
15+
contents: read
16+
pull-requests: write
1717
steps:
1818
- uses: actions/checkout@v6
1919
with:
@@ -86,265 +86,13 @@ jobs:
8686
with:
8787
name: execution-reports-html
8888
path: _build/html/reports
89-
- name: Install Node.js and Netlify CLI
90-
shell: bash -l {0}
91-
run: |
92-
# Install Node.js via system package manager since conda-forge doesn't have npm
93-
sudo apt-get update
94-
sudo apt-get install -y nodejs npm
95-
sudo npm install -g netlify-cli
96-
- name: Detect Changed Lecture Files
97-
id: detect-changes
98-
shell: bash -l {0}
99-
run: |
100-
if [ "${{ github.event_name }}" = "pull_request" ]; then
101-
echo "Detecting changed lecture files..."
102-
echo "Base SHA: ${{ github.event.pull_request.base.sha }}"
103-
echo "Head SHA: ${{ github.event.pull_request.head.sha }}"
104-
105-
# Ensure we have both base and head commits available
106-
git fetch origin ${{ github.event.pull_request.base.sha }}:refs/remotes/origin/pr-base || true
107-
git fetch origin ${{ github.event.pull_request.head.sha }}:refs/remotes/origin/pr-head || true
108-
109-
# Get changed files using git diff with status to see the type of change
110-
echo "Getting diff between commits..."
111-
all_changed=$(git diff --name-status ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} 2>/dev/null || echo "")
112-
113-
if [ -z "$all_changed" ]; then
114-
echo "No changes detected or error in git diff"
115-
echo "changed_files=" >> $GITHUB_OUTPUT
116-
exit 0
117-
fi
118-
119-
echo "All changed files with status:"
120-
echo "$all_changed"
121-
122-
# Filter for lecture files that are Added or Modified (not Deleted)
123-
# Format: M lectures/file.md or A lectures/file.md
124-
changed_lecture_files=""
125-
while IFS=$'\t' read -r status file; do
126-
# Skip if empty line
127-
[ -z "$status" ] && continue
128-
129-
echo "Processing: status='$status' file='$file'"
130-
131-
# Only include Added (A) or Modified (M) files, skip Deleted (D)
132-
if [[ "$status" =~ ^[AM] ]] && [[ "$file" =~ ^lectures/.*\.md$ ]] && [[ ! "$file" =~ ^lectures/_ ]] && [[ "$file" != "lectures/intro.md" ]]; then
133-
# Double-check that the file exists and has real content changes
134-
if [ -f "$file" ]; then
135-
# Use git show to check if there are actual content changes (not just metadata)
136-
content_diff=$(git diff ${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }} -- "$file" | grep -E '^[+-]' | grep -v '^[+-]{3}' | wc -l)
137-
if [ "$content_diff" -gt 0 ]; then
138-
echo "✓ Confirmed content changes in: $file"
139-
if [ -z "$changed_lecture_files" ]; then
140-
changed_lecture_files="$file"
141-
else
142-
changed_lecture_files="$changed_lecture_files"$'\n'"$file"
143-
fi
144-
else
145-
echo "⚠ No content changes found in: $file (possibly metadata only)"
146-
fi
147-
else
148-
echo "⚠ File not found in working directory: $file"
149-
fi
150-
else
151-
echo "⚠ Skipping: $file (status: $status, doesn't match lecture file pattern or is excluded)"
152-
fi
153-
done <<< "$all_changed"
154-
155-
if [ ! -z "$changed_lecture_files" ]; then
156-
echo ""
157-
echo "Final validated changed lecture files:"
158-
echo "$changed_lecture_files"
159-
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
160-
echo "$changed_lecture_files" >> $GITHUB_OUTPUT
161-
echo "EOF" >> $GITHUB_OUTPUT
162-
else
163-
echo "No lecture files with actual content changes found"
164-
echo "changed_files=" >> $GITHUB_OUTPUT
165-
fi
166-
else
167-
echo "Not a PR, skipping change detection"
168-
echo "changed_files=" >> $GITHUB_OUTPUT
169-
fi
89+
- name: Setup Node.js
90+
uses: actions/setup-node@v4
91+
with:
92+
node-version: '20'
17093
- name: Preview Deploy to Netlify
171-
id: netlify-deploy
172-
if: >
173-
github.actor != 'dependabot[bot]' &&
174-
(github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) &&
175-
env.NETLIFY_AUTH_TOKEN != '' &&
176-
env.NETLIFY_SITE_ID != ''
177-
shell: bash -l {0}
178-
run: |
179-
if [ "${{ github.event_name }}" = "pull_request" ]; then
180-
# Deploy to Netlify and capture the response
181-
deploy_message="Preview Deploy from GitHub Actions PR #${{ github.event.pull_request.number }} (commit: ${{ github.event.pull_request.head.sha }})"
182-
183-
netlify_output=$(netlify deploy \
184-
--dir _build/html/ \
185-
--site ${{ secrets.NETLIFY_SITE_ID }} \
186-
--auth ${{ secrets.NETLIFY_AUTH_TOKEN }} \
187-
--context pr-preview \
188-
--alias pr-${{ github.event.pull_request.number }} \
189-
--message "${deploy_message}" \
190-
--json)
191-
192-
echo "Netlify deployment output:"
193-
echo "$netlify_output"
194-
195-
# Extract the actual deploy URL from the JSON response
196-
deploy_url=$(echo "$netlify_output" | jq -r '.deploy_url')
197-
198-
echo "deploy_url=$deploy_url" >> $GITHUB_OUTPUT
199-
echo "✅ Deployment completed!"
200-
echo "🌐 Actual Deploy URL: $deploy_url"
201-
202-
# Generate preview URLs for changed files using the actual deploy URL
203-
if [ ! -z "${{ steps.detect-changes.outputs.changed_files }}" ]; then
204-
echo ""
205-
echo "📚 Direct links to changed lecture pages:"
206-
while read -r file; do
207-
if [ ! -z "$file" ]; then
208-
basename=$(basename "$file" .md)
209-
html_file="${basename}.html"
210-
echo "- ${basename}: ${deploy_url}/${html_file}"
211-
fi
212-
done <<< "${{ steps.detect-changes.outputs.changed_files }}"
213-
fi
214-
215-
# Display manual preview page if specified
216-
if [ ! -z "${{ github.event.inputs.preview_page }}" ]; then
217-
echo ""
218-
echo "🎯 Manual preview page: ${deploy_url}/${{ github.event.inputs.preview_page }}"
219-
fi
220-
else
221-
# Handle manual deployment
222-
deploy_message="Manual Deploy from GitHub Actions (commit: ${{ github.sha }})"
223-
224-
netlify_output=$(netlify deploy \
225-
--dir _build/html/ \
226-
--site ${{ secrets.NETLIFY_SITE_ID }} \
227-
--auth ${{ secrets.NETLIFY_AUTH_TOKEN }} \
228-
--alias manual-${{ github.run_id }} \
229-
--context dev \
230-
--message "${deploy_message}" \
231-
--json)
232-
233-
echo "Netlify deployment output:"
234-
echo "$netlify_output"
235-
236-
# Extract the actual deploy URL from the JSON response
237-
deploy_url=$(echo "$netlify_output" | jq -r '.deploy_url')
238-
239-
echo "deploy_url=$deploy_url" >> $GITHUB_OUTPUT
240-
echo "✅ Manual deployment completed!"
241-
echo "🌐 Actual Deploy URL: $deploy_url"
242-
243-
if [ ! -z "${{ github.event.inputs.preview_page }}" ]; then
244-
echo "🎯 Preview page: ${deploy_url}/${{ github.event.inputs.preview_page }}"
245-
fi
246-
fi
247-
- name: Skip Netlify Deploy (no secrets or untrusted actor)
248-
if: >
249-
!(github.actor != 'dependabot[bot]' &&
250-
(github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) &&
251-
env.NETLIFY_AUTH_TOKEN != '' &&
252-
env.NETLIFY_SITE_ID != '')
253-
run: |
254-
echo "Skipping Netlify preview deploy: secrets unavailable or actor not trusted (actor=${{ github.actor }})"
255-
- name: Post PR Comment with Preview Links
256-
if: github.event_name == 'pull_request' && steps.netlify-deploy.outputs.deploy_url != ''
257-
uses: actions/github-script@v7
94+
uses: quantecon/actions/preview-netlify@v0.6.0
25895
with:
259-
script: |
260-
const changedFiles = `${{ steps.detect-changes.outputs.changed_files }}`;
261-
const manualPage = `${{ github.event.inputs.preview_page }}`;
262-
const deployUrl = `${{ steps.netlify-deploy.outputs.deploy_url }}`;
263-
const prNumber = ${{ github.event.pull_request.number }};
264-
const commitSha = `${{ github.event.pull_request.head.sha }}`;
265-
const shortSha = commitSha.substring(0, 7);
266-
267-
console.log(`Checking for existing comments for commit: ${commitSha}`);
268-
console.log(`Deploy URL: ${deployUrl}`);
269-
console.log(`Changed files: ${changedFiles}`);
270-
271-
// Get all comments on this PR to check for duplicates
272-
const comments = await github.rest.issues.listComments({
273-
issue_number: prNumber,
274-
owner: context.repo.owner,
275-
repo: context.repo.repo,
276-
});
277-
278-
console.log(`Found ${comments.data.length} comments on PR`);
279-
280-
// Look for existing comment with this exact commit SHA and deploy URL
281-
const duplicateComment = comments.data.find(comment => {
282-
const hasMarker = comment.body.includes('**📖 Netlify Preview Ready!**');
283-
const hasCommitSha = comment.body.includes(`([${shortSha}]`);
284-
const hasDeployUrl = comment.body.includes(deployUrl);
285-
286-
console.log(`Comment ${comment.id}: hasMarker=${hasMarker}, hasCommitSha=${hasCommitSha}, hasDeployUrl=${hasDeployUrl}`);
287-
288-
return hasMarker && hasCommitSha && hasDeployUrl;
289-
});
290-
291-
if (duplicateComment) {
292-
console.log(`Duplicate comment found (${duplicateComment.id}) for commit ${shortSha} and deploy URL ${deployUrl}, skipping...`);
293-
return;
294-
}
295-
296-
console.log(`No duplicate found, creating new comment for commit ${shortSha}`);
297-
298-
const commitUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${commitSha}`;
299-
300-
let comment = `**📖 Netlify Preview Ready!**\n\n`;
301-
comment += `**Preview URL:** ${deployUrl} ([${shortSha}](${commitUrl}))\n\n`;
302-
303-
// Add manual preview page if specified
304-
if (manualPage) {
305-
comment += `🎯 **Manual Preview:** [${manualPage}](${deployUrl}/${manualPage})\n\n`;
306-
}
307-
308-
// Add direct links to changed lecture pages
309-
if (changedFiles && changedFiles.trim()) {
310-
console.log('Processing changed files for preview links...');
311-
const files = changedFiles.split('\n').filter(f => f.trim() && f.includes('lectures/') && f.endsWith('.md'));
312-
console.log('Filtered lecture files:', files);
313-
314-
if (files.length > 0) {
315-
comment += `📚 **Changed Lecture Pages:** `;
316-
317-
const pageLinks = [];
318-
for (const file of files) {
319-
const cleanFile = file.trim();
320-
if (cleanFile && cleanFile.startsWith('lectures/') && cleanFile.endsWith('.md')) {
321-
const fileName = cleanFile.replace('lectures/', '').replace('.md', '');
322-
console.log(`Creating preview link: ${cleanFile} -> ${fileName}.html`);
323-
const pageUrl = `${deployUrl}/${fileName}.html`;
324-
pageLinks.push(`[${fileName}](${pageUrl})`);
325-
}
326-
}
327-
328-
if (pageLinks.length > 0) {
329-
comment += pageLinks.join(', ') + '\n\n';
330-
} else {
331-
console.log('No valid page links created');
332-
}
333-
} else {
334-
console.log('No lecture files in changed files list');
335-
}
336-
} else {
337-
console.log('No changed files detected');
338-
}
339-
340-
console.log('Final comment:', comment);
341-
342-
// Post the comment
343-
await github.rest.issues.createComment({
344-
issue_number: prNumber,
345-
owner: context.repo.owner,
346-
repo: context.repo.repo,
347-
body: comment
348-
});
349-
350-
console.log('Comment posted successfully');
96+
netlify-auth-token: ${{ secrets.NETLIFY_AUTH_TOKEN }}
97+
netlify-site-id: ${{ secrets.NETLIFY_SITE_ID }}
98+
build-dir: _build/html

0 commit comments

Comments
 (0)