mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
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.
This commit is contained in:
@@ -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}")
|
||||
|
||||
Reference in New Issue
Block a user