mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-30 04:40:37 +00:00
171 lines
7.4 KiB
YAML
171 lines
7.4 KiB
YAML
name: Build PR preview
|
|
|
|
# Every push to a pull request publishes that pull request's preview to
|
|
# Cloudflare Pages under its own branch alias, behind Cloudflare Access. The
|
|
# upload carries build products only: the workflow never grants the deployment
|
|
# platform access to this repository's sources.
|
|
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened]
|
|
|
|
# Within one pull request the newest build wins. Across pull requests there is
|
|
# nothing to serialize: each uploads to its own branch alias, so two deployments
|
|
# never contend for the same URL.
|
|
concurrency:
|
|
group: build-preview-cloudflare-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
|
|
env:
|
|
PRIMARY_NODE_VERSION: '24'
|
|
# Cloudflare Pages project receiving the upload. Its preview deployments are
|
|
# the surface the Access application protects; the project's production branch
|
|
# is deliberately a name no deployment uses, so no unprotected URL exists.
|
|
CF_PROJECT: dsh-build-preview
|
|
# CI runs must never report to the production telemetry endpoint baked into
|
|
# apps/cli/cordis.yml (AppCLIEntry disables the row when set).
|
|
DSH_TELEMETRY_DISABLED: '1'
|
|
|
|
jobs:
|
|
preview:
|
|
runs-on: dsh-ubuntu-24-04-16core
|
|
name: cloudflare pages preview
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: pnpm/action-setup@v4
|
|
with:
|
|
dest: ${{ runner.temp }}/setup-pnpm
|
|
|
|
- uses: actions/setup-node@v6
|
|
with:
|
|
node-version: ${{ env.PRIMARY_NODE_VERSION }}
|
|
|
|
- name: Configure pnpm store path
|
|
id: pnpm-store
|
|
run: |
|
|
store_root="$HOME/.local/share/pnpm/store"
|
|
echo "PNPM_CONFIG_STORE_DIR=$store_root" >> "$GITHUB_ENV"
|
|
store_path=$(PNPM_CONFIG_STORE_DIR="$store_root" pnpm store path --silent)
|
|
echo "path=$store_path" >> "$GITHUB_OUTPUT"
|
|
|
|
# Read-only: the preview lane consumes the default-branch cache without
|
|
# putting cache upload on its own path.
|
|
- uses: actions/cache/restore@v4
|
|
with:
|
|
path: ${{ steps.pnpm-store.outputs.path }}
|
|
key: ${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-${{ hashFiles('pnpm-lock.yaml') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-node-${{ env.PRIMARY_NODE_VERSION }}-pnpm-
|
|
|
|
- name: Install (immutable)
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
# apps/web consumes workspace packages as built lib products, and
|
|
# build:preview packs the image through the packer's installed bin
|
|
# (lib/bin.js), so neither half exists before the full build runs.
|
|
- name: Build workspace
|
|
run: pnpm run build
|
|
|
|
- name: Build the preview page and pack the VFS image
|
|
env:
|
|
DSH_CLIENT_TITLE: DSH preview pr-${{ github.event.pull_request.number }}
|
|
run: pnpm --filter @deepseek-ai/dsh-web-frontend run build:preview
|
|
|
|
# Sourcemaps carry complete sources and stay off the deployment platform.
|
|
# index.html is the served page, which cannot boot without a host
|
|
# injecting window.__DSH_BOOT__; replacing it with the worker page makes
|
|
# the deployment root the usable entry instead of a page that never boots.
|
|
- name: Shape the upload
|
|
run: |
|
|
find apps/web/dist -name '*.map' -delete
|
|
cp apps/web/dist/preview.html apps/web/dist/index.html
|
|
|
|
- name: Upload to Cloudflare Pages
|
|
env:
|
|
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
run: |
|
|
npx --yes wrangler@4 pages deploy apps/web/dist \
|
|
--project-name "$CF_PROJECT" \
|
|
--branch "pr-${{ github.event.pull_request.number }}" \
|
|
--commit-dirty=true
|
|
|
|
# The image is what a worker boot fails on first and least visibly, so the
|
|
# run only passes once the protected URL serves it as gzip bytes. Three
|
|
# facts are asserted, each with its own failure meaning:
|
|
# 200 Access admitted the request; a 302 means the
|
|
# Access policy is missing its Service Auth rule
|
|
# for this token
|
|
# no content-encoding the platform did not claim transport
|
|
# compression, which would make the browser
|
|
# decode the body and leave the worker's
|
|
# DecompressionStream inflating a plain tar
|
|
# gzip magic 1f 8b the bytes really are the gzip member the
|
|
# packer wrote
|
|
# Accept-Encoding is sent because a browser sends it; the assertion is
|
|
# about what the platform does with a body that is already compressed.
|
|
- name: Verify the protected deployment serves the image
|
|
env:
|
|
CF_ACCESS_CLIENT_ID: ${{ secrets.CF_ACCESS_CLIENT_ID }}
|
|
CF_ACCESS_CLIENT_SECRET: ${{ secrets.CF_ACCESS_CLIENT_SECRET }}
|
|
run: |
|
|
url="https://pr-${{ github.event.pull_request.number }}.${CF_PROJECT}.pages.dev"
|
|
image="$url/preview/vfs-image.tar.gz"
|
|
code=000
|
|
for attempt in 1 2 3 4 5; do
|
|
code=$(curl -sS -o image.bin -D headers.txt -w '%{http_code}' \
|
|
-H 'Accept-Encoding: gzip' \
|
|
-H "CF-Access-Client-Id: $CF_ACCESS_CLIENT_ID" \
|
|
-H "CF-Access-Client-Secret: $CF_ACCESS_CLIENT_SECRET" \
|
|
"$image" || echo 000)
|
|
echo "attempt $attempt: HTTP $code"
|
|
if [ "$code" = "200" ]; then break; fi
|
|
sleep 10
|
|
done
|
|
if [ "$code" != "200" ]; then
|
|
echo "the protected image URL answered $code, not 200"
|
|
head -20 headers.txt
|
|
exit 1
|
|
fi
|
|
if grep -qi '^content-encoding:' headers.txt; then
|
|
echo "the platform declared transport compression on an already-compressed image:"
|
|
grep -i '^content-encoding:' headers.txt
|
|
exit 1
|
|
fi
|
|
magic=$(head -c 2 image.bin | od -An -tx1 | tr -d ' \n')
|
|
if [ "$magic" != "1f8b" ]; then
|
|
echo "image does not start with the gzip magic number: $magic"
|
|
exit 1
|
|
fi
|
|
echo "image served as $(wc -c < image.bin) gzip bytes"
|
|
|
|
# The alias URL follows from the pull request number, so it is stable
|
|
# across redeploys and worth stating once. The marker makes the comment
|
|
# idempotent: a pull request opened before this workflow existed never
|
|
# sees an `opened` event, and every later push must not restate the URL.
|
|
- name: Comment the preview URL
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
PR: ${{ github.event.pull_request.number }}
|
|
run: |
|
|
marker='<!-- dsh-preview-url -->'
|
|
existing=$(gh pr view "$PR" --json comments \
|
|
--jq "[.comments[] | select(.body | contains(\"$marker\")) | .url] | first // empty")
|
|
if [ -n "$existing" ]; then
|
|
echo "preview URL already commented: $existing"
|
|
exit 0
|
|
fi
|
|
# The marker sits on its own line: markdown renders no link on a
|
|
# line that opens with a raw HTML comment.
|
|
printf '%s\n\n%s\n' \
|
|
"$marker" \
|
|
"[Preview for #$PR](https://pr-$PR.${CF_PROJECT}.pages.dev) (requires Cloudflare Access sign-in)" \
|
|
| gh pr comment "$PR" --body-file -
|