From 917dbde439dabb52dd37f9b29324ee2551711a7e Mon Sep 17 00:00:00 2001 From: Zach <3724288+zedkipp@users.noreply.github.com> Date: Mon, 22 Jun 2026 11:12:26 -0600 Subject: [PATCH] fix: regen feature stage docs from HEAD & enforce generation (#26528) Generate the experimental and beta tables in docs/install/releases/feature-stages.md from the current source tree instead of release tags + GitHub API because we found the table of beta features was stale in recent release(s). This approach works now that Coder publishes per-release docs. This change was assisted by Coder Agents. --- Makefile | 13 + docs/install/releases/feature-stages.md | 14 +- scripts/release/docs_update_feature_stages.sh | 237 ++++++------------ 3 files changed, 99 insertions(+), 165 deletions(-) diff --git a/Makefile b/Makefile index 6c6d03f362..409b70ba3c 100644 --- a/Makefile +++ b/Makefile @@ -967,6 +967,7 @@ GEN_FILES := \ docs/admin/integrations/prometheus.md \ docs/reference/cli/index.md \ docs/admin/security/audit-logs.md \ + docs/install/releases/feature-stages.md \ coderd/apidoc/swagger.json \ docs/manifest.json \ provisioner/terraform/testdata/version \ @@ -1044,6 +1045,7 @@ gen/mark-fresh: docs/admin/integrations/prometheus.md \ docs/reference/cli/index.md \ docs/admin/security/audit-logs.md \ + docs/install/releases/feature-stages.md \ coderd/apidoc/swagger.json \ docs/manifest.json \ site/e2e/provisionerGenerated.ts \ @@ -1279,6 +1281,17 @@ docs/admin/security/audit-logs.md: node_modules/.installed coderd/database/queri pnpm exec markdown-table-formatter "$$tmpfile" && \ mv "$$tmpfile" "$@" && rm -rf "$$tmpdir" +docs/install/releases/feature-stages.md: \ + node_modules/.installed \ + scripts/release/docs_update_feature_stages.sh \ + codersdk/deployment.go \ + docs/manifest.json | _gen + tmpdir=$$(mktemp -d -p _gen) && tmpfile=$$(realpath "$$tmpdir")/$(notdir $@) && cp "$@" "$$tmpfile" && \ + ./scripts/release/docs_update_feature_stages.sh "$$tmpfile" && \ + pnpm exec markdownlint-cli2 --fix "$$tmpfile" && \ + pnpm exec markdown-table-formatter "$$tmpfile" && \ + mv "$$tmpfile" "$@" && rm -rf "$$tmpdir" + coderd/apidoc/.gen: \ node_modules/.installed \ scripts/apidocgen/node_modules/.installed \ diff --git a/docs/install/releases/feature-stages.md b/docs/install/releases/feature-stages.md index 8cbe79b94a..2ca91c2926 100644 --- a/docs/install/releases/feature-stages.md +++ b/docs/install/releases/feature-stages.md @@ -64,7 +64,7 @@ You can opt-out of a feature after you've enabled it. -Currently no experimental features are available in the latest mainline or stable release. +Currently no experimental features are available. ## Beta @@ -102,12 +102,12 @@ available in the documentation. -| Feature | Description | Available in | -|------------------------------------------------------------------------------|------------------------------------------------|------------------| -| [MCP Server](../../ai-coder/mcp-server.md) | Connect to agents Coder with a MCP server | mainline, stable | -| [JetBrains Toolbox](../../user-guides/workspace-access/jetbrains/toolbox.md) | Access Coder workspaces from JetBrains Toolbox | mainline, stable | -| Agent Firewall | Understanding Agent Firewall in Coder Tasks | stable | -| [Workspace Sharing](../../user-guides/shared-workspaces.md) | Sharing workspaces | mainline, stable | +| Feature | Description | +|------------------------------------------------------------------------------|----------------------------------------------------------------------------| +| [MCP Server](../../ai-coder/mcp-server.md) | Connect AI coding agents to Coder using the MCP server | +| [JetBrains Toolbox](../../user-guides/workspace-access/jetbrains/toolbox.md) | Access Coder workspaces from JetBrains Toolbox | +| [User secrets](../../user-guides/user-secrets.md) | Store secret values in Coder and automatically inject them into workspaces | +| [Coder Agents](../../ai-coder/agents/index.md) | Self-hosted agent by Coder | ## General Availability (GA) diff --git a/scripts/release/docs_update_feature_stages.sh b/scripts/release/docs_update_feature_stages.sh index ccd019e313..4251ea8dbc 100755 --- a/scripts/release/docs_update_feature_stages.sh +++ b/scripts/release/docs_update_feature_stages.sh @@ -1,90 +1,33 @@ #!/usr/bin/env bash -# Usage: ./docs_update_feature_stages.sh +# Usage: ./docs_update_feature_stages.sh [file] # -# Updates generated sections in docs/install/releases/feature-stages.md: -# early-access (experimental) features from codersdk, and beta features from -# docs/manifest.json. Uses sparse checkouts of mainline and stable tags. +# Updates the generated sections of feature-stages.md in place. Defaults +# to docs/install/releases/feature-stages.md (relative to the repo root). +# The file must already exist and contain the BEGIN/END marker comments. set -euo pipefail # shellcheck source=scripts/lib.sh source "$(dirname "${BASH_SOURCE[0]}")/../lib.sh" cdroot -# Ensure GITHUB_TOKEN is available -if [[ -z "${GITHUB_TOKEN:-}" ]]; then - if GITHUB_TOKEN="$(gh auth token 2>/dev/null)"; then - export GITHUB_TOKEN - else - echo "Error: GitHub token not found. Please run 'gh auth login' to authenticate." >&2 - exit 1 - fi -fi - if isdarwin; then dependencies gsed gawk sed() { gsed "$@"; } awk() { gawk "$@"; } fi -echo_latest_stable_version() { - # Extract redirect URL to determine latest stable tag - version="$(curl -fsSLI -o /dev/null -w "%{url_effective}" https://github.com/coder/coder/releases/latest)" - version="${version#https://github.com/coder/coder/releases/tag/v}" - echo "v${version}" -} - -echo_latest_mainline_version() { - # Use GitHub API to get latest release version, authenticated - echo "v$( - curl -fsSL -H "Authorization: token ${GITHUB_TOKEN}" https://api.github.com/repos/coder/coder/releases | - awk -F'"' '/"tag_name"/ {print $4}' | - tr -d v | - tr . ' ' | - sort -k1,1nr -k2,2nr -k3,3nr | - head -n1 | - tr ' ' . - )" -} - -echo_latest_main_version() { - echo origin/main -} - -sparse_clone_codersdk() { - mkdir -p "${1}" - cd "${1}" - rm -rf "${2}" - git clone --quiet --no-checkout "${PROJECT_ROOT}" "${2}" - cd "${2}" - git sparse-checkout set --no-cone codersdk - git checkout "${3}" -- codersdk - echo "${1}/${2}" -} - -clone_sparse_path() { - mkdir -p "${1}" - cd "${1}" - rm -rf "${2}" - git clone --quiet --no-checkout "${PROJECT_ROOT}" "${2}" - cd "${2}" - git sparse-checkout set --no-cone "${4}" - git checkout "${3}" -- "${4}" - echo "${1}/${2}" -} - parse_all_experiments() { - # Try ExperimentsSafe first, then fall back to ExperimentsAll if needed + # Try ExperimentsSafe first, then fall back to ExperimentsAll if needed. experiments_var="ExperimentsSafe" - experiments_output=$(go doc -all -C "${dir}" ./codersdk "${experiments_var}" 2>/dev/null || true) + experiments_output=$(go doc -all ./codersdk "${experiments_var}" 2>/dev/null || true) if [[ -z "${experiments_output}" ]]; then - # Fall back to ExperimentsAll if ExperimentsSafe is not found experiments_var="ExperimentsAll" - experiments_output=$(go doc -all -C "${dir}" ./codersdk "${experiments_var}" 2>/dev/null || true) + experiments_output=$(go doc -all ./codersdk "${experiments_var}" 2>/dev/null || true) if [[ -z "${experiments_output}" ]]; then - log "Warning: Neither ExperimentsSafe nor ExperimentsAll found in ${dir}" + log "Warning: Neither ExperimentsSafe nor ExperimentsAll found in ./codersdk" return fi fi @@ -97,7 +40,7 @@ parse_all_experiments() { } parse_experiments() { - go doc -all -C "${1}" ./codersdk Experiment | + go doc -all ./codersdk Experiment | sed \ -e 's/\t\(Experiment[^ ]*\)\ \ *Experiment = "\([^"]*\)"\(.*\/\/ \(.*\)\)\?/\1|\2|\4/' \ -e 's/\t\/\/ \(.*\)/||\1/' | @@ -106,136 +49,116 @@ parse_experiments() { parse_beta_features() { jq -r ' - .routes[] - | recurse(.children[]?) + # Collect paths that live under any beta-marked subtree. We exclude + # the beta node itself so a beta root still emits as a row; only its + # descendants are suppressed. + [ + .routes[] | recurse(.children[]?) + | select((.state // []) | index("beta")) + | .children[]? | recurse(.children[]?) + | .path | select(. != null) + ] as $covered + | + # Emit every beta node whose path is not covered. A doc cross-listed + # under both a beta and a non-beta parent is treated as beta-covered + # and dropped from the table. + .routes[] | recurse(.children[]?) | select((.state // []) | index("beta")) + | select((.path // "") as $p | $covered | index($p) | not) | [.title, (.description // ""), (.path // "")] | join("|") - ' "${1}/docs/manifest.json" + ' "${PROJECT_ROOT}/docs/manifest.json" } -workdir=build/docs/feature-stages -dest=docs/install/releases/feature-stages.md +dest=${1:-docs/install/releases/feature-stages.md} log "Updating generated feature-stages sections in ${dest}" -declare -A experiments=() experiment_tags=() -declare -A beta_features=() beta_feature_descriptions=() beta_feature_tags=() +# Collect experiments from the current codersdk package. +declare -A experiments=() +declare -A all_experiments=() +all_experiments_out="$(parse_all_experiments)" +if [[ -n "${all_experiments_out}" ]]; then + readarray -t all_experiments_tmp <<<"${all_experiments_out}" + for exp in "${all_experiments_tmp[@]}"; do + all_experiments[$exp]=1 + done +fi -for channel in mainline stable; do - log "Fetching experiments from ${channel}" +maybe_desc= +while read -r line; do + line=${line//$'\n'/} + readarray -d '|' -t parts <<<"$line" - tag=$(echo_latest_"${channel}"_version) - if [[ -z "${tag}" || "${tag}" == "v" ]]; then - echo "Error: Failed to retrieve valid ${channel} version tag. Check your GitHub token or rate limit." >&2 - exit 1 + if [[ -z ${parts[0]} ]]; then + maybe_desc+="${parts[2]//$'\n'/ }" + continue fi - dir="$(sparse_clone_codersdk "${workdir}" "${channel}" "${tag}")" + var="${parts[0]}" + key="${parts[1]}" + desc="${parts[2]}" + desc=${desc//$'\n'/} - declare -A all_experiments=() - all_experiments_out="$(parse_all_experiments "${dir}")" - if [[ -n "${all_experiments_out}" ]]; then - readarray -t all_experiments_tmp <<<"${all_experiments_out}" - for exp in "${all_experiments_tmp[@]}"; do - all_experiments[$exp]=1 - done + if [[ -z "${desc}" ]]; then + desc="${maybe_desc% }" fi - maybe_desc= - while read -r line; do - line=${line//$'\n'/} - readarray -d '|' -t parts <<<"$line" + if [[ ! -v all_experiments[$var] ]]; then + log "Skipping ${var}, not listed in experiments list" + continue + fi - if [[ -z ${parts[0]} ]]; then - maybe_desc+="${parts[2]//$'\n'/ }" - continue - fi - - var="${parts[0]}" - key="${parts[1]}" - desc="${parts[2]}" - desc=${desc//$'\n'/} - - if [[ -z "${desc}" ]]; then - desc="${maybe_desc% }" - fi - maybe_desc= - - if [[ ! -v all_experiments[$var] ]]; then - log "Skipping ${var}, not listed in experiments list" - continue - fi - - if [[ ! -v experiments[$key] ]]; then - experiments[$key]="$desc" - fi - - experiment_tags[$key]+="${channel}, " - done < <(parse_experiments "${dir}") -done + experiments[$key]="$desc" +done < <(parse_experiments) table="$( if [[ "${#experiments[@]}" -eq 0 ]]; then - echo "Currently no experimental features are available in the latest mainline or stable release." + echo "Currently no experimental features are available." exit 0 fi - echo "| Feature | Description | Available in |" - echo "| ------- | ----------- | ------------ |" + echo "| Feature | Description |" + echo "| ------- | ----------- |" for key in "${!experiments[@]}"; do desc=${experiments[$key]} - tags=${experiment_tags[$key]%, } - echo "| \`$key\` | $desc | ${tags} |" + echo "| \`$key\` | $desc |" done )" -for channel in mainline stable; do - log "Fetching beta features from ${channel}" - - tag=$(echo_latest_"${channel}"_version) - if [[ -z "${tag}" || "${tag}" == "v" ]]; then - echo "Error: Failed to retrieve valid ${channel} version tag. Check your GitHub token or rate limit." >&2 - exit 1 +# Collect beta features from the current docs/manifest.json. Keying on the +# route path also dedupes routes that appear under more than one parent. +declare -A beta_features=() beta_feature_descriptions=() +while IFS='|' read -r title desc doc_path; do + if [[ -z "${title}" ]]; then + continue fi - dir="$(clone_sparse_path "${workdir}" "docs-${channel}" "${tag}" "docs/manifest.json")" + key="${doc_path}" + if [[ -z "${key}" ]]; then + key="${title}" + fi - while IFS='|' read -r title desc doc_path; do - if [[ -z "${title}" ]]; then - continue - fi - - key="${doc_path}" - if [[ -z "${key}" ]]; then - key="${title}" - fi - - if [[ ! -v beta_features[$key] ]]; then - beta_features[$key]="${title}" - beta_feature_descriptions[$key]="${desc}" - fi - - beta_feature_tags[$key]+="${channel}, " - done < <(parse_beta_features "${dir}") -done + if [[ ! -v beta_features[$key] ]]; then + beta_features[$key]="${title}" + beta_feature_descriptions[$key]="${desc}" + fi +done < <(parse_beta_features) beta_table="$( if [[ "${#beta_features[@]}" -eq 0 ]]; then - echo "Currently no beta features are available in the latest mainline or stable release." + echo "Currently no beta features are available." exit 0 fi - echo "| Feature | Description | Available in |" - echo "| ------- | ----------- | ------------ |" + echo "| Feature | Description |" + echo "| ------- | ----------- |" for key in "${!beta_features[@]}"; do title=${beta_features[$key]} desc=${beta_feature_descriptions[$key]} - tags=${beta_feature_tags[$key]%, } - # Only link when the target exists in this tree. Stable and mainline - # manifests can diverge; avoid broken relative links in feature-stages.md. + # Linkify when the target exists in this tree. if [[ "${key}" == ./* ]]; then rel="${key#./}" if [[ -f "${PROJECT_ROOT}/docs/${rel}" ]]; then @@ -243,7 +166,7 @@ beta_table="$( fi fi - echo "| ${title} | ${desc} | ${tags} |" + echo "| ${title} | ${desc} |" done )" @@ -261,5 +184,3 @@ awk \ "${dest}" \ >"${dest}".tmp mv "${dest}".tmp "${dest}" - -(cd site && pnpm exec prettier --cache --write ../"${dest}")