mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
e32fdc813b
Fixes DOCS-174: the docs-preview workflow only fired on `pull_request: opened`. Subsequent pushes left the preview comment stale. ## Changes - Add `synchronize` and `reopened` to trigger types so subsequent pushes retrigger the workflow. - Add a workflow-level `concurrency` group keyed by PR number with `cancel-in-progress: true` so rapid successive pushes don't race the comment-upsert lookup. - Replace always-create comment logic with an upsert: find the existing comment containing `<!-- docs-preview -->` and PATCH it; fall through to create only when none exists or the PATCH itself fails (comment was deleted between find and update). - Filter the upsert lookup to comments authored by `github-actions[bot]` so a human comment containing the marker is never silently overwritten. - Decouple the `gh api` lookup from the `head -n 1` pipe so API failures (network, auth, rate-limit) propagate immediately instead of being swallowed by `|| true`. - Delete the stale preview comment when a `synchronize` push drops all Markdown changes (e.g. a follow-up push that removes the file an earlier push had previewed but still touches `docs/`). The previous preview comment would otherwise point at a deleted page. - Extract the marker and the comment-selector jq into a single `DOCS_PREVIEW_MARKER` variable and a `list_docs_preview_comments` shell function so the stale-cleanup and upsert branches share one source of truth. ## Out of scope Vercel ISR cache invalidation for feature branch previews requires a coder.com change (the `algolia-docs-sync` endpoint only accepts `main` and `release/*` refs). Tracked separately in DOCS-174 out-of-scope notes. Pulls that fully revert their `docs/` changes in a follow-up push won't fire this workflow at all (GitHub's `paths` filter requires a path match in the diff), so a stale preview comment can survive on that specific edge. Removing the `paths` filter to handle it would run the workflow on every PR push, which is disproportionate. Acknowledged in [CRF-12](https://github.com/coder/coder/pull/25456#discussion_r3313738550). <details> <summary>Implementation notes</summary> **Marker and selector deduplication**: The marker string and jq selector previously appeared at three sites (comment body, stale-cleanup API call, upsert API call). They're now consolidated into `DOCS_PREVIEW_MARKER` plus a `list_docs_preview_comments` shell function so a future marker change updates one place. **Comment body construction**: The double-quoted multi-line string form with escaped backticks (`` \` ``) for the inline-code spans is shellcheck-clean. An earlier draft used `printf -v comment_body` with a single-quoted format string containing backticks, which triggered SC2016; the printf-three-pieces workaround that replaced it has since been simplified to the direct double-quoted form. **Upsert logic**: `gh api --paginate` fetches all PR comments, jq filters to `github-actions[bot]`-authored comments containing the marker, and the workflow PATCHes the first match. If the PATCH fails (404 because the comment was deleted between find and update), the script falls through to `gh pr comment` to create a new one. Self-heals on the next push if both paths somehow fail. **Stale-cleanup logic**: Same selector as upsert, but in the early-exit branch when no Markdown files exist in this push. `DELETE` failures are logged and execution continues (the next push will re-attempt or post a fresh comment), so a transient API failure won't fail the CI job. </details> > Generated by Coder Agents on behalf of @nickvigilante
191 lines
7.9 KiB
YAML
191 lines
7.9 KiB
YAML
# This workflow posts a docs preview link as a PR comment whenever a
|
|
# pull request that touches docs/ is opened or updated. The preview
|
|
# is served by coder.com's branch-preview feature at /docs/@<branch>.
|
|
#
|
|
# The link deep-links to the first added/modified/renamed Markdown file
|
|
# under docs/ so reviewers land on the page that actually changed.
|
|
# Branch names are URL-encoded so that names containing slashes or
|
|
# other special characters produce working links.
|
|
#
|
|
# On subsequent pushes (synchronize) the existing comment is updated
|
|
# rather than creating a duplicate. If a previous push had a Markdown
|
|
# file but the current push has none, the stale comment is deleted so
|
|
# readers don't follow a dead deep-link. If the PR only deletes
|
|
# Markdown files (or only changes non-Markdown files such as images or
|
|
# manifest.json), no comment is posted.
|
|
|
|
name: docs-preview
|
|
|
|
on:
|
|
pull_request:
|
|
types:
|
|
- opened
|
|
- synchronize
|
|
- reopened
|
|
paths:
|
|
- "docs/**"
|
|
|
|
concurrency:
|
|
group: docs-preview-${{ github.event.pull_request.number }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
docs-preview:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
pull-requests: write # needed for commenting on PRs
|
|
steps:
|
|
- name: Post docs preview comment
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
BRANCH: ${{ github.event.pull_request.head.ref }}
|
|
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
# Marker embedded in the comment body so we can find this
|
|
# workflow's own comments later. Keep this in one place so
|
|
# later refactors don't drift between the body construction
|
|
# and the jq selectors used to find existing comments.
|
|
DOCS_PREVIEW_MARKER='<!-- docs-preview -->'
|
|
|
|
# Returns IDs of github-actions[bot] comments on the PR whose
|
|
# body contains DOCS_PREVIEW_MARKER. Used by both the stale-
|
|
# comment-cleanup branch (when this push has no Markdown
|
|
# changes) and the upsert branch below.
|
|
list_docs_preview_comments() {
|
|
gh api --paginate \
|
|
"repos/${REPO}/issues/${PR_NUMBER}/comments" \
|
|
--jq ".[] | select(.user.login == \"github-actions[bot]\") | select(.body | contains(\"${DOCS_PREVIEW_MARKER}\")) | .id"
|
|
}
|
|
|
|
# Fetch the list of non-deleted files from the PR. This is
|
|
# intentionally not piped into grep so that a gh-api failure
|
|
# (network, auth, rate-limit) propagates immediately instead
|
|
# of being swallowed by `|| true`.
|
|
all_files=$(gh api --paginate \
|
|
"repos/${REPO}/pulls/${PR_NUMBER}/files" \
|
|
--jq '.[] | select(.status != "removed") | .filename')
|
|
|
|
# Pick the first Markdown file under docs/. `|| true` keeps
|
|
# the pipeline from failing when grep finds no matches or
|
|
# head triggers SIGPIPE under `set -o pipefail`.
|
|
first_doc=$(printf '%s\n' "$all_files" \
|
|
| grep -E '^docs/.*\.md$' \
|
|
| head -n 1) || true
|
|
|
|
if [ -z "$first_doc" ]; then
|
|
echo "No added/modified Markdown files under docs/ on this push."
|
|
|
|
# Now that the workflow fires on synchronize, this branch
|
|
# is reachable on pushes that drop all Markdown while still
|
|
# touching docs/ (e.g. a push that removes the file an
|
|
# earlier push had previewed but adds a new image). The
|
|
# previous preview comment now points at a deleted page;
|
|
# delete it so readers don't follow a dead deep-link.
|
|
#
|
|
# Intentionally decoupled from head so that a gh-api failure
|
|
# propagates here instead of being swallowed by `|| true`. In
|
|
# this branch the workflow has no preview link to post anyway
|
|
# (no Markdown in the push), so a transient list failure is a
|
|
# cosmetic miss; log and exit cleanly rather than red-checking
|
|
# every docs-touching PR during a comments-endpoint hiccup.
|
|
# The next push will retry the cleanup. The upsert path below
|
|
# uses strict propagation by contrast, because silent failure
|
|
# there would create duplicate comments.
|
|
stale_comment_ids=$(list_docs_preview_comments) || {
|
|
echo "Could not list preview comments; skipping cleanup."
|
|
exit 0
|
|
}
|
|
stale_id=$(printf '%s\n' "$stale_comment_ids" | head -n 1) || true
|
|
|
|
if [ -n "$stale_id" ]; then
|
|
if gh api --method DELETE \
|
|
"repos/${REPO}/issues/comments/${stale_id}"; then
|
|
echo "Deleted stale docs preview comment (id=${stale_id})."
|
|
else
|
|
echo "Failed to delete stale docs preview comment (id=${stale_id}); leaving in place."
|
|
fi
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
# Map the repo path to the docs site URL path.
|
|
# docs/README.md -> "" (docs root)
|
|
# docs/<dir>/index.md -> "<dir>" (directory index)
|
|
# docs/<dir>/README.md -> "<dir>" (directory index)
|
|
# docs/<dir>/<file>.md -> "<dir>/<file>"
|
|
rel="${first_doc#docs/}"
|
|
case "$rel" in
|
|
README.md)
|
|
page_path=""
|
|
;;
|
|
*)
|
|
base="$(basename "$rel")"
|
|
dir="$(dirname "$rel")"
|
|
if [ "$dir" = "." ]; then
|
|
dir=""
|
|
fi
|
|
case "$base" in
|
|
index.md|README.md)
|
|
page_path="$dir"
|
|
;;
|
|
*)
|
|
stripped="${base%.md}"
|
|
if [ -z "$dir" ]; then
|
|
page_path="$stripped"
|
|
else
|
|
page_path="${dir}/${stripped}"
|
|
fi
|
|
;;
|
|
esac
|
|
;;
|
|
esac
|
|
|
|
# URL-encode the branch name so slashes and special
|
|
# characters don't break the preview URL. The page path is
|
|
# left as-is because its components are simple ASCII path
|
|
# segments and the slashes between them must be preserved.
|
|
encoded_branch=$(jq -rn --arg b "$BRANCH" '$b | @uri')
|
|
url="https://coder.com/docs/@${encoded_branch}"
|
|
if [ -n "$page_path" ]; then
|
|
url="${url}/${page_path}"
|
|
fi
|
|
|
|
# The literal backticks around ${first_doc} are escaped so
|
|
# they survive the double-quoted string as Markdown inline
|
|
# code; ${url} and ${first_doc} expand normally.
|
|
comment_body="## Docs preview
|
|
[:book: View docs preview](${url}) for \`${first_doc}\`
|
|
|
|
${DOCS_PREVIEW_MARKER}"
|
|
|
|
# Upsert: update the existing docs-preview comment if one
|
|
# exists, otherwise create a new one. This prevents duplicate
|
|
# preview comments on every push to the PR.
|
|
#
|
|
# Intentionally not piped into head so that a gh-api failure
|
|
# (network, auth, rate-limit) propagates immediately instead
|
|
# of being swallowed by `|| true`.
|
|
all_comment_ids=$(list_docs_preview_comments)
|
|
existing_id=$(printf '%s\n' "$all_comment_ids" | head -n 1) || true
|
|
|
|
if [ -n "$existing_id" ]; then
|
|
if ! gh api --method PATCH \
|
|
"repos/${REPO}/issues/comments/${existing_id}" \
|
|
--field body="$comment_body"; then
|
|
echo "PATCH failed (comment may have been deleted); creating a new comment."
|
|
existing_id=""
|
|
else
|
|
echo "Updated existing docs preview comment (id=${existing_id})."
|
|
fi
|
|
fi
|
|
if [ -z "$existing_id" ]; then
|
|
gh pr comment "${PR_NUMBER}" \
|
|
--repo "${REPO}" \
|
|
--body "$comment_body"
|
|
echo "Created new docs preview comment."
|
|
fi
|