Skip to content

Commit a7caa26

Browse files
committed
ci(web-ui): deploy previews to Cloudflare Pages
Closes #77. Adds a path-filtered workflow that builds apps/web-ui/ on every push to a PR touching apps/web-ui/** and on push to main, deploys the static export to a Cloudflare Pages project named cv-builder-web, and posts/updates a preview-URL comment on the PR. One-time setup required before this workflow will succeed: 1. Create the cv-builder-web Cloudflare Pages project (dashboard or `wrangler pages project create cv-builder-web`). 2. Add two repo secrets: CLOUDFLARE_API_TOKEN (Edit Cloudflare Pages template, scoped to cv-builder-web) CLOUDFLARE_ACCOUNT_ID next.config.ts gets output: "export" so `pnpm build` produces an apps/web-ui/out/ directory that Pages can serve directly. The app is already fully client-side, so this is a drop-in. Future PRs must not add server-only features (API routes, server actions, middleware). Verified locally: pnpm install --frozen-lockfile, pnpm build, python3 -m http.server serves the export, biome lint clean, workflow YAML valid, bash syntax of the run: block checks out.
1 parent 7c0fba6 commit a7caa26

4 files changed

Lines changed: 131 additions & 1 deletion

File tree

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
name: Deploy web UI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths:
7+
- "apps/web-ui/**"
8+
# include the workflow itself so edits to this file re-trigger a deploy
9+
- ".github/workflows/deploy-web-ui.yml"
10+
pull_request:
11+
branches: [main]
12+
paths:
13+
- "apps/web-ui/**"
14+
- ".github/workflows/deploy-web-ui.yml"
15+
16+
# Cancel an in-flight deploy for the same ref when a new commit lands.
17+
# Prevents stale previews and saves Cloudflare build minutes.
18+
concurrency:
19+
group: deploy-web-ui-${{ github.ref }}
20+
cancel-in-progress: true
21+
22+
jobs:
23+
deploy:
24+
runs-on: ubuntu-latest
25+
permissions:
26+
contents: read
27+
pull-requests: write # required to post the preview URL comment
28+
deployments: write
29+
steps:
30+
- uses: actions/checkout@v4
31+
32+
- uses: pnpm/action-setup@v4
33+
34+
- uses: actions/setup-node@v4
35+
with:
36+
node-version: 22
37+
cache: pnpm
38+
39+
- name: Install
40+
run: pnpm install --frozen-lockfile
41+
42+
- name: Build (static export → apps/web-ui/out)
43+
working-directory: apps/web-ui
44+
env:
45+
# Plumbed for when the fastify backend lands; harmless local default until then. See issue #77.
46+
NEXT_PUBLIC_API_URL: ${{ vars.NEXT_PUBLIC_API_URL || 'http://localhost:3001' }}
47+
run: pnpm build
48+
49+
- name: Deploy to Cloudflare Pages
50+
id: deploy
51+
uses: cloudflare/wrangler-action@v3
52+
with:
53+
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
54+
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
55+
# --commit-dirty=true is required: the wrangler-action sets CI=1 and would otherwise refuse a deploy with apps/web-ui/out/ as an uncommitted change.
56+
command: pages deploy apps/web-ui/out --project-name=cv-builder-web --commit-dirty=true
57+
58+
- name: Post preview URL on PR
59+
if: github.event_name == 'pull_request'
60+
env:
61+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
62+
PR_NUMBER: ${{ github.event.pull_request.number }}
63+
REPO: ${{ github.repository }}
64+
ALIAS_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }}
65+
COMMENT_MARKER: cv-builder-web-preview-deploy
66+
run: |
67+
set -euo pipefail
68+
69+
BODY=$(cat <<EOF
70+
<!-- ${COMMENT_MARKER} -->
71+
🌐 **Preview deployment:** ${ALIAS_URL}
72+
73+
_Updated automatically on every push to this PR._
74+
EOF
75+
)
76+
77+
AUTH=(-H "Authorization: token ${GH_TOKEN}" -H "Accept: application/vnd.github+json")
78+
JSON=$(jq -nc --arg body "${BODY}" '{body: $body}')
79+
80+
# Find the existing bot comment for this PR (if any) by the marker, so
81+
# we update it instead of posting a duplicate on every push.
82+
EXISTING_ID=$(curl -fsS "${AUTH[@]}" \
83+
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments?per_page=100" \
84+
| jq -r --arg m "${COMMENT_MARKER}" \
85+
'.[] | select(.body | contains($m)) | .id' \
86+
| head -n1)
87+
88+
if [[ "${EXISTING_ID}" =~ ^[0-9]+$ ]]; then
89+
curl -fsS -X PATCH "${AUTH[@]}" \
90+
"https://api.github.com/repos/${REPO}/issues/comments/${EXISTING_ID}" \
91+
-d "${JSON}"
92+
echo "Updated existing comment ${EXISTING_ID}"
93+
else
94+
curl -fsS -X POST "${AUTH[@]}" \
95+
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments" \
96+
-d "${JSON}"
97+
echo "Posted new comment"
98+
fi

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,26 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for the full guide.
126126

127127
---
128128

129+
## Deployment
130+
131+
The web UI (`apps/web-ui/`) is deployed to **Cloudflare Pages**. Every push to a
132+
PR that changes `apps/web-ui/**` triggers the `Deploy web UI` workflow, which
133+
builds a static export and posts a preview URL as a comment on the PR. Pushes
134+
to `main` deploy to the production site.
135+
136+
### Required repo secrets
137+
138+
| Secret | Where to get it |
139+
| --- | --- |
140+
| `CLOUDFLARE_API_TOKEN` | Cloudflare dashboard → My Profile → API Tokens → Create Token → use the "Edit Cloudflare Pages" template, scoped to the account and `cv-builder-web` project |
141+
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare dashboard → Workers & Pages → right sidebar |
142+
143+
The `cv-builder-web` Cloudflare Pages project must exist before the first
144+
deploy — create it once via the dashboard or
145+
`wrangler pages project create cv-builder-web`.
146+
147+
---
148+
129149
## Supported Role Archetypes
130150

131151
Currently built-in:

apps/web-ui/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,14 @@ Then open [http://localhost:3000](http://localhost:3000).
2929
- `src/app/layout.tsx` - app shell and metadata
3030
- `src/app/page.tsx` - homepage
3131
- `src/app/globals.css` - global styles
32+
33+
## Previews
34+
35+
PRs that touch this package automatically get a Cloudflare Pages preview link,
36+
posted as a comment on the PR by the `Deploy web UI` workflow. Pushing new
37+
commits updates the same comment instead of posting duplicates. Merging to
38+
`main` redeploys the production site at `https://cv-builder-web.pages.dev`.
39+
40+
The build is a static export (`output: "export"` in `next.config.ts`) — the
41+
app must remain free of server-only features (API routes, server actions,
42+
middleware, etc.).

apps/web-ui/next.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import type { NextConfig } from "next";
22

33
const nextConfig: NextConfig = {
4-
/* config options here */
4+
// Static export for Cloudflare Pages; the app must stay server-feature-free. See issue #77.
5+
output: "export",
56
reactCompiler: true,
67
};
78

0 commit comments

Comments
 (0)