diff --git a/README.md b/README.md index fe57693..1ff0b2f 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,9 @@ Each action is fully self-contained and can be used independently in any GitHub ## 📚 Action Catalog -This repository contains **30 reusable GitHub Actions** for CI/CD automation. +This repository contains **29 reusable GitHub Actions** for CI/CD automation. -### Quick Reference (30 Actions) +### Quick Reference (29 Actions) | Icon | Action | Category | Description | Key Features | |:----:|:-----------------------------------------------------|:-----------|:----------------------------------------------------------------|:---------------------------------------------| @@ -57,7 +57,6 @@ This repository contains **30 reusable GitHub Actions** for CI/CD automation. | 🏷️ | [`sync-labels`][sync-labels] | Repository | Sync labels from a YAML file to a GitHub repository | Token auth, Outputs | | 🖥️ | [`terraform-lint-fix`][terraform-lint-fix] | Linting | Lints and fixes Terraform files with advanced validation and... | Token auth, Outputs | | 🛡️ | [`validate-inputs`][validate-inputs] | Validation | Centralized Python-based input validation for GitHub Actions... | Token auth, Outputs | -| 📦 | [`version-file-parser`][version-file-parser] | Utilities | Universal parser for common version detection files (.tool-v... | Auto-detection, Outputs | ### Actions by Category @@ -68,12 +67,11 @@ This repository contains **30 reusable GitHub Actions** for CI/CD automation. | 📝 [`language-version-detect`][language-version-detect] | Detects language version from project configuratio... | PHP, Python, Go, .NET, Node.js | Auto-detection, Token auth, Outputs | | 🖥️ [`node-setup`][node-setup] | Sets up Node.js environment with version detection... | Node.js, JavaScript, TypeScript | Auto-detection, Token auth, Outputs | -#### 🛠️ Utilities (2 actions) +#### 🛠️ Utilities (1 action) -| Action | Description | Languages | Features | -|:------------------------------------------------|:------------------------------------------------------|:-------------------|:------------------------| -| 🔀 [`action-versioning`][action-versioning] | Automatically update SHA-pinned action references ... | GitHub Actions | Token auth, Outputs | -| 📦 [`version-file-parser`][version-file-parser] | Universal parser for common version detection file... | Multiple Languages | Auto-detection, Outputs | +| Action | Description | Languages | Features | +|:--------------------------------------------|:------------------------------------------------------|:---------------|:--------------------| +| 🔀 [`action-versioning`][action-versioning] | Automatically update SHA-pinned action references ... | GitHub Actions | Token auth, Outputs | #### 📝 Linting (10 actions) @@ -164,7 +162,6 @@ This repository contains **30 reusable GitHub Actions** for CI/CD automation. | [`sync-labels`][sync-labels] | - | - | ✅ | ✅ | | [`terraform-lint-fix`][terraform-lint-fix] | - | - | ✅ | ✅ | | [`validate-inputs`][validate-inputs] | - | - | ✅ | ✅ | -| [`version-file-parser`][version-file-parser] | - | ✅ | - | ✅ | ### Language Support @@ -188,7 +185,7 @@ This repository contains **30 reusable GitHub Actions** for CI/CD automation. | JavaScript | [`biome-lint`][biome-lint], [`codeql-analysis`][codeql-analysis], [`eslint-lint`][eslint-lint], [`node-setup`][node-setup], [`prettier-lint`][prettier-lint] | | Laravel | [`php-laravel-phpunit`][php-laravel-phpunit] | | Markdown | [`prettier-lint`][prettier-lint] | -| Multiple Languages | [`pre-commit`][pre-commit], [`version-file-parser`][version-file-parser] | +| Multiple Languages | [`pre-commit`][pre-commit] | | Node.js | [`language-version-detect`][language-version-detect], [`node-setup`][node-setup], [`npm-publish`][npm-publish] | | PHP | [`language-version-detect`][language-version-detect], [`php-composer`][php-composer], [`php-laravel-phpunit`][php-laravel-phpunit], [`php-tests`][php-tests] | | PNG | [`compress-images`][compress-images] | @@ -248,7 +245,6 @@ All actions can be used independently in your workflows: [sync-labels]: sync-labels/README.md [terraform-lint-fix]: terraform-lint-fix/README.md [validate-inputs]: validate-inputs/README.md -[version-file-parser]: version-file-parser/README.md --- diff --git a/_tests/integration/workflows/node-setup-test.yml b/_tests/integration/workflows/node-setup-test.yml index 98d4e03..abb1f58 100644 --- a/_tests/integration/workflows/node-setup-test.yml +++ b/_tests/integration/workflows/node-setup-test.yml @@ -5,9 +5,7 @@ on: push: paths: - 'node-setup/**' - - 'version-file-parser/**' - 'common-cache/**' - - 'common-retry/**' - '_tests/integration/workflows/node-setup-test.yml' jobs: diff --git a/_tests/integration/workflows/version-file-parser-test.yml b/_tests/integration/workflows/version-file-parser-test.yml deleted file mode 100644 index 2c2d83b..0000000 --- a/_tests/integration/workflows/version-file-parser-test.yml +++ /dev/null @@ -1,241 +0,0 @@ ---- -name: Test version-file-parser Integration -on: - workflow_dispatch: - push: - paths: - - 'version-file-parser/**' - - '_tests/integration/workflows/version-file-parser-test.yml' - -jobs: - test-version-file-parser: - runs-on: ubuntu-latest - strategy: - matrix: - test-case: - - name: 'Node.js project' - language: 'node' - tool-versions-key: 'nodejs' - dockerfile-image: 'node' - expected-version: '18.0.0' - setup-files: | - echo "18.17.0" > .nvmrc - cat > package.json <=18.0.0" } - } - EOF - touch package-lock.json - - - name: 'PHP project' - language: 'php' - tool-versions-key: 'php' - dockerfile-image: 'php' - expected-version: '8.1' - setup-files: | - cat > composer.json < .python-version - cat > pyproject.toml < go.mod < .tool-versions - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Clean up test files from previous runs - run: | - rm -f .nvmrc package.json package-lock.json composer.json .python-version pyproject.toml go.mod .tool-versions - - - name: Setup test files - run: ${{ matrix.test-case.setup-files }} - - - name: Test version-file-parser - id: test-action - uses: ./version-file-parser - with: - language: ${{ matrix.test-case.language }} - tool-versions-key: ${{ matrix.test-case.tool-versions-key }} - dockerfile-image: ${{ matrix.test-case.dockerfile-image }} - default-version: '1.0.0' - - - name: Validate outputs - run: | - echo "Test case: ${{ matrix.test-case.name }}" - echo "Expected version: ${{ matrix.test-case.expected-version }}" - echo "Detected version: ${{ steps.test-action.outputs.detected-version }}" - echo "Package manager: ${{ steps.test-action.outputs.package-manager }}" - - # Validate that we got some version - if [[ -z "${{ steps.test-action.outputs.detected-version }}" ]]; then - echo "❌ ERROR: No version detected" - exit 1 - fi - - # Validate version format (basic semver check) - if ! echo "${{ steps.test-action.outputs.detected-version }}" | grep -E '^[0-9]+\.[0-9]+(\.[0-9]+)?'; then - echo "❌ ERROR: Invalid version format: ${{ steps.test-action.outputs.detected-version }}" - exit 1 - fi - - # Validate detected version matches expected version (not the fallback) - if [[ "${{ steps.test-action.outputs.detected-version }}" != "${{ matrix.test-case.expected-version }}" ]]; then - echo "❌ ERROR: Version mismatch" - echo "Expected: ${{ matrix.test-case.expected-version }}" - echo "Got: ${{ steps.test-action.outputs.detected-version }}" - exit 1 - fi - - echo "✅ Version validation passed" - - # Skip external reference test in local/CI environment to avoid auth issues - - name: Test external reference (info only) - run: | - echo "External reference test would use: ivuorinen/actions/version-file-parser@main" - echo "Skipping to avoid authentication issues in local testing" - - test-edge-cases: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Clean up test files from previous runs - run: | - rm -f .nvmrc package.json package-lock.json composer.json .python-version pyproject.toml go.mod .tool-versions - - - name: Setup test files (package.json engines) - shell: bash - run: | - set -Eeuo pipefail - cat > package.json <<'EOF' - { - "name": "edge-case", - "engines": { "node": ">=18.0.0" } - } - EOF - echo "18.17.0" > .nvmrc - - - name: Test version detection from existing files - id: existing-version - uses: ./version-file-parser - with: - language: 'node' - tool-versions-key: 'nodejs' - dockerfile-image: 'node' - default-version: '20.0.0' - - - name: Validate existing version detection - run: | - # The action detects Node.js version from package.json engines field - # package.json >=18.0.0 is parsed as 18.0.0 - # Note: .nvmrc exists but package.json takes precedence in this implementation - expected_version="18.0.0" - detected_version="${{ steps.existing-version.outputs.detected-version }}" - - if [[ "$detected_version" != "$expected_version" ]]; then - echo "❌ ERROR: Version mismatch" - echo "Expected: $expected_version" - echo "Got: $detected_version" - exit 1 - fi - echo "✅ Existing version detection works correctly" - - - name: Clean up before invalid regex test - run: | - rm -f .nvmrc package.json package-lock.json - - - name: Test with invalid regex - id: invalid-regex - uses: ./version-file-parser - with: - language: 'node' - tool-versions-key: 'nodejs' - dockerfile-image: 'node' - validation-regex: 'invalid[regex' - default-version: '18.0.0' - continue-on-error: true - - - name: Validate regex error handling - run: | - echo "Testing regex error handling completed" - # Action should handle invalid regex gracefully - if [ "${{ steps.invalid-regex.outcome }}" != "failure" ]; then - echo "::error::Expected invalid-regex step to fail, but it was: ${{ steps.invalid-regex.outcome }}" - exit 1 - fi - echo "✅ Invalid regex properly failed as expected" - - test-dockerfile-parsing: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Clean up test files from previous runs - run: | - rm -f .nvmrc package.json package-lock.json composer.json .python-version pyproject.toml go.mod .tool-versions Dockerfile - - - name: Create Dockerfile with Node.js - run: | - cat > Dockerfile <>"$GITHUB_OUTPUT" - echo "package-manager=npm" >>"$GITHUB_OUTPUT" - ;; "node-setup") echo "node-version=18.0.0" >>"$GITHUB_OUTPUT" echo "package-manager=npm" >>"$GITHUB_OUTPUT" diff --git a/_tests/unit/version-file-parser/validation.spec.sh b/_tests/unit/version-file-parser/validation.spec.sh deleted file mode 100755 index f94fe6d..0000000 --- a/_tests/unit/version-file-parser/validation.spec.sh +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env shellspec -# Unit tests for version-file-parser action validation and logic -# Framework is automatically loaded via spec_helper.sh - -Describe "version-file-parser action" - ACTION_DIR="version-file-parser" - ACTION_FILE="$ACTION_DIR/action.yml" - - Context "when validating language input" - It "accepts valid language input" - When call validate_input_python "version-file-parser" "language" "node" - The status should be success - End - It "accepts php language" - When call validate_input_python "version-file-parser" "language" "php" - The status should be success - End - It "accepts python language" - When call validate_input_python "version-file-parser" "language" "python" - The status should be success - End - It "accepts go language" - When call validate_input_python "version-file-parser" "language" "go" - The status should be success - End - It "rejects invalid language with special characters" - When call validate_input_python "version-file-parser" "language" "node; rm -rf /" - The status should be failure - End - It "rejects empty required inputs" - When call validate_input_python "version-file-parser" "language" "" - The status should be failure - End - End - - Context "when validating dockerfile-image input" - It "accepts valid dockerfile image" - When call validate_input_python "version-file-parser" "dockerfile-image" "node" - The status should be success - End - It "accepts php dockerfile image" - When call validate_input_python "version-file-parser" "dockerfile-image" "php" - The status should be success - End - It "accepts python dockerfile image" - When call validate_input_python "version-file-parser" "dockerfile-image" "python" - The status should be success - End - It "rejects injection in dockerfile image" - When call validate_input_python "version-file-parser" "dockerfile-image" "node;malicious" - The status should be failure - End - End - - Context "when validating optional inputs" - It "accepts valid validation regex" - When call validate_input_python "version-file-parser" "validation-regex" "^[0-9]+\.[0-9]+(\.[0-9]+)?$" - The status should be success - End - It "accepts valid default version" - When call validate_input_python "version-file-parser" "default-version" "18.0.0" - The status should be success - End - It "accepts tool versions key" - When call validate_input_python "version-file-parser" "tool-versions-key" "nodejs" - The status should be success - End - End - - Context "when checking action.yml structure" - It "has valid YAML syntax" - When call validate_action_yml_quiet "$ACTION_FILE" - The status should be success - End - - It "contains required metadata" - When call get_action_name "$ACTION_FILE" - The output should equal "Version File Parser" - End - - It "defines expected inputs" - When call get_action_inputs "$ACTION_FILE" - The output should include "language" - The output should include "tool-versions-key" - The output should include "dockerfile-image" - End - - It "defines expected outputs" - When call get_action_outputs "$ACTION_FILE" - The output should include "detected-version" - The output should include "package-manager" - End - End - - Context "when validating security" - It "rejects injection in language parameter" - When call validate_input_python "version-file-parser" "language" "node&&malicious" - The status should be failure - End - - It "rejects pipe injection in tool versions key" - When call validate_input_python "version-file-parser" "tool-versions-key" "nodejs|dangerous" - The status should be failure - End - - It "validates regex patterns safely" - When call validate_input_python "version-file-parser" "validation-regex" "^[0-9]+\.[0-9]+$" - The status should be success - End - - It "rejects malicious regex patterns" - When call validate_input_python "version-file-parser" "validation-regex" ".*; rm -rf /" - The status should be failure - End - End - - Context "when testing outputs" - It "produces all expected outputs consistently" - When call test_action_outputs "$ACTION_DIR" "language" "node" "dockerfile-image" "node" - The status should be success - The stderr should include "Testing action outputs for: version-file-parser" - The stderr should include "Output test passed for: version-file-parser" - End - End -End diff --git a/validate-inputs/tests/test_version-file-parser_custom.py b/validate-inputs/tests/test_version-file-parser_custom.py deleted file mode 100644 index a9254b6..0000000 --- a/validate-inputs/tests/test_version-file-parser_custom.py +++ /dev/null @@ -1,74 +0,0 @@ -"""Tests for version-file-parser custom validator. - -Generated by generate-tests.py - Do not edit manually. -""" -# pylint: disable=invalid-name # Test file name matches action name - -import sys -from pathlib import Path - -# Add action directory to path to import custom validator -action_path = Path(__file__).parent.parent.parent / "version-file-parser" -sys.path.insert(0, str(action_path)) - -# pylint: disable=wrong-import-position -from CustomValidator import CustomValidator - - -class TestCustomVersionFileParserValidator: - """Test cases for version-file-parser custom validator.""" - - def setup_method(self): - """Set up test fixtures.""" - self.validator = CustomValidator("version-file-parser") - - def teardown_method(self): - """Clean up after tests.""" - self.validator.clear_errors() - - def test_validate_inputs_valid(self): - """Test validation with valid inputs.""" - # TODO: Add specific valid inputs for version-file-parser - inputs = {} - result = self.validator.validate_inputs(inputs) - # Adjust assertion based on required inputs - assert isinstance(result, bool) - - def test_validate_inputs_invalid(self): - """Test validation with invalid inputs.""" - # TODO: Add specific invalid inputs for version-file-parser - inputs = {"invalid_key": "invalid_value"} - result = self.validator.validate_inputs(inputs) - # Custom validators may have specific validation rules - assert isinstance(result, bool) - - def test_required_inputs(self): - """Test required inputs detection.""" - required = self.validator.get_required_inputs() - assert isinstance(required, list) - # TODO: Assert specific required inputs for version-file-parser - - def test_validation_rules(self): - """Test validation rules.""" - rules = self.validator.get_validation_rules() - assert isinstance(rules, dict) - # TODO: Assert specific validation rules for version-file-parser - - def test_github_expressions(self): - """Test GitHub expression handling.""" - inputs = { - "test_input": "${{ github.token }}", - } - result = self.validator.validate_inputs(inputs) - assert isinstance(result, bool) - # GitHub expressions should generally be accepted - - def test_error_propagation(self): - """Test error propagation from sub-validators.""" - # Custom validators often use sub-validators - # Test that errors are properly propagated - inputs = {"test": "value"} - self.validator.validate_inputs(inputs) - # Check error handling - if self.validator.has_errors(): - assert len(self.validator.errors) > 0 diff --git a/version-file-parser/CustomValidator.py b/version-file-parser/CustomValidator.py deleted file mode 100755 index a969c5a..0000000 --- a/version-file-parser/CustomValidator.py +++ /dev/null @@ -1,115 +0,0 @@ -#!/usr/bin/env python3 -"""Custom validator for version-file-parser action.""" - -from __future__ import annotations - -from pathlib import Path -import sys - -# Add validate-inputs directory to path to import validators -validate_inputs_path = Path(__file__).parent.parent / "validate-inputs" -sys.path.insert(0, str(validate_inputs_path)) - -from validators.base import BaseValidator - - -class CustomValidator(BaseValidator): - """Custom validator for version-file-parser action.""" - - def __init__(self, action_type: str = "version-file-parser") -> None: - """Initialize version-file-parser validator.""" - super().__init__(action_type) - - def validate_inputs(self, inputs: dict[str, str]) -> bool: - """Validate version-file-parser action inputs.""" - valid = True - - # Validate required input: language - if "language" not in inputs or not inputs["language"]: - self.add_error("Input 'language' is required") - valid = False - elif inputs["language"]: - # Validate language is one of the supported values - valid_languages = [ - "node", - "python", - "go", - "rust", - "ruby", - "php", - "java", - "dotnet", - "elixir", - ] - if inputs["language"] not in valid_languages: - self.add_error( - f"Invalid language: {inputs['language']}. " - f"Must be one of: {', '.join(valid_languages)}" - ) - valid = False - - # Validate dockerfile-image for injection - dockerfile_key = None - if "dockerfile-image" in inputs: - dockerfile_key = "dockerfile-image" - elif "dockerfile_image" in inputs: - dockerfile_key = "dockerfile_image" - - if dockerfile_key and inputs[dockerfile_key]: - value = inputs[dockerfile_key] - if ";" in value or "|" in value or "&" in value or "`" in value: - self.add_error("dockerfile-image contains potentially dangerous characters") - valid = False - - # Validate tool-versions-key for injection - tool_key = None - if "tool-versions-key" in inputs: - tool_key = "tool-versions-key" - elif "tool_versions_key" in inputs: - tool_key = "tool_versions_key" - - if tool_key and inputs[tool_key]: - value = inputs[tool_key] - if "|" in value or ";" in value or "&" in value or "`" in value: - self.add_error("tool-versions-key contains potentially dangerous characters") - valid = False - - # Validate validation-regex for malicious patterns - regex_key = None - if "validation-regex" in inputs: - regex_key = "validation-regex" - elif "validation_regex" in inputs: - regex_key = "validation_regex" - - if regex_key and inputs[regex_key]: - value = inputs[regex_key] - # Check for shell command injection in regex - if ";" in value or "|" in value or "`" in value or "rm " in value: - self.add_error("validation-regex contains potentially dangerous patterns") - valid = False - - return valid - - def get_required_inputs(self) -> list[str]: - """Get list of required inputs.""" - return ["language"] - - def get_validation_rules(self) -> dict: - """Get validation rules.""" - return { - "language": { - "type": "string", - "required": True, - "description": "Language identifier", - }, - "tool-versions-key": { - "type": "string", - "required": False, - "description": "Key in .tool-versions", - }, - "dockerfile-image": { - "type": "string", - "required": False, - "description": "Dockerfile image name", - }, - } diff --git a/version-file-parser/README.md b/version-file-parser/README.md deleted file mode 100644 index c4b2708..0000000 --- a/version-file-parser/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# ivuorinen/actions/version-file-parser - -## Version File Parser - -### Description - -Universal parser for common version detection files (.tool-versions, Dockerfile, devcontainer.json, etc.) - -### Inputs - -| name | description | required | default | -|---------------------|------------------------------------------------------------------------------|----------|-------------------------------| -| `language` |

Programming language name (node, python, php, go, dotnet)

| `true` | `""` | -| `tool-versions-key` |

Key name in .tool-versions file (nodejs, python, php, golang, dotnet)

| `true` | `""` | -| `dockerfile-image` |

Docker image name pattern (node, python, php, golang, dotnet)

| `true` | `""` | -| `version-file` |

Language-specific version file (.nvmrc, .python-version, etc.)

| `false` | `""` | -| `validation-regex` |

Version validation regex pattern

| `false` | `^[0-9]+\.[0-9]+(\.[0-9]+)?$` | -| `default-version` |

Default version to use if no version is detected

| `false` | `""` | - -### Outputs - -| name | description | -|-------------------------|-----------------------------------------------------------------------------------| -| `tool-versions-version` |

Version found in .tool-versions

| -| `dockerfile-version` |

Version found in Dockerfile

| -| `devcontainer-version` |

Version found in devcontainer.json

| -| `version-file-version` |

Version found in language-specific version file

| -| `config-file-version` |

Version found in language config files (package.json, composer.json, etc.)

| -| `detected-version` |

Final detected version (first found or default)

| -| `package-manager` |

Detected package manager (npm, yarn, pnpm, composer, pip, poetry, etc.)

| - -### Runs - -This action is a `composite` action. - -### Usage - -```yaml -- uses: ivuorinen/actions/version-file-parser@main - with: - language: - # Programming language name (node, python, php, go, dotnet) - # - # Required: true - # Default: "" - - tool-versions-key: - # Key name in .tool-versions file (nodejs, python, php, golang, dotnet) - # - # Required: true - # Default: "" - - dockerfile-image: - # Docker image name pattern (node, python, php, golang, dotnet) - # - # Required: true - # Default: "" - - version-file: - # Language-specific version file (.nvmrc, .python-version, etc.) - # - # Required: false - # Default: "" - - validation-regex: - # Version validation regex pattern - # - # Required: false - # Default: ^[0-9]+\.[0-9]+(\.[0-9]+)?$ - - default-version: - # Default version to use if no version is detected - # - # Required: false - # Default: "" -``` diff --git a/version-file-parser/action.yml b/version-file-parser/action.yml deleted file mode 100644 index 39b3920..0000000 --- a/version-file-parser/action.yml +++ /dev/null @@ -1,365 +0,0 @@ -# yaml-language-server: $schema=https://json.schemastore.org/github-action.json -# permissions: -# - contents: read # Required for reading version files ---- -name: Version File Parser -description: 'Universal parser for common version detection files (.tool-versions, Dockerfile, devcontainer.json, etc.)' -author: 'Ismo Vuorinen' - -branding: - icon: search - color: gray-dark - -inputs: - language: - description: 'Programming language name (node, python, php, go, dotnet)' - required: true - tool-versions-key: - description: 'Key name in .tool-versions file (nodejs, python, php, golang, dotnet)' - required: true - dockerfile-image: - description: 'Docker image name pattern (node, python, php, golang, dotnet)' - required: true - version-file: - description: 'Language-specific version file (.nvmrc, .python-version, etc.)' - required: false - validation-regex: - description: 'Version validation regex pattern' - required: false - default: '^[0-9]+\.[0-9]+(\.[0-9]+)?$' - default-version: - description: 'Default version to use if no version is detected' - required: false - -outputs: - tool-versions-version: - description: 'Version found in .tool-versions' - value: ${{ steps.parse.outputs.tool-versions-version }} - dockerfile-version: - description: 'Version found in Dockerfile' - value: ${{ steps.parse.outputs.dockerfile-version }} - devcontainer-version: - description: 'Version found in devcontainer.json' - value: ${{ steps.parse.outputs.devcontainer-version }} - version-file-version: - description: 'Version found in language-specific version file' - value: ${{ steps.parse.outputs.version-file-version }} - config-file-version: - description: 'Version found in language config files (package.json, composer.json, etc.)' - value: ${{ steps.parse.outputs.config-file-version }} - detected-version: - description: 'Final detected version (first found or default)' - value: ${{ steps.parse.outputs.detected-version }} - package-manager: - description: 'Detected package manager (npm, yarn, pnpm, composer, pip, poetry, etc.)' - value: ${{ steps.parse.outputs.package-manager }} - -runs: - using: composite - steps: - - name: Parse Version Files - id: parse - shell: bash - env: - VALIDATION_REGEX: ${{ inputs.validation-regex }} - LANGUAGE: ${{ inputs.language }} - TOOL_VERSIONS_KEY: ${{ inputs.tool-versions-key }} - DOCKERFILE_IMAGE: ${{ inputs.dockerfile-image }} - VERSION_FILE: ${{ inputs.version-file }} - DEFAULT_VERSION: ${{ inputs.default-version }} - run: |- - set -euo pipefail - - # Function to validate version format - validate_version() { - local version=$1 - local regex="$VALIDATION_REGEX" - - # Test regex validity - if ! bash -c "[[ 'test' =~ \${regex} ]]" 2>/dev/null; then - echo "::error::Invalid validation regex pattern: $regex" >&2 - exit 1 - fi - - # Validate version using safe regex matching with quoted variable - if [[ $version =~ ^${regex}$ ]]; then - return 0 - fi - return 1 - } - - # Function to clean version string - clean_version() { - echo "$1" | sed 's/^[vV]//' | tr -d ' \n\r' - } - - # Initialize outputs - echo "tool-versions-version=" >> $GITHUB_OUTPUT - echo "dockerfile-version=" >> $GITHUB_OUTPUT - echo "devcontainer-version=" >> $GITHUB_OUTPUT - echo "version-file-version=" >> $GITHUB_OUTPUT - echo "config-file-version=" >> $GITHUB_OUTPUT - echo "detected-version=" >> $GITHUB_OUTPUT - echo "package-manager=" >> $GITHUB_OUTPUT - - # Language detection patterns - language="$LANGUAGE" - - # Parse .tool-versions file - if [ -f .tool-versions ]; then - echo "Checking .tool-versions for $TOOL_VERSIONS_KEY..." >&2 - version=$(awk "/^$TOOL_VERSIONS_KEY[[:space:]]/ {gsub(/#.*/, \"\"); print \$2; exit}" .tool-versions 2>/dev/null || echo "") - if [ -n "$version" ]; then - version=$(clean_version "$version") - if validate_version "$version"; then - echo "Found $LANGUAGE version in .tool-versions: $version" >&2 - echo "tool-versions-version=$version" >> $GITHUB_OUTPUT - fi - fi - fi - - # Parse Dockerfile - if [ -f Dockerfile ]; then - echo "Checking Dockerfile for $DOCKERFILE_IMAGE..." >&2 - version=$(grep -iF "FROM" Dockerfile | grep -F "$DOCKERFILE_IMAGE:" | head -1 | \ - sed -n "s/.*$DOCKERFILE_IMAGE:\([0-9]\+\(\.[0-9]\+\)*\)\(-[^:]*\)\?.*/\1/p" || echo "") - if [ -n "$version" ]; then - version=$(clean_version "$version") - if validate_version "$version"; then - echo "Found $LANGUAGE version in Dockerfile: $version" >&2 - echo "dockerfile-version=$version" >> $GITHUB_OUTPUT - fi - fi - fi - - # Parse devcontainer.json - if [ -f .devcontainer/devcontainer.json ]; then - echo "Checking devcontainer.json for $DOCKERFILE_IMAGE..." >&2 - if command -v jq >/dev/null 2>&1; then - version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n "s/.*$DOCKERFILE_IMAGE:\([0-9]\+\(\.[0-9]\+\)*\)\(-[^:]*\)\?.*/\1/p" || echo "") - if [ -n "$version" ]; then - version=$(clean_version "$version") - if validate_version "$version"; then - echo "Found $LANGUAGE version in devcontainer: $version" >&2 - echo "devcontainer-version=$version" >> $GITHUB_OUTPUT - fi - fi - else - echo "jq not available, skipping devcontainer parsing" >&2 - fi - fi - - # Parse language-specific version file - if [ -n "$VERSION_FILE" ] && [ -f "$VERSION_FILE" ]; then - echo "Checking $VERSION_FILE..." >&2 - version=$(tr -d '\r' < "$VERSION_FILE" | head -1) - if [ -n "$version" ]; then - version=$(clean_version "$version") - if validate_version "$version"; then - echo "Found $LANGUAGE version in $VERSION_FILE: $version" >&2 - echo "version-file-version=$version" >> $GITHUB_OUTPUT - fi - fi - fi - - # Parse language-specific configuration files - config_version="" - detected_package_manager="" - - case "$language" in - "node") - # Check package.json - if [ -f package.json ] && command -v jq >/dev/null 2>&1; then - version=$(jq -r '.engines.node // empty' package.json 2>/dev/null | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p') - if [ -n "$version" ] && validate_version "$version"; then - echo "Found Node.js version in package.json: $version" >&2 - config_version="$version" - fi - fi - - # Detect package manager - if [ -f bun.lockb ]; then - detected_package_manager="bun" - elif [ -f pnpm-lock.yaml ]; then - detected_package_manager="pnpm" - elif [ -f yarn.lock ]; then - detected_package_manager="yarn" - elif [ -f package-lock.json ]; then - detected_package_manager="npm" - elif [ -f package.json ] && command -v jq >/dev/null 2>&1; then - # Check packageManager field in package.json - pkg_manager=$(jq -r '.packageManager // empty' package.json 2>/dev/null | sed 's/@.*//') - if [ -n "$pkg_manager" ]; then - detected_package_manager="$pkg_manager" - else - detected_package_manager="npm" - fi - else - detected_package_manager="npm" - fi - ;; - - "php") - # Check composer.json - if [ -f composer.json ] && command -v jq >/dev/null 2>&1; then - # Try require.php first - version=$(jq -r '.require.php // empty' composer.json 2>/dev/null | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p') - if [ -z "$version" ]; then - # Try platform.php - version=$(jq -r '.config.platform.php // empty' composer.json 2>/dev/null | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p') - fi - if [ -n "$version" ] && validate_version "$version"; then - echo "Found PHP version in composer.json: $version" >&2 - config_version="$version" - fi - fi - - # Check phpunit.xml - if [ -z "$config_version" ]; then - phpunit_file="" - if [ -f phpunit.xml ]; then - phpunit_file="phpunit.xml" - elif [ -f phpunit.xml.dist ]; then - phpunit_file="phpunit.xml.dist" - fi - - if [ -n "$phpunit_file" ]; then - version=$(grep -o 'php[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"' "$phpunit_file" | sed -n 's/.*"\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\)".*/\1/p') - if [ -n "$version" ] && validate_version "$version"; then - echo "Found PHP version in $phpunit_file: $version" >&2 - config_version="$version" - fi - fi - fi - - # Detect package manager - if [ -f composer.json ]; then - detected_package_manager="composer" - fi - ;; - - "python") - # Check pyproject.toml - if [ -f pyproject.toml ]; then - # Try PEP 621 requires-python first (allow leading whitespace) - if command -v jq >/dev/null 2>&1 && grep -q '^\[project\]' pyproject.toml; then - # Convert TOML to JSON for PEP 621 parsing (basic approach) - version=$(grep -A 20 '^\[project\]' pyproject.toml | grep -E '^\s*requires-python[[:space:]]*=' | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p' | head -1) - if [ -n "$version" ] && validate_version "$version"; then - echo "Found Python version in pyproject.toml [project] requires-python: $version" >&2 - config_version="$version" - fi - fi - # Fallback to legacy python field if no PEP 621 version found - if [ -z "$config_version" ]; then - version=$(grep -E '^python[[:space:]]*=' pyproject.toml | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p') - if [ -n "$version" ] && validate_version "$version"; then - echo "Found Python version in pyproject.toml: $version" >&2 - config_version="$version" - fi - fi - fi - - # Check setup.py for python_requires - if [ -z "$config_version" ] && [ -f setup.py ]; then - version=$(grep -o 'python_requires[[:space:]]*=[[:space:]]*['\''"].*['\''"]' setup.py | sed -n 's/[^0-9]*\([0-9]\+\.[0-9]\+\(\.[0-9]\+\)\?\).*/\1/p') - if [ -n "$version" ] && validate_version "$version"; then - echo "Found Python version in setup.py: $version" >&2 - config_version="$version" - fi - fi - - # Detect package manager - if [ -f pyproject.toml ] && grep -q '\[tool\.poetry\]' pyproject.toml; then - detected_package_manager="poetry" - elif [ -f Pipfile ]; then - detected_package_manager="pipenv" - elif [ -f requirements.txt ]; then - detected_package_manager="pip" - else - detected_package_manager="pip" - fi - ;; - - "go") - # Check go.mod - if [ -f go.mod ]; then - version=$(grep -E '^go[[:space:]]+[0-9]' go.mod | awk '{print $2}' | head -1 || echo "") - if [ -n "$version" ] && validate_version "$version"; then - echo "Found Go version in go.mod: $version" >&2 - config_version="$version" - fi - fi - - # Detect package manager - if [ -f go.mod ]; then - detected_package_manager="go" - fi - ;; - - "dotnet") - # Check global.json - if [ -f global.json ] && command -v jq >/dev/null 2>&1; then - version=$(jq -r '.sdk.version // empty' global.json 2>/dev/null || echo "") - if [ -n "$version" ] && validate_version "$version"; then - echo "Found .NET version in global.json: $version" >&2 - config_version="$version" - fi - fi - - # Check .csproj files - if [ -z "$config_version" ]; then - # Enable nullglob to handle case when no .csproj files exist - shopt -s nullglob - for csproj in *.csproj; do - if [ -f "$csproj" ]; then - # Handle both TargetFramework and TargetFrameworks, and handle -windows monikers - version=$(grep -oE 'net[0-9]+\.[0-9]+(-[a-z]+)?' "$csproj" | sed -n 's/.*net\([0-9]\+\.[0-9]\+\).*/\1/p' | head -1) - if [ -n "$version" ] && validate_version "$version"; then - echo "Found .NET version in $csproj: $version" >&2 - config_version="$version" - break - fi - fi - done - # Disable nullglob after use - shopt -u nullglob - fi - - # Detect package manager - detected_package_manager="dotnet" - ;; - esac - - # Set config-file-version output - if [ -n "$config_version" ]; then - echo "config-file-version=$config_version" >> $GITHUB_OUTPUT - fi - - # Set package-manager output - if [ -n "$detected_package_manager" ]; then - echo "package-manager=$detected_package_manager" >> $GITHUB_OUTPUT - fi - - # Determine final detected version with priority order - # Priority order: version-file > config-file > tool-versions > dockerfile > devcontainer > default - final_version=$(grep -E "^(version-file|config-file|tool-versions|dockerfile|devcontainer)-version=" $GITHUB_OUTPUT | tac | awk -F= 'NF>1 && $2!="" {print $2; exit}') - - # If no version found from any source, use default - if [ -z "$final_version" ] && [ -n "$DEFAULT_VERSION" ]; then - final_version="$DEFAULT_VERSION" - echo "Using default $LANGUAGE version: $final_version" >&2 - fi - - # Set final detected version - if [ -n "$final_version" ]; then - # Validate the final version against the regex - if ! validate_version "$final_version"; then - echo "::error::Detected version $final_version does not match validation regex" >&2 - exit 1 - fi - echo "detected-version=$final_version" >> $GITHUB_OUTPUT - echo "Final detected $LANGUAGE version: $final_version" >&2 - else - echo "No $LANGUAGE version detected" >&2 - fi diff --git a/version-file-parser/rules.yml b/version-file-parser/rules.yml deleted file mode 100644 index 7795a16..0000000 --- a/version-file-parser/rules.yml +++ /dev/null @@ -1,42 +0,0 @@ ---- -# Validation rules for version-file-parser action -# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY -# Schema version: 1.0 -# Coverage: 50% (3/6 inputs) -# -# This file defines validation rules for the version-file-parser GitHub Action. -# Rules are automatically applied by validate-inputs action when this -# action is used. -# - -schema_version: '1.0' -action: version-file-parser -description: Universal parser for common version detection files (.tool-versions, Dockerfile, devcontainer.json, etc.) -generator_version: 1.0.0 -required_inputs: - - dockerfile-image - - language - - tool-versions-key -optional_inputs: - - default-version - - validation-regex - - version-file -conventions: - default-version: semantic_version - validation-regex: regex_pattern - version-file: file_path -overrides: {} -statistics: - total_inputs: 6 - validated_inputs: 3 - skipped_inputs: 0 - coverage_percentage: 50 -validation_coverage: 50 -auto_detected: true -manual_review_required: true -quality_indicators: - has_required_inputs: true - has_token_validation: false - has_version_validation: true - has_file_validation: true - has_security_validation: false