From 741d922c7eacaa8b3879508e590207445d739f27 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 17 Jul 2026 09:00:47 +0300 Subject: [PATCH 1/2] Harden developer guide redirect updates --- .github/workflows/website-docs.yml | 3 + .../test_update_developer_guide_redirect.sh | 120 ++++++++++++++++++ .../update_developer_guide_redirect.sh | 41 +++++- 3 files changed, 159 insertions(+), 5 deletions(-) create mode 100755 scripts/website/test_update_developer_guide_redirect.sh diff --git a/.github/workflows/website-docs.yml b/.github/workflows/website-docs.yml index b46bd52dcc0..0cf93db1062 100644 --- a/.github/workflows/website-docs.yml +++ b/.github/workflows/website-docs.yml @@ -106,6 +106,9 @@ jobs: env: GITHUB_TOKEN: ${{ github.token }} + - name: Test developer guide PDF redirect resolver + run: scripts/website/test_update_developer_guide_redirect.sh + - name: Update developer guide PDF redirect run: | set -euo pipefail diff --git a/scripts/website/test_update_developer_guide_redirect.sh b/scripts/website/test_update_developer_guide_redirect.sh new file mode 100755 index 00000000000..d2c4ca65640 --- /dev/null +++ b/scripts/website/test_update_developer_guide_redirect.sh @@ -0,0 +1,120 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +UPDATER="${SCRIPT_DIR}/update_developer_guide_redirect.sh" + +tmp_dir="$(mktemp -d)" +cleanup() { + rm -rf "${tmp_dir}" +} +trap cleanup EXIT + +write_redirects() { + cat > "$1" <<'REDIRECTS' +# Existing redirect content +/manual /developer-guide/ 301 +REDIRECTS +} + +cat > "${tmp_dir}/latest-with-pdf.json" <<'JSON' +{ + "tag_name": "7.0.260", + "assets": [ + { + "name": "developer-guide.pdf", + "browser_download_url": "https://github.com/codenameone/CodenameOne/releases/download/7.0.260/developer-guide.pdf" + } + ] +} +JSON + +cat > "${tmp_dir}/latest-without-pdf.json" <<'JSON' +{ + "tag_name": "7.0.260", + "assets": [] +} +JSON + +cat > "${tmp_dir}/release-history.json" <<'JSON' +[ + { + "tag_name": "7.0.260", + "draft": false, + "prerelease": false, + "assets": [] + }, + { + "tag_name": "7.0.259-rc1", + "draft": false, + "prerelease": true, + "assets": [ + { + "name": "developer-guide.pdf", + "browser_download_url": "https://github.com/codenameone/CodenameOne/releases/download/7.0.259-rc1/developer-guide.pdf" + } + ] + }, + { + "tag_name": "7.0.259", + "draft": false, + "prerelease": false, + "assets": [ + { + "name": "developer-guide.pdf", + "browser_download_url": "https://github.com/codenameone/CodenameOne/releases/download/7.0.259/developer-guide.pdf" + } + ] + } +] +JSON + +cat > "${tmp_dir}/no-release-with-pdf.json" <<'JSON' +[ + { + "tag_name": "7.0.260", + "draft": false, + "prerelease": false, + "assets": [] + } +] +JSON + +latest_redirects="${tmp_dir}/latest-redirects" +write_redirects "${latest_redirects}" +REDIRECTS_FILE="${latest_redirects}" \ +RELEASES_URL="file://${tmp_dir}/latest-with-pdf.json" \ +RELEASES_LIST_URL="file://${tmp_dir}/missing-release-history.json" \ + "${UPDATER}" +grep -Fq '# Source release: 7.0.260' "${latest_redirects}" +grep -Fq '/files/developer-guide.pdf https://github.com/codenameone/CodenameOne/releases/download/7.0.260/developer-guide.pdf 302' "${latest_redirects}" +grep -Fq '/manual /developer-guide/ 301' "${latest_redirects}" + +fallback_redirects="${tmp_dir}/fallback-redirects" +write_redirects "${fallback_redirects}" +REDIRECTS_FILE="${fallback_redirects}" \ +RELEASES_URL="file://${tmp_dir}/latest-without-pdf.json" \ +RELEASES_LIST_URL="file://${tmp_dir}/release-history.json" \ + "${UPDATER}" +grep -Fq '# Source release: 7.0.259' "${fallback_redirects}" +grep -Fq '/files/developer-guide.pdf https://github.com/codenameone/CodenameOne/releases/download/7.0.259/developer-guide.pdf 302' "${fallback_redirects}" +if grep -Fq '7.0.259-rc1/developer-guide.pdf' "${fallback_redirects}"; then + echo "Prerelease developer guide was selected as the production fallback." >&2 + exit 1 +fi + +missing_redirects="${tmp_dir}/missing-redirects" +write_redirects "${missing_redirects}" +if REDIRECTS_FILE="${missing_redirects}" \ + RELEASES_URL="file://${tmp_dir}/latest-without-pdf.json" \ + RELEASES_LIST_URL="file://${tmp_dir}/no-release-with-pdf.json" \ + "${UPDATER}"; then + echo "Updater succeeded without finding a valid developer-guide.pdf asset." >&2 + exit 1 +fi +if ! cmp -s "${missing_redirects}" <(printf '%s\n' '# Existing redirect content' '/manual /developer-guide/ 301'); then + echo "Updater modified redirects after failing to resolve an asset." >&2 + exit 1 +fi + +echo "Developer guide redirect updater tests passed." diff --git a/scripts/website/update_developer_guide_redirect.sh b/scripts/website/update_developer_guide_redirect.sh index 8bf82c897e4..9efbe0cc611 100755 --- a/scripts/website/update_developer_guide_redirect.sh +++ b/scripts/website/update_developer_guide_redirect.sh @@ -3,8 +3,9 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)" -REDIRECTS_FILE="${REPO_ROOT}/docs/website/static/_redirects" -RELEASES_URL="https://api.github.com/repos/codenameone/CodenameOne/releases/latest" +REDIRECTS_FILE="${REDIRECTS_FILE:-${REPO_ROOT}/docs/website/static/_redirects}" +RELEASES_URL="${RELEASES_URL:-https://api.github.com/repos/codenameone/CodenameOne/releases/latest}" +RELEASES_LIST_URL="${RELEASES_LIST_URL:-https://api.github.com/repos/codenameone/CodenameOne/releases?per_page=20}" BEGIN_MARKER="# BEGIN: generated developer-guide.pdf redirect" END_MARKER="# END: generated developer-guide.pdf redirect" @@ -30,6 +31,7 @@ cleanup() { trap cleanup EXIT release_json="${tmp_dir}/release.json" +releases_json="${tmp_dir}/releases.json" new_redirects="${tmp_dir}/_redirects.new" curl_args=( @@ -67,9 +69,38 @@ if [ -z "${asset_url}" ]; then asset_url="https://github.com/codenameone/CodenameOne/releases/download/${release_tag}/developer-guide.pdf" echo "developer-guide.pdf asset not found in latest release; using PR fallback ${asset_url}" >&2 else - echo "developer-guide.pdf asset not found in latest release." >&2 - echo "Resolved developer-guide.pdf URL is empty." >&2 - exit 1 + echo "developer-guide.pdf asset not found in latest release ${release_tag}; looking for the newest stable release that contains it." >&2 + curl "${curl_args[@]}" -o "${releases_json}" "${RELEASES_LIST_URL}" + + read -r fallback_tag fallback_url <<<"$( + python3 -c ' +import json, sys +with open(sys.argv[1], "r", encoding="utf-8") as f: + releases = json.load(f) +for release in releases: + if release.get("draft") or release.get("prerelease"): + continue + tag = release.get("tag_name", "") + if not tag: + continue + for asset in release.get("assets", []): + if asset.get("name") == "developer-guide.pdf": + url = asset.get("browser_download_url", "") + if url: + print(f"{tag} {url}") + raise SystemExit(0) +' "${releases_json}" + )" + + if [ -z "${fallback_url:-}" ]; then + echo "No stable Codename One release with a developer-guide.pdf asset was found." >&2 + echo "Resolved developer-guide.pdf URL is empty." >&2 + exit 1 + fi + + release_tag="${fallback_tag}" + asset_url="${fallback_url}" + echo "Using developer-guide.pdf from release ${release_tag} until the latest release asset is available." >&2 fi fi From 83de791ea8961db88e8115eacae86a7113549c4f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Fri, 17 Jul 2026 09:32:54 +0300 Subject: [PATCH 2/2] Fix redirect fallback test event --- scripts/website/test_update_developer_guide_redirect.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/website/test_update_developer_guide_redirect.sh b/scripts/website/test_update_developer_guide_redirect.sh index d2c4ca65640..8a805c10801 100755 --- a/scripts/website/test_update_developer_guide_redirect.sh +++ b/scripts/website/test_update_developer_guide_redirect.sh @@ -4,6 +4,10 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" UPDATER="${SCRIPT_DIR}/update_developer_guide_redirect.sh" +# GitHub sets this to pull_request while the website PR check runs. These +# fixtures validate production behavior, so do not inherit the outer event. +export GITHUB_EVENT_NAME=workflow_dispatch + tmp_dir="$(mktemp -d)" cleanup() { rm -rf "${tmp_dir}"