Skip to content

Commit 59ca08d

Browse files
committed
Add multiple author support and preview workflow
1 parent 4baba06 commit 59ca08d

5 files changed

Lines changed: 268 additions & 4 deletions

File tree

.github/workflows/pr-preview.yml

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
name: PR Preview
2+
3+
on:
4+
pull_request:
5+
types: [opened, synchronize, reopened, ready_for_review]
6+
7+
permissions:
8+
contents: read
9+
issues: write
10+
pull-requests: write
11+
12+
concurrency:
13+
group: pr-preview-${{ github.event.pull_request.number }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
build-preview:
18+
runs-on: ubuntu-latest
19+
outputs:
20+
artifact_name: ${{ steps.meta.outputs.artifact_name }}
21+
screenshot_name: ${{ steps.meta.outputs.screenshot_name }}
22+
post_path: ${{ steps.changed_post.outputs.post_path }}
23+
post_url: ${{ steps.changed_post.outputs.post_url }}
24+
steps:
25+
- name: Set artifact name
26+
id: meta
27+
run: |
28+
echo "artifact_name=site-preview-pr-${{ github.event.number }}" >> "$GITHUB_OUTPUT"
29+
echo "screenshot_name=site-screenshots-pr-${{ github.event.number }}" >> "$GITHUB_OUTPUT"
30+
31+
- name: Checkout
32+
uses: actions/checkout@v4
33+
with:
34+
fetch-depth: 0
35+
36+
- name: Set up Ruby
37+
uses: ruby/setup-ruby@v1
38+
with:
39+
bundler-cache: true
40+
41+
- name: Build site
42+
run: bundle exec jekyll build
43+
44+
- name: Detect changed post
45+
id: changed_post
46+
env:
47+
BASE_SHA: ${{ github.event.pull_request.base.sha }}
48+
HEAD_SHA: ${{ github.sha }}
49+
run: |
50+
mapfile -t posts < <(git diff --name-only "$BASE_SHA" "$HEAD_SHA" -- '_posts/*.md')
51+
if [[ ${#posts[@]} -eq 0 ]]; then
52+
echo "post_path=" >> "$GITHUB_OUTPUT"
53+
echo "post_url=/" >> "$GITHUB_OUTPUT"
54+
exit 0
55+
fi
56+
57+
post="${posts[0]}"
58+
slug="$(basename "$post" .md)"
59+
slug="${slug#????-??-??-}"
60+
echo "post_path=$post" >> "$GITHUB_OUTPUT"
61+
echo "post_url=/$slug/" >> "$GITHUB_OUTPUT"
62+
63+
- name: Upload site artifact
64+
uses: actions/upload-artifact@v4
65+
with:
66+
name: ${{ steps.meta.outputs.artifact_name }}
67+
path: _site
68+
retention-days: 7
69+
70+
- name: Install Playwright Chromium
71+
run: npx -y playwright@1.53.0 install --with-deps chromium
72+
73+
- name: Capture preview screenshot
74+
env:
75+
POST_URL: ${{ steps.changed_post.outputs.post_url }}
76+
run: |
77+
python3 -m http.server --directory _site 4173 > /tmp/pr-preview-server.log 2>&1 &
78+
server_pid=$!
79+
trap 'kill "$server_pid"' EXIT
80+
81+
for i in {1..20}; do
82+
if curl -fsS "http://127.0.0.1:4173${POST_URL}" >/dev/null; then
83+
break
84+
fi
85+
sleep 0.5
86+
done
87+
88+
npx -y playwright@1.53.0 screenshot --browser=chromium --full-page "http://127.0.0.1:4173${POST_URL}" preview-long.png
89+
90+
- name: Upload screenshot artifact
91+
uses: actions/upload-artifact@v4
92+
with:
93+
name: ${{ steps.meta.outputs.screenshot_name }}
94+
path: preview-long.png
95+
retention-days: 7
96+
97+
deploy-cloudflare-preview:
98+
runs-on: ubuntu-latest
99+
needs: build-preview
100+
if: ${{ !github.event.pull_request.head.repo.fork && vars.CLOUDFLARE_PAGES_PROJECT != '' && vars.CLOUDFLARE_ACCOUNT_ID != '' && secrets.CLOUDFLARE_API_TOKEN != '' }}
101+
permissions:
102+
contents: read
103+
deployments: write
104+
outputs:
105+
preview_url: ${{ steps.cf.outputs.alias }}
106+
steps:
107+
- name: Checkout
108+
uses: actions/checkout@v4
109+
110+
- name: Set up Ruby
111+
uses: ruby/setup-ruby@v1
112+
with:
113+
bundler-cache: true
114+
115+
- name: Build site
116+
run: bundle exec jekyll build
117+
118+
- name: Publish to Cloudflare Pages
119+
id: cf
120+
uses: cloudflare/pages-action@v1
121+
with:
122+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
123+
accountId: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
124+
projectName: ${{ vars.CLOUDFLARE_PAGES_PROJECT }}
125+
directory: _site
126+
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
127+
branch: pr-${{ github.event.number }}
128+
wranglerVersion: '3'
129+
130+
comment-preview:
131+
runs-on: ubuntu-latest
132+
needs: [build-preview, deploy-cloudflare-preview]
133+
if: ${{ always() && !github.event.pull_request.head.repo.fork && needs.build-preview.result == 'success' && (needs.deploy-cloudflare-preview.result == 'success' || needs.deploy-cloudflare-preview.result == 'skipped') }}
134+
steps:
135+
- name: Add or update PR preview comment
136+
uses: actions/github-script@v7
137+
env:
138+
RUN_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
139+
ARTIFACT_NAME: ${{ needs.build-preview.outputs.artifact_name }}
140+
SCREENSHOT_NAME: ${{ needs.build-preview.outputs.screenshot_name }}
141+
POST_PATH: ${{ needs.build-preview.outputs.post_path }}
142+
POST_URL: ${{ needs.build-preview.outputs.post_url }}
143+
CLOUDFLARE_URL: ${{ needs.deploy-cloudflare-preview.outputs.preview_url }}
144+
CLOUDFLARE_ENABLED: ${{ vars.CLOUDFLARE_PAGES_PROJECT != '' && vars.CLOUDFLARE_ACCOUNT_ID != '' && secrets.CLOUDFLARE_API_TOKEN != '' }}
145+
CLOUDFLARE_RESULT: ${{ needs.deploy-cloudflare-preview.result }}
146+
with:
147+
script: |
148+
const marker = "<!-- pr-preview-comment -->";
149+
const postLine = process.env.POST_PATH
150+
? `- Changed post detected: \`${process.env.POST_PATH}\`\n- Suggested file to open after extracting: \`_site${process.env.POST_URL}index.html\``
151+
: "- No post markdown file changed in this PR; preview starts at `_site/index.html`.";
152+
153+
const cloudflareLine = process.env.CLOUDFLARE_ENABLED === "true"
154+
? (process.env.CLOUDFLARE_RESULT === "success"
155+
? (process.env.CLOUDFLARE_URL
156+
? `- Hosted preview (Cloudflare Pages): [Open preview](${process.env.CLOUDFLARE_URL})`
157+
: "- Hosted preview (Cloudflare Pages): deployed, but URL output was empty. Check deployment details.")
158+
: "- Hosted preview (Cloudflare Pages): deployment did not succeed; check workflow logs.")
159+
: "- Hosted preview (Cloudflare Pages): not configured (set repository vars/secrets; see README).";
160+
161+
const body = `${marker}
162+
## PR Preview Ready
163+
164+
A static preview was built for this PR.
165+
166+
- Download artifact: **${process.env.ARTIFACT_NAME}** from [this workflow run](${process.env.RUN_URL})
167+
- Download long screenshot artifact: **${process.env.SCREENSHOT_NAME}** from [this workflow run](${process.env.RUN_URL})
168+
${postLine}
169+
${cloudflareLine}
170+
- Quick local preview:
171+
- Extract artifact zip
172+
- Run: \`python3 -m http.server --directory _site 4173\`
173+
- Open: \`http://localhost:4173${process.env.POST_URL}\`
174+
`;
175+
176+
const { owner, repo } = context.repo;
177+
const issue_number = context.issue.number;
178+
const comments = await github.rest.issues.listComments({
179+
owner,
180+
repo,
181+
issue_number,
182+
per_page: 100,
183+
});
184+
185+
const existing = comments.data.find((comment) => comment.body && comment.body.includes(marker));
186+
if (existing) {
187+
await github.rest.issues.updateComment({
188+
owner,
189+
repo,
190+
comment_id: existing.id,
191+
body,
192+
});
193+
} else {
194+
await github.rest.issues.createComment({
195+
owner,
196+
repo,
197+
issue_number,
198+
body,
199+
});
200+
}

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"chat.tools.terminal.autoApprove": {
3+
"bundle": true
4+
}
5+
}

README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,59 @@ The site is generated by Jekyll, based on the [Minimal Mistakes](https://mmistak
88

99
To submit a blog entry, create a `md` file in the `_posts` directory. Look for examples of posts there for formatting help.
1010

11+
Author bios in the sidebar are enabled for posts by default. For a single author, use:
12+
13+
```yaml
14+
author: Oscar Levin
15+
```
16+
17+
For multiple authors, use:
18+
19+
```yaml
20+
authors:
21+
- Oscar Levin
22+
- Steven Clontz
23+
```
24+
25+
Each name must match an entry in `_data/authors.yml`.
26+
1127
## Building the site
1228

1329
When the site is pushed to github, it will automatically rebuild. To preview locally, run `bundle exec jekyll serve`. Note, some changes to `_config.yml` require stopping and starting this, while other changes to the site should be automatically incorporated.
1430

1531
See the [Minimal Mistakes Documentation](https://mmistakes.github.io/minimal-mistakes/docs/quick-start-guide/) for more.
32+
33+
## PR previews
34+
35+
This repo now includes a PR preview workflow at `.github/workflows/pr-preview.yml`.
36+
37+
For each pull request update, it:
38+
39+
1. Builds the site with Jekyll.
40+
2. Uploads the full `_site` output as a workflow artifact.
41+
3. Captures a full-page screenshot artifact for quick visual review.
42+
4. Optionally deploys to Cloudflare Pages for a hosted preview URL.
43+
5. Posts or updates a sticky PR comment with links and instructions.
44+
45+
This gives reviewers a consistent way to preview rendered pages without running Jekyll locally.
46+
47+
### Cloudflare Pages setup (optional)
48+
49+
If you want hosted preview links directly in PRs, set up Cloudflare Pages once and add repo settings.
50+
51+
1. In Cloudflare, create a Pages project (or use an existing one).
52+
- Project name should match `CLOUDFLARE_PAGES_PROJECT` exactly.
53+
2. In GitHub repo settings, add **Repository variables**:
54+
- `CLOUDFLARE_ACCOUNT_ID`: your Cloudflare account ID
55+
- `CLOUDFLARE_PAGES_PROJECT`: the Cloudflare Pages project name
56+
3. In GitHub repo settings, add **Repository secret**:
57+
- `CLOUDFLARE_API_TOKEN`: Cloudflare API token with `Account -> Cloudflare Pages -> Edit`
58+
4. In Cloudflare, create the API token at **My Profile -> API Tokens -> Create Token -> Custom token** and scope it to the specific account.
59+
5. Open or update a PR. The workflow will deploy `_site` and include preview status in the PR comment.
60+
6. Open the PR **Deployments** panel to click through to the hosted Cloudflare preview URL.
61+
62+
Notes:
63+
64+
- Cloudflare deploy is skipped automatically if these vars/secrets are missing.
65+
- Cloudflare deploy is also skipped for forked PRs for security.
66+
- Artifact and screenshot previews still run even when Cloudflare is skipped.

_includes/author-profile.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
{% assign author = page.author | default: page.authors[0] | default: site.author %}
2-
{% assign author = site.data.authors[author] | default: author %}
1+
{% assign author_key = include.author_key | default: page.author | default: page.authors[0] %}
2+
{% assign author = site.data.authors[author_key] | default: author_key | default: site.author %}
33

44
<div itemscope itemtype="https://schema.org/Person" class="h-card">
55

_includes/sidebar.html

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
{% if page.author_profile or layout.author_profile or page.sidebar %}
2-
<div class="sidebar sticky">
3-
{% if page.author_profile or layout.author_profile %}{% include author-profile.html %}{% endif %}
2+
<div class="sidebar">
3+
{% if page.author_profile or layout.author_profile %}
4+
{% if page.authors %}
5+
{% for author_key in page.authors %}
6+
{% include author-profile.html author_key=author_key %}
7+
{% endfor %}
8+
{% else %}
9+
{% include author-profile.html %}
10+
{% endif %}
11+
{% endif %}
412
{% if page.sidebar %}
513
{% for s in page.sidebar %}
614
{% if s.image %}

0 commit comments

Comments
 (0)