-
Notifications
You must be signed in to change notification settings - Fork 1k
305 lines (277 loc) · 13.7 KB
/
preview.yml
File metadata and controls
305 lines (277 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
on:
pull_request:
branches: [main, prerelease]
paths-ignore:
- '.github/**'
issue_comment:
types: [created]
name: Deploy Preview
concurrency:
# Use github.event.pull_request.number on pull requests, so it's unique per pull request
# Use github.event.issue.number on issue comments, so it's unique per comment
# Use github.ref on other branches, so it's unique per branch
group: ${{ github.workflow }}-${{ (github.event.pull_request && format('PR-{0}', github.event.pull_request.number)) || ( github.event.issue && format('comment-{0}', github.event.issue.number) ) || github.ref }}
cancel-in-progress: true
jobs:
is-external-pr:
# Be helpful with reviewer and remind them to trigger a deploy preview if the PR is from a fork.
if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true }}
runs-on: ubuntu-latest
steps:
- name: Error with message for manual deploy
run: |
echo "::error title=Manual action required for preview::PR from fork can't be deployed as preview to Netlify automatically. Use '/deploy-preview' command in comments to trigger the preview manually."
shell: bash
role-of-commenter:
if: github.event.issue.pull_request
runs-on: ubuntu-latest
steps:
- name: Check if commenter is a member, owner or collaborator
id: commenter-check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
commenter_role=$(gh api repos/$GITHUB_REPOSITORY/collaborators/${{ github.event.comment.user.login }}/permission --jq '.permission')
echo "commenter_role=$commenter_role" >> "$GITHUB_OUTPUT"
echo "author_association=${{ github.event.comment.author_association }}" >> "$GITHUB_OUTPUT"
shell: bash
build-deploy-preview:
# Deploy a preview only if
# - the PR is not from a fork,
# - requested by PR comment /deploy-preview, from a repo user or github action bot (user id 41898282)
# FIXME: We need to change the way we filter because somehow some MEMBER in API are seen as CONTRIBUTOR in CI
if: >
(
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.fork != true
) ||
(
github.event.issue.pull_request &&
(
github.event.comment.user.id == '41898282' ||
github.event.comment.user.login == 'gordonwoodhull' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'COLLABORATOR'
) &&
startsWith(github.event.comment.body, '/deploy-preview')
)
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v6
with:
ref: refs/pull/${{ github.event.pull_request.number || github.event.issue.number }}/merge
# On issue_comment events (e.g. /deploy-preview), github.event.pull_request
# is not populated so tj-actions/changed-files can't determine the base commit
# to diff against. Fetch it from the API so we can pass it explicitly.
- name: Get PR base SHA
if: github.event_name != 'pull_request'
id: pr-info
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pr_number=${{ github.event.issue.number }}
base_sha=$(gh -R $GITHUB_REPOSITORY pr view $pr_number --json baseRefOid --jq '.baseRefOid')
echo "base_sha=$base_sha" >> "$GITHUB_OUTPUT"
shell: bash
- name: Get latest pre-release from github
id: github-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo version=$(gh api repos/quarto-dev/quarto-cli/releases | jq -r 'map(select(.prerelease)) | first | .tag_name | sub("^v";"")') >> "$GITHUB_OUTPUT"
- name: Set up Quarto
uses: quarto-dev/quarto-actions/setup@v2
with:
version: ${{ steps.github-release.outputs.version }}
- name: Is it for prerelease website ?
id: prerelease-docs-check
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ "${{ github.event.pull_request.base.ref }}" == "prerelease" ]; then
echo "is_prerelease_docs=true" >> "$GITHUB_OUTPUT"
elif [ -n "${{ github.event.issue.pull_request.url }}" ]; then
# This will trigger when not pull request event, so a PR comment event.
# we need to get the base info from PR number the comment is made in
base_ref=$(gh -R $GITHUB_REPOSITORY pr view ${{ github.event.issue.number }} --json baseRefName --jq '.baseRefName')
if [ "$base_ref" == "prerelease" ]; then
echo "is_prerelease_docs=true" >> "$GITHUB_OUTPUT"
else
echo "is_prerelease_docs=false" >> "$GITHUB_OUTPUT"
fi
else
echo "is_prerelease_docs=false" >> "$GITHUB_OUTPUT"
fi
shell: bash
- name: Render
uses: quarto-dev/quarto-actions/render@v2
env:
QUARTO_PROFILE: ${{ steps.prerelease-docs-check.outputs.is_prerelease_docs == 'true' && 'prerelease-docs,pr-preview' || 'pr-preview' }}
- name: Deploy Preview to Netlify as preview
id: netlify-deploy
uses: nwtgck/actions-netlify@v3
env:
NETLIFY_SITE_ID: 2a3da659-672b-4e5b-8785-e10ebf79a962
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
with:
publish-dir: './_site'
production-deploy: false
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: |
Deploy from GHA: ${{ github.event.pull_request.title || format('manual from PR {0}', github.event.issue.number) }}
alias: deploy-preview-${{ github.event.pull_request.number || github.event.issue.number }}
# these all default to 'true'
enable-pull-request-comment: false
enable-commit-comment: false
enable-commit-status: true
overwrites-pull-request-comment: false
timeout-minutes: 1
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@22103cc46bda19c2b464ffe86db46df6922fd323 # v47.0.5
with:
base_sha: ${{ github.event.pull_request.base.sha || steps.pr-info.outputs.base_sha }}
files: |
# don't consider modifications on file used for includes for now.
license.qmd
docs/**/[^_]*.qmd
docs/**/[^_]*.md
docs/extensions/listings/*.yml
docs/reference/**/*.json
docs/cli/*.json
docs/**/images/*.png
files_ignore: |
**/CLAUDE.md
json: true
escape_json: false
- name: Map changed images to doc pages
id: image-pages
run: |
# Build JSON mapping from image output paths to doc pages using manifest
MANIFEST="_tools/screenshots/manifest.json"
CHANGED='${{ steps.changed-files.outputs.all_changed_files }}'
if [ -f "$MANIFEST" ]; then
# Extract {output: doc.file} pairs, match against changed files
IMAGE_PAGES=$(echo "$CHANGED" | jq -r --slurpfile manifest "$MANIFEST" '
($manifest[0].screenshots | map(select(.doc.file) | {(.output): .doc.file}) | add // {}) as $map |
[.[] | select(test("[.]png$")) |
# Normalize dark variants (foo-dark.png -> foo.png) for manifest lookup
sub("-dark[.]png$"; ".png") as $base |
($map[$base] // empty) | sub("[.]qmd$"; ".html")
] | unique | .[]
')
echo "pages<<EOF" >> "$GITHUB_OUTPUT"
echo "$IMAGE_PAGES" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
fi
shell: bash
- name: Detect draft pages
id: detect-drafts
uses: ./.github/workflows/actions/detect-drafts
with:
changed-files: ${{ steps.changed-files.outputs.all_changed_files }}
- name: Create custom PR comment
if: github.event.pull_request || github.event.issue.pull_request
uses: actions/github-script@v8
env:
DEPLOY_URL: ${{ steps.netlify-deploy.outputs.deploy-url }}
CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }}
IMAGE_PAGES: ${{ steps.image-pages.outputs.pages }}
DRAFT_FILES: ${{ steps.detect-drafts.outputs.files }}
HAS_DRAFTS: ${{ steps.detect-drafts.outputs.found }}
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const prNumber = context.payload.pull_request?.number || context.payload.issue.number;
const deployUrl = process.env.DEPLOY_URL;
const changedFiles = JSON.parse(process.env.CHANGED_FILES || '[]');
const draftFilesList = (process.env.DRAFT_FILES || '').trim().split('\n').filter(Boolean);
const draftSet = new Set(draftFilesList);
let commentBody = `## 📝 Preview Deployment\n\n`;
commentBody += `🔍 Full site preview: [${deployUrl}](${deployUrl})\n\n`;
if (changedFiles.length > 0) {
// Explicit mapping for files that don't follow standard naming conventions
const specialFileMapping = {
'docs/extensions/listings/shortcodes-and-filters.yml': 'docs/extensions/index.html',
'docs/extensions/listings/journal-articles.yml': 'docs/extensions/index.html',
'docs/extensions/listings/custom-formats.yml': 'docs/extensions/index.html',
'docs/extensions/listings/revealjs-formats.yml': 'docs/extensions/index.html',
'docs/extensions/listings/revealjs.yml': 'docs/extensions/index.html',
'docs/cli/cli-info.json': 'docs/reference/index.html',
};
// Projects/ JSON files have many-to-one mapping to .qmd pages
const projectsJsonMapping = {
'book': 'docs/reference/projects/books.html',
'manuscript': 'docs/reference/projects/manuscripts.html',
'project': 'docs/reference/projects/options.html',
'preview': 'docs/reference/projects/options.html',
'serve': 'docs/reference/projects/options.html',
};
const projectsDefault = 'docs/reference/projects/websites.html';
// Resolve each changed file to its preview URL path
// Group files by their target page for deduplication
const pageToFiles = new Map();
changedFiles.forEach(file => {
let fileUrlPath;
if (specialFileMapping[file]) {
fileUrlPath = specialFileMapping[file];
} else if (file.endsWith('.qmd') || file.endsWith('.md')) {
fileUrlPath = file.replace(/\.(qmd|md)$/, '.html');
} else if (file.startsWith('docs/reference/projects/') && file.endsWith('.json')) {
const stem = file.split('/').pop().replace('.json', '');
fileUrlPath = projectsJsonMapping[stem] || projectsDefault;
} else if (file.endsWith('.json')) {
// Generic: formats/, cells/, metadata/ JSON have sibling .qmd files
fileUrlPath = file.replace(/\.json$/, '.html');
}
if (fileUrlPath) {
if (!pageToFiles.has(fileUrlPath)) {
pageToFiles.set(fileUrlPath, []);
}
pageToFiles.get(fileUrlPath).push(file);
}
});
if (pageToFiles.size > 0) {
commentBody += `### 🔄 Modified Documents\n\n`;
for (const [page, files] of pageToFiles) {
const fileUrl = `${deployUrl}/${page}`;
const isDraft = files.some(f => draftSet.has(f));
const draftTag = isDraft ? ' — ⚠️ `draft`' : '';
if (files.length === 1) {
commentBody += `- [${page}](${fileUrl})${draftTag}\n`;
} else {
// Multiple source files map to one page - show page with file summary
const basenames = files.map(f => f.split('/').pop());
const shown = basenames.slice(0, 3).join(', ');
const rest = basenames.length > 3 ? `, +${basenames.length - 3} more` : '';
commentBody += `- [${page}](${fileUrl}) (${shown}${rest})${draftTag}\n`;
}
}
}
}
// Add pages affected by changed screenshots
const imagePages = (process.env.IMAGE_PAGES || '').trim();
if (imagePages) {
const pages = [...new Set(imagePages.split('\n').filter(Boolean))];
if (pages.length > 0) {
commentBody += `\n### 🖼️ Pages with Updated Screenshots\n\n`;
for (const page of pages) {
const fileUrl = `${deployUrl}/${page}`;
commentBody += `- [${page}](${fileUrl})\n`;
}
}
}
// Add draft warning callout (no file list — already tagged inline above)
const hasDrafts = process.env.HAS_DRAFTS === 'true';
if (hasDrafts) {
commentBody += `\n> [!WARNING]\n`;
commentBody += `> This PR contains pages with \`draft: true\`. Remove the draft status before merging if the content is ready to publish.\n`;
}
await github.rest.issues.createComment({
issue_number: prNumber,
owner: context.repo.owner,
repo: context.repo.repo,
body: commentBody
});