-
Notifications
You must be signed in to change notification settings - Fork 131
ci: deploy fork-PR previews via label-gated workflow_run #965
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,104 @@ | ||
| name: Deploy Preview | ||
|
|
||
| on: | ||
| workflow_run: | ||
| workflows: ['Preview Site'] | ||
| types: [completed] | ||
|
|
||
| concurrency: | ||
| group: deploy-preview-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
| actions: read | ||
| pull-requests: write | ||
|
Contributor
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. nitpick: (non-blocking) |
||
|
|
||
| jobs: | ||
| deploy: | ||
| if: > | ||
| github.event.workflow_run.event == 'pull_request' && | ||
| github.event.workflow_run.conclusion == 'success' | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Download prebuilt output | ||
| uses: actions/download-artifact@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. 🔒 Security & Privacy | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
fd -e yml -e yaml .github/workflows -x rg -n 'uses:\s+[^#]+@(?:v[0-9]+|main|master|latest)\b' {}Repository: Comfy-Org/workflow_templates Length of output: 540 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== files ==\n'
fd --type f --hidden --glob '.github/workflows/*' . | sed 's#^\./##' | sort
printf '\n== deploy-preview.yml ==\n'
nl -ba .github/workflows/deploy-preview.yml | sed -n '1,220p'
printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re
for path in sorted(Path('.github/workflows').rglob('*.[yY][aA][mM][lL]')) + sorted(Path('.github/workflows').rglob('*.yml')):
text = path.read_text()
for i, line in enumerate(text.splitlines(), 1):
m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
if m:
ref = m.group(2)
if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
print(f"{path}:{i}:{m.group(1)}@{ref}")
PYRepository: Comfy-Org/workflow_templates Length of output: 245 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== files ==\n'
fd --type f --hidden --glob '.github/workflows/*' . | sed 's#^\./##' | sort
printf '\n== deploy-preview.yml ==\n'
nl -ba .github/workflows/deploy-preview.yml | sed -n '1,220p'
printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re
for path in sorted(Path('.github/workflows').rglob('*.[yY][aA][mM][lL]')) + sorted(Path('.github/workflows').rglob('*.yml')):
text = path.read_text()
for i, line in enumerate(text.splitlines(), 1):
m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
if m:
ref = m.group(2)
if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
print(f"{path}:{i}:{m.group(1)}@{ref}")
PYRepository: Comfy-Org/workflow_templates Length of output: 245 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== deploy-preview.yml ==\n'
sed -n '1,220p' .github/workflows/deploy-preview.yml | cat -n
printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re
workflow_dir = Path('.github/workflows')
for path in sorted([p for p in workflow_dir.rglob('*') if p.suffix.lower() in {'.yml', '.yaml'}]):
for i, line in enumerate(path.read_text().splitlines(), 1):
m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
if m:
ref = m.group(2)
if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
print(f"{path}:{i}:{m.group(1)}@{ref}")
PYRepository: Comfy-Org/workflow_templates Length of output: 10443 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== deploy-preview.yml ==\n'
sed -n '1,220p' .github/workflows/deploy-preview.yml | cat -n
printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re
workflow_dir = Path('.github/workflows')
for path in sorted([p for p in workflow_dir.rglob('*') if p.suffix.lower() in {'.yml', '.yaml'}]):
for i, line in enumerate(path.read_text().splitlines(), 1):
m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
if m:
ref = m.group(2)
if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
print(f"{path}:{i}:{m.group(1)}@{ref}")
PYRepository: Comfy-Org/workflow_templates Length of output: 10443 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== deploy-preview.yml ==\n'
sed -n '1,220p' .github/workflows/deploy-preview.yml | cat -n
printf '\n== mutable uses refs in workflows ==\n'
python3 - <<'PY'
from pathlib import Path
import re
workflow_dir = Path('.github/workflows')
for path in sorted([p for p in workflow_dir.rglob('*') if p.suffix.lower() in {'.yml', '.yaml'}]):
for i, line in enumerate(path.read_text().splitlines(), 1):
m = re.search(r'^\s*uses:\s*([^#\s]+)@([^\s#]+)', line)
if m:
ref = m.group(2)
if ref in {'main', 'master', 'latest'} or re.fullmatch(r'v\d+(\.\d+)*', ref):
print(f"{path}:{i}:{m.group(1)}@{ref}")
PYRepository: Comfy-Org/workflow_templates Length of output: 10443 Pin the workflow actions
🧰 Tools🪛 zizmor (1.26.1)[error] 25-25: 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 |
||
| with: | ||
| name: vercel-preview-output | ||
| path: artifact | ||
| run-id: ${{ github.event.workflow_run.id }} | ||
| github-token: ${{ secrets.GITHUB_TOKEN }} | ||
|
|
||
| - name: Resolve PR number | ||
| id: pr | ||
| run: | | ||
| number="$(tr -d '[:space:]' < artifact/preview-meta/pr-number)" | ||
| if [ -z "$number" ]; then | ||
|
Contributor
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. nitpick: (non-blocking) |
||
| echo "No PR number in artifact; nothing to deploy." | ||
| echo "found=false" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "found=true" >> "$GITHUB_OUTPUT" | ||
| echo "number=$number" >> "$GITHUB_OUTPUT" | ||
|
Comment on lines
+35
to
+41
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. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Validate the artifact PR number before using it in shell templates.
Suggested change - name: Resolve PR number
id: pr
run: |
number="$(tr -d '[:space:]' < artifact/preview-meta/pr-number)"
- if [ -z "$number" ]; then
- echo "No PR number in artifact; nothing to deploy."
+ if [[ ! "$number" =~ ^[0-9]+$ ]]; then
+ echo "Invalid PR number in artifact; nothing to deploy." >&2
echo "found=false" >> "$GITHUB_OUTPUT"
else
echo "found=true" >> "$GITHUB_OUTPUT"
echo "number=$number" >> "$GITHUB_OUTPUT"
fi
@@
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ PR_NUMBER: ${{ steps.pr.outputs.number }}
+ REPOSITORY: ${{ github.repository }}
run: |
- labels="$(gh pr view "${{ steps.pr.outputs.number }}" --repo "${{ github.repository }}" --json labels --jq '.labels[].name')"
+ labels="$(gh pr view "$PR_NUMBER" --repo "$REPOSITORY" --json labels --jq '.labels[].name')"
if printf '%s\n' "$labels" | grep -qx 'preview'; then
echo "approved=true" >> "$GITHUB_OUTPUT"
else
echo "approved=false" >> "$GITHUB_OUTPUT"
- echo "PR #${{ steps.pr.outputs.number }} has no 'preview' label — skipping deploy."
+ echo "PR #$PR_NUMBER has no 'preview' label — skipping deploy."
fiAlso applies to: 50-55 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
| fi | ||
|
|
||
| - name: Check for preview label | ||
| id: gate | ||
| if: steps.pr.outputs.found == 'true' | ||
|
Contributor
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. issue: label approval can be consumed by a later, unreviewed commit. Sequence: (1) maintainer adds Two options: (a) add a |
||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: | | ||
| labels="$(gh pr view "${{ steps.pr.outputs.number }}" --repo "${{ github.repository }}" --json labels --jq '.labels[].name')" | ||
| if printf '%s\n' "$labels" | grep -qx 'preview'; then | ||
| echo "approved=true" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "approved=false" >> "$GITHUB_OUTPUT" | ||
| echo "PR #${{ steps.pr.outputs.number }} has no 'preview' label — skipping deploy." | ||
| fi | ||
|
|
||
| - name: Deploy preview to Vercel | ||
| id: deploy | ||
| if: steps.gate.outputs.approved == 'true' | ||
| env: | ||
| VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} | ||
| VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | ||
| VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} | ||
| run: | | ||
| stdout_file="$RUNNER_TEMP/vercel-preview.stdout" | ||
| stderr_file="$RUNNER_TEMP/vercel-preview.stderr" | ||
| output_dir="$GITHUB_WORKSPACE/.vercel/output" | ||
|
|
||
| if [ ! -d "artifact/site/.vercel/output" ]; then | ||
| echo "Prebuilt output missing from artifact" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| mkdir -p "$GITHUB_WORKSPACE/.vercel" | ||
| rm -rf "$output_dir" | ||
| cp -R "artifact/site/.vercel/output" "$output_dir" | ||
|
|
||
| if npx vercel@latest deploy --cwd "$GITHUB_WORKSPACE" --prebuilt --yes \ | ||
| --token="$VERCEL_TOKEN" >"$stdout_file" 2>"$stderr_file"; then | ||
|
Comment on lines
+79
to
+80
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. 🔒 Security & Privacy | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
fd -e json -e lock -e yml -e yaml -e toml -e js -e ts | xargs rg -n '"vercel"|vercel@|npx vercel'Repository: Comfy-Org/workflow_templates Length of output: 480 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== deploy-preview workflow context =="
nl -ba .github/workflows/deploy-preview.yml | sed -n '60,95p'
echo
echo "== repo search for Vercel CLI references =="
rg -n -C 2 'npx vercel|vercel@latest|vercel@' . --glob '!site/pnpm-lock.yaml'
echo
echo "== package manifests mentioning vercel =="
rg -n -C 2 '"vercel"|vercel@' package.json site/package.json pnpm-lock.yaml site/pnpm-lock.yaml .github/workflows/*.yml .github/workflows/**/*.ymlRepository: Comfy-Org/workflow_templates Length of output: 244 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== deploy-preview workflow context =="
nl -ba .github/workflows/deploy-preview.yml | sed -n '60,95p'
echo
echo "== repo search for Vercel CLI references =="
rg -n -C 2 'npx vercel|vercel@latest|vercel@' . --glob '!site/pnpm-lock.yaml'
echo
echo "== package manifests mentioning vercel =="
rg -n -C 2 '"vercel"|vercel@' package.json site/package.json pnpm-lock.yaml site/pnpm-lock.yaml .github/workflows/*.yml .github/workflows/**/*.ymlRepository: Comfy-Org/workflow_templates Length of output: 244 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== deploy-preview workflow context =="
awk 'NR>=60 && NR<=95 { printf "%4d %s\n", NR, $0 }' .github/workflows/deploy-preview.yml
echo
echo "== repo search for Vercel CLI references =="
rg -n -C 2 'npx vercel|vercel@latest|vercel@' . --glob '!site/pnpm-lock.yaml'
echo
echo "== files that mention vercel =="
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|.*\.yml|.*\.yaml|.*\.json|.*\.js|.*\.ts)$' | xargs rg -n -C 1 '"vercel"|vercel@|npx vercel' --glob '!site/pnpm-lock.yaml'Repository: Comfy-Org/workflow_templates Length of output: 1783 Pin the Vercel CLI in
🤖 Prompt for AI Agents |
||
| cat "$stderr_file" >&2 | ||
| cat "$stdout_file" | ||
|
|
||
| preview_url="$(tail -n 1 "$stdout_file" | tr -d '\r')" | ||
| if [[ ! "$preview_url" =~ ^https?:// ]]; then | ||
| echo "Could not parse preview URL from Vercel CLI output" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "preview-url=$preview_url" >> "$GITHUB_OUTPUT" | ||
| else | ||
| cat "$stderr_file" >&2 | ||
| cat "$stdout_file" | ||
| exit 1 | ||
| fi | ||
|
|
||
| - name: Comment preview URL | ||
| if: steps.deploy.outputs.preview-url != '' | ||
| uses: marocchino/sticky-pull-request-comment@v2 | ||
| with: | ||
| number: ${{ steps.pr.outputs.number }} | ||
| header: vercel-preview | ||
| message: | | ||
| 🚀 **Preview deployed:** ${{ steps.deploy.outputs.preview-url }} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ name: Preview Site | |
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, labeled] | ||
|
Contributor
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. issue: Since the label gate is correctly enforced at deploy-time in |
||
| paths: | ||
| - 'site/**' | ||
| workflow_dispatch: | ||
|
|
@@ -12,7 +13,6 @@ concurrency: | |
|
|
||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| preview: | ||
|
|
@@ -45,50 +45,18 @@ jobs: | |
| PUBLIC_HUB_API_URL: ${{ secrets.HUB_API_URL_PREVIEW }} | ||
| PUBLIC_COMFY_CLOUD_URL: ${{ secrets.COMFY_CLOUD_URL_PREVIEW }} | ||
|
|
||
| - name: Deploy preview to Vercel | ||
| id: deploy | ||
| working-directory: site | ||
| env: | ||
| VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} | ||
| VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} | ||
| VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} | ||
| - name: Save PR metadata | ||
| run: | | ||
| stdout_file="$RUNNER_TEMP/vercel-preview.stdout" | ||
| stderr_file="$RUNNER_TEMP/vercel-preview.stderr" | ||
| site_output_dir="$GITHUB_WORKSPACE/site/.vercel/output" | ||
| root_output_dir="$GITHUB_WORKSPACE/.vercel/output" | ||
|
|
||
| if [ ! -d "$site_output_dir" ]; then | ||
| echo "Expected prebuilt output in $site_output_dir" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| mkdir -p "$GITHUB_WORKSPACE/.vercel" | ||
| rm -rf "$root_output_dir" | ||
| cp -R "$site_output_dir" "$root_output_dir" | ||
|
|
||
| if npx vercel@latest deploy --cwd "$GITHUB_WORKSPACE" --prebuilt --yes \ | ||
| --token="$VERCEL_TOKEN" >"$stdout_file" 2>"$stderr_file"; then | ||
| cat "$stderr_file" >&2 | ||
| cat "$stdout_file" | ||
|
|
||
| preview_url="$(tail -n 1 "$stdout_file" | tr -d '\r')" | ||
| if [[ ! "$preview_url" =~ ^https?:// ]]; then | ||
| echo "Could not parse preview URL from Vercel CLI output" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "preview-url=$preview_url" >> "$GITHUB_OUTPUT" | ||
| else | ||
| cat "$stderr_file" >&2 | ||
| cat "$stdout_file" | ||
| exit 1 | ||
| fi | ||
| mkdir -p preview-meta | ||
| echo "${{ github.event.pull_request.number }}" > preview-meta/pr-number | ||
|
|
||
| - name: Comment preview URL | ||
| if: github.event_name == 'pull_request' | ||
| uses: marocchino/sticky-pull-request-comment@v2 | ||
| - name: Upload prebuilt output | ||
| uses: actions/upload-artifact@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. 🔒 Security & Privacy | 🟠 Major 🧩 Analysis chain🏁 Script executed: #!/bin/bash
fd -e yml -e yaml .github/workflows -x rg -n 'uses:\s+[^#]+@(?:v[0-9]+|main|master|latest)\b' {}Repository: Comfy-Org/workflow_templates Length of output: 540 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the workflow around the referenced line and list all action refs.
echo '--- preview-site.yml (relevant section) ---'
sed -n '1,220p' .github/workflows/preview-site.yml | cat -n
echo
echo '--- all uses: entries in .github/workflows ---'
rg -n --glob '.github/workflows/**/*.yml' --glob '.github/workflows/**/*.yaml' '^\s*uses:\s*[^#]+' .github/workflowsRepository: Comfy-Org/workflow_templates Length of output: 11801 Pin 🧰 Tools🪛 zizmor (1.26.1)[error] 54-54: 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 |
||
| with: | ||
| header: vercel-preview | ||
| message: | | ||
| 🚀 **Preview deployed:** ${{ steps.deploy.outputs.preview-url }} | ||
| name: vercel-preview-output | ||
| path: | | ||
| site/.vercel/output | ||
| preview-meta/pr-number | ||
| include-hidden-files: true | ||
| if-no-files-found: error | ||
| retention-days: 1 | ||
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.
issue:
cancel-in-progress: trueon a deploy workflow can leave a broken Vercel preview if a second push arrives whilevercel deployis mid-flight -- the runner is killed after the upload has started but before it completes, and Vercel may surface a stale or errored deployment.deploy-site.ymlusescancel-in-progress: falsefor exactly this reason. Deploy workflows should queue rather than cancel.