Thanks to visit codestin.com
Credit goes to github.com

Skip to content

docs: replace the oauth2 experiment with CODER_OAUTH2_PROVIDER_ENABLE #5639

docs: replace the oauth2 experiment with CODER_OAUTH2_PROVIDER_ENABLE

docs: replace the oauth2 experiment with CODER_OAUTH2_PROVIDER_ENABLE #5639

Workflow file for this run

# This workflow posts a docs preview comment listing every navigable
# page a pull request touches. The preview is served by coder.com's
# branch-preview feature at /docs/@<branch>.
#
# Each page in the list gets its own preview link plus a Markdown
# task-list checkbox, so a reviewer can tick off pages as they review
# them. State is round-tripped across pushes: a checkbox a reviewer
# already ticked stays ticked as long as that page hasn't changed
# since, but flips back to unchecked the moment new content lands on
# that page, since a checked box should mean "I've reviewed the
# current revision," not "I reviewed some earlier revision of this
# page."
#
# The checkbox contract (reset-on-change) matches GitHub's native
# per-file "Viewed" control, but Viewed tracks the raw diff and can't
# deep-link to the rendered coder.com preview. This checklist tracks
# review of the preview page itself, which the platform doesn't
# provide, so the state is reimplemented here rather than reused.
#
# Only pages that resolve to a route in docs/manifest.json get a
# link. Anything else (docs/.style/** contributor tooling, or a page
# that hasn't been wired into navigation yet) is dropped from the list
# entirely, since those pages 404 on the docs site and would confuse
# reviewers.
#
# 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 eligible
# Markdown files 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/**"
# docs/.style/** is contributor tooling and never deploys to coder.com.
# Skipping the workflow on .style-only PRs avoids posting a preview
# comment with an empty page list. Mixed PRs still trigger; the
# selection logic below filters .style files out of the preview list.
- "!docs/.style/**"
concurrency:
group: docs-preview-${{ github.event.pull_request.number }}
cancel-in-progress: true
permissions:
contents: read
jobs:
docs-preview:
runs-on: ubuntu-latest
permissions:
# Job-level permissions replace (not merge with) the workflow-level
# defaults above, so contents: read has to be repeated here for the
# docs/manifest.json contents-API read below.
contents: read
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 }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# DOCS_PREVIEW_MARKER locates this workflow's own comments.
# STATE_PREFIX carries the last-seen `path -> blob sha` map for
# change detection. Keep this script's map_doc_path, manifest
# filter, and carryover logic in sync with
# test-docs-preview-mapper.sh.
DOCS_PREVIEW_MARKER='<!-- docs-preview -->'
STATE_PREFIX='docs-preview-state:'
# Returns IDs of github-actions[bot] comments on the PR whose
# body contains DOCS_PREVIEW_MARKER.
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"
}
# Deletes the existing docs-preview comment (found earlier as
# existing_id) and exits 0, so a stale comment doesn't point
# readers at a dead deep-link. A failed delete is only cosmetic,
# so it logs and exits clean; the next push retries. The upsert
# path uses strict propagation instead, since silent failure
# there would duplicate comments.
cleanup_stale_and_exit() {
if [ -n "$existing_id" ]; then
if gh api --method DELETE \
"repos/${REPO}/issues/comments/${existing_id}"; then
echo "Deleted stale docs preview comment (id=${existing_id})."
else
echo "Failed to delete stale docs preview comment (id=${existing_id}); leaving in place. This is usually a transient API error, and the next push retries the cleanup." >&2
fi
fi
exit 0
}
# Maps a 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>"
map_doc_path() {
local doc_path="$1"
local rel="${doc_path#docs/}"
local page_path
case "$rel" in
README.md)
page_path=""
;;
*)
local base dir stripped
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
printf '%s' "$page_path"
}
# Look up the existing docs-preview comment id up front so both
# the cleanup path and the upsert path can reuse it without
# listing twice. The body is fetched later, just before state
# recovery, to keep the read-modify-write window small.
#
# Keep the strict list separate from the tolerant head. A real
# list failure (network, auth, rate-limit) must propagate under
# set -e; otherwise the upsert treats it as "no comment" and
# posts a duplicate. The `|| true` only absorbs head's SIGPIPE
# on the printf feeding head.
all_comment_ids=$(list_docs_preview_comments)
existing_id=$(printf '%s\n' "$all_comment_ids" | head -n 1) || true
# Fetch the non-removed Markdown files under docs/ (excluding
# docs/.style/**) this PR currently touches, one <filename>\t<sha>
# pair per line. `.sha` is the blob sha of the file's content at
# this push, which is what lets later runs detect "this page
# changed since it was last listed" without a full checkout.
#
# 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`.
#
# `pulls/files` truncates at GitHub's 3000-file ceiling, which
# --paginate does not lift. A PR that changes 3000+ files would
# list only the first 3000; docs PRs never approach that.
changed_tsv=$(gh api --paginate \
"repos/${REPO}/pulls/${PR_NUMBER}/files" \
--jq '.[] | select(.status != "removed") | select(.filename | test("^docs/.*\\.md$")) | select((.filename | test("^docs/\\.style/")) | not) | [.filename, .sha] | @tsv')
if [ -z "$changed_tsv" ]; then
echo "No added/modified Markdown files under docs/ (outside docs/.style/) on this push."
cleanup_stale_and_exit
fi
# Fetch docs/manifest.json at the PR head sha (this job never
# checks the repo out) and collect every object with a "path"
# key, which in the manifest is always a navigable page entry.
# Manifest paths are written "./foo/bar.md" or "foo/bar.md"
# relative to docs/; normalize both to "docs/foo/bar.md" so
# they compare directly against the PR-files filenames above.
#
# Request the raw blob rather than the JSON envelope so the
# read has no 1MB inline-body ceiling, which would otherwise
# return an empty body and silently drop every page.
#
# This makes the manifest an implicit hard dependency of comment
# persistence: if the manifest schema ever drops or renames the
# "path" key, the fetch still succeeds but allowed_paths comes
# back empty, no page is eligible, and the run takes
# cleanup_stale_and_exit, deleting the comment and its checkbox
# state. "Parsed fine, zero matches" is indistinguishable from
# "format changed," so a future manifest refactor must keep this
# extraction in step.
manifest_content=$(gh api -H "Accept: application/vnd.github.raw" "repos/${REPO}/contents/docs/manifest.json?ref=${HEAD_SHA}")
allowed_paths=$(printf '%s' "$manifest_content" \
| jq -r '[.. | objects | select(has("path")) | .path] | .[]' \
| sed -E 's#^\./##; s#^#docs/#')
# Intersect the changed-files set with the manifest allowlist.
# A file with no manifest route 404s on the docs site, so drop
# it from the list rather than link to a broken preview.
eligible_tsv=$(printf '%s\n' "$changed_tsv" | while IFS=$'\t' read -r filename sha; do
[ -z "$filename" ] && continue
if printf '%s\n' "$allowed_paths" | grep -qxF "$filename"; then
printf '%s\t%s\n' "$filename" "$sha"
fi
done)
if [ -z "$eligible_tsv" ]; then
echo "No changed Markdown files resolve to a docs/manifest.json route."
echo "(If pages you expect are missing, check docs/manifest.json's schema: an empty allowlist looks identical to no eligible pages.)"
cleanup_stale_and_exit
fi
eligible_json=$(printf '%s\n' "$eligible_tsv" \
| jq -R -s '[splits("\n") | select(length > 0) | split("\t") | {filename: .[0], sha: .[1]}]')
# Fetch the existing comment body now, right before reading its
# checkbox state, so a reviewer's toggle isn't overwritten by a
# stale read taken several API calls earlier. `|| true` keeps a
# transient API error from failing the run; state recovery then
# just treats every page as new.
#
# A reviewer toggle that lands in the small window between this
# read and the PATCH below is lost, but reappears correctly on
# the next push. Accepted limitation, not a bug.
existing_body=""
if [ -n "$existing_id" ]; then
existing_body=$(gh api "repos/${REPO}/issues/comments/${existing_id}" --jq '.body' || true)
if [ -z "$existing_body" ]; then
# A docs-preview comment always contains its body, so an
# empty read against a known id is a transient fetch
# failure, not a legitimately empty comment. State recovery
# will reset every checkbox this push; log a breadcrumb so
# the reset isn't silent (it self-heals on the next push).
echo "Could not read existing comment ${existing_id}; checkbox state resets this push (transient, self-heals)." >&2
fi
fi
# Recover state from the existing comment, if any:
# - old_state: the path -> sha map this workflow wrote the
# last time it updated the comment (hidden marker).
# - old_checked: the path -> checked map read from the
# *live* checkbox glyphs in the comment body, which is
# where a reviewer's manual clicks land (GitHub persists a
# checkbox toggle as an edit to the comment body).
old_state_json="{}"
old_checked_json="{}"
if [ -n "$existing_body" ]; then
old_state_b64=$(printf '%s\n' "$existing_body" | grep -oE "${STATE_PREFIX}[A-Za-z0-9+/=]+" | sed "s/^${STATE_PREFIX}//") || true
if [ -n "$old_state_b64" ]; then
# Guard the decode: a truncated or corrupted marker must
# degrade to "treat every page as new", not kill the run
# (base64 -d and jq both run under set -e). Reject an empty
# decode before the type check: on jq < 1.7 `jq -e` exits 0
# on empty input, so the type check alone would accept an
# empty string and `--argjson old_state ""` would abort the
# run. Require a non-empty result that parses as a JSON
# object, else keep {}.
decoded=$(printf '%s' "$old_state_b64" | base64 -d 2>/dev/null || true)
if [ -n "$decoded" ] && printf '%s' "$decoded" | jq -e 'type == "object"' >/dev/null 2>&1; then
old_state_json="$decoded"
fi
fi
# shellcheck disable=SC2016 # backticks below are literal Markdown code-span delimiters, not command substitution.
old_checked_json=$(printf '%s\n' "$existing_body" \
| grep -oE '^[[:space:]]*- \[[ xX]\] \[`[^`]+`\]' \
| sed -E 's/^[[:space:]]*- \[([ xX])\] \[`([^`]+)`\]/\1\t\2/' \
| jq -R -s '[splits("\n") | select(length > 0) | split("\t") | {(.[1]): (.[0] | test("x"; "i"))}] | add // {}') || true
fi
# Decide each page's checked state: carry the live checkbox
# value forward only if the page's blob sha hasn't changed
# since the last time this workflow wrote the state marker.
# New pages, and pages whose sha moved, start unchecked.
final_rows=$(jq -n \
--argjson eligible "$eligible_json" \
--argjson old_state "$old_state_json" \
--argjson old_checked "$old_checked_json" \
'[
$eligible[] | . as $f |
($old_state[$f.filename] // null) as $prev_sha |
(if $prev_sha != null and $prev_sha == $f.sha
then ($old_checked[$f.filename] // false)
else false
end) as $checked |
{filename: $f.filename, sha: $f.sha, checked: $checked}
] | sort_by(.filename)')
# 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_prefix="https://coder.com/docs/@${encoded_branch}"
total_pages=$(printf '%s' "$final_rows" | jq 'length')
# Assemble the comment body for the first N pages: the checklist,
# the hidden base64 state marker, and (when N < total_pages) the
# omitted-pages summary line. Both the checklist and the marker
# derive from the same N rows, so this prints exactly the bytes
# that get posted, which is what lets the caller size the comment
# by measuring rather than estimating.
build_comment_body() {
local n="$1" rows state_json state_b64 checklist="" intro
local filename checked page_path url box omitted
rows=$(printf '%s' "$final_rows" | jq -c --argjson n "$n" '.[:$n]')
state_json=$(printf '%s' "$rows" | jq -c 'map({(.filename): .sha}) | add // {}')
state_b64=$(printf '%s' "$state_json" | base64 -w0)
while IFS=$'\t' read -r filename checked; do
[ -z "$filename" ] && continue
page_path=$(map_doc_path "$filename")
url="$url_prefix"
if [ -n "$page_path" ]; then
url="${url}/${page_path}"
fi
box=" "
if [ "$checked" = "true" ]; then
box="x"
fi
# The backticks are literal Markdown code-span delimiters.
checklist="${checklist}- [${box}] [\`${filename}\`](${url})"$'\n'
done < <(printf '%s' "$rows" | jq -r '.[] | [.filename, (.checked | tostring)] | @tsv')
omitted=$((total_pages - n))
if [ "$omitted" -gt 0 ]; then
checklist="${checklist}"$'\n'"_and ${omitted} more changed page(s) not listed to stay under GitHub's comment size limit. See the [Files tab](https://github.com/${REPO}/pull/${PR_NUMBER}/files) for the full list._"$'\n'
fi
intro="Check off each page once it's been reviewed. If a page changes in a later push, its checkbox clears automatically so it gets a fresh look. Pages not yet wired into the docs navigation aren't listed here."
printf '## Docs preview\n\n%s\n\n%s\n%s\n<!-- %s%s -->' \
"$intro" "$checklist" "$DOCS_PREVIEW_MARKER" "$STATE_PREFIX" "$state_b64"
}
# GitHub caps a comment body at 65536 characters. Estimating the
# per-page cost drifts from the real size (each page adds a
# checklist line and a base64 state entry whose combined length
# depends on the path), so assemble the real body and measure it
# instead. Keep every page if they all fit; otherwise binary
# search for the largest leading prefix that stays under budget.
# Body size grows monotonically with the page count, so the
# search is well defined. 65000 leaves headroom under the limit.
comment_budget=65000
body_bytes() { LC_ALL=C wc -c; }
if [ "$(build_comment_body "$total_pages" | body_bytes)" -le "$comment_budget" ]; then
keep_pages=$total_pages
else
lo=0
hi=$((total_pages - 1))
keep_pages=0
while [ "$lo" -le "$hi" ]; do
mid=$(((lo + hi) / 2))
if [ "$(build_comment_body "$mid" | body_bytes)" -le "$comment_budget" ]; then
keep_pages=$mid
lo=$((mid + 1))
else
hi=$((mid - 1))
fi
done
fi
# Always list at least one page. The binary search can floor at
# 0 only if a single line exceeds the budget (impossible at real
# path lengths), and an empty list under an "and N more" summary
# would be self-contradicting; a floor of 1 makes that
# unreachable state impossible.
if [ "$keep_pages" -lt 1 ]; then
keep_pages=1
fi
omitted_pages=$((total_pages - keep_pages))
echo "Listing ${keep_pages} of ${total_pages} changed page(s); ${omitted_pages} omitted for comment size."
comment_body=$(build_comment_body "$keep_pages")
# Upsert: PATCH the existing comment if we found one, else
# create it. existing_id is re-derived from a live list on
# every run (never persisted), so a genuinely deleted comment
# isn't found and lands in the create branch below.
#
# Therefore a PATCH failure against a known existing_id almost
# always means the comment still exists and the error is
# transient: never create in that case, or we post a permanent
# duplicate (the write-path sibling of the guarded list
# failure). Fail instead; the next push retries.
if [ -n "$existing_id" ]; then
if gh api --method PATCH \
"repos/${REPO}/issues/comments/${existing_id}" \
--raw-field body="$comment_body"; then
echo "Updated existing docs preview comment (id=${existing_id})."
else
echo "Failed to update docs preview comment ${existing_id}; leaving it in place to avoid a duplicate. This is usually a transient API error, and the next push will retry." >&2
exit 1
fi
else
gh pr comment "${PR_NUMBER}" \
--repo "${REPO}" \
--body "$comment_body"
echo "Created new docs preview comment."
fi