Skip to content

Site: sync from product-site-template (star CTA, npm downloads, privacy module) #546

Site: sync from product-site-template (star CTA, npm downloads, privacy module)

Site: sync from product-site-template (star CTA, npm downloads, privacy module) #546

Workflow file for this run

name: Site Preview
# Per-PR preview deploys of site/ to Cloudflare Pages.
#
# PRODUCTION IS NOT TOUCHED BY THIS WORKFLOW. netscli.com is served from
# GitHub Pages via pages.yml, which stays manual-only. This deploys to a
# separate Cloudflare Pages project and always passes an explicit
# `--branch=pr-<N>`, which Cloudflare treats as a *preview* deployment.
# There is no code path here that produces a production deployment.
#
# A missing secret fails this workflow rather than skipping it.
#
# It used to skip and report success. Fork PRs cannot see secrets, so
# skipping looked like the considerate choice -- but the job-level `if`
# below already excludes forks, so the only way to reach the check with no
# token is a misconfigured repository. That state produced a green "Site
# Preview" tick on every PR for weeks while deploying nothing, and the
# notice explaining why was visible only inside the job log. A check that
# passes without doing its job is worse than one that is absent.
on:
pull_request:
branches: [main]
paths:
- 'site/**'
- 'CHANGELOG.md'
- 'scripts/install.ps1'
- 'scripts/install.sh'
- 'apps/netscli-gui/package.json'
- '.github/workflows/site-preview.yml'
# Keep a `main` preview that tracks main, so there is always somewhere to
# see the site as it currently is. It cannot come from the PR path: that
# deploys `--branch=pr-<N>`, so the main alias only ever moved when
# someone ran wrangler by hand, and it silently fell behind between times.
#
# This is still a preview, not production. netscli.com is served from
# GitHub Pages by pages.yml and is untouched here.
push:
branches: [main]
paths:
- 'site/**'
- 'CHANGELOG.md'
- 'scripts/install.ps1'
- 'scripts/install.sh'
- 'apps/netscli-gui/package.json'
- '.github/workflows/site-preview.yml'
concurrency:
# `pull_request.number` is empty on a push, which would put every push in
# one unnamed group with the PR runs. `ref_name` is defined for both.
group: site-preview-${{ github.event.pull_request.number || github.ref_name }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: write
jobs:
preview:
name: Deploy preview
runs-on: ubuntu-latest
# Secrets are not exposed to pull_request runs from forks, so a fork PR
# would otherwise fail on an empty token. Skip rather than fail; the
# maintainer can deploy a preview from a local branch if needed.
#
# A push to main is always same-repo, and `github.event.pull_request` is
# null there -- so without the first clause this guard is false and the
# main preview never runs.
#
# Dependabot PRs are same-repo but run with Dependabot's own secret
# store, not the repository's, so CLOUDFLARE_API_TOKEN is empty there
# too and "Check configuration" failed on all eleven of them (#311-#319,
# #334, #335). A dependency bump does not need a preview.
if: >-
github.actor != 'dependabot[bot]' &&
(github.event_name == 'push' ||
github.event.pull_request.head.repo.full_name == github.repository)
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: site/package-lock.json
- name: Check configuration
env:
CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CF_ACCOUNT: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
run: |
missing=()
[[ -z "$CF_TOKEN" ]] && missing+=("CLOUDFLARE_API_TOKEN")
[[ -z "$CF_ACCOUNT" ]] && missing+=("CLOUDFLARE_ACCOUNT_ID")
if (( ${#missing[@]} > 0 )); then
echo "::error::Cloudflare preview is not configured: ${missing[*]} not set. Add the secret, or delete this workflow if previews are not wanted. See docs/PUBLISHING.md."
exit 1
fi
echo "Cloudflare preview configured."
- name: npm ci
run: npm ci
working-directory: site
# NETSCLI_PREVIEW=1 makes the build emit `robots: noindex, nofollow`
# and suppress the Web Analytics beacon. A preview is still a
# production Astro build, so without it every PR deploy would report
# into netscli.com's real analytics property and be crawlable.
- name: Build site (preview mode)
run: npm run build
working-directory: site
env:
NETSCLI_PREVIEW: '1'
- name: Verify the preview build is not indexable
working-directory: site
run: |
set -euo pipefail
missing=0
while IFS= read -r f; do
grep -q 'name="robots" content="noindex' "$f" || { echo "not noindexed: $f"; missing=1; }
done < <(find dist -name '*.html')
if [[ "$missing" -ne 0 ]]; then
echo "::error::Preview build produced indexable pages — refusing to deploy."
exit 1
fi
if grep -rq "cloudflareinsights" dist/ 2>/dev/null; then
echo "::error::Preview build still loads the analytics beacon — refusing to deploy."
exit 1
fi
echo "All $(find dist -name '*.html' | wc -l) pages are noindex, and no analytics beacon is present."
- name: Deploy to Cloudflare Pages (preview)
id: deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
working-directory: site
run: |
set -euo pipefail
# --branch is what makes this a preview rather than a production
# deployment. Never pass the project's production branch here.
#
# `main` is safe to pass *for this project* because
# netscli-site-preview has no production branch configured -- its
# apex netscli-site-preview.pages.dev returns 404 while the branch
# aliases resolve, which is what an all-preview project looks like.
# If a production branch is ever set on it, and it is set to main,
# this line starts producing production deployments of the preview
# project. Point it at a name that is not the production branch.
npx --yes wrangler@4 pages deploy dist \
--project-name=netscli-site-preview \
--branch="${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.pull_request.number) || 'main' }}" \
--commit-dirty=true \
| tee deploy.log
url=$(grep -oE 'https://[a-z0-9.-]+\.pages\.dev' deploy.log | tail -1)
echo "url=${url}" >> "$GITHUB_OUTPUT"
- name: Comment the preview URL
# Only a pull_request run has somewhere to put this.
if: github.event_name == 'pull_request' && steps.deploy.outputs.url != ''
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
PREVIEW_URL: ${{ steps.deploy.outputs.url }}
with:
script: |
const url = process.env.PREVIEW_URL;
const marker = '<!-- netscli-site-preview -->';
const body = `${marker}\n**Site preview:** ${url}\n\n` +
`Built from ${context.sha.slice(0, 7)} with \`NETSCLI_PREVIEW=1\` — ` +
`noindex, and analytics disabled so it does not report into netscli.com's numbers.\n\n` +
`Production is unaffected: netscli.com is served from GitHub Pages via \`pages.yml\`, which is manual-only.`;
const { data: comments } = await github.rest.issues.listComments({
...context.repo, issue_number: context.issue.number,
});
const existing = comments.find((c) => c.body?.includes(marker));
if (existing) {
await github.rest.issues.updateComment({ ...context.repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ ...context.repo, issue_number: context.issue.number, body });
}