{"id":11925534,"name":"lfreleng-actions/python-supported-versions-action","ecosystem":"actions","description":"Returns Python version(s) for build and JSON for matrix jobs","homepage":"","licenses":"apache-2.0","normalized_licenses":["Apache-2.0"],"repository_url":"https://github.com/lfreleng-actions/python-supported-versions-action","keywords_array":["python","versions"],"namespace":"lfreleng-actions","versions_count":17,"first_release_published_at":"2025-03-16T14:23:49.000Z","latest_release_published_at":"2026-08-24T12:54:04.000Z","latest_release_number":"v0.2.3","last_synced_at":"2026-09-25T12:54:56.824Z","created_at":"2025-07-18T23:30:27.996Z","updated_at":"2026-09-25T12:54:56.825Z","registry_url":"https://github.com/lfreleng-actions/python-supported-versions-action","install_command":null,"documentation_url":null,"metadata":{"name":"🐍 Extract Python Versions Supported by Project","description":"Returns Python version(s) for build and JSON for matrix jobs","inputs":{"path_prefix":{"description":"Directory location containing project code","required":false,"default":"."},"network_timeout":{"description":"Network timeout in seconds for API calls","required":false,"default":"6"},"max_retries":{"description":"Maximum number of retry attempts for API calls","required":false,"default":"2"},"eol_behaviour":{"description":"How to handle EOL Python versions: warn|strip|fail","required":false,"default":"warn"},"offline_mode":{"description":"Disable network lookups and use internal version list only","required":false,"default":"false"},"default_python_version":{"description":"Override build_python (X.Y) when the action is in fallback mode (versions_source=eol-fallback or static-fallback, e.g. no project-side constraint declared, EOL API unreachable, or offline_mode=true). The override is ignored unless it is part of the emitted matrix; out-of-matrix values keep the computed build_python and emit a warning.","required":false,"default":""}},"outputs":{"build_python":{"description":"Most recent Python version supported by project","value":"${{ steps.versions.outputs.build_python }}"},"matrix_json":{"description":"All Python versions supported by project as JSON string","value":"${{ steps.versions.outputs.matrix_json }}"},"supported_versions":{"description":"Space-separated list of all supported Python versions","value":"${{ steps.versions.outputs.supported_versions }}"},"versions_source":{"description":"Source of the version data: requires-python, classifiers, setup-cfg-requires, setup-cfg-classifiers, setup-py-requires, setup-py-classifiers, eol-fallback, or static-fallback","value":"${{ steps.versions.outputs.versions_source }}"}},"runs":{"using":"composite","steps":[{"id":"versions","name":"Process and determine supported Python versions","shell":"bash","env":{"INPUT_PATH_PREFIX":"${{ inputs.path_prefix }}","INPUT_NETWORK_TIMEOUT":"${{ inputs.network_timeout }}","INPUT_MAX_RETRIES":"${{ inputs.max_retries }}","INPUT_EOL_BEHAVIOUR":"${{ inputs.eol_behaviour }}","INPUT_OFFLINE_MODE":"${{ inputs.offline_mode }}","INPUT_DEFAULT_PYTHON_VERSION":"${{ inputs.default_python_version }}"},"run":"# Process and determine supported Python versions\nset -euo pipefail\n\nTIMEOUT=\"${INPUT_NETWORK_TIMEOUT}\"\nRETRIES=\"${INPUT_MAX_RETRIES}\"\nEOL_BEHAVIOUR=\"${INPUT_EOL_BEHAVIOUR}\"\nOFFLINE_MODE=\"${INPUT_OFFLINE_MODE}\"\nDEFAULT_PYTHON_VERSION=\"${INPUT_DEFAULT_PYTHON_VERSION:-}\"\n\n\n# Internal list of supported Python versions (requires periodic update)\nINTERNAL_SUPPORTED_VERSIONS=\"3.10 3.11 3.12 3.13 3.14\"\n\n# Setup and validate environment\npath_prefix=\"${INPUT_PATH_PREFIX}\"\npath_prefix=\"${path_prefix:-'.'}\"\npath_prefix=\"${path_prefix%/}\"\n\n# Set PYPROJECT_FILE after path normalization\nPYPROJECT_FILE=\"${path_prefix}/pyproject.toml\"\n\n# Check tool availability\nif ! command -v jq \u003e/dev/null 2\u003e\u00261; then\n  echo 'Error: jq is required but not available. Please install jq or use a runner with jq pre-installed ❌'\n  exit 1\nfi\n\nif [[ \"$OFFLINE_MODE\" != \"true\" ]]; then\n  if ! command -v curl \u003e/dev/null 2\u003e\u00261; then\n    echo 'Error: curl is required but not available ❌'\n    exit 1\n  fi\nfi\n\n\n# Validate directory existence (project metadata files are optional;\n# legacy projects may rely on setup.cfg or setup.py, or none at all).\nif [[ ! -d \"$path_prefix\" ]]; then\n  echo 'Error: invalid path/prefix to project directory ❌'\n  exit 1\nfi\n\n# Track which metadata files are present for downstream logic.\nHAS_PYPROJECT='false'\nHAS_SETUP_CFG='false'\nHAS_SETUP_PY='false'\n[[ -f \"$path_prefix/pyproject.toml\" ]] \u0026\u0026 HAS_PYPROJECT='true'\n[[ -f \"$path_prefix/setup.cfg\" ]] \u0026\u0026 HAS_SETUP_CFG='true'\n[[ -f \"$path_prefix/setup.py\" ]] \u0026\u0026 HAS_SETUP_PY='true'\necho \"Metadata files: pyproject=$HAS_PYPROJECT setup.cfg=$HAS_SETUP_CFG setup.py=$HAS_SETUP_PY\"\n\n# Validate eol_behaviour input\nif [[ \"$EOL_BEHAVIOUR\" != \"warn\" \u0026\u0026 \"$EOL_BEHAVIOUR\" != \"strip\" \u0026\u0026 \\\n      \"$EOL_BEHAVIOUR\" != \"fail\" ]]; then\n  echo 'Error: eol_behaviour must be one of: warn, strip, fail ❌'\n  exit 1\nfi\n\n# Validate offline_mode input\nif [[ \"$OFFLINE_MODE\" != \"true\" \u0026\u0026 \"$OFFLINE_MODE\" != \"false\" ]]; then\n  echo 'Error: offline_mode must be true or false ❌'\n  exit 1\nfi\n\n# Validate numeric inputs for network timeout and max retries\nif ! [[ \"$TIMEOUT\" =~ ^[0-9]+$ ]]; then\n  echo 'Error: network_timeout must be a non-negative integer ❌'\n  exit 1\nfi\nif ! [[ \"$RETRIES\" =~ ^[0-9]+$ ]]; then\n  echo 'Error: max_retries must be a non-negative integer ❌'\n  exit 1\nfi\n\n# Function: Portable version comparison (major.minor format)\nversion_compare() {\n    local v1=\"$1\"\n    local op=\"$2\"\n    local v2=\"$3\"\n\n    # Split versions into major.minor\n    local v1_major=\"${v1%%.*}\"\n    local v1_minor=\"${v1##*.}\"\n    local v2_major=\"${v2%%.*}\"\n    local v2_minor=\"${v2##*.}\"\n\n    # Convert to integers for comparison\n    v1_major=$((v1_major))\n    v1_minor=$((v1_minor))\n    v2_major=$((v2_major))\n    v2_minor=$((v2_minor))\n\n    case \"$op\" in\n        \"lt\")\n            [[ $v1_major -lt $v2_major ]] || \\\n            [[ $v1_major -eq $v2_major \u0026\u0026 $v1_minor -lt $v2_minor ]]\n            ;;\n        \"le\")\n            [[ $v1_major -lt $v2_major ]] || \\\n            [[ $v1_major -eq $v2_major \u0026\u0026 $v1_minor -le $v2_minor ]]\n            ;;\n        \"gt\")\n            [[ $v1_major -gt $v2_major ]] || \\\n            [[ $v1_major -eq $v2_major \u0026\u0026 $v1_minor -gt $v2_minor ]]\n            ;;\n        \"ge\")\n            [[ $v1_major -gt $v2_major ]] || \\\n            [[ $v1_major -eq $v2_major \u0026\u0026 $v1_minor -ge $v2_minor ]]\n            ;;\n        \"eq\")\n            [[ $v1_major -eq $v2_major \u0026\u0026 $v1_minor -eq $v2_minor ]]\n            ;;\n        *)\n            echo \"Invalid comparison operator: $op\" \u003e\u00262\n            return 1\n            ;;\n    esac\n}\n\n# Function: Portable version sorting\nsort_versions() {\n    local versions=\"$1\"\n    # Use numeric sort on major and minor parts, then join back to space-separated string\n    printf '%s\\n' \"$versions\" | tr ' ' '\\n' | sort -t. -k1,1n -k2,2n | tr '\\n' ' ' | sed 's/ $//'\n}\n\n# Function: Fetch Python EOL data and optionally format as version list\nfetch_python_data() {\n    local timeout=\"$1\"\n    local retries=\"$2\"\n    local format=\"${3:-raw}\"  # Default to raw format\n    local eol_data\n\n    if [[ \"$OFFLINE_MODE\" == \"true\" ]]; then\n        if [[ \"$format\" == \"versions\" ]]; then\n            printf '%s\\n' \"$INTERNAL_SUPPORTED_VERSIONS\"\n        else\n            echo \"[]\"  # Empty array for offline mode\n        fi\n        return 0\n    fi\n\n    # Fetch data with error checking\n    # Determine if curl supports --retry-all-errors; fallback if not\n    local curl_retry_flag=\"\"\n    if curl --help 2\u003e/dev/null | grep -q -- '--retry-all-errors'; then\n        curl_retry_flag=\"--retry-all-errors\"\n    fi\n    if eol_data=$(curl -s --max-time \"$timeout\" --retry \"$retries\" $curl_retry_flag 'https://endoflife.date/api/python.json' 2\u003e/dev/null); then\n        # Verify we got valid JSON array with expected structure\n        if [[ -n \"$eol_data\" ]] \u0026\u0026 echo \"$eol_data\" | jq -e 'type==\"array\" and length\u003e0 and all(.[]; has(\"cycle\"))' \u003e/dev/null 2\u003e\u00261; then\n            if [[ \"$format\" == \"versions\" ]]; then\n                # Return processed version list (3.10+)\n                printf '%s\\n' \"$eol_data\" | \\\n                  jq -r '.[] | select(.cycle | test(\"^(3\\\\.(1[0-9]|[2-9][0-9])|([4-9]|[1-9][0-9]+)\\\\.[0-9]+)$\")) | .cycle' | \\\n                  tr '\\n' ' ' | sed 's/ $//'\n            else\n                # Return raw JSON data\n                echo \"$eol_data\"\n            fi\n            return 0\n        else\n            echo \"Error: Invalid or empty EOL data received\" \u003e\u00262\n            return 1\n        fi\n    else\n        echo \"Error: Failed to fetch EOL data from API\" \u003e\u00262\n        return 1\n    fi\n}\n\n# Function: Check if a version is EOL\ncheck_version_eol() {\n    local version=\"$1\"\n    local eol_data=\"$2\"\n    local current_date eol_date eol_type\n\n    if [[ \"$OFFLINE_MODE\" == \"true\" ]]; then\n        echo \"false\"\n        return 0\n    fi\n\n    current_date=$(date +%Y-%m-%d)\n\n    # Extract EOL date for the specific version, handling different types\n    local eol_info\n    eol_info=$(printf '%s\\n' \"$eol_data\" | \\\n      jq -r --arg ver \"$version\" \\\n      '.[] | select(.cycle == $ver) | {eol: .eol, type: (.eol | type)}' 2\u003e/dev/null)\n\n    if [[ -z \"$eol_info\" || \"$eol_info\" == \"null\" ]]; then\n        echo \"false\"\n        return 0\n    fi\n\n    eol_date=$(echo \"$eol_info\" | jq -r '.eol // \"null\"')\n    eol_type=$(echo \"$eol_info\" | jq -r '.type')\n\n    # Handle different EOL field types\n    case \"$eol_type\" in\n        \"string\")\n            if [[ \"$eol_date\" != \"null\" \u0026\u0026 \"$eol_date\" != \"\" ]]; then\n                if [[ \"$eol_date\" \u003c \"$current_date\" || \"$eol_date\" == \"$current_date\" ]]; then\n                    echo \"$eol_date\"\n                else\n                    echo \"false\"\n                fi\n            else\n                echo \"false\"\n            fi\n            ;;\n        \"boolean\")\n            # If EOL is boolean true, it's EOL (no specific date)\n            if [[ \"$eol_date\" == \"true\" ]]; then\n                echo \"true\"\n            else\n                echo \"false\"\n            fi\n            ;;\n        *)\n            echo \"false\"\n            ;;\n    esac\n}\n\n# Function: Normalize constraint (handle ~= and ^ operators)\nnormalize_constraint() {\n    local constraint=\"$1\"\n\n    # Leave exclusions untouched so the parser can handle them precisely\n    if [[ \"$constraint\" == \\!* ]]; then\n        echo \"$constraint\"\n        return 0\n    fi\n\n    # Handle Poetry caret constraints with patch: ^3.10.1 -\u003e \u003e=3.10,\u003c4.0\n    if [[ \"$constraint\" =~ ^\\^([0-9]+)\\.([0-9]+)\\.([0-9]+)$ ]]; then\n        local major=\"${BASH_REMATCH[1]}\"\n        local minor=\"${BASH_REMATCH[2]}\"\n        local next_major=$((major + 1))\n        echo \"\u003e=${major}.${minor},\u003c${next_major}.0\"\n        return 0\n    fi\n\n    # Handle Poetry caret constraints: ^3.10 -\u003e \u003e=3.10,\u003c4.0\n    if [[ \"$constraint\" =~ ^\\^([0-9]+)\\.([0-9]+)$ ]]; then\n        local major=\"${BASH_REMATCH[1]}\"\n        local minor=\"${BASH_REMATCH[2]}\"\n        local next_major=$((major + 1))\n        echo \"\u003e=${major}.${minor},\u003c${next_major}.0\"\n        return 0\n    fi\n\n    # Handle compatible release with patch: ~=3.10.1 -\u003e \u003e=3.10,\u003c3.11\n    if [[ \"$constraint\" =~ ^~=([0-9]+)\\.([0-9]+)\\.([0-9]+)$ ]]; then\n        local major=\"${BASH_REMATCH[1]}\"\n        local minor=\"${BASH_REMATCH[2]}\"\n        local next_minor=$((minor + 1))\n        echo \"\u003e=${major}.${minor},\u003c${major}.${next_minor}\"\n        return 0\n    fi\n\n    # Handle compatible release: ~=3.10 -\u003e \u003e=3.10,\u003c3.11\n    if [[ \"$constraint\" =~ ^~=([0-9]+)\\.([0-9]+)$ ]]; then\n        local major=\"${BASH_REMATCH[1]}\"\n        local minor=\"${BASH_REMATCH[2]}\"\n        local next_minor=$((minor + 1))\n        echo \"\u003e=${major}.${minor},\u003c${major}.${next_minor}\"\n        return 0\n    fi\n\n    # Handle wildcard constraints: ==3.10.* -\u003e \u003e=3.10,\u003c3.11\n    if [[ \"$constraint\" =~ ^==([0-9]+)\\.([0-9]+)\\.\\*$ ]]; then\n        local major=\"${BASH_REMATCH[1]}\"\n        local minor=\"${BASH_REMATCH[2]}\"\n        local next_minor=$((minor + 1))\n        echo \"\u003e=${major}.${minor},\u003c${major}.${next_minor}\"\n        return 0\n    fi\n\n    # Strip patch versions from bounds: \u003c3.13.0 -\u003e \u003c3.13 (does not affect '!=')\n    constraint=$(echo \"$constraint\" | sed 's/\\([\u003c\u003e=]\\+\\)\\([0-9]\\+\\.[0-9]\\+\\)\\.[0-9]\\+/\\1\\2/g')\n\n    # Return normalized constraint\n    echo \"$constraint\"\n}\n\n# Function: Extract requires-python constraint from pyproject.toml using Python\nextract_requires_python_python() {\n    local pyproject_file=\"$1\"\n\n    if [[ ! -f \"$pyproject_file\" ]]; then\n        return 1\n    fi\n\n    # Try using Python with tomllib (Python 3.11+) or tomli\n    python3 -c \"import sys, importlib.util as iu; fp=sys.argv[1]; tmod='tomllib' if iu.find_spec('tomllib') else ('tomli' if iu.find_spec('tomli') else 'toml'); t=__import__(tmod); data=t.load(open(fp, 'rb' if tmod!='toml' else 'r')); v=(data.get('project', {}).get('requires-python') or data.get('tool', {}).get('poetry', {}).get('dependencies', {}).get('python')); print(v) if v else None; sys.exit(0 if v else 1)\" \"$pyproject_file\" 2\u003e/dev/null\n}\n\n# Function: Extract requires-python constraint from pyproject.toml (fallback)\nextract_requires_python_fallback() {\n    local pyproject_file=\"$1\"\n    local constraint\n\n    if [[ ! -f \"$pyproject_file\" ]]; then\n        return 1\n    fi\n\n    # Extract requires-python constraint, supporting both single and double quotes\n    # Ignore commented lines\n    constraint=$(grep -v '^[[:space:]]*#' \"$pyproject_file\" 2\u003e/dev/null | \\\n                 grep 'requires-python.*=' | \\\n                 sed -E 's/.*requires-python.*=[[:space:]]*['\\''\"]([^'\\''\"]*)['\\''\"].*/\\1/' | \\\n                 head -1)\n\n    if [[ -n \"$constraint\" ]]; then\n        printf '%s\\n' \"$constraint\"\n        return 0\n    fi\n\n    # Fall back to Poetry python dependency (robust section parsing)\n    constraint=$(awk '\n      BEGIN{insec=0}\n      /^\\[tool\\.poetry\\.dependencies\\]/{insec=1; next}\n      /^\\[.*\\]/{if(insec) exit}\n      insec{print}\n    ' \"$pyproject_file\" 2\u003e/dev/null | grep -v '^[[:space:]]*#' | grep -E '^[[:space:]]*python[[:space:]]*=' | sed -E 's/.*=[[:space:]]*['\\''\"]([^'\\''\"]*)['\\''\"].*/\\1/' | head -1)\n\n    if [[ -n \"$constraint\" ]]; then\n        printf '%s\\n' \"$constraint\"\n        return 0\n    else\n        return 1\n    fi\n}\n\n# Function: Extract Programming Language classifiers from pyproject.toml\nextract_classifiers_fallback() {\n    local pyproject_file=\"$1\"\n    local classifiers\n\n    if [[ ! -f \"$pyproject_file\" ]]; then\n        return 1\n    fi\n\n    # Extract Python versions from Programming Language classifiers.\n    # Use first-seen-wins dedup via awk rather than sort -u, which\n    # sorts lexicographically and would place '3.10' before '3.9'\n    # across the 3.9 -\u003e 3.10 boundary. Matches the awk-based dedup\n    # in lib/constraint_utils.sh:extract_classifiers_fallback.\n    classifiers=$(grep -v '^[[:space:]]*#' \"$pyproject_file\" 2\u003e/dev/null | \\\n                  grep -E 'Programming Language :: Python :: [0-9]+\\.[0-9]+' | \\\n                  grep -oE '[0-9]+\\.[0-9]+' | \\\n                  awk '!seen[$0]++' | tr '\\n' ' ' | sed 's/ *$//')\n\n    if [[ -n \"$classifiers\" ]]; then\n        printf '%s\\n' \"$classifiers\"\n        return 0\n    else\n        return 1\n    fi\n}\n\n# Function: Parse version constraint and return all matching versions\nparse_version_constraint() {\n    local constraint=\"$1\"\n    local all_versions=\"$2\"\n    local result=''\n\n    # Normalize the constraint first\n    constraint=$(normalize_constraint \"$constraint\")\n\n    # Handle complex constraints with multiple parts (e.g., \"\u003e=3.11,\u003c3.13\")\n    if [[ \"$constraint\" == *\",\"* ]]; then\n        local candidates=\"$all_versions\"\n\n        # Split by comma and process each constraint part\n        local IFS=','\n        local -a parts\n        read -ra parts \u003c\u003c\u003c \"$constraint\"\n        unset IFS\n\n        for part in \"${parts[@]}\"; do\n            # Trim whitespace\n            part=$(echo \"$part\" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')\n            local temp_result=''\n\n            case \"$part\" in\n                \\\u003e=*)\n                    local min_version=\"${part#\u003e=}\"\n                    for version in $candidates; do\n                        if version_compare \"$version\" \"ge\" \"$min_version\"; then\n                            temp_result=\"$temp_result $version\"\n                        fi\n                    done\n                    ;;\n                \\\u003e*)\n                    local min_version=\"${part#\u003e}\"\n                    for version in $candidates; do\n                        if version_compare \"$version\" \"gt\" \"$min_version\"; then\n                            temp_result=\"$temp_result $version\"\n                        fi\n                    done\n                    ;;\n                \\\u003c=*)\n                    local max_version=\"${part#\u003c=}\"\n                    for version in $candidates; do\n                        if version_compare \"$version\" \"le\" \"$max_version\"; then\n                            temp_result=\"$temp_result $version\"\n                        fi\n                    done\n                    ;;\n                \\\u003c*)\n                    local max_version=\"${part#\u003c}\"\n                    for version in $candidates; do\n                        if version_compare \"$version\" \"lt\" \"$max_version\"; then\n                            temp_result=\"$temp_result $version\"\n                        fi\n                    done\n                    ;;\n                ==*)\n                    local exact_version=\"${part#==}\"\n                    for version in $candidates; do\n                        if version_compare \"$version\" \"eq\" \"$exact_version\"; then\n                            temp_result=\"$version\"\n                            break\n                        fi\n                    done\n                    ;;\n                \\!\\=*)\n                    local exclude_raw=\"${part#!=}\"\n                    local exclude_target=\"\"\n                    if [[ \"$exclude_raw\" =~ ^([0-9]+)\\.([0-9]+) ]]; then\n                        exclude_target=\"${BASH_REMATCH[1]}.${BASH_REMATCH[2]}\"\n                    else\n                        exclude_target=\"$exclude_raw\"\n                    fi\n                    for version in $candidates; do\n                        if ! version_compare \"$version\" \"eq\" \"$exclude_target\"; then\n                            temp_result=\"$temp_result $version\"\n                        fi\n                    done\n                    ;;\n                *)\n                    echo \"Warning: Unsupported constraint part: $part\" \u003e\u00262\n                    return 1\n                    ;;\n            esac\n\n            # Update candidates to be the filtered result.\n            # Use awk for order-preserving dedup; sort -u would\n            # interleave '3.10' before '3.9' lexicographically.\n            # The final result is re-sorted numerically below;\n            # this keeps the intermediate state consistent too.\n            if [[ -n \"$temp_result\" ]]; then\n                candidates=$(echo \"$temp_result\" | tr ' ' '\\n' | grep -v '^$' | \\\n                           awk '!seen[$0]++' | tr '\\n' ' ' | sed 's/ *$//')\n            else\n                candidates=''\n                break  # No matches found, no point continuing\n            fi\n        done\n\n        result=\"$candidates\"\n    else\n        # Handle simple constraints\n        case \"$constraint\" in\n            \\\u003e=*)\n                local min_version=\"${constraint#\u003e=}\"\n                for version in $all_versions; do\n                    if version_compare \"$version\" \"ge\" \"$min_version\"; then\n                        result=\"$result $version\"\n                    fi\n                done\n                ;;\n            \\\u003e*)\n                local min_version=\"${constraint#\u003e}\"\n                for version in $all_versions; do\n                    if version_compare \"$version\" \"gt\" \"$min_version\"; then\n                        result=\"$result $version\"\n                    fi\n                done\n                ;;\n            \\\u003c=*)\n                local max_version=\"${constraint#\u003c=}\"\n                for version in $all_versions; do\n                    if version_compare \"$version\" \"le\" \"$max_version\"; then\n                        result=\"$result $version\"\n                    fi\n                done\n                ;;\n            \\\u003c*)\n                local max_version=\"${constraint#\u003c}\"\n                for version in $all_versions; do\n                    if version_compare \"$version\" \"lt\" \"$max_version\"; then\n                        result=\"$result $version\"\n                    fi\n                done\n                ;;\n            ==*)\n                local exact_version=\"${constraint#==}\"\n                for version in $all_versions; do\n                    if version_compare \"$version\" \"eq\" \"$exact_version\"; then\n                        result=\"$version\"\n                        break\n                    fi\n                done\n                ;;\n            \\!\\=*)\n                local exclude_raw=\"${constraint#!=}\"\n                local exclude_target=\"\"\n                if [[ \"$exclude_raw\" =~ ^([0-9]+)\\.([0-9]+) ]]; then\n                    exclude_target=\"${BASH_REMATCH[1]}.${BASH_REMATCH[2]}\"\n                else\n                    exclude_target=\"$exclude_raw\"\n                fi\n                for version in $all_versions; do\n                    if ! version_compare \"$version\" \"eq\" \"$exclude_target\"; then\n                        result=\"$result $version\"\n                    fi\n                done\n                ;;\n            *)\n                echo \"Warning: Unsupported constraint format: $constraint\" \u003e\u00262\n                return 1\n                ;;\n        esac\n    fi\n\n    # Clean up, sort, and return result\n    result=$(printf '%s\\n' \"$result\" | sed 's/^ *//' | sed 's/ *$//')\n    if [[ -n \"$result\" ]]; then\n        # Ensure final result is properly sorted\n        result=$(sort_versions \"$result\")\n        printf '%s\\n' \"$result\"\n        return 0\n    else\n        return 1\n    fi\n}\n\n# Function: Handle EOL versions based on behaviour setting\nhandle_eol_versions() {\n    local requested_versions=\"$1\"\n    local eol_behaviour=\"$2\"\n    local timeout=\"$3\"\n    local retries=\"$4\"\n    local final_versions=\"\"\n    local eol_data\n\n    if [[ \"$OFFLINE_MODE\" == \"true\" ]]; then\n        [ -n \"${GITHUB_STEP_SUMMARY:-}\" ] \u0026\u0026 echo \"⚠️ Warning: Offline mode enabled, EOL filtering disabled\" \u003e\u003e \"$GITHUB_STEP_SUMMARY\"\n        printf '%s\\n' \"$requested_versions\"\n        return 0\n    fi\n\n    # Fetch EOL data once\n    if ! eol_data=$(fetch_python_data \"$timeout\" \"$retries\" \"raw\"); then\n      echo \"⚠️ Warning: Could not fetch EOL data, proceeding with all requested versions\" \u003e\u00262\n      [ -n \"${GITHUB_STEP_SUMMARY:-}\" ] \u0026\u0026 echo \"⚠️ Warning: EOL filtering disabled due to API failure\" \u003e\u003e \"$GITHUB_STEP_SUMMARY\"\n      printf '%s\\n' \"$requested_versions\"\n      return 0\n    fi\n\n    echo \"✅ Successfully fetched EOL data for version filtering\" \u003e\u00262\n\n    # Process each version individually\n    for version in $requested_versions; do\n        local eol_result\n        eol_result=$(check_version_eol \"$version\" \"$eol_data\")\n\n        if [[ \"$eol_result\" != \"false\" ]]; then\n            # This version is EOL\n            case \"$eol_behaviour\" in\n                \"warn\")\n                    local message\n                    if [[ \"$eol_result\" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then\n                      message=\"⚠️ Warning: Python $version is EOL (end-of-life date: $eol_result)\"\n                    else\n                      message=\"⚠️ Warning: Python $version is EOL\"\n                    fi\n                    echo \"$message\" \u003e\u00262\n                    [ -n \"${GITHUB_STEP_SUMMARY:-}\" ] \u0026\u0026 echo \"$message\" \u003e\u003e \"$GITHUB_STEP_SUMMARY\"\n                    # Include this version in final list\n                    final_versions=\"$final_versions $version\"\n                    ;;\n                \"strip\")\n                    local message\n                    if [[ \"$eol_result\" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then\n                      message=\"⚠️ Warning: Python $version is EOL (end-of-life date: $eol_result) - removing from matrix\"\n                    else\n                      message=\"⚠️ Warning: Python $version is EOL - removing from matrix\"\n                    fi\n                    echo \"$message\" \u003e\u00262\n                    [ -n \"${GITHUB_STEP_SUMMARY:-}\" ] \u0026\u0026 echo \"$message\" \u003e\u003e \"$GITHUB_STEP_SUMMARY\"\n                    # Do not include this version in final list\n                    ;;\n                \"fail\")\n                    local message\n                    if [[ \"$eol_result\" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}$ ]]; then\n                      message=\"🛑 Error: Python $version is EOL (end-of-life date: $eol_result)\"\n                    else\n                      message=\"🛑 Error: Python $version is EOL\"\n                    fi\n                    echo \"$message\" \u003e\u00262\n                    [ -n \"${GITHUB_STEP_SUMMARY:-}\" ] \u0026\u0026 echo \"$message\" \u003e\u003e \"$GITHUB_STEP_SUMMARY\"\n                    exit 1\n                    ;;\n            esac\n        else\n            # This version is not EOL, include it\n            final_versions=\"$final_versions $version\"\n        fi\n    done\n\n    # Clean up whitespace and return\n    final_versions=$(echo \"$final_versions\" | sed 's/^ *//' | sed 's/ *$//')\n    printf '%s\\n' \"$final_versions\"\n}\n\n# Function: Generate matrix JSON from space-separated version list\ngenerate_matrix_json() {\n    local versions=\"$1\"\n\n    if [[ -z \"$versions\" ]]; then\n        echo '{\"python-version\": []}'\n        return 0\n    fi\n\n    # Use jq to properly construct JSON array\n    echo \"$versions\" | tr ' ' '\\n' | jq -R . | jq -s -c '{\"python-version\": .}'\n}\n\n# Function: Get the latest/build version from a list of versions\nget_build_version() {\n    local versions=\"$1\"\n\n    if [[ -z \"$versions\" ]]; then\n        return 1\n    fi\n\n    # Sort and get the last (highest) version\n    local sorted_versions\n    sorted_versions=$(sort_versions \"$versions\")\n    echo \"$sorted_versions\" | tr ' ' '\\n' | tail -1\n}\n\n# Function: Validate that all versions have correct format\nvalidate_version_format() {\n    local versions=\"$1\"\n\n    for version in $versions; do\n        if [[ ! \"$version\" =~ ^[0-9]+\\.[0-9]+$ ]]; then\n            echo \"Invalid version format: '$version'\" \u003e\u00262\n            return 1\n        fi\n    done\n\n    return 0\n}\n\n# Function: Validate JSON format using jq\nvalidate_json_format() {\n    local json=\"$1\"\n\n    if echo \"$json\" | jq -e . \u003e/dev/null 2\u003e\u00261; then\n        return 0\n    else\n        return 1\n    fi\n}\n\n### Main execution starts here ###\n\n# Source shared library helpers AFTER all inline function definitions.\n# This ensures the library's unit-tested implementations override the\n# inline duplicates (parse_version_constraint, sort_versions, ...) so\n# the layered processor and the inline call sites both go through the\n# same code path.\n# shellcheck disable=SC1091\nif [[ -n \"${GITHUB_ACTION_PATH:-}\" \u0026\u0026 \\\n      -f \"$GITHUB_ACTION_PATH/lib/constraint_utils.sh\" ]]; then\n  source \"$GITHUB_ACTION_PATH/lib/constraint_utils.sh\"\nfi\n\n# Get supported Python versions and remember whether we got them from\n# the live API (eol-fallback) or the internal static list (static-fallback).\nVERSIONS_SOURCE_FALLBACK=\"eol-fallback\"\nif SUPPORTED_VERSIONS=$(fetch_python_data \"$TIMEOUT\" \"$RETRIES\" \"versions\"); then\n  if [[ \"$OFFLINE_MODE\" == \"true\" ]]; then\n      echo \"Using internal supported Python versions (offline mode) 📴\"\n      VERSIONS_SOURCE_FALLBACK=\"static-fallback\"\n  else\n      echo \"Retrieved supported Python versions from API service 🌍\"\n  fi\nelse\n  echo \"Unable to retrieve supported Python versions, using internal list ⚠️\"\n  SUPPORTED_VERSIONS=\"$INTERNAL_SUPPORTED_VERSIONS\"\n  VERSIONS_SOURCE_FALLBACK=\"static-fallback\"\nfi\necho \"Supported versions: $SUPPORTED_VERSIONS\"\n\n# Try the layered project-side extraction (pyproject.toml → setup.cfg\n# → setup.py) via the shared library. If the library is not available\n# for some reason, fall back to the legacy pyproject-only path.\nREQUESTED_PYTHON_VERSIONS=\"\"\nVERSIONS_SOURCE=\"\"\n\nif declare -F process_python_constraints_layered \u003e/dev/null 2\u003e\u00261; then\n  LAYERED_OUT=\"\"\n  if LAYERED_OUT=$(process_python_constraints_layered \"$path_prefix\" \"$SUPPORTED_VERSIONS\"); then\n    REQUESTED_PYTHON_VERSIONS=$(printf '%s\\n' \"$LAYERED_OUT\" | sed -n '1p')\n    VERSIONS_SOURCE=$(printf '%s\\n' \"$LAYERED_OUT\" | sed -n '2p')\n    echo \"🔍 Found Python version data via: $VERSIONS_SOURCE\"\n  fi\nelif [[ -f \"$PYPROJECT_FILE\" ]]; then\n  # Legacy inline path: pyproject-only.\n  REQUIRES_PYTHON=\"\"\n  if command -v python3 \u003e/dev/null 2\u003e\u00261; then\n    REQUIRES_PYTHON=$(extract_requires_python_python \"$PYPROJECT_FILE\" || true)\n  fi\n  if [[ -z \"$REQUIRES_PYTHON\" ]]; then\n    REQUIRES_PYTHON=$(extract_requires_python_fallback \"$PYPROJECT_FILE\" || true)\n  fi\n  CLASSIFIERS=$(extract_classifiers_fallback \"$PYPROJECT_FILE\" || true)\n  if [[ -n \"$REQUIRES_PYTHON\" ]]; then\n    if REQUESTED_PYTHON_VERSIONS=$(parse_version_constraint \"$REQUIRES_PYTHON\" \"$SUPPORTED_VERSIONS\"); then\n      VERSIONS_SOURCE=\"requires-python\"\n    fi\n  fi\n  if [[ -z \"$REQUESTED_PYTHON_VERSIONS\" \u0026\u0026 -n \"$CLASSIFIERS\" ]]; then\n    # Filter classifier-derived versions to those present in the\n    # supported list, mirroring _filter_to_available in the shared\n    # library so both code paths emit consistent matrices.\n    FILTERED_CLASSIFIERS=\"\"\n    for v in $CLASSIFIERS; do\n      for a in $SUPPORTED_VERSIONS; do\n        if [[ \"$v\" == \"$a\" ]]; then\n          FILTERED_CLASSIFIERS=\"$FILTERED_CLASSIFIERS $v\"\n          break\n        fi\n      done\n    done\n    FILTERED_CLASSIFIERS=$(echo \"$FILTERED_CLASSIFIERS\" | sed 's/^ *//; s/ *$//')\n    if [[ -n \"$FILTERED_CLASSIFIERS\" ]]; then\n      REQUESTED_PYTHON_VERSIONS=\"$FILTERED_CLASSIFIERS\"\n      VERSIONS_SOURCE=\"classifiers\"\n    fi\n  fi\nfi\n\n# If nothing was found, fall back to the EOL-aware (or static)\n# supported-versions list and emit a warning. Do NOT exit non-zero —\n# legacy projects with no declared constraint are expected. Note: this\n# branch also covers the case where a constraint *was* declared but\n# produced zero matches against SUPPORTED_VERSIONS (for example\n# `requires-python = \"\u003e=4.0\"` against a 3.x-only available list);\n# the warning text is therefore phrased to cover both cases.\nif [[ -z \"$REQUESTED_PYTHON_VERSIONS\" ]]; then\n  echo \"::warning::No usable Python version constraint could be determined from pyproject.toml, setup.cfg or setup.py (none declared, or the declared constraint matched no supported versions) — falling back to the EOL-aware supported-versions list. Add 'requires-python' (or 'python_requires') to silence this warning.\"\n  REQUESTED_PYTHON_VERSIONS=\"$SUPPORTED_VERSIONS\"\n  VERSIONS_SOURCE=\"$VERSIONS_SOURCE_FALLBACK\"\nfi\n\nif ! validate_version_format \"$REQUESTED_PYTHON_VERSIONS\"; then\n    echo \"Error: Invalid version format detected ❌\"\n    echo \"Versions: $REQUESTED_PYTHON_VERSIONS\"\n    exit 1\nfi\n\necho \"Python versions from constraints: $REQUESTED_PYTHON_VERSIONS\"\necho \"Versions source: $VERSIONS_SOURCE\"\n\n# Handle EOL versions based on eol_behaviour setting\nFINAL_PYTHON_VERSIONS=$(handle_eol_versions \"$REQUESTED_PYTHON_VERSIONS\" \"$EOL_BEHAVIOUR\" \"$TIMEOUT\" \"$RETRIES\")\n\n# Validate final results\nif [[ -z \"$FINAL_PYTHON_VERSIONS\" ]]; then\n    echo \"Error: No supported Python versions after EOL processing ❌\"\n    exit 1\nfi\n\n# Sort and clean final versions\nFINAL_PYTHON_VERSIONS=$(sort_versions \"$FINAL_PYTHON_VERSIONS\")\n\n# Generate build version and matrix JSON\nif BUILD_PYTHON=$(get_build_version \"$FINAL_PYTHON_VERSIONS\"); then\n  echo \"Build Python: $BUILD_PYTHON\"\nelse\n  echo \"Error: Failed to determine build Python version ❌\"\n  exit 1\nfi\n\n# When falling back to the EOL-aware list and the caller has supplied\n# a default_python_version, override build_python with it (still emit\n# the full fallback matrix). Useful for org-wide policy.\nif [[ -n \"$DEFAULT_PYTHON_VERSION\" \u0026\u0026 \\\n      ( \"$VERSIONS_SOURCE\" == \"eol-fallback\" || \\\n        \"$VERSIONS_SOURCE\" == \"static-fallback\" ) ]]; then\n  if [[ ! \"$DEFAULT_PYTHON_VERSION\" =~ ^[0-9]+\\.[0-9]+$ ]]; then\n    echo \"::warning::Ignoring default_python_version='$DEFAULT_PYTHON_VERSION' (must be in X.Y form)\"\n  else\n    # Only override when the requested version is actually present in\n    # the final matrix, so downstream steps that consume build_python\n    # are guaranteed to find it in matrix_json.\n    override_ok='false'\n    for _v in $FINAL_PYTHON_VERSIONS; do\n      if [[ \"$_v\" == \"$DEFAULT_PYTHON_VERSION\" ]]; then\n        override_ok='true'\n        break\n      fi\n    done\n    if [[ \"$override_ok\" == 'true' ]]; then\n      echo \"Overriding build_python with default_python_version=$DEFAULT_PYTHON_VERSION (fallback in effect)\"\n      BUILD_PYTHON=\"$DEFAULT_PYTHON_VERSION\"\n    else\n      echo \"::warning::default_python_version='$DEFAULT_PYTHON_VERSION' is not in the supported matrix [$FINAL_PYTHON_VERSIONS]; keeping computed build_python=$BUILD_PYTHON\"\n    fi\n  fi\nfi\n\nif MATRIX_JSON=$(generate_matrix_json \"$FINAL_PYTHON_VERSIONS\") \u0026\u0026 \\\n  validate_json_format \"$MATRIX_JSON\"; then\n  echo \"Matrix JSON generated successfully\"\nelse\n  echo \"Error: Failed to generate matrix JSON ❌\"\n  exit 1\nfi\n\n# Set outputs\necho \"build_python=$BUILD_PYTHON\" \u003e\u003e \"$GITHUB_OUTPUT\"\n{\n  echo \"matrix_json\u003c\u003c__PY_MATRIX_JSON__\"\n  echo \"$MATRIX_JSON\"\n  echo \"__PY_MATRIX_JSON__\"\n} \u003e\u003e \"$GITHUB_OUTPUT\"\necho \"supported_versions=$FINAL_PYTHON_VERSIONS\" \u003e\u003e \"$GITHUB_OUTPUT\"\necho \"versions_source=$VERSIONS_SOURCE\" \u003e\u003e \"$GITHUB_OUTPUT\"\n\n# Final clean output\necho \"✅ Build Python: $BUILD_PYTHON\"\necho \"✅ Supported versions: $FINAL_PYTHON_VERSIONS\"\necho \"✅ Matrix JSON: $MATRIX_JSON\"\n"}]},"default_branch":"main","path":null},"repo_metadata":{"id":282433301,"uuid":"947919055","full_name":"lfreleng-actions/python-supported-versions-action","owner":"lfreleng-actions","description":"Extracts supported Python versions from the project metadata file","archived":false,"fork":false,"pushed_at":"2026-09-21T21:45:38.000Z","size":479,"stargazers_count":0,"open_issues_count":0,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-09-25T10:27:36.805Z","etag":null,"topics":["python","versions"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lfreleng-actions.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null,"disclosure":null}},"created_at":"2025-03-13T13:05:56.000Z","updated_at":"2026-09-21T21:45:43.000Z","dependencies_parsed_at":"2026-05-27T11:01:41.937Z","dependency_job_id":null,"html_url":"https://github.com/lfreleng-actions/python-supported-versions-action","commit_stats":null,"previous_names":["lfreleng-actions/python-supported-versions-action"],"tags_count":17,"template":false,"template_full_name":"lfreleng-actions/actions-template","purl":"pkg:github/lfreleng-actions/python-supported-versions-action","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lfreleng-actions","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":341189360,"owners_count":37677391,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-08-22T15:14:58.755Z","status":"online","status_checked_at":"2026-09-25T02:00:20.896Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"},"owner_record":{"login":"lfreleng-actions","name":"LF Release Engineering Actions","uuid":"202967442","kind":"organization","description":"GitHub Actions used by LF Release Engineering","email":"releng+gha@linuxfoundation.org","website":"https://docs.releng.linuxfoundation.org/","location":null,"twitter":null,"company":null,"icon_url":"https://avatars.githubusercontent.com/u/202967442?v=4","repositories_count":1,"last_synced_at":"2025-03-12T16:39:35.120Z","metadata":{"has_sponsors_listing":false},"html_url":"https://github.com/lfreleng-actions","funding_links":[],"total_stars":0,"followers":0,"following":0,"created_at":"2025-03-12T16:39:35.144Z","updated_at":"2025-03-12T16:39:35.144Z","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lfreleng-actions","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lfreleng-actions/repositories"},"tags":[{"name":"v0.2.3","sha":"109a0d1865cb4f42feeb76fcd24e74a7186950e2","kind":"tag","published_at":"2026-08-24T12:54:04.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.2.3","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.2.3","dependencies_parsed_at":"2026-09-24T10:35:30.718Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.2.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.2.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.2.3/manifests"},{"name":"v0.2.2","sha":"12018ee751415260147d2ca2f0e990cd1481f3b0","kind":"tag","published_at":"2026-06-24T10:45:36.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.2.2","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.2.2","dependencies_parsed_at":"2026-08-08T10:51:53.825Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.2.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.2.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.2.2/manifests"},{"name":"v0.2.1","sha":"9816c9bfa58a583e1311f6c9cd395de91ab9da70","kind":"tag","published_at":"2026-05-22T10:38:49.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.2.1","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.2.1","dependencies_parsed_at":"2026-08-08T10:51:53.875Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.2.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.2.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.2.1/manifests"},{"name":"v0.2.0","sha":"78063d3b1de85859b934d1740036fa3bf3e6c45f","kind":"tag","published_at":"2026-05-07T02:08:00.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.2.0","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.2.0","dependencies_parsed_at":"2026-08-08T10:51:53.893Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.2.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.2.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.2.0/manifests"},{"name":"v0.1.12","sha":"78063d3b1de85859b934d1740036fa3bf3e6c45f","kind":"tag","published_at":"2026-05-07T02:07:03.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.12","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.12","dependencies_parsed_at":"2026-08-08T10:51:53.912Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.12","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.12","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.12/manifests"},{"name":"v0.1.11","sha":"55afbeffb9162574b10f8c6f89c3ba931db77c0f","kind":"tag","published_at":"2026-04-22T13:09:31.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.11","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.11","dependencies_parsed_at":"2026-08-08T10:51:53.951Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.11","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.11","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.11/manifests"},{"name":"v0.1.10","sha":"206ef1eec65ca46c8cace56db14f1172e172eafe","kind":"tag","published_at":"2025-08-20T16:56:51.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.10","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.10","dependencies_parsed_at":"2025-08-28T07:52:47.720Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.10","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.10","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.10/manifests"},{"name":"v0.1.9","sha":"c580b8937b1f2485970207d899a316f155da049e","kind":"tag","published_at":"2025-08-20T13:48:51.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.9","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.9","dependencies_parsed_at":"2025-08-28T07:52:47.975Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.9","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.9","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.9/manifests"},{"name":"v0.1.8","sha":"de90658c86815a5fd377497e4d45c93ece3eb499","kind":"tag","published_at":"2025-08-07T22:56:26.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.8","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.8","dependencies_parsed_at":"2026-08-08T10:51:53.886Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.8","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.8","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.8/manifests"},{"name":"v0.1.7","sha":"10baeaabac508008f3c4f29b35c49bceb9b1662e","kind":"tag","published_at":"2025-07-15T17:35:17.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.7","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.7","dependencies_parsed_at":"2025-07-21T05:11:37.035Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.7","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.7","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.7/manifests"},{"name":"v0.1.6","sha":"14c5f9025a652062849adb771ff75da771886732","kind":"tag","published_at":"2025-07-10T18:19:01.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.6","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.6","dependencies_parsed_at":"2025-07-21T05:11:39.461Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.6","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.6","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.6/manifests"},{"name":"v0.1.5","sha":"011ec4085152a59eb60acf06e1da0e023feebaad","kind":"tag","published_at":"2025-04-09T01:43:56.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.5","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.5","dependencies_parsed_at":"2025-07-21T05:11:53.677Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.5","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.5","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.5/manifests"},{"name":"v0.1.4","sha":"e5f39afc35307199a3c03176f6c7d3f9e6d4b1ea","kind":"tag","published_at":"2025-04-09T01:23:53.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.4","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.4","dependencies_parsed_at":"2025-07-21T05:11:47.333Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.4/manifests"},{"name":"v0.1.3","sha":"7040daaa69e77cb3fe4dda9d40d88a14b71bc501","kind":"tag","published_at":"2025-04-05T02:58:28.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.3","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.3","dependencies_parsed_at":"2025-07-21T05:11:42.805Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.3/manifests"},{"name":"v0.1.2","sha":"53523fcecb0fb32f2e09d2b6c587b6d876cfeb1d","kind":"tag","published_at":"2025-04-05T02:38:12.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.2","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.2","dependencies_parsed_at":"2025-07-21T05:11:39.613Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.2/manifests"},{"name":"v0.1.1","sha":"4a13b86900547ed05d67e0332c4c6749f1391d85","kind":"tag","published_at":"2025-03-28T16:35:50.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.1","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.1","dependencies_parsed_at":"2025-07-21T05:11:34.631Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.1/manifests"},{"name":"v0.1.0","sha":"2466ad0e7e119e5484bb404b84b97253c862991b","kind":"tag","published_at":"2025-03-16T14:23:49.000Z","download_url":"https://codeload.github.com/lfreleng-actions/python-supported-versions-action/tar.gz/v0.1.0","html_url":"https://github.com/lfreleng-actions/python-supported-versions-action/releases/tag/v0.1.0","dependencies_parsed_at":"2025-07-21T05:11:32.931Z","dependency_job_id":null,"purl":"pkg:github/lfreleng-actions/python-supported-versions-action@v0.1.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/tags/v0.1.0/manifests"}]},"repo_metadata_updated_at":"2026-09-25T12:54:53.240Z","dependent_packages_count":0,"downloads":null,"downloads_period":null,"dependent_repos_count":0,"rankings":{"downloads":null,"dependent_repos_count":30.472652347652346,"dependent_packages_count":0.0,"stargazers_count":null,"forks_count":null,"docker_downloads_count":null,"average":15.236326173826173},"purl":"pkg:githubactions/lfreleng-actions/python-supported-versions-action","advisories":[],"docker_usage_url":"https://docker.ecosyste.ms/usage/actions/lfreleng-actions/python-supported-versions-action","docker_dependents_count":null,"docker_downloads_count":null,"usage_url":"https://repos.ecosyste.ms/usage/actions/lfreleng-actions/python-supported-versions-action","dependent_repositories_url":"https://repos.ecosyste.ms/api/v1/usage/actions/lfreleng-actions/python-supported-versions-action/dependencies","status":null,"funding_links":[],"critical":null,"issue_metadata":{"last_synced_at":"2026-09-25T08:15:29.951Z","issues_count":0,"pull_requests_count":109,"avg_time_to_close_issue":null,"avg_time_to_close_pull_request":76403.96808510639,"issues_closed_count":0,"pull_requests_closed_count":94,"pull_request_authors_count":5,"issue_authors_count":0,"avg_comments_per_issue":null,"avg_comments_per_pull_request":0.05504587155963303,"merged_pull_requests_count":92,"bot_issues_count":0,"bot_pull_requests_count":78,"past_year_issues_count":0,"past_year_pull_requests_count":56,"past_year_avg_time_to_close_issue":null,"past_year_avg_time_to_close_pull_request":88137.38888888889,"past_year_issues_closed_count":0,"past_year_pull_requests_closed_count":54,"past_year_pull_request_authors_count":4,"past_year_issue_authors_count":0,"past_year_avg_comments_per_issue":null,"past_year_avg_comments_per_pull_request":0.017857142857142856,"past_year_bot_issues_count":0,"past_year_bot_pull_requests_count":43,"past_year_merged_pull_requests_count":53,"issues_url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/repositories/lfreleng-actions%2Fpython-supported-versions-action/issues","maintainers":[],"active_maintainers":[]},"versions_url":"https://packages.ecosyste.ms/api/v1/registries/github%20actions/packages/lfreleng-actions%2Fpython-supported-versions-action/versions","version_numbers_url":"https://packages.ecosyste.ms/api/v1/registries/github%20actions/packages/lfreleng-actions%2Fpython-supported-versions-action/version_numbers","latest_version_url":"https://packages.ecosyste.ms/api/v1/registries/github%20actions/packages/lfreleng-actions%2Fpython-supported-versions-action/latest_version","dependent_packages_url":"https://packages.ecosyste.ms/api/v1/registries/github%20actions/packages/lfreleng-actions%2Fpython-supported-versions-action/dependent_packages","related_packages_url":"https://packages.ecosyste.ms/api/v1/registries/github%20actions/packages/lfreleng-actions%2Fpython-supported-versions-action/related_packages","codemeta_url":"https://packages.ecosyste.ms/api/v1/registries/github%20actions/packages/lfreleng-actions%2Fpython-supported-versions-action/codemeta","maintainers":[]}