|
11 | 11 | jobs: |
12 | 12 | preview: |
13 | 13 | 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 |
17 | 17 | steps: |
18 | 18 | - uses: actions/checkout@v6 |
19 | 19 | with: |
@@ -86,265 +86,13 @@ jobs: |
86 | 86 | with: |
87 | 87 | name: execution-reports-html |
88 | 88 | 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' |
170 | 93 | - 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 |
258 | 95 | 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