refactor: remove deprecated version-file-parser action

Remove version-file-parser after successful inlining into node-setup:
- Delete version-file-parser action directory
- Delete version-file-parser unit and integration tests
- Remove version-file-parser references from spec_helper.sh
- Remove version-file-parser path trigger from node-setup-test.yml
- Regenerate action catalog (29 actions, down from 30)

All version detection functionality now inlined into individual actions:
- go-build: Go version detection
- csharp-build/csharp-lint-check/csharp-publish: .NET version detection
- python-lint-fix: Python version detection
- php-laravel-phpunit: PHP version detection
- node-setup: Node.js version detection and package manager detection

Reduces external dependencies and improves initialization performance across all actions.
This commit is contained in:
2025-11-20 10:40:49 +02:00
parent 77ee000e50
commit ea47df1ab1
10 changed files with 7 additions and 1065 deletions

View File

@@ -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
---

View File

@@ -5,9 +5,7 @@ on:
push:
paths:
- 'node-setup/**'
- 'version-file-parser/**'
- 'common-cache/**'
- 'common-retry/**'
- '_tests/integration/workflows/node-setup-test.yml'
jobs:

View File

@@ -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 <<EOF
{
"name": "test-project",
"engines": { "node": ">=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 <<EOF
{
"require": { "php": "^8.1" }
}
EOF
- name: 'Python project'
language: 'python'
tool-versions-key: 'python'
dockerfile-image: 'python'
expected-version: '3.9'
setup-files: |
echo "3.9.0" > .python-version
cat > pyproject.toml <<EOF
[tool.poetry.dependencies]
python = "^3.9"
EOF
- name: 'Go project'
language: 'go'
tool-versions-key: 'golang'
dockerfile-image: 'golang'
expected-version: '1.21'
setup-files: |
cat > go.mod <<EOF
module test-project
go 1.21
EOF
- name: '.tool-versions file'
language: 'node'
tool-versions-key: 'nodejs'
dockerfile-image: 'node'
expected-version: '18.16.0'
setup-files: |
echo "nodejs 18.16.0" > .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 <<EOF
FROM node:18.17.0-alpine
WORKDIR /app
COPY . .
EOF
- name: Test Dockerfile parsing
id: dockerfile-test
uses: ./version-file-parser
with:
language: 'node'
tool-versions-key: 'nodejs'
dockerfile-image: 'node'
- name: Validate Dockerfile parsing
run: |
expected_version="18.17.0"
detected_version="${{ steps.dockerfile-test.outputs.dockerfile-version }}"
echo "Expected version: $expected_version"
echo "Detected version: $detected_version"
if [[ "$detected_version" != "$expected_version" ]]; then
echo "❌ ERROR: Version mismatch"
echo "Expected: $expected_version"
echo "Got: $detected_version"
exit 1
fi
echo "✅ Dockerfile parsing successful"

View File

@@ -114,11 +114,6 @@ setup_default_inputs() {
"validate-inputs")
[[ "$input_name" != "action-type" && "$input_name" != "action" && "$input_name" != "rules-file" && "$input_name" != "fail-on-error" ]] && export INPUT_ACTION_TYPE="test-action"
;;
"version-file-parser")
[[ "$input_name" != "language" ]] && export INPUT_LANGUAGE="node"
[[ "$input_name" != "tool-versions-key" ]] && export INPUT_TOOL_VERSIONS_KEY="nodejs"
[[ "$input_name" != "dockerfile-image" ]] && export INPUT_DOCKERFILE_IMAGE="node"
;;
"codeql-analysis")
[[ "$input_name" != "language" ]] && export INPUT_LANGUAGE="javascript"
[[ "$input_name" != "token" ]] && export INPUT_TOKEN="ghp_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
@@ -185,11 +180,6 @@ cleanup_default_inputs() {
"validate-inputs")
[[ "$input_name" != "action-type" && "$input_name" != "action" && "$input_name" != "rules-file" && "$input_name" != "fail-on-error" ]] && unset INPUT_ACTION_TYPE
;;
"version-file-parser")
[[ "$input_name" != "language" ]] && unset INPUT_LANGUAGE
[[ "$input_name" != "tool-versions-key" ]] && unset INPUT_TOOL_VERSIONS_KEY
[[ "$input_name" != "dockerfile-image" ]] && unset INPUT_DOCKERFILE_IMAGE
;;
"codeql-analysis")
[[ "$input_name" != "language" ]] && unset INPUT_LANGUAGE
[[ "$input_name" != "token" ]] && unset INPUT_TOKEN
@@ -244,10 +234,6 @@ shellspec_mock_action_run() {
action_name=$(basename "$action_dir")
case "$action_name" in
"version-file-parser")
echo "detected-version=1.0.0" >>"$GITHUB_OUTPUT"
echo "package-manager=npm" >>"$GITHUB_OUTPUT"
;;
"node-setup")
echo "node-version=18.0.0" >>"$GITHUB_OUTPUT"
echo "package-manager=npm" >>"$GITHUB_OUTPUT"

View File

@@ -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

View File

@@ -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

View File

@@ -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",
},
}

View File

@@ -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` | <p>Programming language name (node, python, php, go, dotnet)</p> | `true` | `""` |
| `tool-versions-key` | <p>Key name in .tool-versions file (nodejs, python, php, golang, dotnet)</p> | `true` | `""` |
| `dockerfile-image` | <p>Docker image name pattern (node, python, php, golang, dotnet)</p> | `true` | `""` |
| `version-file` | <p>Language-specific version file (.nvmrc, .python-version, etc.)</p> | `false` | `""` |
| `validation-regex` | <p>Version validation regex pattern</p> | `false` | `^[0-9]+\.[0-9]+(\.[0-9]+)?$` |
| `default-version` | <p>Default version to use if no version is detected</p> | `false` | `""` |
### Outputs
| name | description |
|-------------------------|-----------------------------------------------------------------------------------|
| `tool-versions-version` | <p>Version found in .tool-versions</p> |
| `dockerfile-version` | <p>Version found in Dockerfile</p> |
| `devcontainer-version` | <p>Version found in devcontainer.json</p> |
| `version-file-version` | <p>Version found in language-specific version file</p> |
| `config-file-version` | <p>Version found in language config files (package.json, composer.json, etc.)</p> |
| `detected-version` | <p>Final detected version (first found or default)</p> |
| `package-manager` | <p>Detected package manager (npm, yarn, pnpm, composer, pip, poetry, etc.)</p> |
### 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: ""
```

View File

@@ -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 '<TargetFrameworks?>net[0-9]+\.[0-9]+(-[a-z]+)?</TargetFrameworks?>' "$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

View File

@@ -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