11# Build half of the PR-preview family, delegated to the reusable workflow in
22# Morrison-Lab/gha. Renders the Quarto site in the (possibly fork) PR context and
33# uploads it + PR metadata as an artifact; the deploy half (preview-deploy.yml)
4- # publishes it to gh-pages. This job is read-only (contents: read) — it must
5- # never write to the base repo.
4+ # publishes it to gh-pages. Every job here is read-only — the workflow-level
5+ # `permissions:` block below grants `pull-requests: read` (needed by the
6+ # `filter` job's paths-filter API call) and the render job re-narrows itself
7+ # to `contents: read`. Nothing here may write to the base repo.
68#
79# IMPORTANT: keep this workflow's `name:` in sync with the `workflows:` list in
810# preview-deploy.yml — that is how the deploy half finds this run.
@@ -21,42 +23,83 @@ name: Quarto Preview Build
2123on :
2224 pull_request :
2325 types : [opened, reopened, synchronize, labeled, closed]
24- paths :
25- - ' man/**'
26- - ' pkgdown/**'
27- - ' vignettes/**'
28- - ' _extensions/**'
29- - ' _quarto*.yml'
30- - ' .github/workflows/preview.yml'
31- - ' *.qmd'
32- - ' chapters/**/*.qmd'
33- - ' _subfiles/**'
34- - ' *.scss'
35- - ' latex-macros'
36- - ' references.bib'
37- # Dependency changes are the changes most likely to alter rendered
38- # output, yet without these two entries a version-only PR got no
39- # render at all: this is the only workflow that runs quarto render,
40- # and it has no workflow_dispatch. The first render against new
41- # package versions therefore happened post-merge on main, where a
42- # failure breaks the published site instead of a PR check (rme#1075).
43- #
44- # Caveat worth knowing when reading a green build on such a PR: the
45- # freeze cache does NOT isolate dependency changes. gha's preview
46- # composite restores _freeze through a restore-keys chain whose last
47- # entry, `quarto-freezer-<os>-`, omits the renv.lock hash entirely,
48- # so a version-only PR restores a freeze computed under the OLD
49- # versions and may serve those cached results instead of re-running
50- # anything. Add the `clear freezer` label to a dependency PR when the
51- # point is to exercise the new versions -- the composite skips the
52- # restore when it is present.
53- - ' renv.lock'
54- - ' DESCRIPTION'
26+
27+ permissions :
28+ pull-requests : read
29+ contents : read
5530
5631jobs :
57- build :
32+ filter :
33+ runs-on : ubuntu-latest
34+ outputs :
35+ render : ${{ steps.filter.outputs.render }}
36+ steps :
37+ - uses : dorny/paths-filter@v3
38+ id : filter
39+ with :
40+ filters : |
41+ render:
42+ - 'man/**'
43+ - 'pkgdown/**'
44+ - 'vignettes/**'
45+ - '_extensions/**'
46+ - '_quarto*.yml'
47+ - '.github/workflows/preview.yml'
48+ - '*.qmd'
49+ - 'chapters/**/*.qmd'
50+ - '_subfiles/**'
51+ - '*.scss'
52+ - 'latex-macros'
53+ - 'references.bib'
54+ # Dependency changes are the changes most likely to alter rendered
55+ # output, yet without these two entries a version-only PR got no
56+ # render at all: this is the only workflow that runs quarto render,
57+ # and it has no workflow_dispatch. The first render against new
58+ # package versions therefore happened post-merge on main, where a
59+ # failure breaks the published site instead of a PR check (rme#1075).
60+ #
61+ # Caveat worth knowing when reading a green build on such a PR: the
62+ # freeze cache does NOT isolate dependency changes. gha's preview
63+ # composite restores _freeze through a restore-keys chain whose last
64+ # entry, `quarto-freezer-<os>-`, omits the renv.lock hash entirely,
65+ # so a version-only PR restores a freeze computed under the OLD
66+ # versions and may serve those cached results instead of re-running
67+ # anything. Add the `clear freezer` label to a dependency PR when the
68+ # point is to exercise the new versions -- the composite skips the
69+ # restore when it is present.
70+ - 'renv.lock'
71+ - 'DESCRIPTION'
72+ # The reusable workflow installs this package before rendering,
73+ # so a broken R source breaks the render even though no .qmd
74+ # calls into R/ today. Under the old trigger-level `paths:`
75+ # filter an R-only PR got no render at all and stalled, which
76+ # was at least visible; routing it to `bypass` instead would
77+ # report a green `build / build` over an unbuilt package.
78+ - 'R/**'
79+ - 'NAMESPACE'
80+
81+ render :
82+ name : build
83+ needs : filter
84+ # Fail toward doing the real render. If the `filter` job itself fails or
85+ # errors (a dorny/paths-filter bug, a transient API error), its `render`
86+ # output is empty rather than 'false', so this job still runs: `build /
87+ # build` is reported on every PR regardless of paths touched (rme#1108),
88+ # and it is reported by the job that actually verifies the render rather
89+ # than by the no-op. `!cancelled()` rather than `always()` so a cancelled
90+ # run does not start a full render.
91+ if : ${{ !cancelled() && needs.filter.outputs.render != 'false' }}
92+ # Re-narrow to the scope this job had before `pull-requests: read` was
93+ # added at workflow level for `filter`: the render must not inherit it.
5894 permissions :
5995 contents : read
6096 uses : Morrison-Lab/gha/.github/workflows/preview.yml@v2
6197 with :
6298 r-version : ' 4.6.0'
99+
100+ bypass :
101+ name : build
102+ needs : filter
103+ if : needs.filter.outputs.render == 'false'
104+ # zizmor suggests $/... for self-repository calls, but we use explicit paths for consistency with other repos
105+ uses : ./.github/workflows/_preview-bypass-reusable.yml # zizmor: ignore[self-repository]
0 commit comments