feat(docs): deploy CLI docs to Cloudflare with Craft - #1354
Conversation
|
|
Out of draft — CI is green across the board (lint & typecheck, unit tests, docs build/preview, CodeQL, warden, socket, dependency review) and self-review turned up nothing. The pushable half of the migration (wrangler config + |
|
|
Applied the The diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 45b0956..d02896e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -758,15 +758,6 @@ jobs:
# be deployed to production.
- name: Remove sourcemaps from output
run: find apps/cli-docs/dist -name '*.map' -delete
- - name: Package Docs
- run: |
- cp .nojekyll apps/cli-docs/dist/
- cd apps/cli-docs/dist && zip -r "$GITHUB_WORKSPACE/gh-pages.zip" .
- - name: Upload docs artifact
- uses: actions/upload-artifact@v7
- with:
- name: gh-pages
- path: gh-pages.zip
ci-status:
name: CI Status
diff --git a/.github/workflows/cleanup-doc-previews.yml b/.github/workflows/cleanup-doc-previews.yml
deleted file mode 100644
index 6a59b85..0000000
--- a/.github/workflows/cleanup-doc-previews.yml
+++ /dev/null
@@ -1,99 +0,0 @@
-name: Cleanup Doc Previews
-
-# Safety net for the on-close cleanup in docs-preview.yml: sweeps gh-pages for
-# `_preview/pr-<n>` directories whose PR is no longer open and removes them.
-# Catches previews that slipped through (e.g. closed while the preview workflow
-# was failing) so gh-pages never bloats past GitHub Pages' build limits again.
-
-on:
- schedule:
- # Every Sunday at 05:30 UTC (before the nightly-tag cleanup).
- - cron: "30 5 * * 0"
- workflow_dispatch:
-
-permissions:
- contents: write
- pull-requests: read
-
-concurrency:
- # Serialize cleanup runs with each other. Races against docs-preview deploys
- # are handled by the push retry loop below (a non-fast-forward push is
- # rejected, never applied, so it can't corrupt gh-pages).
- group: gh-pages-cleanup
- cancel-in-progress: false
-
-jobs:
- prune:
- name: Prune previews for closed PRs
- runs-on: ubuntu-latest
- steps:
- - name: Prune stale previews
- env:
- GH_TOKEN: ${{ github.token }}
- REPO: ${{ github.repository }}
- run: |
- set -euo pipefail
-
- # Blobless, no-checkout clone so we never download the (large) preview
- # file contents — we only need trees to rewrite the index.
- git clone --filter=blob:none --no-checkout --single-branch \
- --branch gh-pages \
- "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" ghp
- cd ghp
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
-
- # Retry loop: another gh-pages writer (a docs-preview deploy) may land
- # between our read and push. A non-fast-forward push is REJECTED (never
- # applied, so no corruption); we re-sync onto the new tip and re-prune.
- max_attempts=3
- attempt=0
- while :; do
- attempt=$((attempt + 1))
- git read-tree HEAD
-
- removed=0
- # One entry per top-level _preview/<dir>.
- for dir in $(git ls-files _preview/ | sed -E 's#(_preview/[^/]+)/.*#\1#' | sort -u); do
- name="${dir#_preview/}"
- # Keep the production/main preview.
- if [ "$name" = "pr-main" ]; then
- continue
- fi
- num="${name#pr-}"
- # Skip anything that isn't a pr-<number> directory.
- case "$num" in
- ''|*[!0-9]*) continue ;;
- esac
- state=$(gh pr view "$num" --repo "$REPO" --json state --jq .state 2>/dev/null || echo "UNKNOWN")
- # Only prune when the PR is CONFIRMED closed/merged. Keep the
- # preview on OPEN and on any API error/unknown state so a transient
- # glitch never deletes a live preview.
- case "$state" in
- CLOSED|MERGED)
- echo "Removing $dir (PR #$num state=$state)"
- git rm -r -q --cached "$dir"
- removed=$((removed + 1))
- ;;
- esac
- done
-
- if [ "$removed" -eq 0 ]; then
- echo "No stale previews to prune."
- exit 0
- fi
-
- git commit -q -m "chore(docs): prune ${removed} preview(s) for closed PRs [skip ci]"
- if git push origin gh-pages; then
- echo "Pruned ${removed} stale preview(s)."
- exit 0
- fi
-
- if [ "$attempt" -ge "$max_attempts" ]; then
- echo "gh-pages push kept failing after ${max_attempts} attempts; retrying next run." >&2
- exit 1
- fi
- echo "gh-pages moved underneath us; re-syncing and retrying (attempt ${attempt})…"
- git fetch --quiet origin gh-pages
- git reset --soft FETCH_HEAD
- done
diff --git a/.github/workflows/docs-deploy.yml b/.github/workflows/docs-deploy.yml
new file mode 100644
index 0000000..51ec6b1
--- /dev/null
+++ b/.github/workflows/docs-deploy.yml
@@ -0,0 +1,82 @@
+name: Docs Deploy
+
+# Release-gated production deploy of the CLI docs site to Cloudflare Workers
+# (static assets). Fires when a release is published so the deployed docs always
+# track the latest published CLI version — the same "docs tied to release"
+# guarantee the old Craft gh-pages target provided, just on Cloudflare instead
+# of GitHub Pages. Pre-releases (nightlies, -dev versions) are skipped.
+on:
+ release:
+ types: [published]
+ workflow_dispatch:
+ inputs:
+ version:
+ description: "Release version to deploy (e.g., 0.24.0)"
+ required: true
+ type: string
+
+permissions:
+ contents: read
+
+concurrency:
+ group: docs-deploy
+ cancel-in-progress: false
+
+env:
+ # Pin Node to an exact patched version (single source of truth). A floating
+ # "24" can reuse the runner's pre-cached 24.17.0, which carries the
+ # ERR_STREAM_PREMATURE_CLOSE regression on keep-alive fetch reuse
+ # (nodejs/node#64004, fixed in 24.18.0).
+ NODE_VERSION_24: "24.18.0"
+
+jobs:
+ deploy:
+ name: Deploy docs to Cloudflare
+ runs-on: ubuntu-latest
+ environment: production
+ # Skip pre-releases (nightlies, dev versions) on automatic trigger; always
+ # run on manual dispatch.
+ if: github.event_name == 'workflow_dispatch' || !github.event.release.prerelease
+ env:
+ # Both secrets are scoped to the "production" environment.
+ SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
+ CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ # Bare semver tag (e.g. "0.24.0", no "v" prefix), matching the
+ # Sentry.init() release value and sourcemap uploads.
+ VERSION: ${{ github.event.release.tag_name || inputs.version }}
+ steps:
+ - uses: actions/checkout@v6
+ - uses: pnpm/action-setup@v4
+ # Astro 6 requires Node >= 22.12. Pin an exact patched version (see
+ # NODE_VERSION_24) so the docs build doesn't rely on the runner default.
+ - uses: actions/setup-node@v6
+ with:
+ node-version: ${{ env.NODE_VERSION_24 }}
+ - run: pnpm install --frozen-lockfile
+ - name: Generate docs content
+ run: pnpm run generate:schema && pnpm run generate:docs
+ - name: Build Docs
+ working-directory: apps/cli-docs
+ env:
+ PUBLIC_SENTRY_ENVIRONMENT: production
+ SENTRY_RELEASE: ${{ env.VERSION }}
+ PUBLIC_SENTRY_RELEASE: ${{ env.VERSION }}
+ run: pnpm run build
+ # Inject debug IDs and upload sourcemaps to Sentry (see ci.yml build-docs).
+ - name: Inject debug IDs and upload sourcemaps
+ if: env.SENTRY_AUTH_TOKEN != ''
+ env:
+ SENTRY_ORG: sentry
+ SENTRY_PROJECT: cli-website
+ run: |
+ pnpm run cli sourcemap inject "$GITHUB_WORKSPACE/apps/cli-docs/dist/"
+ pnpm run cli sourcemap upload "$GITHUB_WORKSPACE/apps/cli-docs/dist/" \
+ --release "$VERSION" \
+ --url-prefix "~/"
+ # Remove .map files — uploaded to Sentry but shouldn't be deployed.
+ - name: Remove sourcemaps from output
+ run: find apps/cli-docs/dist -name '*.map' -delete
+ - name: Deploy to Cloudflare
+ working-directory: apps/cli-docs
+ run: pnpm run deploy
diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml
index cf83134..71c7606 100644
--- a/.github/workflows/docs-preview.yml
+++ b/.github/workflows/docs-preview.yml
@@ -1,5 +1,10 @@
name: Docs Preview
+# Builds the CLI docs on every docs-touching PR and uploads a Cloudflare Workers
+# preview version, then comments the preview URL on the PR. Replaces the old
+# gh-pages + rossjrw/pr-preview-action flow: Cloudflare version previews are
+# isolated per upload and expire on their own, so there is no umbrella branch to
+# clean up and no `closed`-event handling.
on:
push:
branches: [main]
@@ -11,15 +16,16 @@ on:
- 'packages/cli/install'
- '.github/workflows/docs-preview.yml'
pull_request:
- # No paths filter here: the 'closed' event must ALWAYS fire so
- # pr-preview-action can remove the deployed preview. A paths-filter step
- # inside the job (below) skips the build/deploy for non-docs PRs. Without
- # this, closed PRs never clean up and gh-pages bloats until GitHub Pages
- # exceeds its build limits and starts failing.
- types: [opened, reopened, synchronize, closed]
+ paths:
+ - 'apps/cli-docs/**'
+ - 'packages/cli/src/**'
+ - 'packages/cli/script/generate-command-docs.ts'
+ - 'packages/cli/script/generate-skill.ts'
+ - 'packages/cli/install'
+ - '.github/workflows/docs-preview.yml'
permissions:
- contents: write
+ contents: read
pull-requests: write
concurrency:
@@ -35,63 +41,29 @@ env:
jobs:
preview:
+ name: Build and upload docs preview
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- # Detect whether this PR actually touches docs. Skipped on 'closed' (and
- # on push), where the gate below decides what to do without a diff.
- - name: Check for docs changes
- id: filter
- if: github.event_name == 'pull_request' && github.event.action != 'closed'
- uses: dorny/paths-filter@v4
- with:
- filters: |
- docs:
- - 'apps/cli-docs/**'
- - 'packages/cli/src/**'
- - 'packages/cli/script/generate-command-docs.ts'
- - 'packages/cli/script/generate-skill.ts'
- - 'packages/cli/install'
- - '.github/workflows/docs-preview.yml'
-
- # Central gate. `build` = produce the site (push, or a docs PR being
- # opened/updated). `deploy` = publish/remove on gh-pages (build, or any
- # 'closed' event to clean up), but never for fork PRs which lack write
- # access. Fork PRs still build to surface compilation errors.
- - name: Compute gates
+ # Fork PRs have no access to the Cloudflare secrets, so they build only
+ # (to surface compilation errors) and skip the upload/comment steps.
+ - name: Compute deploy gate
id: gate
env:
- EVENT: ${{ github.event_name }}
- ACTION: ${{ github.event.action }}
- DOCS_CHANGED: ${{ steps.filter.outputs.docs }}
IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
- run: |
- build=false
- deploy=false
- if [ "$EVENT" = "push" ] || { [ "$ACTION" != "closed" ] && [ "$DOCS_CHANGED" = "true" ]; }; then
- build=true
- fi
- if { [ "$build" = "true" ] || [ "$ACTION" = "closed" ]; } && [ "$IS_FORK" != "true" ]; then
- deploy=true
- fi
- echo "build=$build" >> "$GITHUB_OUTPUT"
- echo "deploy=$deploy" >> "$GITHUB_OUTPUT"
+ run: echo "deploy=$([ "$IS_FORK" = "true" ] && echo false || echo true)" >> "$GITHUB_OUTPUT"
- uses: pnpm/action-setup@v4
- if: steps.gate.outputs.build == 'true'
# Astro 6 requires Node >= 22.12. Pin an exact patched version (see
- # NODE_VERSION_24) so the docs build doesn't rely on the runner image's
- # default.
+ # NODE_VERSION_24) so the docs build doesn't rely on the runner default.
- uses: actions/setup-node@v6
- if: steps.gate.outputs.build == 'true'
with:
node-version: ${{ env.NODE_VERSION_24 }}
- uses: actions/cache@v5
id: cache
- if: steps.gate.outputs.build == 'true'
with:
path: |
node_modules
@@ -99,94 +71,46 @@ jobs:
apps/*/node_modules
key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }}
- - if: steps.gate.outputs.build == 'true' && steps.cache.outputs.cache-hit != 'true'
+ - if: steps.cache.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile
- name: Get CLI version
id: version
- if: steps.gate.outputs.build == 'true'
run: echo "version=$(node -p 'require("./packages/cli/package.json").version')" >> "$GITHUB_OUTPUT"
- name: Generate docs content
- if: steps.gate.outputs.build == 'true'
run: pnpm run generate:schema && pnpm run generate:docs
- name: Build Docs for Preview
- if: steps.gate.outputs.build == 'true'
working-directory: apps/cli-docs
env:
- DOCS_BASE_PATH: ${{ github.event_name == 'push'
- && '/_preview/pr-main'
- || format('/_preview/pr-{0}', github.event.pull_request.number) }}
PUBLIC_SENTRY_ENVIRONMENT: staging
SENTRY_RELEASE: ${{ steps.version.outputs.version }}
PUBLIC_SENTRY_RELEASE: ${{ steps.version.outputs.version }}
- run: |
- pnpm install --frozen-lockfile
- pnpm run build
+ run: pnpm run build
- - name: Inject debug IDs and upload sourcemaps
- if: steps.gate.outputs.build == 'true' && env.SENTRY_AUTH_TOKEN != ''
- env:
- SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- SENTRY_ORG: sentry
- SENTRY_PROJECT: cli-website
- run: |
- pnpm run cli sourcemap inject "$GITHUB_WORKSPACE/apps/cli-docs/dist/"
- pnpm run cli sourcemap upload "$GITHUB_WORKSPACE/apps/cli-docs/dist/" \
- --release "${{ steps.version.outputs.version }}" \
- --url-prefix "~/"
-
- # Remove .map files — uploaded to Sentry but shouldn't be deployed.
+ # Remove .map files — sourcemaps aren't uploaded for previews and
+ # shouldn't ship in the preview deployment.
- name: Remove sourcemaps from output
- if: steps.gate.outputs.build == 'true'
run: find apps/cli-docs/dist -name '*.map' -delete
- - name: Ensure .nojekyll at gh-pages root
- # Runs whenever we deploy (build or a 'closed' cleanup), but never for
- # fork PRs which lack write access to the base repo's gh-pages branch.
+ - name: Upload Cloudflare preview
+ id: preview
if: steps.gate.outputs.deploy == 'true'
- run: |
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
-
- # Try to fetch the gh-pages branch
- if git fetch origin gh-pages:gh-pages 2>/dev/null; then
- # Branch exists remotely, check if .nojekyll is present
- if git show gh-pages:.nojekyll &>/dev/null; then
- echo ".nojekyll already exists at gh-pages root"
- else
- echo "Adding .nojekyll to existing gh-pages branch"
- git checkout gh-pages
- touch .nojekyll
- git add .nojekyll
- git commit -m "Add .nojekyll to disable Jekyll processing"
- git push origin gh-pages
- git checkout -
- fi
- else
- # Branch doesn't exist, create it as an orphan branch
- echo "Creating gh-pages branch with .nojekyll"
- git checkout --orphan gh-pages
- git rm -rf .
- touch .nojekyll
- git add .nojekyll
- git commit -m "Initialize gh-pages with .nojekyll"
- git push origin gh-pages
- git checkout -
- fi
-
- - name: Deploy Preview
- # Deploys on build; on a 'closed' event `auto` removes the preview
- # instead — this is what keeps gh-pages from accumulating stale
- # previews. Skipped for fork PRs (no write access).
- if: steps.gate.outputs.deploy == 'true'
- uses: rossjrw/pr-preview-action@v1
+ uses: cloudflare/wrangler-action@v4
+ with:
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ workingDirectory: apps/cli-docs
+ # Uses the workspace-pinned wrangler (apps/cli-docs devDependency)
+ # rather than letting the action install its own copy.
+ wranglerVersion: "4"
+ command: versions upload
+
+ - name: Comment preview URL
+ if: steps.gate.outputs.deploy == 'true' && github.event_name == 'pull_request'
+ uses: marocchino/sticky-pull-request-comment@v2
with:
- source-dir: apps/cli-docs/dist/
- preview-branch: gh-pages
- umbrella-dir: _preview
- pages-base-url: cli.sentry.dev
- action: ${{ github.event_name == 'push' && 'deploy' || 'auto' }}
- pr-number: ${{ github.event_name == 'push' && 'main' || github.event.pull_request.number }}
- comment: ${{ github.event_name != 'push' }}
+ header: docs-preview
+ message: |
+ Docs preview for this PR: ${{ steps.preview.outputs.deployment-url }}
|
|
Jared, we're supposed to deploy the site using Craft again: https://craft.sentry.dev/targets/cloudflare/ |
|
Updated the workflow patch to match BYK's Craft-based approach. Two changes since the last patch: dropped the standalone
The jared token still lacks Workflow patch to apply (needs `workflows` permission)diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -758,15 +758,19 @@ jobs:
# be deployed to production.
- name: Remove sourcemaps from output
run: find apps/cli-docs/dist -name '*.map' -delete
+ # Package the built site plus its wrangler config as cloudflare.zip so
+ # Craft's cloudflare target (deployType: worker) can run `wrangler deploy`
+ # from the extracted artifact on release. Zip from inside apps/cli-docs so
+ # wrangler.jsonc and dist/ sit at the archive root (no wrapping dir), which
+ # is where wrangler expects to run.
- name: Package Docs
run: |
- cp .nojekyll apps/cli-docs/dist/
- cd apps/cli-docs/dist && zip -r "$GITHUB_WORKSPACE/gh-pages.zip" .
+ cd apps/cli-docs && zip -r "$GITHUB_WORKSPACE/cloudflare.zip" wrangler.jsonc dist
- name: Upload docs artifact
uses: actions/upload-artifact@v7
with:
- name: gh-pages
- path: gh-pages.zip
+ name: cloudflare
+ path: cloudflare.zip
ci-status:
name: CI Status
diff --git a/.github/workflows/cleanup-doc-previews.yml b/.github/workflows/cleanup-doc-previews.yml
deleted file mode 100644
index 6a59b85..0000000
--- a/.github/workflows/cleanup-doc-previews.yml
+++ /dev/null
@@ -1,99 +0,0 @@
-name: Cleanup Doc Previews
-
-# Safety net for the on-close cleanup in docs-preview.yml: sweeps gh-pages for
-# `_preview/pr-<n>` directories whose PR is no longer open and removes them.
-# Catches previews that slipped through (e.g. closed while the preview workflow
-# was failing) so gh-pages never bloats past GitHub Pages' build limits again.
-
-on:
- schedule:
- # Every Sunday at 05:30 UTC (before the nightly-tag cleanup).
- - cron: "30 5 * * 0"
- workflow_dispatch:
-
-permissions:
- contents: write
- pull-requests: read
-
-concurrency:
- # Serialize cleanup runs with each other. Races against docs-preview deploys
- # are handled by the push retry loop below (a non-fast-forward push is
- # rejected, never applied, so it can't corrupt gh-pages).
- group: gh-pages-cleanup
- cancel-in-progress: false
-
-jobs:
- prune:
- name: Prune previews for closed PRs
- runs-on: ubuntu-latest
- steps:
- - name: Prune stale previews
- env:
- GH_TOKEN: ${{ github.token }}
- REPO: ${{ github.repository }}
- run: |
- set -euo pipefail
-
- # Blobless, no-checkout clone so we never download the (large) preview
- # file contents — we only need trees to rewrite the index.
- git clone --filter=blob:none --no-checkout --single-branch \
- --branch gh-pages \
- "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" ghp
- cd ghp
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
-
- # Retry loop: another gh-pages writer (a docs-preview deploy) may land
- # between our read and push. A non-fast-forward push is REJECTED (never
- # applied, so no corruption); we re-sync onto the new tip and re-prune.
- max_attempts=3
- attempt=0
- while :; do
- attempt=$((attempt + 1))
- git read-tree HEAD
-
- removed=0
- # One entry per top-level _preview/<dir>.
- for dir in $(git ls-files _preview/ | sed -E 's#(_preview/[^/]+)/.*#\1#' | sort -u); do
- name="${dir#_preview/}"
- # Keep the production/main preview.
- if [ "$name" = "pr-main" ]; then
- continue
- fi
- num="${name#pr-}"
- # Skip anything that isn't a pr-<number> directory.
- case "$num" in
- ''|*[!0-9]*) continue ;;
- esac
- state=$(gh pr view "$num" --repo "$REPO" --json state --jq .state 2>/dev/null || echo "UNKNOWN")
- # Only prune when the PR is CONFIRMED closed/merged. Keep the
- # preview on OPEN and on any API error/unknown state so a transient
- # glitch never deletes a live preview.
- case "$state" in
- CLOSED|MERGED)
- echo "Removing $dir (PR #$num state=$state)"
- git rm -r -q --cached "$dir"
- removed=$((removed + 1))
- ;;
- esac
- done
-
- if [ "$removed" -eq 0 ]; then
- echo "No stale previews to prune."
- exit 0
- fi
-
- git commit -q -m "chore(docs): prune ${removed} preview(s) for closed PRs [skip ci]"
- if git push origin gh-pages; then
- echo "Pruned ${removed} stale preview(s)."
- exit 0
- fi
-
- if [ "$attempt" -ge "$max_attempts" ]; then
- echo "gh-pages push kept failing after ${max_attempts} attempts; retrying next run." >&2
- exit 1
- fi
- echo "gh-pages moved underneath us; re-syncing and retrying (attempt ${attempt})…"
- git fetch --quiet origin gh-pages
- git reset --soft FETCH_HEAD
- done
diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml
index cf83134..71c7606 100644
--- a/.github/workflows/docs-preview.yml
+++ b/.github/workflows/docs-preview.yml
@@ -1,5 +1,10 @@
name: Docs Preview
+# Builds the CLI docs on every docs-touching PR and uploads a Cloudflare Workers
+# preview version, then comments the preview URL on the PR. Replaces the old
+# gh-pages + rossjrw/pr-preview-action flow: Cloudflare version previews are
+# isolated per upload and expire on their own, so there is no umbrella branch to
+# clean up and no `closed`-event handling.
on:
push:
branches: [main]
@@ -11,15 +16,16 @@ on:
- 'packages/cli/install'
- '.github/workflows/docs-preview.yml'
pull_request:
- # No paths filter here: the 'closed' event must ALWAYS fire so
- # pr-preview-action can remove the deployed preview. A paths-filter step
- # inside the job (below) skips the build/deploy for non-docs PRs. Without
- # this, closed PRs never clean up and gh-pages bloats until GitHub Pages
- # exceeds its build limits and starts failing.
- types: [opened, reopened, synchronize, closed]
+ paths:
+ - 'apps/cli-docs/**'
+ - 'packages/cli/src/**'
+ - 'packages/cli/script/generate-command-docs.ts'
+ - 'packages/cli/script/generate-skill.ts'
+ - 'packages/cli/install'
+ - '.github/workflows/docs-preview.yml'
permissions:
- contents: write
+ contents: read
pull-requests: write
concurrency:
@@ -35,63 +41,29 @@ env:
jobs:
preview:
+ name: Build and upload docs preview
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- # Detect whether this PR actually touches docs. Skipped on 'closed' (and
- # on push), where the gate below decides what to do without a diff.
- - name: Check for docs changes
- id: filter
- if: github.event_name == 'pull_request' && github.event.action != 'closed'
- uses: dorny/paths-filter@v4
- with:
- filters: |
- docs:
- - 'apps/cli-docs/**'
- - 'packages/cli/src/**'
- - 'packages/cli/script/generate-command-docs.ts'
- - 'packages/cli/script/generate-skill.ts'
- - 'packages/cli/install'
- - '.github/workflows/docs-preview.yml'
-
- # Central gate. `build` = produce the site (push, or a docs PR being
- # opened/updated). `deploy` = publish/remove on gh-pages (build, or any
- # 'closed' event to clean up), but never for fork PRs which lack write
- # access. Fork PRs still build to surface compilation errors.
- - name: Compute gates
+ # Fork PRs have no access to the Cloudflare secrets, so they build only
+ # (to surface compilation errors) and skip the upload/comment steps.
+ - name: Compute deploy gate
id: gate
env:
- EVENT: ${{ github.event_name }}
- ACTION: ${{ github.event.action }}
- DOCS_CHANGED: ${{ steps.filter.outputs.docs }}
IS_FORK: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository }}
- run: |
- build=false
- deploy=false
- if [ "$EVENT" = "push" ] || { [ "$ACTION" != "closed" ] && [ "$DOCS_CHANGED" = "true" ]; }; then
- build=true
- fi
- if { [ "$build" = "true" ] || [ "$ACTION" = "closed" ]; } && [ "$IS_FORK" != "true" ]; then
- deploy=true
- fi
- echo "build=$build" >> "$GITHUB_OUTPUT"
- echo "deploy=$deploy" >> "$GITHUB_OUTPUT"
+ run: echo "deploy=$([ "$IS_FORK" = "true" ] && echo false || echo true)" >> "$GITHUB_OUTPUT"
- uses: pnpm/action-setup@v4
- if: steps.gate.outputs.build == 'true'
# Astro 6 requires Node >= 22.12. Pin an exact patched version (see
- # NODE_VERSION_24) so the docs build doesn't rely on the runner image's
- # default.
+ # NODE_VERSION_24) so the docs build doesn't rely on the runner default.
- uses: actions/setup-node@v6
- if: steps.gate.outputs.build == 'true'
with:
node-version: ${{ env.NODE_VERSION_24 }}
- uses: actions/cache@v5
id: cache
- if: steps.gate.outputs.build == 'true'
with:
path: |
node_modules
@@ -99,94 +71,46 @@ jobs:
apps/*/node_modules
key: node-modules-${{ hashFiles('pnpm-lock.yaml', '.npmrc', 'packages/cli/patches/**') }}
- - if: steps.gate.outputs.build == 'true' && steps.cache.outputs.cache-hit != 'true'
+ - if: steps.cache.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile
- name: Get CLI version
id: version
- if: steps.gate.outputs.build == 'true'
run: echo "version=$(node -p 'require("./packages/cli/package.json").version')" >> "$GITHUB_OUTPUT"
- name: Generate docs content
- if: steps.gate.outputs.build == 'true'
run: pnpm run generate:schema && pnpm run generate:docs
- name: Build Docs for Preview
- if: steps.gate.outputs.build == 'true'
working-directory: apps/cli-docs
env:
- DOCS_BASE_PATH: ${{ github.event_name == 'push'
- && '/_preview/pr-main'
- || format('/_preview/pr-{0}', github.event.pull_request.number) }}
PUBLIC_SENTRY_ENVIRONMENT: staging
SENTRY_RELEASE: ${{ steps.version.outputs.version }}
PUBLIC_SENTRY_RELEASE: ${{ steps.version.outputs.version }}
- run: |
- pnpm install --frozen-lockfile
- pnpm run build
+ run: pnpm run build
- - name: Inject debug IDs and upload sourcemaps
- if: steps.gate.outputs.build == 'true' && env.SENTRY_AUTH_TOKEN != ''
- env:
- SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- SENTRY_ORG: sentry
- SENTRY_PROJECT: cli-website
- run: |
- pnpm run cli sourcemap inject "$GITHUB_WORKSPACE/apps/cli-docs/dist/"
- pnpm run cli sourcemap upload "$GITHUB_WORKSPACE/apps/cli-docs/dist/" \
- --release "${{ steps.version.outputs.version }}" \
- --url-prefix "~/"
-
- # Remove .map files — uploaded to Sentry but shouldn't be deployed.
+ # Remove .map files — sourcemaps aren't uploaded for previews and
+ # shouldn't ship in the preview deployment.
- name: Remove sourcemaps from output
- if: steps.gate.outputs.build == 'true'
run: find apps/cli-docs/dist -name '*.map' -delete
- - name: Ensure .nojekyll at gh-pages root
- # Runs whenever we deploy (build or a 'closed' cleanup), but never for
- # fork PRs which lack write access to the base repo's gh-pages branch.
+ - name: Upload Cloudflare preview
+ id: preview
if: steps.gate.outputs.deploy == 'true'
- run: |
- git config user.name "github-actions[bot]"
- git config user.email "github-actions[bot]@users.noreply.github.com"
-
- # Try to fetch the gh-pages branch
- if git fetch origin gh-pages:gh-pages 2>/dev/null; then
- # Branch exists remotely, check if .nojekyll is present
- if git show gh-pages:.nojekyll &>/dev/null; then
- echo ".nojekyll already exists at gh-pages root"
- else
- echo "Adding .nojekyll to existing gh-pages branch"
- git checkout gh-pages
- touch .nojekyll
- git add .nojekyll
- git commit -m "Add .nojekyll to disable Jekyll processing"
- git push origin gh-pages
- git checkout -
- fi
- else
- # Branch doesn't exist, create it as an orphan branch
- echo "Creating gh-pages branch with .nojekyll"
- git checkout --orphan gh-pages
- git rm -rf .
- touch .nojekyll
- git add .nojekyll
- git commit -m "Initialize gh-pages with .nojekyll"
- git push origin gh-pages
- git checkout -
- fi
-
- - name: Deploy Preview
- # Deploys on build; on a 'closed' event `auto` removes the preview
- # instead — this is what keeps gh-pages from accumulating stale
- # previews. Skipped for fork PRs (no write access).
- if: steps.gate.outputs.deploy == 'true'
- uses: rossjrw/pr-preview-action@v1
+ uses: cloudflare/wrangler-action@v4
+ with:
+ apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
+ accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
+ workingDirectory: apps/cli-docs
+ # Uses the workspace-pinned wrangler (apps/cli-docs devDependency)
+ # rather than letting the action install its own copy.
+ wranglerVersion: "4"
+ command: versions upload
+
+ - name: Comment preview URL
+ if: steps.gate.outputs.deploy == 'true' && github.event_name == 'pull_request'
+ uses: marocchino/sticky-pull-request-comment@v2
with:
- source-dir: apps/cli-docs/dist/
- preview-branch: gh-pages
- umbrella-dir: _preview
- pages-base-url: cli.sentry.dev
- action: ${{ github.event_name == 'push' && 'deploy' || 'auto' }}
- pr-number: ${{ github.event_name == 'push' && 'main' || github.event.pull_request.number }}
- comment: ${{ github.event_name != 'push' }}
+ header: docs-preview
+ message: |
+ Docs preview for this PR: ${{ steps.preview.outputs.deployment-url }} |
|
fix-ci: attempt 1 — the E2E failure is infra, not code. The job's "Download Linux binary" step hit |
Adds the wrangler config (assets-only Worker serving Astro's dist/) and the wrangler devDependency for the cli.sentry.dev docs site, in preparation for moving off GitHub Pages onto a release-gated Cloudflare deploy. The workflow changes that wire this up (release-gated docs-deploy.yml, Cloudflare PR previews, and dropping the gh-pages Craft target) require the `workflows` GitHub-App permission to push and are attached to the PR for a maintainer to apply — see the PR description. Refs #1245
…ories wrangler pulls in undici 7.28.0, which now trips GHSA-4cwx-7wf7-3272 (high) plus four moderates — all patched in 7.29.0. undici is a dev-only transitive dep (docs deploy), never shipped in the CLI.
Per BYK, the docs site should ship through Craft's cloudflare target (deployType: worker) rather than a standalone GitHub Actions deploy — same release-gated model the old gh-pages target used. Adds the cloudflare artifact + target; CI packages the worker as cloudflare.zip.
BYK: the Cloudflare worker is named just `cli` (cli.getsentry.workers.dev), not sentry-cli-docs. Also drop `workingDir` from the craft cloudflare target and package cloudflare.zip with wrangler.jsonc + dist/ at the archive root, so Craft deploys from where wrangler.jsonc lives after extraction (Craft flattens a single wrapping dir, which would have broken a workingDir: apps/cli-docs).
a49dfb1 to
1df6e2d
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1df6e2d. Configure here.

Summary
Deploys the
cli.sentry.devstatic documentation site to a Cloudflare Worker through Craft’scloudflaretarget. This replaces the GitHub Pages release artifact and removes the obsolete GitHub Pages preview cleanup workflow.Changes
cli, serving the built Astro site with the generated 404 page.pnpm run deploy.wrangler.jsoncanddist/into thecloudflarebuild artifact, which Craft deploys on release.gh-pagesCraft target, GitHub Pages artifact, and cleanup workflow.Verification
pnpm install --frozen-lockfilepnpm run generate:schemapnpm run generate:docspnpm --dir apps/cli-docs run buildpnpm --dir apps/cli-docs exec wrangler deploy --dry-runRefs #1245