Skip to content

docs(newsletter): add August 2026 edition #291

docs(newsletter): add August 2026 edition

docs(newsletter): add August 2026 edition #291

Workflow file for this run

name: PR Preview
on:
pull_request:
types: [opened, synchronize, reopened]
concurrency:
group: "preview-${{ github.head_ref }}"
cancel-in-progress: true
jobs:
deploy-preview:
runs-on: ubuntu-latest
# Secrets (and write access to gh-pages) are unavailable for forked PRs, so only
# deploy previews for branches pushed to this repository.
if: github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: write
pull-requests: write
issues: write
deployments: read
steps:
- name: Mark existing preview comment as updating
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request.number;
const sha = context.payload.pull_request.head.sha;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
});
const existing = comments.find(
c => c.user.login === 'github-actions[bot]' && c.body.includes('PR Preview Deployed')
);
if (existing) {
// Strip any prior "(deploying ...)" marker rather than stacking, in case
// a previous run was cancelled (via concurrency) before it could clean up.
const body = existing.body
.replace(/ _\(deploying .+?\)_$/, '')
.trimEnd();
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: body + ` _(deploying ${sha}...)_`,
});
}
- name: Checkout code
uses: actions/checkout@v4
with:
# The deploy below authenticates with an explicit token URL, so the
# checkout credential is unused and best not persisted before pnpm install --frozen-lockfile.
persist-credentials: false
- name: Setup pnpm
uses: pnpm/action-setup@v6
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build site (preview base URL)
run: pnpm run build
env:
BASE_URL: /previews/pr-${{ github.event.pull_request.number }}/
REPO_GITHUB_API_TOKEN: ${{ secrets.REPO_GITHUB_API_TOKEN }}
- name: Deploy to gh-pages branch (preview subdirectory)
id: deploy
run: |
set -euo pipefail
repo_url="https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
# gh-pages commits in its own temporary clone, so set the identity globally
# rather than via --user (whose parser rejects the github-actions[bot]
# brackets). Retry on a non-fast-forward rejection in case a concurrent
# gh-pages write (another preview, the cleanup job, or the production deploy)
# lands first; each run re-clones the branch, so clear the cache between attempts.
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
pushed=false
for attempt in 1 2 3; do
rm -rf node_modules/.cache/gh-pages
if pnpm exec gh-pages \
--dist dist/public \
--dest previews/pr-${{ github.event.pull_request.number }} \
--branch gh-pages \
--nojekyll \
--add \
--message "preview: pr-${{ github.event.pull_request.number }} @ ${{ github.sha }}" \
--repo "$repo_url"; then
pushed=true
break
fi
echo "gh-pages push failed (attempt ${attempt}); retrying."
sleep $((attempt * 3))
done
$pushed || { echo "Failed to deploy preview after 3 attempts."; exit 1; }
# gh-pages nests its clone under find-cache-dir's path, not directly at
# node_modules/.cache/gh-pages, so read the pushed tip back from the
# remote rather than relying on that cache layout.
echo "sha=$(git ls-remote "$repo_url" gh-pages | cut -f1)" >> "$GITHUB_OUTPUT"
- name: Wait for GitHub Pages deployment
run: |
set -uo pipefail
sha="${{ steps.deploy.outputs.sha }}"
api="https://api.github.com/repos/${{ github.repository }}"
auth=(-H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" -H "Accept: application/vnd.github+json")
gh_api() { curl -s --max-time 15 "${auth[@]}" "$@"; }
id=""
for attempt in $(seq 1 30); do
if [ -z "$id" ]; then
# Query by exact commit SHA; the legacy /pages/builds/latest endpoint
# isn't a reliable per-commit signal.
id=$(gh_api "${api}/deployments?sha=${sha}&environment=github-pages" | jq -r '.[0].id // empty')
if [ -z "$id" ]; then
echo "No GitHub Pages deployment recorded yet for ${sha} (attempt ${attempt}/30)..."
sleep 10
continue
fi
fi
state=$(gh_api "${api}/deployments/${id}/statuses" | jq -r '.[0].state // empty')
if [ "$state" = "success" ]; then
echo "GitHub Pages deployment for ${sha} succeeded."
exit 0
fi
if [ "$state" = "failure" ] || [ "$state" = "error" ]; then
echo "GitHub Pages deployment for ${sha} failed (state: ${state})."
exit 1
fi
echo "GitHub Pages deployment for ${sha} is ${state:-pending} (attempt ${attempt}/30)..."
sleep 10
done
echo "GitHub Pages deployment did not confirm within 5 minutes; posting the link anyway."
- name: Post preview URL to PR
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request.number;
const sha = context.payload.pull_request.head.sha.slice(0, 7);
const url = `https://markojs.com/previews/pr-${pr}/`;
// Find and update an existing bot comment, or create a new one.
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
});
const existing = comments.find(
c => c.user.login === 'github-actions[bot]' && c.body.includes('PR Preview Deployed')
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: existing.body
.replace('### PR Preview Deployed _(removed)_', '### PR Preview Deployed')
.replace(/ _\(deploying .+?\)_$/, '')
.replace(/###### commit .+$/, `###### commit ${sha}`),
});
} else {
const body = [
`### PR Preview Deployed`,
``,
`Your changes are live at [**markojs.com/previews/pr-${pr}**](${url}).`,
``,
`###### commit ${sha}`,
].join('\n');
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr,
body,
});
}