-
Notifications
You must be signed in to change notification settings - Fork 13
ci(web-ui): deploy previews to Cloudflare Pages #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,98 @@ | ||||||||||||||
| name: Deploy web UI | ||||||||||||||
|
|
||||||||||||||
| on: | ||||||||||||||
| push: | ||||||||||||||
| branches: [main] | ||||||||||||||
| paths: | ||||||||||||||
| - "apps/web-ui/**" | ||||||||||||||
| # include the workflow itself so edits to this file re-trigger a deploy | ||||||||||||||
| - ".github/workflows/deploy-web-ui.yml" | ||||||||||||||
| pull_request: | ||||||||||||||
| branches: [main] | ||||||||||||||
| paths: | ||||||||||||||
| - "apps/web-ui/**" | ||||||||||||||
| - ".github/workflows/deploy-web-ui.yml" | ||||||||||||||
|
|
||||||||||||||
| # Cancel an in-flight deploy for the same ref when a new commit lands. | ||||||||||||||
| # Prevents stale previews and saves Cloudflare build minutes. | ||||||||||||||
| concurrency: | ||||||||||||||
| group: deploy-web-ui-${{ github.ref }} | ||||||||||||||
| cancel-in-progress: true | ||||||||||||||
|
|
||||||||||||||
| jobs: | ||||||||||||||
| deploy: | ||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||
| permissions: | ||||||||||||||
| contents: read | ||||||||||||||
| pull-requests: write # required to post the preview URL comment | ||||||||||||||
| deployments: write | ||||||||||||||
| steps: | ||||||||||||||
| - uses: actions/checkout@v4 | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, find and examine the workflow file
fd "deploy-web-ui.yml" -t fRepository: TechImmigrants/cv-builder Length of output: 51 🏁 Script executed: # List all files in .github/workflows directory
ls -la .github/workflows/ 2>/dev/null || echo "Directory not found"Repository: TechImmigrants/cv-builder Length of output: 303 🏁 Script executed: # Read the deploy-web-ui.yml file to examine line 30 and surrounding context
cat -n .github/workflows/deploy-web-ui.ymlRepository: TechImmigrants/cv-builder Length of output: 4257 Disable persisted checkout credentials for untrusted PR code paths. This workflow runs on Suggested patch - uses: actions/checkout@v4
+ with:
+ persist-credentials: false🧰 Tools🪛 zizmor (1.25.2)[warning] 30-30: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false (artipacked) [error] 30-30: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy) (unpinned-uses) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||
|
|
||||||||||||||
| - uses: pnpm/action-setup@v4 | ||||||||||||||
|
|
||||||||||||||
| - uses: actions/setup-node@v4 | ||||||||||||||
| with: | ||||||||||||||
| node-version: 22 | ||||||||||||||
| cache: pnpm | ||||||||||||||
|
|
||||||||||||||
| - name: Install | ||||||||||||||
| run: pnpm install --frozen-lockfile | ||||||||||||||
|
|
||||||||||||||
| - name: Build (static export → apps/web-ui/out) | ||||||||||||||
| working-directory: apps/web-ui | ||||||||||||||
| env: | ||||||||||||||
| # Plumbed for when the fastify backend lands; harmless local default until then. See issue #77. | ||||||||||||||
| NEXT_PUBLIC_API_URL: ${{ vars.NEXT_PUBLIC_API_URL || 'http://localhost:3001' }} | ||||||||||||||
| run: pnpm build | ||||||||||||||
|
|
||||||||||||||
| - name: Deploy to Cloudflare Pages | ||||||||||||||
| id: deploy | ||||||||||||||
| uses: cloudflare/wrangler-action@v3 | ||||||||||||||
| with: | ||||||||||||||
| apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} | ||||||||||||||
| accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | ||||||||||||||
| # --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. | ||||||||||||||
| command: pages deploy apps/web-ui/out --project-name=cv-builder-web --commit-dirty=true | ||||||||||||||
|
|
||||||||||||||
| - name: Post preview URL on PR | ||||||||||||||
| if: github.event_name == 'pull_request' | ||||||||||||||
| env: | ||||||||||||||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||
| PR_NUMBER: ${{ github.event.pull_request.number }} | ||||||||||||||
| REPO: ${{ github.repository }} | ||||||||||||||
| ALIAS_URL: ${{ steps.deploy.outputs.pages-deployment-alias-url }} | ||||||||||||||
| COMMENT_MARKER: cv-builder-web-preview-deploy | ||||||||||||||
| run: | | ||||||||||||||
| set -euo pipefail | ||||||||||||||
|
|
||||||||||||||
| BODY=$(cat <<EOF | ||||||||||||||
| <!-- ${COMMENT_MARKER} --> | ||||||||||||||
| 🌐 **Preview deployment:** ${ALIAS_URL} | ||||||||||||||
|
|
||||||||||||||
| _Updated automatically on every push to this PR._ | ||||||||||||||
| EOF | ||||||||||||||
| ) | ||||||||||||||
|
|
||||||||||||||
| AUTH=(-H "Authorization: token ${GH_TOKEN}" -H "Accept: application/vnd.github+json") | ||||||||||||||
| JSON=$(jq -nc --arg body "${BODY}" '{body: $body}') | ||||||||||||||
|
|
||||||||||||||
| # Find the existing bot comment for this PR (if any) by the marker, so | ||||||||||||||
| # we update it instead of posting a duplicate on every push. | ||||||||||||||
| EXISTING_ID=$(curl -fsS "${AUTH[@]}" \ | ||||||||||||||
| "https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments?per_page=100" \ | ||||||||||||||
| | jq -r --arg m "${COMMENT_MARKER}" \ | ||||||||||||||
| '.[] | select(.body | contains($m)) | .id' \ | ||||||||||||||
| | head -n1) | ||||||||||||||
|
Comment on lines
+84
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n .github/workflows/deploy-web-ui.yml | head -100Repository: TechImmigrants/cv-builder Length of output: 4257 🏁 Script executed: # Check if there are any other instances of COMMENT_MARKER in the codebase
rg "COMMENT_MARKER|cv-builder-web-preview-deploy" --type yaml --type sh --type bashRepository: TechImmigrants/cv-builder Length of output: 98 🏁 Script executed: rg "COMMENT_MARKER|cv-builder-web-preview-deploy"Repository: TechImmigrants/cv-builder Length of output: 51 Filter preview comment updates to bot-authored comments. The jq filter at lines 84-86 selects any comment containing the marker string, which could match a user-authored comment if they include the marker. This would cause the bot to update that user's comment instead of posting its own. Filter by Suggested change EXISTING_ID=$(curl -fsS "${AUTH[@]}" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments?per_page=100" \
| jq -r --arg m "${COMMENT_MARKER}" \
- '.[] | select(.body | contains($m)) | .id' \
+ '.[] | select((.user.login == "github-actions[bot]") and (.body | contains($m))) | .id' \
| head -n1)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| if [[ "${EXISTING_ID}" =~ ^[0-9]+$ ]]; then | ||||||||||||||
| curl -fsS -X PATCH "${AUTH[@]}" \ | ||||||||||||||
| "https://api.github.com/repos/${REPO}/issues/comments/${EXISTING_ID}" \ | ||||||||||||||
| -d "${JSON}" | ||||||||||||||
| echo "Updated existing comment ${EXISTING_ID}" | ||||||||||||||
| else | ||||||||||||||
| curl -fsS -X POST "${AUTH[@]}" \ | ||||||||||||||
| "https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments" \ | ||||||||||||||
| -d "${JSON}" | ||||||||||||||
| echo "Posted new comment" | ||||||||||||||
| fi | ||||||||||||||
|
Comment on lines
+58
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Make PR-comment API calls non-critical or retryable to avoid false failed deploys. The deployment can succeed but the job can still fail on transient GitHub API issues in this step due to Suggested hardening - name: Post preview URL on PR
if: github.event_name == 'pull_request'
+ continue-on-error: true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@
- EXISTING_ID=$(curl -fsS "${AUTH[@]}" \
+ EXISTING_ID=$(curl -fsS --retry 3 --retry-all-errors --connect-timeout 10 "${AUTH[@]}" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments?per_page=100" \
| jq -r --arg m "${COMMENT_MARKER}" \
'.[] | select(.body | contains($m)) | .id' \
| head -n1)
@@
- curl -fsS -X PATCH "${AUTH[@]}" \
+ curl -fsS --retry 3 --retry-all-errors --connect-timeout 10 -X PATCH "${AUTH[@]}" \
"https://api.github.com/repos/${REPO}/issues/comments/${EXISTING_ID}" \
-d "${JSON}"
@@
- curl -fsS -X POST "${AUTH[@]}" \
+ curl -fsS --retry 3 --retry-all-errors --connect-timeout 10 -X POST "${AUTH[@]}" \
"https://api.github.com/repos/${REPO}/issues/${PR_NUMBER}/comments" \
-d "${JSON}"🤖 Prompt for AI Agents |
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: TechImmigrants/cv-builder
Length of output: 4257
🌐 Web query:
GitHub Actions secrets availability pull_request forked repositories💡 Result:
For GitHub Actions, secrets are NOT available to workflow runs triggered by the pull_request event when the pull request comes from a fork (i.e., “pull request from a forked repository to the base repository”). GitHub withholds access to other secrets for untrusted fork code, leaving the workflow with a read-only GITHUB_TOKEN instead. [1][2] Specifically: - pull_request (from a fork): Actions secrets are not passed to workflows triggered by a pull request from a fork. [3] - pull_request_target (used to get secrets for fork PRs): workflows triggered by pull_request_target run with elevated trust in the base repository context, receiving the base repository’s GITHUB_TOKEN and access to repository/organization secrets (but this trigger must be handled carefully because it can run dangerous attacker-controlled behavior if you check out or execute code from the fork). [1][2] Practical implication: If your goal is “secrets should be accessible for a PR opened from a fork,” you generally cannot do that with pull_request; you must use pull_request_target (or another trusted path), and then follow GitHub’s security guidance to avoid checking out/running untrusted fork code with elevated privileges. [1][2] Note: GitHub also redacts secret values in workflow logs, but that doesn’t make secrets available to workflows that don’t have permission to access them in the first place. [3][4]
Citations:
Guard deploy for forked PRs (missing secrets in
pull_requestcontext).Forked PRs triggering the
pull_requestevent do not have access to repository secrets. Lines 53–54 usesecrets.CLOUDFLARE_API_TOKENandsecrets.CLOUDFLARE_ACCOUNT_ID, causing the deploy job to fail on external contributions. Add a guard to allow same-repo PRs while skipping forks.Suggested change
jobs: deploy: + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository runs-on: ubuntu-latest🤖 Prompt for AI Agents