From e5e084b68c2e929cbf194ec138dd0540aeefc4df Mon Sep 17 00:00:00 2001 From: sbegin0 Date: Tue, 1 Apr 2025 15:33:34 -0400 Subject: [PATCH 1/5] Sam's version --- .gitignore | 3 ++ netlify.toml | 4 ++ netlify/edge-functions/redirect-to-latest.ts | 41 ++++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 netlify/edge-functions/redirect-to-latest.ts diff --git a/.gitignore b/.gitignore index 97cc35437d99..06c01bc160de 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,6 @@ go/** # archived site version archived_version + +# Local Netlify folder +.netlify diff --git a/netlify.toml b/netlify.toml index 9458eae5cfa0..99662359d9dc 100644 --- a/netlify.toml +++ b/netlify.toml @@ -14,3 +14,7 @@ [headers.values] X-Frame-Options = "SAMEORIGIN" X-XSS-Protection = "1; mode=block" + +[[edge_functions]] + path = "/v*" + function = "redirect-to-latest" \ No newline at end of file diff --git a/netlify/edge-functions/redirect-to-latest.ts b/netlify/edge-functions/redirect-to-latest.ts new file mode 100644 index 000000000000..ea4741fed045 --- /dev/null +++ b/netlify/edge-functions/redirect-to-latest.ts @@ -0,0 +1,41 @@ +export default async (request: Request, context) => { + const url = new URL(request.url); + let pathname = url.pathname; + // Normalize pathname by removing consecutive slashes + pathname = pathname.replace(/\/\/+/g, '/'); + // Match version paths like /vX.XX/ + const versionMatch = pathname.match(/^\/v\d+\.\d+/); + + if (versionMatch) { + // Define the main section paths that should be redirected + const redirectableSections = ['/', '/about', '/blog', '/get-involved', '/news', '/search']; + const versionPath = versionMatch[0]; // ex /v1.15 + const remainder = pathname.slice(versionPath.length) || '/'; + // Check if the remainder starts with an optional leading slash followed by a language code + const languageCodeMatch = remainder.match(/^\/?([a-z]{2})(\/|$)/); + let languageCode: string | null = null; + let remainingPathAfterLang = remainder; + + if (languageCodeMatch) { + languageCode = languageCodeMatch[1]; + remainingPathAfterLang = remainder.slice(languageCodeMatch[0].length); + } + // If there's a language code and no further path, redirect to the homepage with the language code + if (languageCode && remainingPathAfterLang === '') { + return Response.redirect(new URL(`/latest/${languageCode}/`, url.origin), 301); + } + // Check if the remaining path (after version and optional language code) starts with a redirectable section + const shouldRedirect = redirectableSections.some(section => + remainingPathAfterLang === section || remainingPathAfterLang.startsWith(`${section}/`) || (section === '/' && remainingPathAfterLang === '')); + + if (shouldRedirect) { + const cleanRemainderPath = remainingPathAfterLang.startsWith('/') ? remainingPathAfterLang.substring(1) : remainingPathAfterLang; + const newPath = `/latest/${languageCode ? `${languageCode}/` : ''}${cleanRemainderPath}`; + return Response.redirect(new URL(newPath, url.origin), 301); + } + // For version-specific paths we want to keep, tell Netlify to stop processing this function + return context.next(); + } + // For all non-version paths, pass through + return context.next(); +}; \ No newline at end of file From fa4342577f52d07655eede4b904457c78cd1b037 Mon Sep 17 00:00:00 2001 From: Craig Box Date: Tue, 27 May 2025 18:50:45 +1200 Subject: [PATCH 2/5] Skynet's revised version --- netlify/edge-functions/redirect-to-latest.ts | 62 +++++++++----------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/netlify/edge-functions/redirect-to-latest.ts b/netlify/edge-functions/redirect-to-latest.ts index ea4741fed045..eac41a2f099d 100644 --- a/netlify/edge-functions/redirect-to-latest.ts +++ b/netlify/edge-functions/redirect-to-latest.ts @@ -1,41 +1,37 @@ export default async (request: Request, context) => { const url = new URL(request.url); let pathname = url.pathname; - // Normalize pathname by removing consecutive slashes + + // Normalize consecutive slashes pathname = pathname.replace(/\/\/+/g, '/'); - // Match version paths like /vX.XX/ + const versionMatch = pathname.match(/^\/v\d+\.\d+/); + if (!versionMatch) return context.next(); + + const versionPath = versionMatch[0]; + let remainder = pathname.slice(versionPath.length) || '/'; + + const languageCodeMatch = remainder.match(/^\/?([a-z]{2})(\/|$)/); + let languageCode: string | null = null; + let remainingPath = remainder; + + if (languageCodeMatch) { + languageCode = languageCodeMatch[1]; + remainingPath = remainder.slice(languageCodeMatch[0].length); + } + + const normalizedPath = remainingPath.replace(/^\/+/, ''); + + const redirectableSections = ['about', 'blog', 'get-involved', 'news', 'search', '']; - if (versionMatch) { - // Define the main section paths that should be redirected - const redirectableSections = ['/', '/about', '/blog', '/get-involved', '/news', '/search']; - const versionPath = versionMatch[0]; // ex /v1.15 - const remainder = pathname.slice(versionPath.length) || '/'; - // Check if the remainder starts with an optional leading slash followed by a language code - const languageCodeMatch = remainder.match(/^\/?([a-z]{2})(\/|$)/); - let languageCode: string | null = null; - let remainingPathAfterLang = remainder; - - if (languageCodeMatch) { - languageCode = languageCodeMatch[1]; - remainingPathAfterLang = remainder.slice(languageCodeMatch[0].length); - } - // If there's a language code and no further path, redirect to the homepage with the language code - if (languageCode && remainingPathAfterLang === '') { - return Response.redirect(new URL(`/latest/${languageCode}/`, url.origin), 301); - } - // Check if the remaining path (after version and optional language code) starts with a redirectable section - const shouldRedirect = redirectableSections.some(section => - remainingPathAfterLang === section || remainingPathAfterLang.startsWith(`${section}/`) || (section === '/' && remainingPathAfterLang === '')); - - if (shouldRedirect) { - const cleanRemainderPath = remainingPathAfterLang.startsWith('/') ? remainingPathAfterLang.substring(1) : remainingPathAfterLang; - const newPath = `/latest/${languageCode ? `${languageCode}/` : ''}${cleanRemainderPath}`; - return Response.redirect(new URL(newPath, url.origin), 301); - } - // For version-specific paths we want to keep, tell Netlify to stop processing this function - return context.next(); + const shouldRedirect = redirectableSections.some(section => + normalizedPath === section || normalizedPath.startsWith(`${section}/`) + ); + + if (shouldRedirect) { + const newPath = `/latest/${languageCode ? `${languageCode}/` : ''}${normalizedPath}`; + return Response.redirect(new URL(newPath, url.origin), 301); } - // For all non-version paths, pass through + return context.next(); -}; \ No newline at end of file +}; From 95e96c9d8b1734e892823a8fa79c63f7f91219bf Mon Sep 17 00:00:00 2001 From: Craig Box Date: Tue, 27 May 2025 21:08:33 +1200 Subject: [PATCH 3/5] Fix test --- netlify/edge-functions/redirect-to-latest.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/netlify/edge-functions/redirect-to-latest.ts b/netlify/edge-functions/redirect-to-latest.ts index eac41a2f099d..34e305f9eccd 100644 --- a/netlify/edge-functions/redirect-to-latest.ts +++ b/netlify/edge-functions/redirect-to-latest.ts @@ -9,7 +9,7 @@ export default async (request: Request, context) => { if (!versionMatch) return context.next(); const versionPath = versionMatch[0]; - let remainder = pathname.slice(versionPath.length) || '/'; + const remainder = pathname.slice(versionPath.length) || '/'; const languageCodeMatch = remainder.match(/^\/?([a-z]{2})(\/|$)/); let languageCode: string | null = null; @@ -22,10 +22,12 @@ export default async (request: Request, context) => { const normalizedPath = remainingPath.replace(/^\/+/, ''); - const redirectableSections = ['about', 'blog', 'get-involved', 'news', 'search', '']; + const redirectableSections = ['about', 'blog', 'get-involved', 'news', 'search']; - const shouldRedirect = redirectableSections.some(section => - normalizedPath === section || normalizedPath.startsWith(`${section}/`) + const shouldRedirect = + normalizedPath === '' || // redirect to /latest/ + redirectableSections.some(section => + normalizedPath === section || normalizedPath.startsWith(`${section}/`) ); if (shouldRedirect) { From 4a17719efdaf7418d0f7bb5bc325f9e743cf3f11 Mon Sep 17 00:00:00 2001 From: Craig Box Date: Tue, 27 May 2025 21:35:40 +1200 Subject: [PATCH 4/5] Add test script --- scripts/test_netlify_redirects.sh | 84 +++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100755 scripts/test_netlify_redirects.sh diff --git a/scripts/test_netlify_redirects.sh b/scripts/test_netlify_redirects.sh new file mode 100755 index 000000000000..45fff571f1e5 --- /dev/null +++ b/scripts/test_netlify_redirects.sh @@ -0,0 +1,84 @@ +#!/bin/bash + +# Copyright Istio Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Test the Netlify redirect scripts by checking various URLs +# Usage: ./test-netlify-redirects.sh [] +set -e + +if [[ "$#" -ne 0 ]]; then + BASE_URL="$*" +else + BASE_URL="https://preliminary.istio.io" +fi + +URLS=( + # Should redirect + "/v1.1/uk" + "/v1.1/uk/" + "/v1.10/news/support/announcing-1.9-eol/" + "/v1.15/get-involved/" + "/v1.20/blog" + "/v1.20/uk/" + "/v1.21/uk/blog" + "/v1.21/zh/" + "/v1.22/zh/news" + "/v1.23/about" + "/v1.24/uk/search" + "/v1.24/zh" + "/v1.24/zh/" + "/v1.25/zh/get-involved" + "/v1.26/" + + # Should NOT redirect + "/archive/" + "/v1.20/docs/concepts" + "/v1.21/zh/docs/setup" + "/v1.22/docs/ops/diagnostic-tools" + "/v1.22/docs/ops/diagnostic-tools/" + "/v1.22/zh/docs/reference/config" + "/v1.23/docs/" + "/v1.24/docs/" + "/v1.24/uk/docs/" + "/v1.24/zh/docs/tasks/security" + "/v1.24/zh/docs/tasks/security/" + "/v1.25/zh/docs/tasks/security" + + # /latest URLs (should not redirect) + "/latest" + "/latest/" + "/latest/blog" + "/latest/docs/" + "/latest/uk/" + "/latest/uk/blog" + "/latest/zh/" + "/latest/zh/docs" +) + +for path in "${URLS[@]}"; do + full_url="${BASE_URL}${path}" + response=$(curl -s -o /dev/null -w "%{http_code} %{redirect_url}" "$full_url") + http_code=$(echo $response | cut -d' ' -f1) + redirect_url=$(echo $response | cut -d' ' -f2-) + + if [[ "$http_code" == "301" || "$http_code" == "302" ]]; then + echo "[REDIRECT] $path → $redirect_url" + elif [[ "$http_code" == "200" ]]; then + echo "[OK] $path" + else + echo "[?] $path - Status: $http_code" + fi +done + From d36a00fb61556429d52a5bb827247d0c64182e6b Mon Sep 17 00:00:00 2001 From: Craig Box Date: Tue, 27 May 2025 21:44:43 +1200 Subject: [PATCH 5/5] Lint test script --- scripts/test_netlify_redirects.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/test_netlify_redirects.sh b/scripts/test_netlify_redirects.sh index 45fff571f1e5..4fd78b4766e2 100755 --- a/scripts/test_netlify_redirects.sh +++ b/scripts/test_netlify_redirects.sh @@ -70,8 +70,8 @@ URLS=( for path in "${URLS[@]}"; do full_url="${BASE_URL}${path}" response=$(curl -s -o /dev/null -w "%{http_code} %{redirect_url}" "$full_url") - http_code=$(echo $response | cut -d' ' -f1) - redirect_url=$(echo $response | cut -d' ' -f2-) + http_code=$(echo "$response" | cut -d' ' -f1) + redirect_url=$(echo "$response" | cut -d' ' -f2-) if [[ "$http_code" == "301" || "$http_code" == "302" ]]; then echo "[REDIRECT] $path → $redirect_url"