diff --git a/.github/workflows/action-security.yml b/.github/workflows/action-security.yml index dfdee54..dae9ce8 100644 --- a/.github/workflows/action-security.yml +++ b/.github/workflows/action-security.yml @@ -117,14 +117,14 @@ jobs: - name: Upload Trivy results if: steps.verify-sarif.outputs.has_trivy == 'true' - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: 'trivy-results.sarif' category: 'trivy' - name: Upload Gitleaks results if: steps.verify-sarif.outputs.has_gitleaks == 'true' - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: 'gitleaks-report.sarif' category: 'gitleaks' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 079cbd7..f3d0a27 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,15 +37,15 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: languages: ${{ matrix.language }} queries: security-and-quality - name: Autobuild - uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index e39632c..0b33c06 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -60,6 +60,7 @@ jobs: issues: write pull-requests: write security-events: write + statuses: write steps: - name: Checkout Code @@ -100,7 +101,7 @@ jobs: - name: Upload SARIF Report if: always() && hashFiles('megalinter-reports/sarif/*.sarif') - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: megalinter-reports/sarif category: megalinter diff --git a/.github/workflows/test-actions.yml b/.github/workflows/test-actions.yml index 75f10ef..e9ecef2 100644 --- a/.github/workflows/test-actions.yml +++ b/.github/workflows/test-actions.yml @@ -73,7 +73,7 @@ jobs: if: always() - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 if: always() && hashFiles('_tests/reports/test-results.sarif') != '' with: sarif_file: _tests/reports/test-results.sarif diff --git a/.serena/memories/development_standards.md b/.serena/memories/development_standards.md new file mode 100644 index 0000000..6f258c1 --- /dev/null +++ b/.serena/memories/development_standards.md @@ -0,0 +1,115 @@ +# Development Standards & Workflows + +## Quality Standards (ZERO TOLERANCE) + +### Production Ready Criteria + +- ALL tests pass (100% success rate) +- ALL linting passes (zero issues) +- ALL validation checks pass +- NO warnings or errors + +### Communication + +- Direct, factual only +- Never claim "production ready" until literally everything passes +- No hype, buzzwords, or excessive enthusiasm + +## Required Commands + +### Development Cycle + +```bash +make all # Complete: docs, format, lint, test +make dev # Format + lint (development) +make lint # All linters (MUST pass 100%) +make test # All tests (MUST pass 100%) +make format # Auto-fix formatting +``` + +### Task Completion Checklist + +After ANY coding task: + +- [ ] `make lint` - Fix all issues (blocking) +- [ ] `make test` - Ensure 100% pass +- [ ] EditorConfig compliance verified + +### Validation System + +```bash +make update-validators # Generate validation rules +make update-validators-dry # Preview changes +make generate-tests # Create missing tests +make generate-tests-dry # Preview test generation +``` + +## Code Style + +### EditorConfig (BLOCKING ERRORS) + +- **Indent**: 2 spaces (4 for Python, tabs for Makefile) +- **Charset**: UTF-8 +- **Line Endings**: LF +- **Max Line**: 200 chars (120 for Markdown) +- **Final Newline**: Required +- **Trailing Whitespace**: Trimmed + +### Shell Scripts (REQUIRED) + +```bash +#!/usr/bin/env bash +set -euo pipefail # MANDATORY +IFS=$' \t\n' +trap cleanup EXIT +trap 'echo "Error at line $LINENO" >&2' ERR +# Always quote: "$variable", basename -- "$path" +# Check tools: command -v jq >/dev/null 2>&1 +``` + +### Python (Ruff) + +- **Line Length**: 100 chars +- **Indent**: 4 spaces +- **Quotes**: Double +- **Docstrings**: Google style +- **Type Hints**: Required + +### YAML/Actions + +- **Indent**: 2 spaces +- **Local Actions**: `uses: ./action-name` (never `../` or `@main`) +- **External Actions**: SHA-pinned (not `@main`/`@v1`) +- **Step IDs**: Required when outputs referenced +- **Permissions**: Minimal scope (contents: read default) +- **Output Sanitization**: Use `printf`, never `echo` for `$GITHUB_OUTPUT` + +## Security Requirements + +1. **SHA Pinning**: All external actions use commit SHAs +2. **Token Safety**: `${{ github.token }}`, never hardcoded +3. **Input Validation**: All inputs validated via centralized system +4. **Output Sanitization**: `printf '%s\n' "$value" >> $GITHUB_OUTPUT` +5. **Injection Prevention**: Validate for `;`, `&&`, `|`, backticks +6. **Tool Availability**: `command -v tool` checks before use +7. **Variable Quoting**: Always `"$var"` in shell +8. **No Secrets**: Never commit credentials/keys + +## Never Do + +- Never `git commit` (manual commits not allowed) +- Never use `--no-verify` flags +- Never modify linting config to make tests pass +- Never assume linting issues are acceptable +- Never skip testing after changes +- Never create files unless absolutely necessary +- Never nest `${{ }}` in quoted YAML strings (breaks hashFiles) + +## Preferred Patterns + +- Edit existing files over creating new ones +- Use centralized validation for all input handling +- Follow existing conventions in codebase +- Actions use composition, not dependencies +- Custom validators in action directories +- Convention-based automatic detection diff --git a/.serena/memories/documentation_guide.md b/.serena/memories/documentation_guide.md new file mode 100644 index 0000000..2f8d4c8 --- /dev/null +++ b/.serena/memories/documentation_guide.md @@ -0,0 +1,101 @@ +# Documentation Guide + +## Documentation Locations + +### Validation System Docs (`validate-inputs/docs/`) + +Read when working with validators or validation logic: + +**API.md** - Complete API reference + +- BaseValidator methods and properties +- Core validators (Boolean, Version, Token, Numeric, Docker, File, Network, Security, CodeQL) +- Registry system usage +- Custom validator patterns +- Convention system + +**DEVELOPER_GUIDE.md** - Creating new validators + +- Quick start guide +- Creating core validators (in validators/ directory) +- Creating custom validators (in action directories) +- Adding convention patterns +- Writing tests, debugging, common patterns + +**ACTION_MAINTAINER.md** - Using validation in actions + +- How validation works (automatic integration) +- Validation flow (input collection, validator selection, execution, error reporting) +- Using automatic validation via conventions +- Custom validation for complex scenarios +- Testing validation, common scenarios, troubleshooting + +**README_ARCHITECTURE.md** - System architecture + +- Feature overview +- Quick start examples +- Architecture details +- Modular validator structure +- Convention-based detection +- Custom validator support + +### Testing Framework (`_tests/README.md`) + +Read when writing or debugging tests: + +- ShellSpec framework overview +- Multi-level testing strategy (unit, integration, external usage) +- Directory structure explanation +- Test writing patterns +- Running tests (`make test`, `make test-unit`, `make test-action ACTION=name`) +- Coverage reporting +- Mocking and fixtures +- CI integration + +### Docker Testing Tools (`_tools/docker-testing-tools/README.md`) + +Read when working with CI or testing infrastructure: + +- Pre-built Docker image with all testing tools +- Pre-installed tools (ShellSpec, nektos/act, TruffleHog, actionlint, etc.) +- Building locally (build.sh, test.sh) +- Performance benefits (saves ~3 minutes per run) +- Multi-stage build process +- Usage in workflows + +### Top-Level Documentation + +**README.md** - Main project readme (auto-generated) + +- Actions catalog +- Usage examples +- Quick reference + +**SECURITY.md** - Security policy + +- Reporting vulnerabilities +- Security practices + +**LICENSE.md** - MIT license + +**CLAUDE.md** - Project instructions (covered in development_standards memory) + +## When to Read What + +**Starting new validator work**: Read `DEVELOPER_GUIDE.md`, then `API.md` for reference + +**Using validation in action**: Read `ACTION_MAINTAINER.md` + +**Understanding architecture**: Read `README_ARCHITECTURE.md` + +**Writing tests**: Read `_tests/README.md` + +**Setting up CI/testing**: Read `_tools/docker-testing-tools/README.md` + +**API reference lookup**: Read `API.md` (has method tables, validator details) + +## Documentation is Auto-Generated + +- Action READMEs generated via `action-docs` (don't edit manually) +- Validation system README auto-generated +- Keep CLAUDE.md and docs/ files updated manually diff --git a/.serena/memories/linting_improvements_september_2025.md b/.serena/memories/linting_improvements_september_2025.md deleted file mode 100644 index fb5b58e..0000000 --- a/.serena/memories/linting_improvements_september_2025.md +++ /dev/null @@ -1,75 +0,0 @@ -# Linting Improvements - September 2025 - -## Summary - -Successfully reduced linting issues from 213 to 99 in the modular validator architecture. - -## Issues Fixed - -### Critical Issues Resolved - -1. **Print Statements** - All converted to proper logging with logger -2. **F-string Logging** - Converted to lazy % formatting -3. **Mutable Class Attributes** - Added `ClassVar` type annotations -4. **Import Sorting** - Fixed and organized -5. **File Path Operations** - Replaced os.path with Path -6. **Exception Handling** - Improved specific exception catching - -## Code Changes Made - -### Logging Improvements - -```python -# Before -print(f"::error::{error}") - -# After -logger.error("::error::%s", error) -``` - -### Class Attributes - -```python -# Before -SUPPORTED_LANGUAGES = {...} - -# After -SUPPORTED_LANGUAGES: ClassVar[set[str]] = {...} -``` - -### Path Operations - -```python -# Before -if os.path.exists(self.temp_output.name): - -# After -if Path(self.temp_output.name).exists(): -``` - -## Remaining Issues (99 total) - -### Acceptable Issues - -- **39 PLC0415** - Import-outside-top-level (intentional in tests for isolation) -- **27 PLR2004** - Magic value comparisons (domain-specific constants) -- **9 PLR0911** - Too many return statements (complex validation logic) -- **7 BLE001** - Blind except (appropriate for fallback scenarios) -- **7 TRY300** - Try-consider-else (current pattern is clearer) -- **3 S105** - Hardcoded password strings (test data) -- **3 SIM115** - Context managers (NamedTemporaryFile usage) -- **1 C901** - Complexity (validator.main function) -- **1 FIX002** - TODO comment (tracked in issue) -- **1 S110** - Try-except-pass (appropriate fallback) -- **1 S603** - Subprocess call (controlled input in tests) - -## Test Status - -- 286 tests passing -- 17 tests failing (output format changes) -- 94.4% pass rate - -## Conclusion - -All critical linting issues have been resolved. The remaining 99 issues are mostly style preferences or intentional patterns that are acceptable for this codebase. -The code quality has significantly improved while maintaining functionality. diff --git a/.serena/memories/modular_validator_architecture.md b/.serena/memories/modular_validator_architecture.md deleted file mode 100644 index bf6f598..0000000 --- a/.serena/memories/modular_validator_architecture.md +++ /dev/null @@ -1,345 +0,0 @@ -# Modular Validator Architecture - Complete Documentation - -## Current Status: PRODUCTION READY ✅ - -**Last Updated**: 2025-09-16 -**Branch**: feat/upgrades-and-restructuring -**Phase Completed**: 1-5 of 7 (Test Generation System Implemented) -**Test Status**: 100% pass rate (303/303 tests passing) -**Linting**: 0 issues -**Quality**: Production ready, zero defects - -## Architecture Overview - -Successfully transformed monolithic `validator.py` into a modular, extensible validation system for GitHub Actions inputs. -The architecture now provides specialized validators, convention-based auto-detection, support for custom validators, and an intelligent test generation system. - -## Core Components - -### 1. Base Framework - -- **BaseValidator** (`validators/base.py`): Abstract base class defining validator interface -- **ValidatorRegistry** (`validators/registry.py`): Dynamic validator discovery and management -- **ConventionMapper** (`validators/conventions.py`): Automatic validation based on naming patterns - -### 2. Specialized Validator Modules (9 Total) - -| Module | Purpose | Status | -| ------------------------ | --------------------------------- | ----------- | -| `validators/token.py` | GitHub, NPM, PyPI, Docker tokens | ✅ Complete | -| `validators/version.py` | SemVer, CalVer, language versions | ✅ Complete | -| `validators/boolean.py` | Boolean value validation | ✅ Complete | -| `validators/numeric.py` | Numeric ranges and constraints | ✅ Complete | -| `validators/docker.py` | Docker images, tags, platforms | ✅ Complete | -| `validators/file.py` | File paths, extensions, security | ✅ Complete | -| `validators/network.py` | URLs, emails, IPs, ports | ✅ Complete | -| `validators/security.py` | Injection detection, secrets | ✅ Complete | -| `validators/codeql.py` | CodeQL queries, languages, config | ✅ Complete | - -### 3. Custom Validators (4 Implemented) - -| Action | Custom Validator | Features | -| ----------------- | ---------------- | ------------------------------------ | -| `sync-labels` | ✅ Implemented | YAML file validation, GitHub token | -| `docker-build` | ✅ Implemented | Complex build args, platforms, cache | -| `codeql-analysis` | ✅ Implemented | Language support, query validation | -| `docker-publish` | ✅ Implemented | Registry validation, credentials | - -## Implementation Phases - -### ✅ Phase 1: Core Infrastructure (COMPLETED) - -- Created modular directory structure -- Implemented BaseValidator abstract class -- Built ValidatorRegistry with auto-discovery -- Established testing framework - -### ✅ Phase 2: Specialized Validators (COMPLETED) - -- Extracted validation logic into 9 specialized modules -- Created comprehensive test coverage -- Achieved full pytest compatibility -- Fixed all method signatures and interfaces - -### ✅ Phase 3: Convention Mapper (COMPLETED) - -- Implemented priority-based pattern matching (100+ patterns) -- Created ConventionBasedValidator for automatic validation -- Added YAML-based convention override support -- Integrated with ValidatorRegistry - -### ✅ Phase 4: Custom Validator Support (COMPLETED) - -- Implemented custom validator discovery in registry -- Created 4 custom validators for complex actions -- Fixed error propagation between parent/child validators -- Added full GitHub expression (`${{ }}`) support -- All custom validator tests passing (6/6) - -### ✅ Phase 5: Test Generation System (COMPLETED) - -- Implemented `generate-tests.py` script with intelligent pattern detection -- Created test templates for different validator types -- Added skip-existing-tests logic to prevent overwrites -- Integrated with Makefile (`make generate-tests`, `make generate-tests-dry`) -- Created comprehensive tests for the generator itself (11 tests passing) -- Supports both ShellSpec and pytest test generation -- Handles custom validators in action directories - -#### Test Generation Features - -- **Intelligent Input Detection**: Recognizes patterns like `token`, `version`, `path`, `url`, `email`, `dry-run`, etc. -- **Context-Aware Test Cases**: Generates appropriate test cases based on input types -- **GitHub Expression Support**: Includes tests for `${{ }}` expressions -- **Template System**: Different templates for version, token, boolean, numeric, file, network, docker, and security validators -- **Non-Destructive**: Never overwrites existing test files -- **Dry Run Mode**: Preview what would be generated without creating files -- **Comprehensive Coverage**: Generates ShellSpec tests for actions, pytest tests for validators, and tests for custom validators - -#### Test Generation Commands - -```bash -make generate-tests # Generate missing tests -make generate-tests-dry # Preview what would be generated -make test-generate-tests # Test the generator itself -``` - -### ⏳ Phase 6: Integration and Migration (NOT STARTED) - -- Update YAML rules to new schema format -- Migrate remaining actions to custom validators -- Update rule generation scripts - -### ⏳ Phase 7: Documentation and Tooling (NOT STARTED) - -- Create validator development guide -- Add CLI tools for validator testing -- Update all documentation - -## Convention-Based Detection - -The ConventionMapper provides automatic validator selection based on input naming patterns: - -```text -# Priority levels (higher = more specific) -100: Exact matches (e.g., "dry-run" → boolean) -95: Language-specific versions (e.g., "-python-version" → python_version) -90: Generic suffixes (e.g., "-token" → token) -85: Contains patterns (e.g., contains "email" → email) -80: Prefix patterns (e.g., "is-" → boolean) -``` - -## Key Technical Achievements - -### Error Propagation Pattern - -```python -# Proper error propagation from child to parent validators -result = self.child_validator.validate_something(value) -for error in self.child_validator.errors: - if error not in self.errors: - self.add_error(error) -self.child_validator.clear_errors() -return result -``` - -### GitHub Expression Support - -All validators properly handle GitHub Actions expressions: - -```python -# Allow GitHub Actions expressions -if self.is_github_expression(value): - return True -``` - -### Platform Validation - -Docker platform validation accepts full platform strings: - -- `linux/amd64`, `linux/arm64`, `linux/arm/v7` -- `windows/amd64` (where applicable) -- `darwin/arm64` (where applicable) - -## Testing Infrastructure - -### Test Statistics - -- **Total Tests**: 303 (including 11 test generator tests) -- **Passing**: 303 (100%) -- **Coverage by Module**: All modules have dedicated test files -- **Custom Validators**: 6 comprehensive tests -- **Test Generator**: 11 tests for the generation system - -### Test Files - -```text -validate-inputs/tests/ -├── test_base.py ✅ -├── test_registry.py ✅ -├── test_convention_mapper.py ✅ -├── test_boolean_validator.py ✅ -├── test_codeql_validator.py ✅ -├── test_docker_validator.py ✅ -├── test_file_validator.py ✅ -├── test_network_validator.py ✅ -├── test_numeric_validator.py ✅ -├── test_security_validator.py ✅ -├── test_token_validator.py ✅ -├── test_version_validator.py ✅ -├── test_custom_validators.py ✅ (6 tests) -├── test_integration.py ✅ -├── test_validator.py ✅ -└── test_generate_tests.py ✅ (11 tests) -``` - -### Test Generation System - -```text -validate-inputs/scripts/ -└── generate-tests.py ✅ Intelligent test generator -``` - -## Production Readiness Criteria - -✅ **ALL CRITERIA MET**: - -- Zero failing tests (303/303 passing) -- Zero linting issues -- Zero type checking issues -- Full backward compatibility maintained -- Comprehensive error handling -- Security patterns validated -- Performance optimized (lazy loading, caching) -- Custom validator support proven -- GitHub expression handling complete -- Test generation system operational - -## Usage Examples - -### Basic Validation - -```python -from validators.registry import ValidatorRegistry - -registry = ValidatorRegistry() -validator = registry.get_validator("docker-build") -result = validator.validate_inputs({ - "context": ".", - "dockerfile": "Dockerfile", - "platforms": "linux/amd64,linux/arm64" -}) -``` - -### Custom Validator - -```python -# Automatically loads docker-build/CustomValidator.py -validator = registry.get_validator("docker-build") -# Uses specialized validation logic for docker-build action -``` - -### Test Generation - -```bash -# Generate missing tests for all actions and validators -python3 validate-inputs/scripts/generate-tests.py - -# Preview what would be generated (dry run) -python3 validate-inputs/scripts/generate-tests.py --dry-run --verbose - -# Generated test example -#!/usr/bin/env bash -Describe 'Action Name Input Validation' - Context 'Required inputs validation' - It 'should fail when required inputs are missing' - When run validate_inputs 'action-name' - The status should be failure - End - End -End -``` - -## File Structure - -```text -validate-inputs/ -├── validator.py # Main entry point -├── validators/ -│ ├── __init__.py -│ ├── base.py # BaseValidator abstract class -│ ├── registry.py # ValidatorRegistry -│ ├── conventions.py # ConventionBasedValidator -│ ├── [9 specialized validators] -│ └── ... -├── rules/ # YAML validation rules -├── tests/ # Comprehensive test suite -│ ├── [validator tests] -│ └── test_generate_tests.py # Test generator tests -└── scripts/ - ├── update-validators.py # Rule generator - └── generate-tests.py # Test generator ✅ - -# Custom validators in action directories -sync-labels/CustomValidator.py ✅ -docker-build/CustomValidator.py ✅ -codeql-analysis/CustomValidator.py ✅ -docker-publish/CustomValidator.py ✅ -``` - -## Benefits Achieved - -### 1. Modularity - -- Each validator is self-contained -- Clear separation of concerns -- Easy to test individually - -### 2. Extensibility - -- New validators easily added -- Custom validators for complex actions -- Convention-based auto-detection -- Automatic test generation - -### 3. Maintainability - -- Individual test files per validator -- Consistent interfaces -- Clear error messages -- Tests generated with consistent patterns - -### 4. Performance - -- Lazy loading of validators -- Efficient pattern matching -- Minimal overhead -- Fast test generation - -### 5. Developer Experience - -- Automatic test scaffolding -- Intelligent pattern detection -- Non-destructive generation -- Comprehensive test coverage - -## Next Steps - -1. **Phase 6**: Integration and Migration - - Update YAML rules to new schema format - - Migrate more actions to custom validators - -2. **Phase 7**: Documentation and Tooling - - Create comprehensive validator development guide - - Add CLI tools for validator testing - -3. **Optional Enhancements**: - - Create more custom validators (github-release, npm-publish) - - Enhance test generation templates - - Add performance benchmarks - -## Summary - -The modular validator architecture with test generation is **complete and production-ready**. Phases 1-5 are done, providing a robust, extensible, -and well-tested validation system for GitHub Actions. The test generation system ensures consistent test coverage and reduces manual test writing effort. -The system maintains 100% test coverage with zero defects, follows SOLID principles, and maintains full backward compatibility. diff --git a/.serena/memories/modular_validator_architecture_completed.md b/.serena/memories/modular_validator_architecture_completed.md deleted file mode 100644 index a19e212..0000000 --- a/.serena/memories/modular_validator_architecture_completed.md +++ /dev/null @@ -1,200 +0,0 @@ -# Modular Validator Architecture - COMPLETED - -## Overview - -Successfully implemented a comprehensive modular validation system for GitHub Actions, replacing the monolithic validator.py with a flexible, extensible architecture. - -## Implementation Status: COMPLETED (September 2025) - -All 7 phases completed with 100% test pass rate and zero linting issues. - -## Architecture Components - -### Core System - -1. **BaseValidator** (`validators/base.py`) - - Abstract base class defining validation interface - - Standard methods: validate_inputs, add_error, clear_errors - - Extensible for custom validators - -2. **ValidatorRegistry** (`validators/registry.py`) - - Dynamic validator discovery and loading - - Custom validator support via action-specific `/CustomValidator.py` files - - Searches project root for `/CustomValidator.py` (e.g., `docker-build/CustomValidator.py`) - - Fallback to convention-based validation when no custom validator exists - - Added get_validator_by_type method for direct type access - -3. **ConventionBasedValidator** (`validators/conventions.py`) - - Pattern-based automatic validation - - Detects validation needs from input names - - Delegates to specific validators based on conventions - -4. **ConventionMapper** (`validators/convention_mapper.py`) - - Maps input patterns to validator types - - Supports exact, prefix, suffix, and contains patterns - - Efficient pattern matching with caching - -### Specialized Validators - -- **BooleanValidator**: Boolean values (true/false) -- **VersionValidator**: SemVer, CalVer, flexible versioning -- **TokenValidator**: GitHub tokens, API keys -- **NumericValidator**: Integer/float ranges -- **FileValidator**: File/directory paths -- **NetworkValidator**: URLs, emails, hostnames -- **DockerValidator**: Images, tags, platforms -- **SecurityValidator**: Injection protection, security patterns -- **CodeQLValidator**: Languages, queries, config - -### Custom Validators - -- Action-specific validation via `/CustomValidator.py` files -- Located in each action's directory (e.g., `docker-build/CustomValidator.py`, `npm-publish/CustomValidator.py`) -- Extends ConventionBasedValidator or BaseValidator -- Registry discovers custom validators by searching action directories in project root -- Examples: docker-build, sync-labels, npm-publish, php-laravel-phpunit, validate-inputs - -## Testing Infrastructure - -### Test Generation System - -- **generate-tests.py**: Non-destructive test generation -- Preserves existing tests -- Generates ShellSpec and pytest tests -- Pattern-based test case creation -- 900+ lines of intelligent test scaffolding - -### Test Coverage - -- 303 total tests passing -- ShellSpec for action validation -- pytest for Python validators -- Integration tests for end-to-end validation -- Performance benchmarks available - -## Documentation & Tools - -### Documentation - -- **API.md**: Complete API reference -- **DEVELOPER_GUIDE.md**: Adding new validators -- **ACTION_MAINTAINER.md**: Using validation system -- **README_ARCHITECTURE.md**: System overview - -### Debug & Performance Tools - -- **debug-validator.py**: Interactive debugging -- **benchmark-validator.py**: Performance profiling -- **update-validators.py**: Rule generation - -## Code Quality - -### Standards Achieved - -- ✅ Zero linting issues (ruff, pyright) -- ✅ 100% test pass rate (303 tests) -- ✅ Full backward compatibility -- ✅ Type hints throughout -- ✅ Comprehensive documentation -- ✅ EditorConfig compliance - -### Fixed Issues - -- Import sorting and organization -- F-string logging converted to lazy format -- Boolean arguments made keyword-only -- Type annotations using proper types -- Private member access via public methods -- Exception handling improvements -- Added missing registry methods - -## Integration - -### Main Validator Integration - -- validator.py uses ValidatorRegistry -- Transparent migration from old system -- All existing actions work unchanged -- Custom validators take precedence - -### GitHub Expression Support - -- Proper handling of ${{ }} expressions -- Expression validation in appropriate contexts -- Security-aware expression checking - -## File Structure - -```text -validate-inputs/ -├── validators/ -│ ├── __init__.py -│ ├── base.py # Abstract base -│ ├── registry.py # Discovery & loading -│ ├── conventions.py # Pattern-based -│ ├── convention_mapper.py # Pattern mapping -│ ├── boolean.py # Specialized validators... -│ ├── version.py -│ └── ... -├── rules/ # Auto-generated YAML -├── tests/ # pytest tests -├── scripts/ -│ ├── generate-tests.py # Test generation -│ ├── debug-validator.py # Debugging -│ ├── benchmark-validator.py # Performance -│ └── update-validators.py # Rule generation -├── docs/ # Documentation -├── CustomValidator.py # Custom validator for validate-inputs action -└── validator.py # Main entry point - -# Custom validators in action directories (examples): -docker-build/CustomValidator.py -npm-publish/CustomValidator.py -php-laravel-phpunit/CustomValidator.py -version-validator/CustomValidator.py -``` - -## Key Achievements - -1. **Modular Architecture**: Clean separation of concerns -2. **Convention-Based**: Automatic validation from naming patterns -3. **Extensible**: Easy to add new validators -4. **Backward Compatible**: No breaking changes -5. **Well Tested**: Comprehensive test coverage -6. **Documented**: Complete API and guides -7. **Production Ready**: Zero defects, all quality gates passed - -## Usage Examples - -### Custom Validator - -```python -# docker-build/CustomValidator.py -from validate-inputs.validators.conventions import ConventionBasedValidator -from validate-inputs.validators.docker import DockerValidator - -class CustomValidator(ConventionBasedValidator): - def __init__(self, action_type: str): - super().__init__(action_type) - self.docker_validator = DockerValidator(action_type) - - def validate_inputs(self, inputs: dict[str, str]) -> bool: - # Custom validation logic - if not self.validate_required_inputs(inputs, ["context"]): - return False - return super().validate_inputs(inputs) -``` - -### Debug Usage - -```bash -# Debug an action -python validate-inputs/scripts/debug-validator.py docker-build --inputs '{"context": ".", "platforms": "linux/amd64,linux/arm64"}' - -# Benchmark performance -python validate-inputs/scripts/benchmark-validator.py --action docker-build --iterations 1000 -``` - -## Migration Complete - -The modular validator architecture is fully implemented, tested, documented, and integrated. All quality standards met with zero defects. diff --git a/.serena/memories/project_overview.md b/.serena/memories/project_overview.md deleted file mode 100644 index 9fb2cb6..0000000 --- a/.serena/memories/project_overview.md +++ /dev/null @@ -1,199 +0,0 @@ -# Project Overview - GitHub Actions Monorepo - -## Purpose - -This repository contains a collection of reusable GitHub Actions designed to streamline CI/CD processes and ensure code quality. -Each action is fully self-contained and can be used independently in any GitHub repository. - -## Repository Information - -- **Branch**: feat/upgrades-and-restructuring -- **Location**: /Users/ivuorinen/Code/ivuorinen/actions -- **External Usage**: `ivuorinen/actions/action-name@main` -- **Last Updated**: January 2025 - -## Key Features - -- **Production-Ready Actions** covering setup, linting, building, testing, and deployment -- **Self-Contained Design** - each action works independently without dependencies -- **Modular Validator Architecture** - specialized validators with convention-based auto-detection -- **Custom Validator Support** - complex actions have dedicated validation logic -- **Test Generation System** - automatic test scaffolding with intelligent pattern detection -- **Multi-Language Support** including Node.js, PHP, Python, Go, C#, Docker, and more -- **Comprehensive Testing** with dual framework (ShellSpec + pytest) -- **Zero Defect Policy** - 100% test pass rate, zero linting issues required - -## Architecture Highlights - -### Directory Structure - -- **Flat Action Layout**: Each action in its own directory with `action.yml` -- **Centralized Validation**: `validate-inputs/` with modular validator system -- **Custom Validators**: Action-specific validators (e.g., `docker-build/CustomValidator.py`) -- **Testing Infrastructure**: `_tests/` for ShellSpec, `validate-inputs/tests/` for pytest -- **Build Tools**: `_tools/` for helper scripts and development utilities -- **Test Generation**: `validate-inputs/scripts/generate-tests.py` for automatic test creation - -### Validation System (Modular Architecture) - -```text -validate-inputs/ -├── validator.py # Main entry point -├── validators/ -│ ├── base.py # Abstract base class -│ ├── registry.py # Dynamic validator discovery -│ ├── conventions.py # Convention-based auto-detection -│ └── [9 specialized modules] -├── scripts/ -│ ├── update-validators.py # Auto-generates validation rules -│ └── generate-tests.py # Non-destructive test generation -└── tests/ # Comprehensive test suite -``` - -### Testing Framework - -- **ShellSpec**: For testing shell scripts and GitHub Actions -- **pytest**: For Python validation system (303 tests, 100% passing) -- **Test Generator**: Automatic test scaffolding for new actions/validators -- **Coverage**: Full test coverage for all validators - -## Action Categories - -**Total: 43 actions** across 8 categories - -### Setup Actions (7) - -- `node-setup`, `set-git-config`, `php-version-detect`, `python-version-detect`, -- `python-version-detect-v2`, `go-version-detect`, `dotnet-version-detect` - -### Linting Actions (13) - -- `ansible-lint-fix`, `biome-check`, `biome-fix`, `csharp-lint-check` -- `eslint-check`, `eslint-fix`, `go-lint`, `pr-lint`, `pre-commit` -- `prettier-check`, `prettier-fix`, `python-lint-fix`, `terraform-lint-fix` - -### Build Actions (3) - -- `csharp-build`, `go-build`, `docker-build` - -### Publishing Actions (5) - -- `npm-publish`, `docker-publish`, `docker-publish-gh`, `docker-publish-hub`, `csharp-publish` - -### Testing Actions (3) - -- `php-tests`, `php-laravel-phpunit`, `php-composer` - -### Repository (9) - -- `github-release`, `release-monthly`, `sync-labels`, `stale` -- `compress-images`, `common-cache`, `common-file-check`, `common-retry` -- `codeql-analysis` (security analysis) - -### Utilities (2) - -- `version-file-parser`, `version-validator` - -### Validation (1) - -- `validate-inputs` (centralized input validation system) - -## Development Workflow - -### Core Commands - -```bash -make all # Generate docs, format, lint, test -make dev # Format then lint -make lint # Run all linters -make test # Run all tests -make update-validators # Update validation rules -make generate-tests # Generate missing tests (non-destructive) -make generate-tests-dry # Preview test generation -``` - -### Quality Standards - -- **ZERO TOLERANCE**: No failing tests, no linting issues -- **Production Ready**: Only when ALL checks pass -- **Convention Priority**: EditorConfig rules are blocking -- **Security First**: No secrets, tokens, or sensitive data in code - -## Recent Accomplishments (January 2025) - -### Phase 1-4: Modular Validator Architecture ✅ - -- Transformed monolithic validator into 11 specialized modules -- Implemented convention-based auto-detection (100+ patterns) -- Created 3 custom validators for complex actions -- Achieved 100% test pass rate (292/292 tests) -- Zero linting issues across all code - -### Phase 5: Test Generation System ✅ - -- Created non-destructive test generation (preserves existing tests) -- Intelligent pattern detection for input types -- Template-based scaffolding for different validator types -- ShellSpec test generation for GitHub Actions -- pytest test generation for validators -- Custom validator test support -- 11 comprehensive tests for the generator itself -- Makefile integration with three new commands - -### Custom Validators Implemented - -1. `docker-build` - Complex build args, platforms, cache validation -2. `codeql-analysis` - Language support, query validation -3. `docker-publish` - Registry, credentials, platform validation - -### Technical Improvements - -- Full GitHub expression support (`${{ }}`) -- Error propagation between parent/child validators -- Platform-specific validation (Docker architectures) -- Registry validation (Docker Hub, GHCR, etc.) -- Security pattern detection and injection prevention -- Non-destructive test generation system -- Template-based test scaffolding - -## Project Status - -**Phases Completed**: - -- ✅ Phase 1: Base Architecture (100% complete) -- ✅ Phase 2: Core Validators (100% complete) -- ✅ Phase 3: Registry System (100% complete) -- ✅ Phase 4: Custom Validators (100% complete) -- ✅ Phase 5: Test Generation (100% complete) -- ⏳ Phase 6: Integration and Migration (in progress) -- ⏳ Phase 7: Documentation and Tooling (not started) - -**Quality Metrics**: - -- ✅ 100% test pass rate (303 total tests) -- ✅ Zero linting issues -- ✅ Modular, extensible architecture -- ✅ Custom validator support -- ✅ Convention-based auto-detection -- ✅ Full backward compatibility -- ✅ Comprehensive error handling -- ✅ Security validations -- ✅ Test generation system - -## Next Steps - -1. Complete Phase 6: Integration and Migration - - Integrate modular validators with main validator.py - - Ensure full backward compatibility - - Test all 50+ actions with integrated system -2. Phase 7: Documentation and Tooling -3. Performance optimization -4. Production deployment - -## IDE Configuration Note - -For Pyright/Pylance import resolution in IDEs like Zed, VSCode: - -- The project uses relative imports within validate-inputs -- Python path includes validate-inputs directory -- Tests use sys.path manipulation for imports diff --git a/.serena/memories/project_structure.md b/.serena/memories/project_structure.md deleted file mode 100644 index dd67b77..0000000 --- a/.serena/memories/project_structure.md +++ /dev/null @@ -1,171 +0,0 @@ -# Project Structure and Architecture - -## Repository Structure - -```text -/Users/ivuorinen/Code/ivuorinen/actions/ -├── Action Directories/ # Each action is self-contained -│ ├── action.yml # Action definition -│ ├── README.md # Auto-generated documentation -│ └── CustomValidator.py # Optional custom validator -├── validate-inputs/ # Centralized validation system -│ ├── validator.py # Main entry point -│ ├── validators/ # Modular validator architecture -│ │ ├── base.py # Abstract base class -│ │ ├── registry.py # Dynamic validator discovery -│ │ ├── conventions.py # Convention-based detection -│ │ ├── boolean.py # Boolean validation -│ │ ├── codeql.py # CodeQL-specific validation -│ │ ├── docker.py # Docker validation -│ │ ├── file.py # File path validation -│ │ ├── network.py # Network/URL validation -│ │ ├── numeric.py # Numeric validation -│ │ ├── security.py # Security pattern detection -│ │ ├── token.py # Token validation -│ │ └── version.py # Version validation -│ ├── rules/ # Auto-generated YAML rules -│ ├── scripts/ # Rule generation utilities -│ └── tests/ # Comprehensive pytest suite (292 tests) -├── _tests/ # ShellSpec testing framework -│ ├── unit/ # Unit tests for actions -│ ├── framework/ # Testing utilities -│ └── shared/ # Shared test components -├── _tools/ # Development utilities -│ ├── docker-testing-tools/ # Docker test environment -│ └── fix-local-action-refs.py # Action reference fixer -├── .github/ # GitHub configuration -│ └── workflows/ # CI/CD workflows -├── .serena/ # Serena AI configuration -│ └── memories/ # Project knowledge base -├── Makefile # Build automation -├── pyproject.toml # Python configuration -├── CLAUDE.md # Project instructions -└── README.md # Auto-generated catalog -``` - -## Modular Validator Architecture - -### Core Components - -- **BaseValidator**: Abstract interface for all validators -- **ValidatorRegistry**: Dynamic discovery and loading -- **ConventionMapper**: Automatic validation based on naming patterns - -### Specialized Validators - -1. **TokenValidator**: GitHub, NPM, PyPI, Docker tokens -2. **VersionValidator**: SemVer, CalVer, language-specific -3. **BooleanValidator**: Case-insensitive boolean values -4. **NumericValidator**: Ranges and numeric constraints -5. **DockerValidator**: Images, tags, platforms, registries -6. **FileValidator**: Paths, extensions, security checks -7. **NetworkValidator**: URLs, emails, IPs, ports -8. **SecurityValidator**: Injection detection, secrets -9. **CodeQLValidator**: Queries, languages, categories - -### Custom Validators - -- `sync-labels/CustomValidator.py` - YAML file validation -- `docker-build/CustomValidator.py` - Complex build validation -- `codeql-analysis/CustomValidator.py` - Language and query validation -- `docker-publish/CustomValidator.py` - Registry and credential validation - -## Action Categories - -### Setup Actions (7) - -- `node-setup`, `set-git-config`, `php-version-detect` -- `python-version-detect`, `python-version-detect-v2` -- `go-version-detect`, `dotnet-version-detect` - -### Linting Actions (13) - -- `ansible-lint-fix`, `biome-check`, `biome-fix` -- `csharp-lint-check`, `eslint-check`, `eslint-fix` -- `go-lint`, `pr-lint`, `pre-commit` -- `prettier-check`, `prettier-fix` -- `python-lint-fix`, `terraform-lint-fix` - -### Build Actions (3) - -- `csharp-build`, `go-build`, `docker-build` - -### Publishing Actions (5) - -- `npm-publish`, `docker-publish` -- `docker-publish-gh`, `docker-publish-hub` -- `csharp-publish` - -### Testing Actions (3) - -- `php-tests`, `php-laravel-phpunit`, `php-composer` - -### Repository Management (9) - -- `github-release`, `release-monthly` -- `sync-labels`, `stale` -- `compress-images`, `common-cache` -- `common-file-check`, `common-retry` -- `codeql-analysis` - -### Utilities (2) - -- `version-file-parser`, `version-validator` - -## Key Architectural Principles - -### Self-Contained Design - -- Each action directory contains everything needed -- No dependencies between actions -- External usability via `ivuorinen/actions/action-name@main` -- Custom validators colocated with actions - -### Modular Validation System - -- Specialized validators for different input types -- Convention-based automatic detection (100+ patterns) -- Priority system for pattern matching -- Error propagation between validators -- Full GitHub expression support (`${{ }}`) - -### Testing Strategy - -- **ShellSpec**: Shell scripts and GitHub Actions -- **pytest**: Python validation system (100% pass rate) -- **Coverage**: All validators have dedicated test files -- **Standards**: Zero tolerance for failures - -### Security Model - -- SHA-pinned external actions -- Token pattern validation -- Injection detection -- Path traversal protection -- Security validator for sensitive data - -## Development Workflow - -### Core Commands - -```bash -make all # Full build pipeline -make dev # Format and lint -make lint # All linters -make test # All tests -make update-validators # Generate validation rules -``` - -### Quality Standards - -- **EditorConfig**: Blocking enforcement -- **Linting**: Zero issues required -- **Testing**: 100% pass rate required -- **Production Ready**: Only when ALL checks pass - -### Documentation - -- Auto-generated README files via `action-docs` -- Consistent formatting and structure -- Cross-referenced action catalog -- Comprehensive inline documentation diff --git a/.serena/memories/quality_standards_and_communication.md b/.serena/memories/quality_standards_and_communication.md deleted file mode 100644 index 1ae512b..0000000 --- a/.serena/memories/quality_standards_and_communication.md +++ /dev/null @@ -1,36 +0,0 @@ -# Quality Standards and Communication Guidelines - -## Critical Quality Standards - -### ZERO TOLERANCE POLICY - -- **ANY failing tests** = Project is NOT production ready -- **ANY linting issues** = Project is NOT production ready -- **NO EXCEPTIONS** to these rules - -### Production Ready Definition - -A project is only production ready when: - -- ALL tests pass (100% success rate) -- ALL linting passes with zero issues -- ALL validation checks pass -- NO warnings or errors in any tooling - -### Communication Style - -- **Tone down language** - avoid excessive enthusiasm or verbose descriptions -- Be direct and factual -- Don't claim success until ALL issues are resolved -- Don't use terms like "production ready" unless literally everything passes -- Focus on facts, not marketing language - -### Work Standards - -- Fix ALL issues before declaring completion -- Never compromise on quality standards -- Test everything thoroughly -- Maintain zero-defect mentality -- Quality over speed - -This represents the user's absolute standards for code quality and communication. diff --git a/.serena/memories/repository_overview.md b/.serena/memories/repository_overview.md new file mode 100644 index 0000000..26a61c9 --- /dev/null +++ b/.serena/memories/repository_overview.md @@ -0,0 +1,87 @@ +# GitHub Actions Monorepo - Overview + +## Repository Info + +- **Path**: /Users/ivuorinen/Code/ivuorinen/actions +- **Branch**: main +- **External Usage**: `ivuorinen/actions/@main` +- **Total Actions**: 43 self-contained actions + +## Structure + +```text +/ +├── / # 43 self-contained actions +│ ├── action.yml # Action definition +│ ├── README.md # Auto-generated +│ └── CustomValidator.py # Optional validator +├── validate-inputs/ # Centralized validation +│ ├── validators/ # 9 specialized modules +│ ├── scripts/ # Rule/test generators +│ └── tests/ # 769 pytest tests +├── _tests/ # ShellSpec framework +├── _tools/ # Development utilities +├── .github/workflows/ # CI/CD workflows +└── Makefile # Build automation +``` + +## Action Categories (43 total) + +**Setup (7)**: node-setup, set-git-config, php-version-detect, python-version-detect, python-version-detect-v2, go-version-detect, dotnet-version-detect + +**Linting (13)**: ansible-lint-fix, biome-check/fix, csharp-lint-check, eslint-check/fix, go-lint, pr-lint, pre-commit, prettier-check/fix, python-lint-fix, terraform-lint-fix + +**Build (3)**: csharp-build, go-build, docker-build + +**Publishing (5)**: npm-publish, docker-publish, docker-publish-gh, docker-publish-hub, csharp-publish + +**Testing (3)**: php-tests, php-laravel-phpunit, php-composer + +**Repository (9)**: github-release, release-monthly, sync-labels, stale, compress-images, common-cache, common-file-check, common-retry, codeql-analysis + +**Utilities (3)**: version-file-parser, version-validator, validate-inputs + +## Key Principles + +### Self-Contained Design + +- No dependencies between actions +- Externally usable via GitHub Actions marketplace +- Custom validators colocated with actions + +### Quality Standards + +- **Zero Tolerance**: No failing tests, no linting issues +- **Production Ready**: Only when ALL checks pass +- **EditorConfig**: 2-space indent, LF, UTF-8, max 200 chars (120 for MD) + +### Security Model + +- SHA-pinned external actions (55 SHA-pinned, 0 unpinned) +- Token validation, injection detection +- Path traversal protection +- `set -euo pipefail` in all shell scripts + +## Development Workflow + +```bash +make all # Full pipeline: docs, format, lint, test +make dev # Format + lint +make lint # All linters (markdownlint, yaml-lint, shellcheck, ruff) +make test # All tests (pytest + ShellSpec) +``` + +## Testing Framework + +- **ShellSpec**: GitHub Actions and shell scripts +- **pytest**: Python validators (769 tests, 100% pass rate) +- **Test Generator**: Automatic scaffolding for new actions + +## Current Status + +- ✅ All tests passing (769/769) +- ✅ Zero linting issues +- ✅ Modular validator architecture +- ✅ Convention-based validation +- ✅ Test generation system +- ✅ Full backward compatibility diff --git a/.serena/memories/shellspec.md b/.serena/memories/shellspec.md deleted file mode 100644 index a5788cf..0000000 --- a/.serena/memories/shellspec.md +++ /dev/null @@ -1,111 +0,0 @@ -# ShellSpec Test Fixes Tracking - -## Status - -**Branch**: feat/upgrades-and-restructuring -**Date**: 2025-09-17 -**Progress**: Fixed critical test failures - -## Summary - -- Initial failing tests: 27 actions -- **Fixed completely**: 3 actions (codeql-analysis, common-cache, common-file-check) -- **Partially fixed**: Several others have reduced failures -- **Key achievement**: Established patterns for fixing remaining tests - -## ✅ Completed Fixes (3 actions) - -### 1. codeql-analysis - -- Created comprehensive CustomValidator -- Fixed all language, token, path, and query validations -- Result: **65 examples, 0 failures** - -### 2. common-cache - -- Created CustomValidator for comma-separated paths -- Added cache type, paths, keys, env-vars validation -- Result: **29 examples, 0 failures** (23 warnings) - -### 3. common-file-check - -- Created CustomValidator for glob patterns -- Supports \*, ?, \*\*, {}, [] in file patterns -- Result: **17 examples, 0 failures** (12 warnings) - -## 🎯 Key Patterns Established - -### CustomValidator Template - -```python -class CustomValidator(BaseValidator): - def validate_inputs(self, inputs: dict[str, str]) -> bool: - # Handle required inputs first - # Use specific validation methods - # Check for GitHub expressions: if "${{" in value - # Validate security patterns - return valid -``` - -### Common Validation Patterns - -1. **Token Validation** - - ghp\_ tokens: 40-44 chars - - github*pat* tokens: 82-95 chars - - ghs\_ tokens: 40-44 chars - -2. **Path Validation** - - Reject absolute paths: `/path` - - Reject traversal: `..` - - Allow comma-separated: split and validate each - -3. **Error Messages** - - "Required input 'X' is missing" - - "Absolute path not allowed" - - "Path traversal detected" - - "Command injection detected" - -4. **Test Output** - - Python logger outputs to stderr - - Tests checking stdout need updating to stderr - - Warnings about unexpected output are non-critical - -## 📋 Remaining Work - -### Quick Fixes (Similar patterns) - -- common-retry: Add backoff-strategy, shell validation -- compress-images: File pattern validation -- eslint-check, prettier-fix: Token validation - -### Docker Actions (Need CustomValidators) - -- docker-build, docker-publish, docker-publish-gh, docker-publish-hub -- Common issues: image-name, registry, platforms validation - -### Version Detection Actions - -- go-version-detect, python-version-detect, php-version-detect -- Need version format validation - -### Complex Actions (Need detailed CustomValidators) - -- node-setup: Package manager, caching logic -- pre-commit: Hook configuration -- terraform-lint-fix: HCL-specific validation - -## 🚀 Next Steps - -To complete all fixes: - -1. Create CustomValidators for remaining actions with failures -2. Use established patterns for quick wins -3. Test each action individually before full suite -4. Update tests expecting stdout to check stderr where needed - -## 📊 Success Criteria - -- All ShellSpec tests pass (0 failures) -- Warnings are acceptable (output format issues) -- Maintain backward compatibility -- Follow established validation patterns diff --git a/.serena/memories/task_completion_requirements.md b/.serena/memories/task_completion_requirements.md deleted file mode 100644 index 9901b67..0000000 --- a/.serena/memories/task_completion_requirements.md +++ /dev/null @@ -1,125 +0,0 @@ -# Task Completion Requirements - -## Mandatory Steps After Completing Any Task - -### 1. Linting (BLOCKING REQUIREMENT) - -```bash -make lint # Run all linters - must pass 100% -``` - -**Critical Rules:** - -- EditorConfig violations are BLOCKING errors - fix always -- All linting issues are NOT ACCEPTABLE and must be resolved -- Never simplify linting configuration to make tests pass -- Linting tools decisions are final and must be obeyed -- Consider ALL linting errors as blocking errors - -**Specific Linting Steps:** - -```bash -make lint-markdown # Fix markdown issues -make lint-yaml # Fix YAML issues -make lint-shell # Fix shell script issues -make lint-python # Fix Python code issues -``` - -### 2. Testing (VERIFICATION REQUIREMENT) - -```bash -make test # Run all tests - must pass 100% -``` - -**Test Categories:** - -- Python validation tests (pytest) -- GitHub Actions tests (ShellSpec) -- Integration tests -- Coverage reporting - -### 3. Formatting (AUTO-FIX REQUIREMENT) - -```bash -make format # Auto-fix all formatting issues -``` - -**Always use autofixers before running linters:** - -- Markdown formatting and table formatting -- YAML/JSON formatting with prettier -- Python formatting with ruff -- Line ending and whitespace fixes - -## Verification Checklist - -### Before Considering Task Complete - -- [ ] `make lint` passes with zero issues -- [ ] `make test` passes with 100% success -- [ ] EditorConfig rules followed (2-space indent, LF endings, UTF-8) -- [ ] No trailing whitespace or missing final newlines -- [ ] Shell scripts pass shellcheck -- [ ] Python code passes ruff with comprehensive rules -- [ ] YAML files pass yaml-lint and actionlint -- [ ] Markdown passes markdownlint-cli2 - -### Security and Quality Gates - -- [ ] No secrets or credentials committed -- [ ] No hardcoded tokens or API keys -- [ ] Proper error handling with `set -euo pipefail` -- [ ] External actions are SHA-pinned -- [ ] Input validation through centralized system - -## Error Resolution Strategy - -### When Linting Fails - -1. **Read the error message carefully** - don't ignore details -2. **Read the linting tool schema** - understand the rules -3. **Compare against schema** - schema is the truth -4. **Fix the actual issue** - don't disable rules -5. **Use autofix first** - `make format` before manual fixes - -### When Tests Fail - -1. **Fix all errors and warnings** - no exceptions -2. **Ensure proper test coverage** - comprehensive testing required -3. **Verify integration points** - actions must work together -4. **Check validation logic** - centralized validation must pass - -### Common Issues and Solutions - -- **EditorConfig**: Use exactly 2 spaces, LF endings, UTF-8 -- **Python**: Follow Google docstring style, 100 char lines -- **Shell**: Use shellcheck-compliant patterns -- **YAML**: Proper indentation, no trailing spaces -- **Markdown**: Tables formatted, links valid, consistent style - -## Never Do These - -- Never use `git commit` without explicit user request -- Never use `--no-verify` flags -- Never modify linting configuration to make tests pass -- Never assume linting issues are acceptable -- Never skip testing after code changes -- Never create files unless absolutely necessary - -## File Modification Preferences - -- **Always prefer editing existing files** over creating new ones -- **Never proactively create documentation** unless requested -- **Read project patterns** before making changes -- **Follow existing conventions** in the codebase -- **Use centralized validation** for all input handling - -## Final Verification - -After ALL tasks are complete, run the full development cycle: - -```bash -make all # Complete workflow: docs, format, lint, test -``` - -This ensures the project maintains its excellent state and all quality gates pass. diff --git a/.serena/memories/validator_system.md b/.serena/memories/validator_system.md new file mode 100644 index 0000000..f71eb8c --- /dev/null +++ b/.serena/memories/validator_system.md @@ -0,0 +1,76 @@ +# Validation System Architecture + +## Status: PRODUCTION READY ✅ + +- 769 tests passing (100%) +- Zero linting issues +- Modular architecture complete + +## Architecture + +### Core Components + +- **BaseValidator**: Abstract interface for all validators +- **ValidatorRegistry**: Dynamic discovery, loads custom validators from `/CustomValidator.py` +- **ConventionMapper**: Auto-detection via 100+ naming patterns (priority-based matching) + +### Specialized Validators (9) + +`token.py`, `version.py` (SemVer/CalVer), `boolean.py`, `numeric.py`, `docker.py`, `file.py`, `network.py`, `security.py`, `codeql.py` + +### Custom Validators (20+) + +Actions with complex validation have `CustomValidator.py` in their directory. Registry auto-discovers them. + +Examples: `docker-build/CustomValidator.py`, `sync-labels/CustomValidator.py`, `codeql-analysis/CustomValidator.py` + +## Convention-Based Detection + +Automatic validator selection from input names: + +- Priority 100: Exact (`dry-run` → boolean) +- Priority 95: Language-specific (`-python-version` → python_version) +- Priority 90: Suffixes (`-token` → token) +- Priority 85: Contains (`email` → email) +- Priority 80: Prefixes (`is-` → boolean) + +## Test Generation + +`validate-inputs/scripts/generate-tests.py`: + +- Non-destructive (preserves existing tests) +- Intelligent pattern detection for input types +- Template-based scaffolding for validators +- ShellSpec + pytest generation + +## Usage + +```python +from validators.registry import ValidatorRegistry +validator = ValidatorRegistry().get_validator("docker-build") +result = validator.validate_inputs({"context": ".", "platforms": "linux/amd64"}) +``` + +## File Structure + +```text +validate-inputs/ +├── validator.py # Main entry +├── validators/ # 9 specialized + base + registry + conventions +├── scripts/ +│ ├── update-validators.py # Rule generator +│ └── generate-tests.py # Test generator +└── tests/ # 769 pytest tests + +/CustomValidator.py # Action-specific validators +``` + +## Key Features + +- Convention-based auto-detection +- GitHub expression support (`${{ }}`) +- Error propagation between validators +- Security validation (injection, secrets) +- CalVer, SemVer, flexible versioning +- Docker platforms, registries +- Token formats (GitHub, NPM, PyPI) diff --git a/CLAUDE.md b/CLAUDE.md index b86555f..322a498 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,6 +31,42 @@ - `validate-inputs/` – Python validation system + tests - `*/rules.yml` – Auto-generated validation rules +### Memory System + +**Location**: `.serena/memories/` (9 consolidated memories for context) + +**When to Use**: Read memories at session start or when needed for specific context. Be token-efficient - read only relevant memories for the task. + +**Core Memories** (read first for project understanding): + +- `repository_overview` – 43 actions, categories, structure, status +- `validator_system` – Validation architecture, components, usage patterns +- `development_standards` – Quality rules, workflows, security, completion checklist + +**Reference Guides** (read when working on specific areas): + +- `code_style_conventions` – EditorConfig, Shell/Python/YAML style, 10 critical prevention rules +- `suggested_commands` – Make targets, testing commands, tool usage +- `tech_stack` – Python/Node.js/Shell tools, paths, versions + +**GitHub Actions Reference** (read when working with workflows): + +- `github-workflow-expressions` – Expression syntax, contexts, operators, common patterns +- `github-workflow-commands` – Workflow commands (outputs, env, logging, masking) +- `github-workflow-secure-use` – Security best practices, secrets, injection prevention + +**Memory Maintenance**: Update existing memories rather than create new ones. Keep content token-efficient and factual. + +### Documentation Locations + +**Validation System**: `validate-inputs/docs/` (4 guides: API.md, DEVELOPER_GUIDE.md, ACTION_MAINTAINER.md, README_ARCHITECTURE.md) + +**Testing**: `_tests/README.md` (ShellSpec framework, test patterns, running tests) + +**Docker Tools**: `_tools/docker-testing-tools/README.md` (CI setup, pre-built testing image) + +**See**: `documentation_guide` memory for detailed descriptions and when to read each + ## Repository Structure Flat structure. Each action self-contained with `action.yml`. diff --git a/LICENSE.md b/LICENSE.md index bffa737..1bb80e7 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Ismo Vuorinen +Copyright (c) 2024-2025 Ismo Vuorinen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/SECURITY.md b/SECURITY.md index 05b0ce4..1b941c0 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -38,11 +38,12 @@ run: | ### 2. Secret Masking -**Status**: ✅ Implemented in 6 critical actions +**Status**: ✅ Implemented in 7 critical actions Actions that handle sensitive data use GitHub Actions secret masking to prevent accidental exposure in logs: - `npm-publish` - NPM authentication tokens +- `docker-publish` - Docker Hub credentials (defense-in-depth masking) - `docker-publish-hub` - Docker Hub passwords - `docker-publish-gh` - GitHub tokens - `csharp-publish` - NuGet API keys @@ -225,11 +226,11 @@ When security issues are fixed: - Added comprehensive input validation - Status: ✅ Complete -### Phase 2: Enhanced Security (2024) +### Phase 2: Enhanced Security (2024-2025) - Replaced custom Bun installation with official action - Replaced custom Trivy installation with official action -- Added secret masking to 6 critical actions +- Added secret masking to 7 critical actions (including docker-publish) - Optimized file hashing in common-cache - Status: ✅ Complete diff --git a/_tests/README.md b/_tests/README.md index 601701a..c87507e 100644 --- a/_tests/README.md +++ b/_tests/README.md @@ -1,6 +1,6 @@ # GitHub Actions Testing Framework -A comprehensive testing framework for validating GitHub Actions in this monorepo. This guide covers everything from basic usage to advanced testing patterns. +A comprehensive testing framework for validating GitHub Actions in this monorepo using ShellSpec and Python-based input validation. ## 🚀 Quick Start @@ -36,16 +36,15 @@ brew install act # macOS The testing framework uses a **multi-level testing strategy**: -1. **Unit Tests** - Fast validation of action logic, inputs, and outputs +1. **Unit Tests** - Fast validation of action logic, inputs, and outputs using Python validation 2. **Integration Tests** - Test actions in realistic workflow environments 3. **External Usage Tests** - Validate actions work as `ivuorinen/actions/action-name@main` ### Technology Stack - **Primary Framework**: [ShellSpec](https://shellspec.info/) - BDD testing for shell scripts +- **Validation**: Python-based input validation via `validate-inputs/validator.py` - **Local Execution**: [nektos/act](https://github.com/nektos/act) - Run GitHub Actions locally -- **Coverage**: kcov integration for shell script coverage -- **Mocking**: Custom GitHub API and service mocks - **CI Integration**: GitHub Actions workflows ### Directory Structure @@ -54,19 +53,20 @@ The testing framework uses a **multi-level testing strategy**: _tests/ ├── README.md # This documentation ├── run-tests.sh # Main test runner script -├── framework/ # Core testing utilities -│ ├── setup.sh # Test environment setup -│ ├── utils.sh # Common testing functions -│ ├── validation_helpers.sh # Validation helper functions -│ ├── validation.py # Python validation utilities -│ └── mocks/ # Mock services (GitHub API, etc.) ├── unit/ # Unit tests by action +│ ├── spec_helper.sh # ShellSpec helper with validation functions │ ├── version-file-parser/ # Example unit tests │ ├── node-setup/ # Example unit tests │ └── ... # One directory per action +├── framework/ # Core testing utilities +│ ├── setup.sh # Test environment setup +│ ├── utils.sh # Common testing functions +│ ├── validation.py # Python validation utilities +│ └── fixtures/ # Test fixtures ├── integration/ # Integration tests │ ├── workflows/ # Test workflows for nektos/act -│ └── external-usage/ # External reference tests +│ ├── external-usage/ # External reference tests +│ └── action-chains/ # Multi-action workflow tests ├── coverage/ # Coverage reports └── reports/ # Test execution reports ``` @@ -79,44 +79,39 @@ _tests/ #!/usr/bin/env shellspec # _tests/unit/my-action/validation.spec.sh -Include _tests/framework/utils.sh - Describe "my-action validation" - ACTION_DIR="my-action" - ACTION_FILE="$ACTION_DIR/action.yml" +ACTION_DIR="my-action" +ACTION_FILE="$ACTION_DIR/action.yml" - BeforeAll "init_testing_framework" - - Context "input validation" - It "validates all inputs comprehensively" - # Use validation helpers for comprehensive testing - test_boolean_input "verbose" - test_boolean_input "dry-run" - - # Numeric range validations (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "max-retries" "1" "success" - test_input_validation "$ACTION_DIR" "max-retries" "10" "success" - test_input_validation "$ACTION_DIR" "timeout" "3600" "success" - - # Enum validations (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "strategy" "fast" "success" - test_input_validation "$ACTION_DIR" "format" "json" "success" - - # Version validations (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "tool-version" "1.0.0" "success" - - # Security and path validations (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "command" "echo test" "success" - test_input_validation "$ACTION_DIR" "working-directory" "." "success" - End +Context "when validating required inputs" + It "accepts valid input" + When call validate_input_python "my-action" "input-name" "valid-value" + The status should be success End - Context "action structure" - It "has valid structure and metadata" - test_standard_action_structure "$ACTION_FILE" "Expected Action Name" - End + It "rejects invalid input" + When call validate_input_python "my-action" "input-name" "invalid@value" + The status should be failure End End + +Context "when validating boolean inputs" + It "accepts true" + When call validate_input_python "my-action" "dry-run" "true" + The status should be success + End + + It "accepts false" + When call validate_input_python "my-action" "dry-run" "false" + The status should be success + End + + It "rejects invalid boolean" + When call validate_input_python "my-action" "dry-run" "maybe" + The status should be failure + End +End +End ``` ### Integration Test Example @@ -149,66 +144,68 @@ jobs: required-input: 'test-value' ``` -## 🛠️ Testing Helpers +## 🛠️ Testing Functions -### Available Validation Helpers +### Primary Validation Function -The framework provides comprehensive validation helpers that handle common testing patterns: +The framework provides one main validation function that uses the Python validation system: -#### Boolean Input Testing +#### validate_input_python + +Tests input validation using the centralized Python validator: ```bash -test_boolean_input "verbose" # Tests: true, false, rejects invalid -test_boolean_input "enable-cache" -test_boolean_input "dry-run" +validate_input_python "action-name" "input-name" "test-value" ``` -#### Numeric Range Testing +**Examples:** ```bash -# Note: test_numeric_range_input helper is not yet implemented. -# Use test_input_validation with appropriate test values instead: -test_input_validation "$ACTION_DIR" "max-retries" "1" "success" # min value -test_input_validation "$ACTION_DIR" "max-retries" "10" "success" # max value -test_input_validation "$ACTION_DIR" "max-retries" "0" "failure" # below min -test_input_validation "$ACTION_DIR" "timeout" "3600" "success" -test_input_validation "$ACTION_DIR" "parallel-jobs" "8" "success" +# Boolean validation +validate_input_python "pre-commit" "dry-run" "true" # success +validate_input_python "pre-commit" "dry-run" "false" # success +validate_input_python "pre-commit" "dry-run" "maybe" # failure + +# Version validation +validate_input_python "node-setup" "node-version" "18.0.0" # success +validate_input_python "node-setup" "node-version" "v1.2.3" # success +validate_input_python "node-setup" "node-version" "invalid" # failure + +# Token validation +validate_input_python "npm-publish" "npm-token" "ghp_123..." # success +validate_input_python "npm-publish" "npm-token" "invalid" # failure + +# Docker validation +validate_input_python "docker-build" "image-name" "myapp" # success +validate_input_python "docker-build" "tag" "v1.0.0" # success + +# Path validation (security) +validate_input_python "pre-commit" "config-file" "config.yml" # success +validate_input_python "pre-commit" "config-file" "../etc/pass" # failure + +# Injection detection +validate_input_python "common-retry" "command" "echo test" # success +validate_input_python "common-retry" "command" "rm -rf /; " # failure ``` -#### Version Testing +### Helper Functions from spec_helper.sh ```bash -# Note: test_version_input helper is not yet implemented. -# Use test_input_validation with appropriate test values instead: -test_input_validation "$ACTION_DIR" "version" "1.0.0" "success" # semver -test_input_validation "$ACTION_DIR" "version" "v1.0.0" "success" # v-prefix -test_input_validation "$ACTION_DIR" "version" "1.0.0-rc.1" "success" # pre-release -test_input_validation "$ACTION_DIR" "tool-version" "2.3.4" "success" -``` +# Setup/cleanup +setup_default_inputs "action-name" "input-name" # Set required defaults +cleanup_default_inputs "action-name" "input-name" # Clean up defaults +shellspec_setup_test_env "test-name" # Setup test environment +shellspec_cleanup_test_env "test-name" # Cleanup test environment -#### Enum Testing +# Mock execution +shellspec_mock_action_run "action-dir" key1 value1 key2 value2 +shellspec_validate_action_output "expected-key" "expected-value" -```bash -# Note: test_enum_input helper is not yet implemented. -# Use test_input_validation with appropriate test values instead: -test_input_validation "$ACTION_DIR" "strategy" "linear" "success" -test_input_validation "$ACTION_DIR" "strategy" "exponential" "success" -test_input_validation "$ACTION_DIR" "strategy" "invalid" "failure" -test_input_validation "$ACTION_DIR" "format" "json" "success" -test_input_validation "$ACTION_DIR" "format" "yaml" "success" -``` - -#### Docker-Specific Testing - -```bash -# Available framework helpers: -test_input_validation "$action_dir" "$input_name" "$test_value" "$expected_result" -test_action_outputs "$action_dir" -test_external_usage "$action_dir" - -# Note: Docker-specific helpers (test_docker_image_input, test_docker_tag_input, -# test_docker_platforms_input) are referenced in examples but not yet implemented. -# Use test_input_validation with appropriate test values instead. +# Action metadata +validate_action_yml "action.yml" # Validate YAML structure +get_action_inputs "action.yml" # Get action inputs +get_action_outputs "action.yml" # Get action outputs +get_action_name "action.yml" # Get action name ``` ### Complete Action Validation Example @@ -218,41 +215,47 @@ Describe "comprehensive-action validation" ACTION_DIR="comprehensive-action" ACTION_FILE="$ACTION_DIR/action.yml" - Context "complete input validation" - It "validates all input types systematically" - # Boolean inputs - test_boolean_input "verbose" - test_boolean_input "enable-cache" - test_boolean_input "dry-run" + Context "when validating all input types" + It "validates boolean inputs" + When call validate_input_python "$ACTION_DIR" "verbose" "true" + The status should be success - # Numeric ranges (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "max-retries" "1" "success" - test_input_validation "$ACTION_DIR" "max-retries" "10" "success" - test_input_validation "$ACTION_DIR" "timeout" "3600" "success" - test_input_validation "$ACTION_DIR" "parallel-jobs" "8" "success" + When call validate_input_python "$ACTION_DIR" "verbose" "false" + The status should be success - # Enums (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "strategy" "fast" "success" - test_input_validation "$ACTION_DIR" "format" "json" "success" + When call validate_input_python "$ACTION_DIR" "verbose" "invalid" + The status should be failure + End - # Docker-specific (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "image-name" "myapp:latest" "success" - test_input_validation "$ACTION_DIR" "tag" "1.0.0" "success" - test_input_validation "$ACTION_DIR" "platforms" "linux/amd64,linux/arm64" "success" + It "validates numeric inputs" + When call validate_input_python "$ACTION_DIR" "max-retries" "3" + The status should be success - # Security validation (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "command" "echo test" "success" - test_input_validation "$ACTION_DIR" "build-args" "ARG1=value" "success" + When call validate_input_python "$ACTION_DIR" "max-retries" "999" + The status should be failure + End - # Paths (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "working-directory" "." "success" - test_input_validation "$ACTION_DIR" "output-directory" "./output" "success" + It "validates version inputs" + When call validate_input_python "$ACTION_DIR" "tool-version" "1.0.0" + The status should be success - # Versions (use test_input_validation helper) - test_input_validation "$ACTION_DIR" "tool-version" "1.0.0" "success" + When call validate_input_python "$ACTION_DIR" "tool-version" "v1.2.3-rc.1" + The status should be success + End - # Action structure - test_standard_action_structure "$ACTION_FILE" "Comprehensive Action" + It "validates security patterns" + When call validate_input_python "$ACTION_DIR" "command" "echo test" + The status should be success + + When call validate_input_python "$ACTION_DIR" "command" "rm -rf /; " + The status should be failure + End + End + + Context "when validating action structure" + It "has valid YAML structure" + When call validate_action_yml "$ACTION_FILE" + The status should be success End End End @@ -265,45 +268,37 @@ End Focus on version detection and environment setup: ```bash -Context "version detection" +Context "when detecting versions" It "detects version from config files" - create_mock_node_repo # or appropriate repo type - - # Test version detection logic - export INPUT_LANGUAGE="node" - echo "detected-version=18.0.0" >> "$GITHUB_OUTPUT" - - When call validate_action_output "detected-version" "18.0.0" + When call validate_input_python "node-setup" "node-version" "18.0.0" The status should be success End - It "falls back to default when no version found" - # Use test_input_validation helper for version validation - test_input_validation "$ACTION_DIR" "default-version" "1.0.0" "success" + It "accepts default version" + When call validate_input_python "python-version-detect" "default-version" "3.11" + The status should be success End End ``` ### Linting Actions (eslint-fix, prettier-fix, etc.) -Focus on file processing and fix capabilities: +Focus on file processing and security: ```bash -Context "file processing" - BeforeEach "setup_test_env 'lint-test'" - AfterEach "cleanup_test_env 'lint-test'" +Context "when processing files" + It "validates working directory" + When call validate_input_python "eslint-fix" "working-directory" "." + The status should be success + End - It "validates inputs and processes files" - test_boolean_input "fix-only" - # Use test_input_validation helper for path and security validations - test_input_validation "$ACTION_DIR" "working-directory" "." "success" - test_input_validation "$ACTION_DIR" "custom-command" "echo test" "success" + It "rejects path traversal" + When call validate_input_python "eslint-fix" "working-directory" "../etc" + The status should be failure + End - # Mock file processing - echo "files_changed=3" >> "$GITHUB_OUTPUT" - echo "status=changes_made" >> "$GITHUB_OUTPUT" - - When call validate_action_output "status" "changes_made" + It "validates boolean flags" + When call validate_input_python "eslint-fix" "fix-only" "true" The status should be success End End @@ -311,25 +306,22 @@ End ### Build Actions (docker-build, go-build, etc.) -Focus on build processes and artifact generation: +Focus on build configuration: ```bash -Context "build process" - BeforeEach "setup_test_env 'build-test'" - AfterEach "cleanup_test_env 'build-test'" +Context "when building" + It "validates image name" + When call validate_input_python "docker-build" "image-name" "myapp" + The status should be success + End - It "validates build inputs" - # Use test_input_validation helper for Docker inputs - test_input_validation "$ACTION_DIR" "image-name" "myapp:latest" "success" - test_input_validation "$ACTION_DIR" "tag" "1.0.0" "success" - test_input_validation "$ACTION_DIR" "platforms" "linux/amd64,linux/arm64" "success" - test_input_validation "$ACTION_DIR" "parallel-builds" "8" "success" + It "validates tag format" + When call validate_input_python "docker-build" "tag" "v1.0.0" + The status should be success + End - # Mock successful build - echo "build-status=success" >> "$GITHUB_OUTPUT" - echo "build-time=45" >> "$GITHUB_OUTPUT" - - When call validate_action_output "build-status" "success" + It "validates platforms" + When call validate_input_python "docker-build" "platforms" "linux/amd64,linux/arm64" The status should be success End End @@ -337,25 +329,22 @@ End ### Publishing Actions (npm-publish, docker-publish, etc.) -Focus on registry interactions using mocks: +Focus on credentials and registry validation: ```bash -Context "publishing" - BeforeEach "setup_mock_environment" - AfterEach "cleanup_mock_environment" +Context "when publishing" + It "validates token format" + When call validate_input_python "npm-publish" "npm-token" "ghp_123456789012345678901234567890123456" + The status should be success + End - It "validates publishing inputs" - # Use test_input_validation helper for version, security, and enum validations - test_input_validation "$ACTION_DIR" "package-version" "1.0.0" "success" - test_input_validation "$ACTION_DIR" "registry-token" "ghp_test123" "success" - test_input_validation "$ACTION_DIR" "registry" "npm" "success" - test_input_validation "$ACTION_DIR" "registry" "github" "success" + It "rejects invalid token" + When call validate_input_python "npm-publish" "npm-token" "invalid-token" + The status should be failure + End - # Mock successful publish - echo "publish-status=success" >> "$GITHUB_OUTPUT" - echo "registry-url=https://registry.npmjs.org/" >> "$GITHUB_OUTPUT" - - When call validate_action_output "publish-status" "success" + It "validates version" + When call validate_input_python "npm-publish" "package-version" "1.0.0" The status should be success End End @@ -409,33 +398,33 @@ make test-action ACTION=name # Test specific action mkdir -p _tests/unit/new-action ``` -2. **Write Comprehensive Unit Tests** +2. **Write Unit Tests** ```bash - # Copy template and customize - cp _tests/unit/version-file-parser/validation.spec.sh \ - _tests/unit/new-action/validation.spec.sh + # _tests/unit/new-action/validation.spec.sh + #!/usr/bin/env shellspec + + Describe "new-action validation" + ACTION_DIR="new-action" + ACTION_FILE="$ACTION_DIR/action.yml" + + Context "when validating inputs" + It "validates required input" + When call validate_input_python "new-action" "required-input" "value" + The status should be success + End + End + End ``` -3. **Use Validation Helpers** +3. **Create Integration Test** ```bash - # Focus on using helpers for comprehensive coverage - test_boolean_input "verbose" - # Use test_input_validation helper for numeric, security, and other validations - test_input_validation "$ACTION_DIR" "timeout" "3600" "success" - test_input_validation "$ACTION_DIR" "command" "echo test" "success" - test_standard_action_structure "$ACTION_FILE" "New Action" + # _tests/integration/workflows/new-action-test.yml + # (See integration test example above) ``` -4. **Create Integration Test** - - ```bash - cp _tests/integration/workflows/version-file-parser-test.yml \ - _tests/integration/workflows/new-action-test.yml - ``` - -5. **Test Your Tests** +4. **Test Your Tests** ```bash make test-action ACTION=new-action @@ -443,7 +432,7 @@ make test-action ACTION=name # Test specific action ### Pull Request Checklist -- [ ] Tests use validation helpers for common patterns +- [ ] Tests use `validate_input_python` for input validation - [ ] All test types pass locally (`make test`) - [ ] Integration test workflow created - [ ] Security testing included for user inputs @@ -453,24 +442,21 @@ make test-action ACTION=name # Test specific action ## 💡 Best Practices -### 1. Use Validation Helpers +### 1. Use validate_input_python for All Input Testing ✅ **Good**: ```bash -test_boolean_input "verbose" -# Use test_input_validation helper for other validations -test_input_validation "$ACTION_DIR" "timeout" "3600" "success" -test_input_validation "$ACTION_DIR" "format" "json" "success" +When call validate_input_python "my-action" "verbose" "true" +The status should be success ``` ❌ **Avoid**: ```bash -# Don't write manual tests for boolean inputs when test_boolean_input exists -When call test_input_validation "$ACTION_DIR" "verbose" "true" "success" -When call test_input_validation "$ACTION_DIR" "verbose" "false" "success" -# Use test_boolean_input "verbose" instead +# Don't manually test validation - use the Python validator +export INPUT_VERBOSE="true" +python3 validate-inputs/validator.py ``` ### 2. Group Related Validations @@ -478,26 +464,33 @@ When call test_input_validation "$ACTION_DIR" "verbose" "false" "success" ✅ **Good**: ```bash -Context "complete input validation" - It "validates all input types" - test_boolean_input "verbose" - # Use test_input_validation helper for other validations - test_input_validation "$ACTION_DIR" "timeout" "3600" "success" - test_input_validation "$ACTION_DIR" "format" "json" "success" - test_input_validation "$ACTION_DIR" "command" "echo test" "success" +Context "when validating configuration" + It "accepts valid boolean" + When call validate_input_python "my-action" "dry-run" "true" + The status should be success + End + + It "accepts valid version" + When call validate_input_python "my-action" "tool-version" "1.0.0" + The status should be success End End ``` -### 3. Include Security Testing +### 3. Always Include Security Testing ✅ **Always include**: ```bash -# Use test_input_validation helper for security and path validations -test_input_validation "$ACTION_DIR" "command" "echo test" "success" -test_input_validation "$ACTION_DIR" "user-script" "#!/bin/bash" "success" -test_input_validation "$ACTION_DIR" "working-directory" "." "success" +It "rejects command injection" + When call validate_input_python "common-retry" "command" "rm -rf /; " + The status should be failure +End + +It "rejects path traversal" + When call validate_input_python "pre-commit" "config-file" "../etc/passwd" + The status should be failure +End ``` ### 4. Write Descriptive Test Names @@ -528,46 +521,34 @@ It "works correctly" ### Test Environment Setup -```bash -# Setup test environment -setup_test_env "test-name" - -# Create mock repositories -create_mock_repo "node" # Node.js project -create_mock_repo "php" # PHP project -create_mock_repo "python" # Python project -create_mock_repo "go" # Go project -create_mock_repo "dotnet" # .NET project - -# Cleanup -cleanup_test_env "test-name" -``` - -### Mock Services - -Built-in mocks for external services: - -- **GitHub API** - Repository, releases, packages, workflows -- **NPM Registry** - Package publishing and retrieval -- **Docker Registry** - Image push/pull operations -- **Container Registries** - GitHub Container Registry, Docker Hub - -### Available Environment Variables +The framework automatically sets up test environments via `spec_helper.sh`: ```bash -# Test environment paths -$TEST_WORKSPACE # Current test workspace -$GITHUB_OUTPUT # Mock GitHub outputs file -$GITHUB_ENV # Mock GitHub environment file -$GITHUB_STEP_SUMMARY # Mock step summary file +# Automatic setup on load +- GitHub Actions environment variables +- Temporary directories +- Mock GITHUB_OUTPUT files +- Default required inputs for actions -# Test framework paths -$TEST_ROOT # _tests/ directory -$FRAMEWORK_DIR # _tests/framework/ directory -$FIXTURES_DIR # _tests/framework/fixtures/ -$MOCKS_DIR # _tests/framework/mocks/ +# Available variables +$PROJECT_ROOT # Repository root +$TEST_ROOT # _tests/ directory +$FRAMEWORK_DIR # _tests/framework/ +$FIXTURES_DIR # _tests/framework/fixtures/ +$TEMP_DIR # Temporary test directory +$GITHUB_OUTPUT # Mock outputs file +$GITHUB_ENV # Mock environment file ``` +### Python Validation Integration + +All input validation uses the centralized Python validation system from `validate-inputs/`: + +- Convention-based automatic validation +- 9 specialized validators (Boolean, Version, Token, Numeric, File, Network, Docker, Security, CodeQL) +- Custom validator support per action +- Injection and security pattern detection + ## 🚨 Troubleshooting ### Common Issues @@ -618,58 +599,67 @@ find _tests/ -name "*.sh" -exec chmod +x {} \; shellspec _tests/unit/my-action/validation.spec.sh ``` -3. **Check Test Output** +3. **Enable Debug Mode** + + ```bash + export SHELLSPEC_DEBUG=1 + shellspec _tests/unit/my-action/validation.spec.sh + ``` + +4. **Check Test Output** ```bash # Test results stored in _tests/reports/ cat _tests/reports/unit/my-action.txt ``` -4. **Debug Mock Environment** - - ```bash - # Enable mock debugging - export MOCK_DEBUG=true - ``` - ## 📚 Resources - [ShellSpec Documentation](https://shellspec.info/) - [nektos/act Documentation](https://nektosact.com/) - [GitHub Actions Documentation](https://docs.github.com/en/actions) - [Testing GitHub Actions Best Practices](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action#testing-your-action) - ---- +- [validate-inputs Documentation](../validate-inputs/docs/README_ARCHITECTURE.md) ## Framework Development -### Adding New Framework Features +### Framework File Structure -1. **New Test Utilities** +```text +_tests/ +├── unit/ +│ └── spec_helper.sh # ShellSpec configuration and helpers +├── framework/ +│ ├── setup.sh # Test environment initialization +│ ├── utils.sh # Common utility functions +│ ├── validation.py # Python validation helpers +│ └── fixtures/ # Test fixtures +└── integration/ + ├── workflows/ # Integration test workflows + ├── external-usage/ # External reference tests + └── action-chains/ # Multi-action tests +``` - ```bash - # Add to _tests/framework/utils.sh - your_new_function() { - local param="$1" - # Implementation - } +### Available Functions - # Export for availability - export -f your_new_function - ``` +**From spec_helper.sh (\_tests/unit/spec_helper.sh):** -2. **New Mock Services** +- `validate_input_python(action, input_name, value)` - Main validation function +- `setup_default_inputs(action, input_name)` - Set default required inputs +- `cleanup_default_inputs(action, input_name)` - Clean up default inputs +- `shellspec_setup_test_env(name)` - Setup test environment +- `shellspec_cleanup_test_env(name)` - Cleanup test environment +- `shellspec_mock_action_run(action_dir, ...)` - Mock action execution +- `shellspec_validate_action_output(key, value)` - Validate outputs - ```bash - # Create _tests/framework/mocks/new-service.sh - # Follow existing patterns in github-api.sh - ``` +**From utils.sh (\_tests/framework/utils.sh):** -3. **New Validation Helpers** +- `validate_action_yml(file)` - Validate action YAML +- `get_action_inputs(file)` - Extract action inputs +- `get_action_outputs(file)` - Extract action outputs +- `get_action_name(file)` - Get action name +- `test_input_validation(dir, name, value, expected)` - Test input +- `test_action_outputs(dir)` - Test action outputs +- `test_external_usage(dir)` - Test external usage - ```bash - # Add to _tests/framework/validation_helpers.sh - # Update this documentation - ``` - -**Last Updated:** August 17, 2025 +**Last Updated:** October 15, 2025 diff --git a/_tests/integration/workflows/common-cache-test.yml b/_tests/integration/workflows/common-cache-test.yml new file mode 100644 index 0000000..750c482 --- /dev/null +++ b/_tests/integration/workflows/common-cache-test.yml @@ -0,0 +1,471 @@ +--- +name: Integration Test - Common Cache +on: + workflow_dispatch: + push: + paths: + - 'common-cache/**' + - '_tests/integration/workflows/common-cache-test.yml' + +jobs: + test-common-cache-key-generation: + name: Test Cache Key Generation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test basic key generation + run: | + RUNNER_OS="Linux" + CACHE_TYPE="npm" + KEY_PREFIX="" + + cache_key="$RUNNER_OS" + [ -n "$CACHE_TYPE" ] && cache_key="${cache_key}-${CACHE_TYPE}" + + expected="Linux-npm" + if [[ "$cache_key" != "$expected" ]]; then + echo "❌ ERROR: Expected '$expected', got '$cache_key'" + exit 1 + fi + echo "✓ Basic cache key generation works" + + - name: Test key with prefix + run: | + RUNNER_OS="Linux" + CACHE_TYPE="npm" + KEY_PREFIX="node-20" + + cache_key="$RUNNER_OS" + [ -n "$KEY_PREFIX" ] && cache_key="${cache_key}-${KEY_PREFIX}" + [ -n "$CACHE_TYPE" ] && cache_key="${cache_key}-${CACHE_TYPE}" + + expected="Linux-node-20-npm" + if [[ "$cache_key" != "$expected" ]]; then + echo "❌ ERROR: Expected '$expected', got '$cache_key'" + exit 1 + fi + echo "✓ Cache key with prefix works" + + - name: Test OS-specific keys + run: | + for os in "Linux" "macOS" "Windows"; do + CACHE_TYPE="test" + cache_key="$os-$CACHE_TYPE" + if [[ ! "$cache_key" =~ ^(Linux|macOS|Windows)-test$ ]]; then + echo "❌ ERROR: Invalid key for OS $os: $cache_key" + exit 1 + fi + echo "✓ OS-specific key for $os: $cache_key" + done + + test-common-cache-file-hashing: + name: Test File Hashing + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create test files + run: | + mkdir -p test-cache + cd test-cache + echo "content1" > file1.txt + echo "content2" > file2.txt + echo "content3" > file3.txt + + - name: Test single file hash + run: | + cd test-cache + file_hash=$(cat file1.txt | sha256sum | cut -d' ' -f1) + + if [[ ! "$file_hash" =~ ^[a-f0-9]{64}$ ]]; then + echo "❌ ERROR: Invalid hash format: $file_hash" + exit 1 + fi + echo "✓ Single file hash: $file_hash" + + - name: Test multiple file hash + run: | + cd test-cache + multi_hash=$(cat file1.txt file2.txt file3.txt | sha256sum | cut -d' ' -f1) + + if [[ ! "$multi_hash" =~ ^[a-f0-9]{64}$ ]]; then + echo "❌ ERROR: Invalid hash format: $multi_hash" + exit 1 + fi + echo "✓ Multiple file hash: $multi_hash" + + - name: Test hash changes with content + run: | + cd test-cache + + # Get initial hash + hash1=$(cat file1.txt | sha256sum | cut -d' ' -f1) + + # Modify file + echo "modified" > file1.txt + + # Get new hash + hash2=$(cat file1.txt | sha256sum | cut -d' ' -f1) + + if [[ "$hash1" == "$hash2" ]]; then + echo "❌ ERROR: Hash should change when content changes" + exit 1 + fi + echo "✓ Hash changes with content modification" + + - name: Test comma-separated file list processing + run: | + cd test-cache + + KEY_FILES="file1.txt,file2.txt,file3.txt" + IFS=',' read -ra FILES <<< "$KEY_FILES" + + existing_files=() + for file in "${FILES[@]}"; do + file=$(echo "$file" | xargs) + if [ -f "$file" ]; then + existing_files+=("$file") + fi + done + + if [ ${#existing_files[@]} -ne 3 ]; then + echo "❌ ERROR: Should find 3 files, found ${#existing_files[@]}" + exit 1 + fi + + echo "✓ Comma-separated file list processing works" + + - name: Test missing file handling + run: | + cd test-cache + + KEY_FILES="file1.txt,missing.txt,file2.txt" + IFS=',' read -ra FILES <<< "$KEY_FILES" + + existing_files=() + for file in "${FILES[@]}"; do + file=$(echo "$file" | xargs) + if [ -f "$file" ]; then + existing_files+=("$file") + fi + done + + if [ ${#existing_files[@]} -ne 2 ]; then + echo "❌ ERROR: Should find 2 files, found ${#existing_files[@]}" + exit 1 + fi + + echo "✓ Missing files correctly skipped" + + test-common-cache-env-vars: + name: Test Environment Variables + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test single env var inclusion + run: | + export NODE_VERSION="20.9.0" + ENV_VARS="NODE_VERSION" + + IFS=',' read -ra VARS <<< "$ENV_VARS" + env_hash="" + for var in "${VARS[@]}"; do + if [ -n "${!var}" ]; then + env_hash="${env_hash}-${var}-${!var}" + fi + done + + expected="-NODE_VERSION-20.9.0" + if [[ "$env_hash" != "$expected" ]]; then + echo "❌ ERROR: Expected '$expected', got '$env_hash'" + exit 1 + fi + echo "✓ Single env var inclusion works" + + - name: Test multiple env vars + run: | + export NODE_VERSION="20.9.0" + export PACKAGE_MANAGER="npm" + ENV_VARS="NODE_VERSION,PACKAGE_MANAGER" + + IFS=',' read -ra VARS <<< "$ENV_VARS" + env_hash="" + for var in "${VARS[@]}"; do + if [ -n "${!var}" ]; then + env_hash="${env_hash}-${var}-${!var}" + fi + done + + expected="-NODE_VERSION-20.9.0-PACKAGE_MANAGER-npm" + if [[ "$env_hash" != "$expected" ]]; then + echo "❌ ERROR: Expected '$expected', got '$env_hash'" + exit 1 + fi + echo "✓ Multiple env vars inclusion works" + + - name: Test undefined env var skipping + run: | + export NODE_VERSION="20.9.0" + ENV_VARS="NODE_VERSION,UNDEFINED_VAR" + + IFS=',' read -ra VARS <<< "$ENV_VARS" + env_hash="" + for var in "${VARS[@]}"; do + if [ -n "${!var}" ]; then + env_hash="${env_hash}-${var}-${!var}" + fi + done + + # Should only include NODE_VERSION + expected="-NODE_VERSION-20.9.0" + if [[ "$env_hash" != "$expected" ]]; then + echo "❌ ERROR: Expected '$expected', got '$env_hash'" + exit 1 + fi + echo "✓ Undefined env vars correctly skipped" + + test-common-cache-path-processing: + name: Test Path Processing + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test single path + run: | + CACHE_PATHS="~/.npm" + IFS=',' read -ra PATHS <<< "$CACHE_PATHS" + + if [ ${#PATHS[@]} -ne 1 ]; then + echo "❌ ERROR: Should have 1 path, got ${#PATHS[@]}" + exit 1 + fi + echo "✓ Single path processing works" + + - name: Test multiple paths + run: | + CACHE_PATHS="~/.npm,~/.yarn/cache,node_modules" + IFS=',' read -ra PATHS <<< "$CACHE_PATHS" + + if [ ${#PATHS[@]} -ne 3 ]; then + echo "❌ ERROR: Should have 3 paths, got ${#PATHS[@]}" + exit 1 + fi + echo "✓ Multiple paths processing works" + + - name: Test path with spaces (trimming) + run: | + CACHE_PATHS=" ~/.npm , ~/.yarn/cache , node_modules " + IFS=',' read -ra PATHS <<< "$CACHE_PATHS" + + trimmed_paths=() + for path in "${PATHS[@]}"; do + trimmed=$(echo "$path" | xargs) + trimmed_paths+=("$trimmed") + done + + # Check first path is trimmed + if [[ "${trimmed_paths[0]}" != "~/.npm" ]]; then + echo "❌ ERROR: Path not trimmed: '${trimmed_paths[0]}'" + exit 1 + fi + echo "✓ Path trimming works" + + test-common-cache-complete-key-generation: + name: Test Complete Key Generation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create test files + run: | + mkdir -p test-complete + cd test-complete + echo "package-lock content" > package-lock.json + + - name: Test complete cache key with all components + run: | + cd test-complete + + RUNNER_OS="Linux" + CACHE_TYPE="npm" + KEY_PREFIX="node-20" + + # Generate file hash + files_hash=$(cat package-lock.json | sha256sum | cut -d' ' -f1) + + # Generate env hash + export NODE_VERSION="20.9.0" + env_hash="-NODE_VERSION-20.9.0" + + # Generate final key + cache_key="$RUNNER_OS" + [ -n "$KEY_PREFIX" ] && cache_key="${cache_key}-${KEY_PREFIX}" + [ -n "$CACHE_TYPE" ] && cache_key="${cache_key}-${CACHE_TYPE}" + [ -n "$files_hash" ] && cache_key="${cache_key}-${files_hash}" + [ -n "$env_hash" ] && cache_key="${cache_key}${env_hash}" + + echo "Generated cache key: $cache_key" + + # Verify structure + if [[ ! "$cache_key" =~ ^Linux-node-20-npm-[a-f0-9]{64}-NODE_VERSION-20\.9\.0$ ]]; then + echo "❌ ERROR: Invalid cache key structure: $cache_key" + exit 1 + fi + echo "✓ Complete cache key generation works" + + test-common-cache-restore-keys: + name: Test Restore Keys + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test single restore key + run: | + RESTORE_KEYS="Linux-npm-" + + if [[ -z "$RESTORE_KEYS" ]]; then + echo "❌ ERROR: Restore keys should not be empty" + exit 1 + fi + echo "✓ Single restore key: $RESTORE_KEYS" + + - name: Test multiple restore keys + run: | + RESTORE_KEYS="Linux-node-20-npm-,Linux-node-npm-,Linux-npm-" + + IFS=',' read -ra KEYS <<< "$RESTORE_KEYS" + if [ ${#KEYS[@]} -ne 3 ]; then + echo "❌ ERROR: Should have 3 restore keys, got ${#KEYS[@]}" + exit 1 + fi + echo "✓ Multiple restore keys work" + + test-common-cache-type-specific-scenarios: + name: Test Type-Specific Scenarios + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test NPM cache key + run: | + TYPE="npm" + FILES="package-lock.json" + PATHS="~/.npm,node_modules" + + echo "✓ NPM cache configuration valid" + echo " Type: $TYPE" + echo " Key files: $FILES" + echo " Paths: $PATHS" + + - name: Test Composer cache key + run: | + TYPE="composer" + FILES="composer.lock" + PATHS="~/.composer/cache,vendor" + + echo "✓ Composer cache configuration valid" + echo " Type: $TYPE" + echo " Key files: $FILES" + echo " Paths: $PATHS" + + - name: Test Go cache key + run: | + TYPE="go" + FILES="go.sum" + PATHS="~/go/pkg/mod,~/.cache/go-build" + + echo "✓ Go cache configuration valid" + echo " Type: $TYPE" + echo " Key files: $FILES" + echo " Paths: $PATHS" + + - name: Test Pip cache key + run: | + TYPE="pip" + FILES="requirements.txt" + PATHS="~/.cache/pip" + + echo "✓ Pip cache configuration valid" + echo " Type: $TYPE" + echo " Key files: $FILES" + echo " Paths: $PATHS" + + test-common-cache-edge-cases: + name: Test Edge Cases + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test empty prefix + run: | + KEY_PREFIX="" + cache_key="Linux" + [ -n "$KEY_PREFIX" ] && cache_key="${cache_key}-${KEY_PREFIX}" + + if [[ "$cache_key" != "Linux" ]]; then + echo "❌ ERROR: Empty prefix should not modify key" + exit 1 + fi + echo "✓ Empty prefix handling works" + + - name: Test no key files + run: | + KEY_FILES="" + files_hash="" + + if [ -n "$KEY_FILES" ]; then + echo "❌ ERROR: Should detect empty key files" + exit 1 + fi + echo "✓ No key files handling works" + + - name: Test no env vars + run: | + ENV_VARS="" + env_hash="" + + if [ -n "$ENV_VARS" ]; then + echo "❌ ERROR: Should detect empty env vars" + exit 1 + fi + echo "✓ No env vars handling works" + + integration-test-summary: + name: Integration Test Summary + runs-on: ubuntu-latest + needs: + - test-common-cache-key-generation + - test-common-cache-file-hashing + - test-common-cache-env-vars + - test-common-cache-path-processing + - test-common-cache-complete-key-generation + - test-common-cache-restore-keys + - test-common-cache-type-specific-scenarios + - test-common-cache-edge-cases + steps: + - name: Summary + run: | + echo "==========================================" + echo "Common Cache Integration Tests - PASSED" + echo "==========================================" + echo "" + echo "✓ Cache key generation tests" + echo "✓ File hashing tests" + echo "✓ Environment variable tests" + echo "✓ Path processing tests" + echo "✓ Complete key generation tests" + echo "✓ Restore keys tests" + echo "✓ Type-specific scenario tests" + echo "✓ Edge case tests" + echo "" + echo "All common-cache integration tests completed successfully!" diff --git a/_tests/integration/workflows/docker-build-publish-test.yml b/_tests/integration/workflows/docker-build-publish-test.yml new file mode 100644 index 0000000..6777b59 --- /dev/null +++ b/_tests/integration/workflows/docker-build-publish-test.yml @@ -0,0 +1,186 @@ +--- +name: Test Docker Build & Publish Integration +on: + workflow_dispatch: + push: + paths: + - 'docker-build/**' + - 'docker-publish/**' + - 'docker-publish-gh/**' + - 'docker-publish-hub/**' + - '_tests/integration/workflows/docker-build-publish-test.yml' + +jobs: + test-docker-build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create test Dockerfile + run: | + cat > Dockerfile < test.sh < Dockerfile < Dockerfile </dev/null 2>&1; then + echo "✓ gh CLI is available in this environment" + gh --version + else + echo "⚠️ gh CLI not available in test environment (would fail in actual action)" + echo "✓ Tool detection logic works correctly" + fi + + - name: Test jq detection logic + run: | + # Test the logic for checking jq availability + # In actual action, this would fail if jq is not available + if command -v jq >/dev/null 2>&1; then + echo "✓ jq is available in this environment" + jq --version + else + echo "⚠️ jq not available in test environment (would fail in actual action)" + echo "✓ Tool detection logic works correctly" + fi + + - name: Test tool requirement validation + run: | + # Verify the action correctly checks for required tools + REQUIRED_TOOLS=("gh" "jq") + echo "Testing tool requirement checks..." + + for tool in "${REQUIRED_TOOLS[@]}"; do + if command -v "$tool" >/dev/null 2>&1; then + echo " ✓ $tool: available" + else + echo " ⚠️ $tool: not available (action would fail at this check)" + fi + done + + echo "✓ Tool requirement validation logic verified" + + - name: Test gh authentication logic + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Test authentication detection logic + if [[ -n "$GITHUB_TOKEN" ]]; then + echo "✓ GITHUB_TOKEN environment variable is set" + else + echo "⚠️ GITHUB_TOKEN not set in test environment" + fi + + # Test gh auth status check (without requiring it to pass) + if command -v gh >/dev/null 2>&1; then + if gh auth status >/dev/null 2>&1; then + echo "✓ gh authentication successful" + else + echo "⚠️ gh auth check failed (expected without proper setup)" + fi + else + echo "⚠️ gh not available, skipping auth check" + fi + + echo "✓ Authentication detection logic verified" + + test-github-release-changelog-validation: + name: Test Changelog Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test empty changelog (should trigger autogenerated notes) + run: | + echo "Testing empty changelog handling..." + CHANGELOG="" + if [[ -n "$CHANGELOG" ]]; then + echo "❌ ERROR: Empty string should be empty" + exit 1 + fi + echo "✓ Empty changelog correctly detected" + + - name: Test normal changelog + run: | + echo "Testing normal changelog..." + CHANGELOG="## Features + - Added new feature + - Improved performance" + + if [[ -z "$CHANGELOG" ]]; then + echo "❌ ERROR: Changelog should not be empty" + exit 1 + fi + + if [[ ${#CHANGELOG} -gt 10000 ]]; then + echo "⚠️ Changelog is very long" + fi + + echo "✓ Normal changelog processed correctly" + + - name: Test very long changelog warning + run: | + echo "Testing very long changelog..." + # Create a changelog with >10000 characters + CHANGELOG=$(printf 'A%.0s' {1..10001}) + + if [[ ${#CHANGELOG} -le 10000 ]]; then + echo "❌ ERROR: Test changelog should be >10000 chars" + exit 1 + fi + + echo "✓ Long changelog warning would trigger (${#CHANGELOG} characters)" + + test-github-release-changelog-types: + name: Test Changelog Content Types + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test multiline changelog + run: | + echo "Testing multiline changelog..." + CHANGELOG="## Version 1.2.3 + + ### Features + - Feature A + - Feature B + + ### Bug Fixes + - Fix #123 + - Fix #456" + + echo "✓ Multiline changelog supported" + + - name: Test changelog with special characters + run: | + echo "Testing changelog with special characters..." + CHANGELOG='## Release Notes + + Special chars: $, `, \, ", '\'', !, @, #, %, &, *, (, )' + + echo "✓ Special characters in changelog supported" + + - name: Test changelog with markdown + run: | + echo "Testing changelog with markdown..." + CHANGELOG="## Changes + + **Bold** and *italic* text + + - [x] Task completed + - [ ] Task pending + + \`\`\`bash + echo 'code block' + \`\`\` + + | Table | Header | + |-------|--------| + | Cell | Data |" + + echo "✓ Markdown in changelog supported" + + test-github-release-output-format: + name: Test Output Format + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Verify output structure (mock test) + run: | + echo "Testing output structure..." + + # Check if jq is available for this test + if ! command -v jq >/dev/null 2>&1; then + echo "⚠️ jq not available, skipping JSON parsing test" + echo "✓ Output format validation logic verified (jq would be required in actual action)" + exit 0 + fi + + # Mock release info JSON (similar to gh release view output) + RELEASE_INFO='{ + "url": "https://github.com/owner/repo/releases/tag/v1.0.0", + "id": "RE_12345", + "uploadUrl": "https://uploads.github.com/repos/owner/repo/releases/12345/assets{?name,label}" + }' + + # Extract outputs + release_url=$(echo "$RELEASE_INFO" | jq -r '.url') + release_id=$(echo "$RELEASE_INFO" | jq -r '.id') + upload_url=$(echo "$RELEASE_INFO" | jq -r '.uploadUrl') + + echo "Release URL: $release_url" + echo "Release ID: $release_id" + echo "Upload URL: $upload_url" + + # Verify output format + if [[ ! "$release_url" =~ ^https://github\.com/.*/releases/tag/.* ]]; then + echo "❌ ERROR: Invalid release URL format" + exit 1 + fi + + if [[ ! "$release_id" =~ ^RE_.* ]]; then + echo "❌ ERROR: Invalid release ID format" + exit 1 + fi + + if [[ ! "$upload_url" =~ ^https://uploads\.github\.com/.* ]]; then + echo "❌ ERROR: Invalid upload URL format" + exit 1 + fi + + echo "✓ Output format validation passed" + + test-github-release-integration-scenarios: + name: Test Integration Scenarios + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test release workflow without actual creation + run: | + echo "Simulating release workflow..." + + # Validate version + VERSION="v1.2.3-test" + if ! [[ "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then + echo "❌ ERROR: Version validation failed" + exit 1 + fi + echo "✓ Version validation passed" + + # Check tool availability (non-fatal for test environment) + gh_available=false + jq_available=false + + if command -v gh >/dev/null 2>&1; then + echo "✓ gh CLI is available" + gh_available=true + else + echo "⚠️ gh not available (would fail in actual action)" + fi + + if command -v jq >/dev/null 2>&1; then + echo "✓ jq is available" + jq_available=true + else + echo "⚠️ jq not available (would fail in actual action)" + fi + + # Create mock changelog + CHANGELOG="Test release notes" + NOTES_FILE="$(mktemp)" + printf '%s' "$CHANGELOG" > "$NOTES_FILE" + + # Verify changelog file + if [[ ! -f "$NOTES_FILE" ]]; then + echo "❌ ERROR: Changelog file not created" + exit 1 + fi + + CONTENT=$(cat "$NOTES_FILE") + if [[ "$CONTENT" != "$CHANGELOG" ]]; then + echo "❌ ERROR: Changelog content mismatch" + exit 1 + fi + + rm -f "$NOTES_FILE" + + echo "✓ Release workflow simulation passed" + + - name: Test autogenerated changelog scenario + run: | + echo "Testing autogenerated changelog scenario..." + + VERSION="v2.0.0" + CHANGELOG="" + + if [[ -z "$CHANGELOG" ]]; then + echo "✓ Would use --generate-notes flag" + else + echo "✓ Would use custom changelog" + fi + + - name: Test custom changelog scenario + run: | + echo "Testing custom changelog scenario..." + + VERSION="v2.1.0" + CHANGELOG="## Custom Release Notes + + This release includes: + - Feature X + - Bug fix Y" + + if [[ -n "$CHANGELOG" ]]; then + echo "✓ Would use --notes-file with custom changelog" + else + echo "✓ Would use --generate-notes" + fi + + integration-test-summary: + name: Integration Test Summary + runs-on: ubuntu-latest + needs: + - test-github-release-validation + - test-github-release-version-formats + - test-github-release-tool-availability + - test-github-release-changelog-validation + - test-github-release-changelog-types + - test-github-release-output-format + - test-github-release-integration-scenarios + steps: + - name: Summary + run: | + echo "==========================================" + echo "GitHub Release Integration Tests - PASSED" + echo "==========================================" + echo "" + echo "✓ Input validation tests" + echo "✓ Version format tests" + echo "✓ Tool availability tests" + echo "✓ Changelog validation tests" + echo "✓ Changelog content tests" + echo "✓ Output format tests" + echo "✓ Integration scenario tests" + echo "" + echo "All github-release integration tests completed successfully!" diff --git a/_tests/integration/workflows/lint-fix-chain-test.yml b/_tests/integration/workflows/lint-fix-chain-test.yml new file mode 100644 index 0000000..d6a66b5 --- /dev/null +++ b/_tests/integration/workflows/lint-fix-chain-test.yml @@ -0,0 +1,316 @@ +--- +name: Test Lint & Fix Action Chains +on: + workflow_dispatch: + push: + paths: + - 'eslint-check/**' + - 'eslint-fix/**' + - 'prettier-check/**' + - 'prettier-fix/**' + - 'node-setup/**' + - 'common-cache/**' + - '_tests/integration/workflows/lint-fix-chain-test.yml' + +jobs: + test-eslint-chain: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create test JavaScript files + run: | + mkdir -p test-project/src + + # Create package.json + cat > test-project/package.json < test-project/.eslintrc.json < test-project/src/test.js < test-prettier/package.json < test-prettier/.prettierrc < test-prettier/test.js < test-prettier/test.json < test-chain/package.json < test-chain/.eslintrc.json < test-chain/.prettierrc < test-chain/src/app.js < 30)" + else + echo "❌ ERROR: Should reject Node.js $VERSION" + exit 1 + fi + + - name: Test valid version formats + run: | + for version in "20" "20.9" "20.9.0" "18" "22.1.0"; do + if [[ "$version" =~ ^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$ ]]; then + major=$(echo "$version" | cut -d'.' -f1) + if [ "$major" -ge 14 ] && [ "$major" -le 30 ]; then + echo "✓ Version $version accepted" + else + echo "❌ ERROR: Version $version should be accepted" + exit 1 + fi + else + echo "❌ ERROR: Version $version format validation failed" + exit 1 + fi + done + + test-node-setup-package-manager-validation: + name: Test Package Manager Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test valid package managers + run: | + for pm in "npm" "yarn" "pnpm" "bun" "auto"; do + case "$pm" in + "npm"|"yarn"|"pnpm"|"bun"|"auto") + echo "✓ Package manager $pm accepted" + ;; + *) + echo "❌ ERROR: Valid package manager $pm rejected" + exit 1 + ;; + esac + done + + - name: Test invalid package manager + run: | + PM="invalid-pm" + case "$PM" in + "npm"|"yarn"|"pnpm"|"bun"|"auto") + echo "❌ ERROR: Invalid package manager should be rejected" + exit 1 + ;; + *) + echo "✓ Invalid package manager correctly rejected" + ;; + esac + + test-node-setup-url-validation: + name: Test URL Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test valid registry URLs + run: | + for url in "https://registry.npmjs.org" "http://localhost:4873" "https://npm.custom.com/"; do + if [[ "$url" == "https://"* ]] || [[ "$url" == "http://"* ]]; then + echo "✓ Registry URL $url accepted" + else + echo "❌ ERROR: Valid URL $url rejected" + exit 1 + fi + done + + - name: Test invalid registry URLs + run: | + for url in "ftp://registry.com" "not-a-url" "registry.com"; do + if [[ "$url" == "https://"* ]] || [[ "$url" == "http://"* ]]; then + echo "❌ ERROR: Invalid URL $url should be rejected" + exit 1 + else + echo "✓ Invalid URL $url correctly rejected" + fi + done + + test-node-setup-retries-validation: + name: Test Retries Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test valid retry counts + run: | + for retries in "1" "3" "5" "10"; do + if [[ "$retries" =~ ^[0-9]+$ ]] && [ "$retries" -gt 0 ] && [ "$retries" -le 10 ]; then + echo "✓ Max retries $retries accepted" + else + echo "❌ ERROR: Valid retry count $retries rejected" + exit 1 + fi + done + + - name: Test invalid retry counts + run: | + for retries in "0" "11" "abc" "-1"; do + if [[ "$retries" =~ ^[0-9]+$ ]] && [ "$retries" -gt 0 ] && [ "$retries" -le 10 ]; then + echo "❌ ERROR: Invalid retry count $retries should be rejected" + exit 1 + else + echo "✓ Invalid retry count $retries correctly rejected" + fi + done + + test-node-setup-boolean-validation: + name: Test Boolean Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test valid boolean values + run: | + for value in "true" "false"; do + if [[ "$value" == "true" ]] || [[ "$value" == "false" ]]; then + echo "✓ Boolean value $value accepted" + else + echo "❌ ERROR: Valid boolean $value rejected" + exit 1 + fi + done + + - name: Test invalid boolean values + run: | + for value in "yes" "no" "1" "0" "True" "FALSE" ""; do + if [[ "$value" != "true" ]] && [[ "$value" != "false" ]]; then + echo "✓ Invalid boolean value '$value' correctly rejected" + else + echo "❌ ERROR: Invalid boolean $value should be rejected" + exit 1 + fi + done + + test-node-setup-token-validation: + name: Test Auth Token Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test injection pattern detection + run: | + for token in "token;malicious" "token&&command" "token|pipe"; do + if [[ "$token" == *";"* ]] || [[ "$token" == *"&&"* ]] || [[ "$token" == *"|"* ]]; then + echo "✓ Injection pattern in token correctly detected" + else + echo "❌ ERROR: Should detect injection pattern in: $token" + exit 1 + fi + done + + - name: Test valid tokens + run: | + for token in "npm_AbCdEf1234567890" "github_pat_12345abcdef" "simple-token"; do + if [[ "$token" == *";"* ]] || [[ "$token" == *"&&"* ]] || [[ "$token" == *"|"* ]]; then + echo "❌ ERROR: Valid token should not be rejected: $token" + exit 1 + else + echo "✓ Valid token accepted" + fi + done + + test-node-setup-package-manager-resolution: + name: Test Package Manager Resolution + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test auto detection with detected PM + run: | + INPUT_PM="auto" + DETECTED_PM="pnpm" + + if [ "$INPUT_PM" = "auto" ]; then + if [ -n "$DETECTED_PM" ]; then + FINAL_PM="$DETECTED_PM" + else + FINAL_PM="npm" + fi + else + FINAL_PM="$INPUT_PM" + fi + + if [[ "$FINAL_PM" != "pnpm" ]]; then + echo "❌ ERROR: Should use detected PM (pnpm)" + exit 1 + fi + echo "✓ Auto-detected package manager correctly resolved" + + - name: Test auto detection without detected PM + run: | + INPUT_PM="auto" + DETECTED_PM="" + + if [ "$INPUT_PM" = "auto" ]; then + if [ -n "$DETECTED_PM" ]; then + FINAL_PM="$DETECTED_PM" + else + FINAL_PM="npm" + fi + else + FINAL_PM="$INPUT_PM" + fi + + if [[ "$FINAL_PM" != "npm" ]]; then + echo "❌ ERROR: Should default to npm" + exit 1 + fi + echo "✓ Defaults to npm when no detection" + + - name: Test explicit package manager + run: | + INPUT_PM="yarn" + DETECTED_PM="pnpm" + + if [ "$INPUT_PM" = "auto" ]; then + if [ -n "$DETECTED_PM" ]; then + FINAL_PM="$DETECTED_PM" + else + FINAL_PM="npm" + fi + else + FINAL_PM="$INPUT_PM" + fi + + if [[ "$FINAL_PM" != "yarn" ]]; then + echo "❌ ERROR: Should use explicit PM (yarn)" + exit 1 + fi + echo "✓ Explicit package manager correctly used" + + test-node-setup-feature-detection: + name: Test Feature Detection + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create test package.json with ESM + run: | + mkdir -p test-esm + cd test-esm + cat > package.json <<'EOF' + { + "name": "test-esm", + "version": "1.0.0", + "type": "module" + } + EOF + + - name: Test ESM detection + run: | + cd test-esm + if command -v jq >/dev/null 2>&1; then + pkg_type=$(jq -r '.type // "commonjs"' package.json 2>/dev/null) + if [[ "$pkg_type" == "module" ]]; then + echo "✓ ESM support correctly detected" + else + echo "❌ ERROR: Should detect ESM support" + exit 1 + fi + else + echo "⚠️ jq not available, skipping ESM detection test" + echo "✓ ESM detection logic verified (jq would be required in actual action)" + fi + + - name: Create test with TypeScript + run: | + mkdir -p test-ts + cd test-ts + touch tsconfig.json + cat > package.json <<'EOF' + { + "name": "test-ts", + "devDependencies": { + "typescript": "^5.0.0" + } + } + EOF + + - name: Test TypeScript detection + run: | + cd test-ts + typescript_support="false" + if [ -f tsconfig.json ]; then + typescript_support="true" + fi + if [[ "$typescript_support" != "true" ]]; then + echo "❌ ERROR: Should detect TypeScript" + exit 1 + fi + echo "✓ TypeScript support correctly detected" + + - name: Create test with frameworks + run: | + mkdir -p test-frameworks + cd test-frameworks + cat > package.json <<'EOF' + { + "name": "test-frameworks", + "dependencies": { + "react": "^18.0.0", + "next": "^14.0.0" + } + } + EOF + + - name: Test framework detection + run: | + cd test-frameworks + if command -v jq >/dev/null 2>&1; then + has_next=$(jq -e '.dependencies.next or .devDependencies.next' package.json >/dev/null 2>&1 && echo "yes" || echo "no") + has_react=$(jq -e '.dependencies.react or .devDependencies.react' package.json >/dev/null 2>&1 && echo "yes" || echo "no") + + if [[ "$has_next" == "yes" ]] && [[ "$has_react" == "yes" ]]; then + echo "✓ Frameworks (Next.js, React) correctly detected" + else + echo "❌ ERROR: Should detect Next.js and React" + exit 1 + fi + else + echo "⚠️ jq not available, skipping framework detection test" + echo "✓ Framework detection logic verified (jq would be required in actual action)" + fi + + test-node-setup-security: + name: Test Security Measures + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test token sanitization + run: | + TOKEN="test-token + with-newline" + + # Should remove newlines + sanitized=$(echo "$TOKEN" | tr -d '\n\r') + + if [[ "$sanitized" == *$'\n'* ]] || [[ "$sanitized" == *$'\r'* ]]; then + echo "❌ ERROR: Newlines not removed" + exit 1 + fi + echo "✓ Token sanitization works correctly" + + - name: Test package manager sanitization + run: | + PM="npm + with-newline" + + # Should remove newlines + sanitized=$(echo "$PM" | tr -d '\n\r') + + if [[ "$sanitized" == *$'\n'* ]] || [[ "$sanitized" == *$'\r'* ]]; then + echo "❌ ERROR: Newlines not removed from PM" + exit 1 + fi + echo "✓ Package manager sanitization works correctly" + + test-node-setup-integration-workflow: + name: Test Integration Workflow + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Simulate complete workflow + run: | + echo "=== Simulating Node Setup Workflow ===" + + # 1. Validation + echo "Step 1: Validate inputs" + DEFAULT_VERSION="20" + PACKAGE_MANAGER="npm" + REGISTRY_URL="https://registry.npmjs.org" + CACHE="true" + INSTALL="true" + MAX_RETRIES="3" + echo "✓ Inputs validated" + + # 2. Version parsing + echo "Step 2: Parse Node.js version" + NODE_VERSION="20.9.0" + echo "✓ Version parsed: $NODE_VERSION" + + # 3. Package manager resolution + echo "Step 3: Resolve package manager" + if [ "$PACKAGE_MANAGER" = "auto" ]; then + FINAL_PM="npm" + else + FINAL_PM="$PACKAGE_MANAGER" + fi + echo "✓ Package manager resolved: $FINAL_PM" + + # 4. Setup Node.js + echo "Step 4: Setup Node.js $NODE_VERSION" + if command -v node >/dev/null 2>&1; then + echo "✓ Node.js available: $(node --version)" + fi + + # 5. Enable Corepack + echo "Step 5: Enable Corepack" + if command -v corepack >/dev/null 2>&1; then + echo "✓ Corepack available" + else + echo "⚠️ Corepack not available in test environment" + fi + + # 6. Cache dependencies + if [[ "$CACHE" == "true" ]]; then + echo "Step 6: Cache dependencies" + echo "✓ Would use common-cache action" + fi + + # 7. Install dependencies + if [[ "$INSTALL" == "true" ]]; then + echo "Step 7: Install dependencies" + echo "✓ Would run: $FINAL_PM install" + fi + + echo "=== Workflow simulation completed ===" + + integration-test-summary: + name: Integration Test Summary + runs-on: ubuntu-latest + needs: + - test-node-setup-version-validation + - test-node-setup-package-manager-validation + - test-node-setup-url-validation + - test-node-setup-retries-validation + - test-node-setup-boolean-validation + - test-node-setup-token-validation + - test-node-setup-package-manager-resolution + - test-node-setup-feature-detection + - test-node-setup-security + - test-node-setup-integration-workflow + steps: + - name: Summary + run: | + echo "==========================================" + echo "Node Setup Integration Tests - PASSED" + echo "==========================================" + echo "" + echo "✓ Version validation tests" + echo "✓ Package manager validation tests" + echo "✓ URL validation tests" + echo "✓ Retries validation tests" + echo "✓ Boolean validation tests" + echo "✓ Token validation tests" + echo "✓ Package manager resolution tests" + echo "✓ Feature detection tests" + echo "✓ Security measure tests" + echo "✓ Integration workflow tests" + echo "" + echo "All node-setup integration tests completed successfully!" diff --git a/_tests/integration/workflows/npm-publish-test.yml b/_tests/integration/workflows/npm-publish-test.yml new file mode 100644 index 0000000..b846bda --- /dev/null +++ b/_tests/integration/workflows/npm-publish-test.yml @@ -0,0 +1,353 @@ +--- +name: Integration Test - NPM Publish +on: + workflow_dispatch: + push: + paths: + - 'npm-publish/**' + - 'node-setup/**' + - '_tests/integration/workflows/npm-publish-test.yml' + +jobs: + test-npm-publish-validation: + name: Test Input Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create test package.json + run: | + mkdir -p test-package + cd test-package + cat > package.json <<'EOF' + { + "name": "@test/integration-test", + "version": "1.0.0", + "description": "Test package for npm-publish integration", + "main": "index.js" + } + EOF + echo "module.exports = { test: true };" > index.js + + - name: Test valid inputs (should succeed validation) + id: valid-test + uses: ./npm-publish + continue-on-error: true + with: + registry-url: 'https://registry.npmjs.org/' + scope: '@test' + package-version: '1.0.0' + npm_token: 'test-token-12345678' + env: + GITHUB_WORKSPACE: ${{ github.workspace }}/test-package + + - name: Validate success (validation only) + run: | + # This will fail at publish step but validation should pass + echo "✓ Input validation passed for valid inputs" + + - name: Test invalid registry URL + id: invalid-registry + uses: ./npm-publish + continue-on-error: true + with: + registry-url: 'not-a-url' + scope: '@test' + package-version: '1.0.0' + npm_token: 'test-token' + env: + GITHUB_WORKSPACE: ${{ github.workspace }}/test-package + + - name: Verify invalid registry URL failed + run: | + if [[ "${{ steps.invalid-registry.outcome }}" == "success" ]]; then + echo "❌ ERROR: Invalid registry URL should have failed" + exit 1 + fi + echo "✓ Invalid registry URL correctly rejected" + + - name: Test invalid version format + id: invalid-version + uses: ./npm-publish + continue-on-error: true + with: + registry-url: 'https://registry.npmjs.org/' + scope: '@test' + package-version: 'not.a.version' + npm_token: 'test-token' + env: + GITHUB_WORKSPACE: ${{ github.workspace }}/test-package + + - name: Verify invalid version failed + run: | + if [[ "${{ steps.invalid-version.outcome }}" == "success" ]]; then + echo "❌ ERROR: Invalid version should have failed" + exit 1 + fi + echo "✓ Invalid version format correctly rejected" + + - name: Test invalid scope format + id: invalid-scope + uses: ./npm-publish + continue-on-error: true + with: + registry-url: 'https://registry.npmjs.org/' + scope: 'invalid-scope' + package-version: '1.0.0' + npm_token: 'test-token' + env: + GITHUB_WORKSPACE: ${{ github.workspace }}/test-package + + - name: Verify invalid scope failed + run: | + if [[ "${{ steps.invalid-scope.outcome }}" == "success" ]]; then + echo "❌ ERROR: Invalid scope format should have failed" + exit 1 + fi + echo "✓ Invalid scope format correctly rejected" + + - name: Test missing npm token + id: missing-token + uses: ./npm-publish + continue-on-error: true + with: + registry-url: 'https://registry.npmjs.org/' + scope: '@test' + package-version: '1.0.0' + npm_token: '' + env: + GITHUB_WORKSPACE: ${{ github.workspace }}/test-package + + - name: Verify missing token failed + run: | + if [[ "${{ steps.missing-token.outcome }}" == "success" ]]; then + echo "❌ ERROR: Missing token should have failed" + exit 1 + fi + echo "✓ Missing NPM token correctly rejected" + + test-npm-publish-package-validation: + name: Test Package Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test missing package.json + id: missing-package + uses: ./npm-publish + continue-on-error: true + with: + registry-url: 'https://registry.npmjs.org/' + scope: '@test' + package-version: '1.0.0' + npm_token: 'test-token' + + - name: Verify missing package.json failed + run: | + if [[ "${{ steps.missing-package.outcome }}" == "success" ]]; then + echo "❌ ERROR: Missing package.json should have failed" + exit 1 + fi + echo "✓ Missing package.json correctly detected" + + - name: Create test package with version mismatch + run: | + mkdir -p test-mismatch + cd test-mismatch + cat > package.json <<'EOF' + { + "name": "@test/mismatch-test", + "version": "2.0.0", + "description": "Test version mismatch" + } + EOF + + - name: Test version mismatch detection + id: version-mismatch + uses: ./npm-publish + continue-on-error: true + with: + registry-url: 'https://registry.npmjs.org/' + scope: '@test' + package-version: '1.0.0' + npm_token: 'test-token' + env: + GITHUB_WORKSPACE: ${{ github.workspace }}/test-mismatch + + - name: Verify version mismatch failed + run: | + if [[ "${{ steps.version-mismatch.outcome }}" == "success" ]]; then + echo "❌ ERROR: Version mismatch should have been detected" + exit 1 + fi + echo "✓ Version mismatch correctly detected" + + test-npm-publish-version-formats: + name: Test Version Format Support + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test SemVer with v prefix + run: | + mkdir -p test-v-prefix + cd test-v-prefix + cat > package.json <<'EOF' + { + "name": "@test/v-prefix", + "version": "1.2.3", + "description": "Test v prefix" + } + EOF + + # Should accept v1.2.3 and strip to 1.2.3 + echo "Testing v prefix version..." + + - name: Test prerelease versions + run: | + mkdir -p test-prerelease + cd test-prerelease + cat > package.json <<'EOF' + { + "name": "@test/prerelease", + "version": "1.0.0-alpha.1", + "description": "Test prerelease" + } + EOF + + echo "Testing prerelease version format..." + + - name: Test build metadata + run: | + mkdir -p test-build + cd test-build + cat > package.json <<'EOF' + { + "name": "@test/build-meta", + "version": "1.0.0+build.123", + "description": "Test build metadata" + } + EOF + + echo "Testing build metadata format..." + + test-npm-publish-outputs: + name: Test Output Values + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create test package + run: | + mkdir -p test-outputs + cd test-outputs + cat > package.json <<'EOF' + { + "name": "@test/outputs-test", + "version": "1.5.0", + "description": "Test outputs" + } + EOF + + - name: Run npm-publish (validation only) + id: publish-outputs + uses: ./npm-publish + continue-on-error: true + with: + registry-url: 'https://npm.custom.com/' + scope: '@custom-scope' + package-version: '1.5.0' + npm_token: 'test-token-outputs' + env: + GITHUB_WORKSPACE: ${{ github.workspace }}/test-outputs + + - name: Verify outputs + run: | + registry="${{ steps.publish-outputs.outputs.registry-url }}" + scope="${{ steps.publish-outputs.outputs.scope }}" + version="${{ steps.publish-outputs.outputs.package-version }}" + + echo "Registry URL: $registry" + echo "Scope: $scope" + echo "Version: $version" + + # Verify output values match inputs + if [[ "$registry" != "https://npm.custom.com/" ]]; then + echo "❌ ERROR: Registry URL output mismatch" + exit 1 + fi + + if [[ "$scope" != "@custom-scope" ]]; then + echo "❌ ERROR: Scope output mismatch" + exit 1 + fi + + if [[ "$version" != "1.5.0" ]]; then + echo "❌ ERROR: Version output mismatch" + exit 1 + fi + + echo "✓ All outputs match expected values" + + test-npm-publish-secret-masking: + name: Test Secret Masking + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Create test package + run: | + mkdir -p test-secrets + cd test-secrets + cat > package.json <<'EOF' + { + "name": "@test/secrets-test", + "version": "1.0.0" + } + EOF + + - name: Test that token gets masked + id: test-masking + uses: ./npm-publish + continue-on-error: true + with: + registry-url: 'https://registry.npmjs.org/' + scope: '@test' + package-version: '1.0.0' + npm_token: 'super-secret-token-12345' + env: + GITHUB_WORKSPACE: ${{ github.workspace }}/test-secrets + + - name: Verify token is not in logs + run: | + echo "✓ Token should be masked in GitHub Actions logs" + echo "✓ Secret masking test completed" + + integration-test-summary: + name: Integration Test Summary + runs-on: ubuntu-latest + needs: + - test-npm-publish-validation + - test-npm-publish-package-validation + - test-npm-publish-version-formats + - test-npm-publish-outputs + - test-npm-publish-secret-masking + steps: + - name: Summary + run: | + echo "==========================================" + echo "NPM Publish Integration Tests - PASSED" + echo "==========================================" + echo "" + echo "✓ Input validation tests" + echo "✓ Package validation tests" + echo "✓ Version format tests" + echo "✓ Output verification tests" + echo "✓ Secret masking tests" + echo "" + echo "All npm-publish integration tests completed successfully!" diff --git a/_tests/integration/workflows/pre-commit-test.yml b/_tests/integration/workflows/pre-commit-test.yml new file mode 100644 index 0000000..136a7f9 --- /dev/null +++ b/_tests/integration/workflows/pre-commit-test.yml @@ -0,0 +1,435 @@ +--- +name: Integration Test - Pre-commit +on: + workflow_dispatch: + push: + paths: + - 'pre-commit/**' + - 'set-git-config/**' + - 'validate-inputs/**' + - '_tests/integration/workflows/pre-commit-test.yml' + +jobs: + test-pre-commit-validation: + name: Test Input Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test with default inputs (should pass validation) + id: default-inputs + uses: ./pre-commit + continue-on-error: true + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify validation passed + run: | + echo "✓ Default inputs validation completed" + + - name: Test with custom config file + id: custom-config + uses: ./pre-commit + continue-on-error: true + with: + pre-commit-config: '.pre-commit-config.yaml' + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify custom config accepted + run: | + echo "✓ Custom config file accepted" + + - name: Test with base branch + id: with-base-branch + uses: ./pre-commit + continue-on-error: true + with: + base-branch: 'main' + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Verify base branch accepted + run: | + echo "✓ Base branch parameter accepted" + + test-pre-commit-git-config: + name: Test Git Configuration + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test custom git user + run: | + # Simulate set-git-config step + git config user.name "Test User" + git config user.email "test@example.com" + + # Verify configuration + user_name=$(git config user.name) + user_email=$(git config user.email) + + if [[ "$user_name" != "Test User" ]]; then + echo "❌ ERROR: Git user name not set correctly" + exit 1 + fi + + if [[ "$user_email" != "test@example.com" ]]; then + echo "❌ ERROR: Git user email not set correctly" + exit 1 + fi + + echo "✓ Git configuration works correctly" + + - name: Test default git user + run: | + # Simulate default configuration + git config user.name "GitHub Actions" + git config user.email "github-actions@github.com" + + # Verify default configuration + user_name=$(git config user.name) + user_email=$(git config user.email) + + if [[ "$user_name" != "GitHub Actions" ]]; then + echo "❌ ERROR: Default git user name not set correctly" + exit 1 + fi + + if [[ "$user_email" != "github-actions@github.com" ]]; then + echo "❌ ERROR: Default git user email not set correctly" + exit 1 + fi + + echo "✓ Default git configuration works correctly" + + test-pre-commit-option-generation: + name: Test Option Generation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test all-files option (no base branch) + run: | + BASE_BRANCH="" + if [ -z "$BASE_BRANCH" ]; then + option="--all-files" + else + option="--from-ref $BASE_BRANCH --to-ref HEAD" + fi + + if [[ "$option" != "--all-files" ]]; then + echo "❌ ERROR: Should use --all-files when no base branch" + exit 1 + fi + + echo "✓ Correctly generates --all-files option" + + - name: Test diff option (with base branch) + run: | + BASE_BRANCH="main" + if [ -z "$BASE_BRANCH" ]; then + option="--all-files" + else + option="--from-ref $BASE_BRANCH --to-ref HEAD" + fi + + expected="--from-ref main --to-ref HEAD" + if [[ "$option" != "$expected" ]]; then + echo "❌ ERROR: Option mismatch. Expected: $expected, Got: $option" + exit 1 + fi + + echo "✓ Correctly generates diff option with base branch" + + test-pre-commit-config-file-detection: + name: Test Config File Detection + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Verify default config exists + run: | + if [[ -f ".pre-commit-config.yaml" ]]; then + echo "✓ Default .pre-commit-config.yaml found" + else + echo "⚠️ Default config not found (may use repo default)" + fi + + - name: Test custom config path validation + run: | + CONFIG_FILE="custom-pre-commit-config.yaml" + + # In real action, this would be validated + if [[ ! -f "$CONFIG_FILE" ]]; then + echo "✓ Custom config file validation would fail (expected)" + else + echo "✓ Custom config file exists" + fi + + test-pre-commit-hook-execution: + name: Test Hook Execution Scenarios + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test pre-commit installed + run: | + if command -v pre-commit >/dev/null 2>&1; then + echo "✓ pre-commit is installed" + pre-commit --version + else + echo "⚠️ pre-commit not installed (would be installed by action)" + fi + + - name: Create test file with issues + run: | + mkdir -p test-pre-commit + cd test-pre-commit + + # Create a file with trailing whitespace + echo "Line with trailing spaces " > test.txt + echo "Line without issues" >> test.txt + + # Create a minimal .pre-commit-config.yaml + cat > .pre-commit-config.yaml <<'EOF' + repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + EOF + + echo "✓ Test environment created" + + - name: Test hook detection of issues + run: | + cd test-pre-commit + + # Check if trailing whitespace exists + if grep -q " $" test.txt; then + echo "✓ Test file has trailing whitespace (as expected)" + else + echo "❌ ERROR: Test file should have trailing whitespace" + exit 1 + fi + + test-pre-commit-outputs: + name: Test Output Values + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test hooks_passed output + run: | + # Simulate successful hooks + HOOKS_OUTCOME="success" + + if [[ "$HOOKS_OUTCOME" == "success" ]]; then + hooks_passed="true" + else + hooks_passed="false" + fi + + if [[ "$hooks_passed" != "true" ]]; then + echo "❌ ERROR: hooks_passed should be true for success" + exit 1 + fi + + echo "✓ hooks_passed output correct for success" + + - name: Test hooks_passed output on failure + run: | + # Simulate failed hooks + HOOKS_OUTCOME="failure" + + if [[ "$HOOKS_OUTCOME" == "success" ]]; then + hooks_passed="true" + else + hooks_passed="false" + fi + + if [[ "$hooks_passed" != "false" ]]; then + echo "❌ ERROR: hooks_passed should be false for failure" + exit 1 + fi + + echo "✓ hooks_passed output correct for failure" + + - name: Test files_changed output + run: | + # Simulate git status check + echo "test.txt" > /tmp/test-changes.txt + + if [[ -s /tmp/test-changes.txt ]]; then + files_changed="true" + else + files_changed="false" + fi + + if [[ "$files_changed" != "true" ]]; then + echo "❌ ERROR: files_changed should be true when files exist" + exit 1 + fi + + echo "✓ files_changed output correct" + + test-pre-commit-uv-integration: + name: Test UV Integration + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test PRE_COMMIT_USE_UV environment variable + run: | + PRE_COMMIT_USE_UV='1' + + if [[ "$PRE_COMMIT_USE_UV" != "1" ]]; then + echo "❌ ERROR: PRE_COMMIT_USE_UV should be set to 1" + exit 1 + fi + + echo "✓ PRE_COMMIT_USE_UV correctly set" + echo "✓ pre-commit will use UV for faster installations" + + test-pre-commit-workflow-scenarios: + name: Test Workflow Scenarios + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test full workflow (all files) + run: | + echo "Simulating full workflow with --all-files..." + + # 1. Validate inputs + CONFIG_FILE=".pre-commit-config.yaml" + echo "✓ Step 1: Validate inputs" + + # 2. Set git config + git config user.name "Test User" + git config user.email "test@example.com" + echo "✓ Step 2: Set git config" + + # 3. Determine option + BASE_BRANCH="" + if [ -z "$BASE_BRANCH" ]; then + OPTION="--all-files" + else + OPTION="--from-ref $BASE_BRANCH --to-ref HEAD" + fi + echo "✓ Step 3: Option set to: $OPTION" + + # 4. Run pre-commit (simulated) + echo "✓ Step 4: Would run: pre-commit run $OPTION" + + # 5. Check for changes + echo "✓ Step 5: Check for changes to commit" + + echo "✓ Full workflow simulation completed" + + - name: Test diff workflow (with base branch) + run: | + echo "Simulating diff workflow with base branch..." + + # 1. Validate inputs + CONFIG_FILE=".pre-commit-config.yaml" + BASE_BRANCH="main" + echo "✓ Step 1: Validate inputs (base-branch: $BASE_BRANCH)" + + # 2. Set git config + git config user.name "GitHub Actions" + git config user.email "github-actions@github.com" + echo "✓ Step 2: Set git config" + + # 3. Determine option + if [ -z "$BASE_BRANCH" ]; then + OPTION="--all-files" + else + OPTION="--from-ref $BASE_BRANCH --to-ref HEAD" + fi + echo "✓ Step 3: Option set to: $OPTION" + + # 4. Run pre-commit (simulated) + echo "✓ Step 4: Would run: pre-commit run $OPTION" + + # 5. Check for changes + echo "✓ Step 5: Check for changes to commit" + + echo "✓ Diff workflow simulation completed" + + test-pre-commit-autofix-behavior: + name: Test Autofix Behavior + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test autofix commit message + run: | + COMMIT_MESSAGE="style(pre-commit): autofix" + + if [[ "$COMMIT_MESSAGE" != "style(pre-commit): autofix" ]]; then + echo "❌ ERROR: Incorrect commit message" + exit 1 + fi + + echo "✓ Autofix commit message correct" + + - name: Test git add options + run: | + ADD_OPTIONS="-u" + + if [[ "$ADD_OPTIONS" != "-u" ]]; then + echo "❌ ERROR: Incorrect add options" + exit 1 + fi + + echo "✓ Git add options correct (-u for tracked files)" + + - name: Test autofix always runs + run: | + # Simulate pre-commit failure + PRECOMMIT_FAILED=true + + # Autofix should still run (if: always()) + echo "✓ Autofix runs even when pre-commit fails" + + integration-test-summary: + name: Integration Test Summary + runs-on: ubuntu-latest + needs: + - test-pre-commit-validation + - test-pre-commit-git-config + - test-pre-commit-option-generation + - test-pre-commit-config-file-detection + - test-pre-commit-hook-execution + - test-pre-commit-outputs + - test-pre-commit-uv-integration + - test-pre-commit-workflow-scenarios + - test-pre-commit-autofix-behavior + steps: + - name: Summary + run: | + echo "==========================================" + echo "Pre-commit Integration Tests - PASSED" + echo "==========================================" + echo "" + echo "✓ Input validation tests" + echo "✓ Git configuration tests" + echo "✓ Option generation tests" + echo "✓ Config file detection tests" + echo "✓ Hook execution tests" + echo "✓ Output verification tests" + echo "✓ UV integration tests" + echo "✓ Workflow scenario tests" + echo "✓ Autofix behavior tests" + echo "" + echo "All pre-commit integration tests completed successfully!" diff --git a/_tests/integration/workflows/version-validator-test.yml b/_tests/integration/workflows/version-validator-test.yml new file mode 100644 index 0000000..7a77464 --- /dev/null +++ b/_tests/integration/workflows/version-validator-test.yml @@ -0,0 +1,414 @@ +--- +name: Integration Test - Version Validator +on: + workflow_dispatch: + push: + paths: + - 'version-validator/**' + - '_tests/integration/workflows/version-validator-test.yml' + +jobs: + test-version-validator-input-validation: + name: Test Input Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test empty version (should fail) + run: | + VERSION="" + if [[ -z "$VERSION" ]]; then + echo "✓ Empty version correctly rejected" + else + echo "❌ ERROR: Empty version should be rejected" + exit 1 + fi + + - name: Test dangerous characters in version + run: | + for version in "1.2.3;rm -rf /" "1.0&&echo" "1.0|cat" '1.0`cmd`' "1.0\$variable"; do + if [[ "$version" == *";"* ]] || [[ "$version" == *"&&"* ]] || \ + [[ "$version" == *"|"* ]] || [[ "$version" == *"\`"* ]] || [[ "$version" == *"\$"* ]]; then + echo "✓ Dangerous version '$version' correctly detected" + else + echo "❌ ERROR: Should detect dangerous characters in: $version" + exit 1 + fi + done + + - name: Test valid version strings + run: | + for version in "1.2.3" "v1.0.0" "2.0.0-alpha" "1.0.0+build"; do + if [[ "$version" == *";"* ]] || [[ "$version" == *"&&"* ]] || \ + [[ "$version" == *"|"* ]] || [[ "$version" == *"\`"* ]] || [[ "$version" == *"\$"* ]]; then + echo "❌ ERROR: Valid version should not be rejected: $version" + exit 1 + else + echo "✓ Valid version '$version' accepted" + fi + done + + test-version-validator-regex-validation: + name: Test Regex Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test empty regex (should fail) + run: | + REGEX="" + if [[ -z "$REGEX" ]]; then + echo "✓ Empty regex correctly rejected" + else + echo "❌ ERROR: Empty regex should be rejected" + exit 1 + fi + + - name: Test potential ReDoS patterns + run: | + for regex in ".*.*" ".+.+"; do + if [[ "$regex" == *".*.*"* ]] || [[ "$regex" == *".+.+"* ]]; then + echo "✓ ReDoS pattern '$regex' detected (would show warning)" + else + echo "❌ ERROR: Should detect ReDoS pattern: $regex" + exit 1 + fi + done + + - name: Test safe regex patterns + run: | + for regex in "^[0-9]+\.[0-9]+$" "^v?[0-9]+"; do + if [[ "$regex" == *".*.*"* ]] || [[ "$regex" == *".+.+"* ]]; then + echo "❌ ERROR: Safe regex should not be flagged: $regex" + exit 1 + else + echo "✓ Safe regex '$regex' accepted" + fi + done + + test-version-validator-language-validation: + name: Test Language Parameter Validation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test dangerous characters in language + run: | + for lang in "node;rm" "python&&cmd" "ruby|cat"; do + if [[ "$lang" == *";"* ]] || [[ "$lang" == *"&&"* ]] || [[ "$lang" == *"|"* ]]; then + echo "✓ Dangerous language parameter '$lang' correctly detected" + else + echo "❌ ERROR: Should detect dangerous characters in: $lang" + exit 1 + fi + done + + - name: Test valid language parameters + run: | + for lang in "node" "python" "ruby" "go" "java"; do + if [[ "$lang" == *";"* ]] || [[ "$lang" == *"&&"* ]] || [[ "$lang" == *"|"* ]]; then + echo "❌ ERROR: Valid language should not be rejected: $lang" + exit 1 + else + echo "✓ Valid language '$lang' accepted" + fi + done + + test-version-validator-version-cleaning: + name: Test Version Cleaning + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test v prefix removal + run: | + for input in "v1.2.3" "V2.0.0"; do + cleaned=$(echo "$input" | sed -e 's/^[vV]//') + if [[ "$cleaned" == "1.2.3" ]] || [[ "$cleaned" == "2.0.0" ]]; then + echo "✓ v prefix removed from '$input' -> '$cleaned'" + else + echo "❌ ERROR: Failed to clean '$input', got '$cleaned'" + exit 1 + fi + done + + - name: Test whitespace removal + run: | + input=" 1.2.3 " + cleaned=$(echo "$input" | tr -d ' ') + if [[ "$cleaned" == "1.2.3" ]]; then + echo "✓ Whitespace removed: '$input' -> '$cleaned'" + else + echo "❌ ERROR: Failed to remove whitespace" + exit 1 + fi + + - name: Test newline removal + run: | + input=$'1.2.3\n' + cleaned=$(echo "$input" | tr -d '\n' | tr -d '\r') + if [[ "$cleaned" == "1.2.3" ]]; then + echo "✓ Newlines removed" + else + echo "❌ ERROR: Failed to remove newlines" + exit 1 + fi + + test-version-validator-regex-matching: + name: Test Regex Matching + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test default SemVer regex + run: | + REGEX='^[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$' + + for version in "1.0.0" "1.2" "1.0.0-alpha" "1.0.0+build" "2.1.0-rc.1+build.123"; do + if [[ $version =~ $REGEX ]]; then + echo "✓ Version '$version' matches SemVer regex" + else + echo "❌ ERROR: Version '$version' should match SemVer" + exit 1 + fi + done + + - name: Test invalid versions against SemVer regex + run: | + REGEX='^[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$' + + for version in "abc" "1.a.b" "not.a.version"; do + if [[ $version =~ $REGEX ]]; then + echo "❌ ERROR: Invalid version '$version' should not match" + exit 1 + else + echo "✓ Invalid version '$version' correctly rejected" + fi + done + + - name: Test custom strict regex + run: | + REGEX='^[0-9]+\.[0-9]+\.[0-9]+$' + + # Should match + for version in "1.0.0" "2.5.10"; do + if [[ $version =~ $REGEX ]]; then + echo "✓ Version '$version' matches strict regex" + else + echo "❌ ERROR: Version '$version' should match strict regex" + exit 1 + fi + done + + # Should not match + for version in "1.0" "1.0.0-alpha"; do + if [[ $version =~ $REGEX ]]; then + echo "❌ ERROR: Version '$version' should not match strict regex" + exit 1 + else + echo "✓ Version '$version' correctly rejected by strict regex" + fi + done + + test-version-validator-outputs: + name: Test Output Generation + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test valid version outputs (simulation) + run: | + VERSION="v1.2.3" + REGEX='^[0-9]+\.[0-9]+\.[0-9]+$' + + # Clean version + cleaned=$(echo "$VERSION" | sed -e 's/^[vV]//' | tr -d ' ' | tr -d '\n' | tr -d '\r') + + # Validate + if [[ $cleaned =~ $REGEX ]]; then + is_valid="true" + validated_version="$cleaned" + error_message="" + + echo "is_valid=$is_valid" + echo "validated_version=$validated_version" + echo "error_message=$error_message" + + if [[ "$is_valid" != "true" ]]; then + echo "❌ ERROR: Should be valid" + exit 1 + fi + if [[ "$validated_version" != "1.2.3" ]]; then + echo "❌ ERROR: Wrong validated version" + exit 1 + fi + echo "✓ Valid version outputs correct" + fi + + - name: Test invalid version outputs (simulation) + run: | + VERSION="not.a.version" + REGEX='^[0-9]+\.[0-9]+\.[0-9]+$' + LANGUAGE="test" + + # Clean version + cleaned=$(echo "$VERSION" | sed -e 's/^[vV]//' | tr -d ' ' | tr -d '\n' | tr -d '\r') + + # Validate + if [[ $cleaned =~ $REGEX ]]; then + is_valid="true" + else + is_valid="false" + validated_version="" + error_msg="Invalid $LANGUAGE version format: '$VERSION' (cleaned: '$cleaned'). Expected pattern: $REGEX" + error_message=$(echo "$error_msg" | tr -d '\n\r') + + echo "is_valid=$is_valid" + echo "validated_version=$validated_version" + echo "error_message=$error_message" + + if [[ "$is_valid" != "false" ]]; then + echo "❌ ERROR: Should be invalid" + exit 1 + fi + if [[ -n "$validated_version" ]]; then + echo "❌ ERROR: Validated version should be empty" + exit 1 + fi + if [[ -z "$error_message" ]]; then + echo "❌ ERROR: Error message should not be empty" + exit 1 + fi + echo "✓ Invalid version outputs correct" + fi + + test-version-validator-sanitization: + name: Test Output Sanitization + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test error message sanitization + run: | + error_msg=$'Error message\nwith newlines' + + sanitized=$(echo "$error_msg" | tr -d '\n\r') + + if [[ "$sanitized" == *$'\n'* ]] || [[ "$sanitized" == *$'\r'* ]]; then + echo "❌ ERROR: Newlines not removed from error message" + exit 1 + fi + echo "✓ Error message sanitization works" + + - name: Test validated version sanitization + run: | + VERSION=$'1.2.3\n' + cleaned=$(echo "$VERSION" | sed -e 's/^[vV]//' | tr -d ' ' | tr -d '\n' | tr -d '\r') + + if [[ "$cleaned" == *$'\n'* ]] || [[ "$cleaned" == *$'\r'* ]]; then + echo "❌ ERROR: Newlines not removed from validated version" + exit 1 + fi + echo "✓ Validated version sanitization works" + + test-version-validator-real-world-scenarios: + name: Test Real World Scenarios + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Test Node.js version validation + run: | + REGEX='^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$' + + for version in "20" "20.9" "20.9.0" "18.17.1"; do + cleaned=$(echo "$version" | sed -e 's/^[vV]//') + if [[ $cleaned =~ $REGEX ]]; then + echo "✓ Node.js version '$version' valid" + else + echo "❌ ERROR: Node.js version should be valid" + exit 1 + fi + done + + - name: Test Python version validation + run: | + REGEX='^[0-9]+\.[0-9]+(\.[0-9]+)?$' + + for version in "3.11" "3.11.5" "3.12.0"; do + cleaned=$(echo "$version" | sed -e 's/^[vV]//') + if [[ $cleaned =~ $REGEX ]]; then + echo "✓ Python version '$version' valid" + else + echo "❌ ERROR: Python version should be valid" + exit 1 + fi + done + + - name: Test CalVer validation + run: | + REGEX='^[0-9]{4}\.[0-9]{1,2}(\.[0-9]+)?$' + + for version in "2024.3" "2024.3.15" "2024.10.1"; do + cleaned=$(echo "$version" | sed -e 's/^[vV]//') + if [[ $cleaned =~ $REGEX ]]; then + echo "✓ CalVer version '$version' valid" + else + echo "❌ ERROR: CalVer version should be valid" + exit 1 + fi + done + + - name: Test Docker tag validation + run: | + REGEX='^[a-z0-9][a-z0-9._-]*$' + + for tag in "latest" "v1.2.3" "stable-alpine" "2024.10.15"; do + cleaned=$(echo "$tag" | sed -e 's/^[vV]//') + # Note: Docker tags are case-insensitive, so convert to lowercase + cleaned=$(echo "$cleaned" | tr '[:upper:]' '[:lower:]') + if [[ $cleaned =~ $REGEX ]]; then + echo "✓ Docker tag '$tag' valid" + else + echo "❌ ERROR: Docker tag should be valid: $tag" + exit 1 + fi + done + + integration-test-summary: + name: Integration Test Summary + runs-on: ubuntu-latest + needs: + - test-version-validator-input-validation + - test-version-validator-regex-validation + - test-version-validator-language-validation + - test-version-validator-version-cleaning + - test-version-validator-regex-matching + - test-version-validator-outputs + - test-version-validator-sanitization + - test-version-validator-real-world-scenarios + steps: + - name: Summary + run: | + echo "==========================================" + echo "Version Validator Integration Tests - PASSED" + echo "==========================================" + echo "" + echo "✓ Input validation tests" + echo "✓ Regex validation tests" + echo "✓ Language validation tests" + echo "✓ Version cleaning tests" + echo "✓ Regex matching tests" + echo "✓ Output generation tests" + echo "✓ Sanitization tests" + echo "✓ Real world scenario tests" + echo "" + echo "All version-validator integration tests completed successfully!" diff --git a/ansible-lint-fix/README.md b/ansible-lint-fix/README.md index 95ac77d..cdb38c6 100644 --- a/ansible-lint-fix/README.md +++ b/ansible-lint-fix/README.md @@ -10,7 +10,7 @@ Lints and fixes Ansible playbooks, commits changes, and uploads SARIF report. | name | description | required | default | |---------------|--------------------------------------------------------------------|----------|-----------------------------| -| `token` |

GitHub token for authentication

| `false` | `${{ github.token }}` | +| `token` |

GitHub token for authentication

| `false` | `""` | | `username` |

GitHub username for commits

| `false` | `github-actions` | | `email` |

GitHub email for commits

| `false` | `github-actions@github.com` | | `max-retries` |

Maximum number of retry attempts for pip install operations

| `false` | `3` | @@ -36,7 +36,7 @@ This action is a `composite` action. # GitHub token for authentication # # Required: false - # Default: ${{ github.token }} + # Default: "" username: # GitHub username for commits diff --git a/ansible-lint-fix/action.yml b/ansible-lint-fix/action.yml index 5a98450..1b576d1 100644 --- a/ansible-lint-fix/action.yml +++ b/ansible-lint-fix/action.yml @@ -15,7 +15,7 @@ inputs: token: description: 'GitHub token for authentication' required: false - default: ${{ github.token }} + default: '' username: description: 'GitHub username for commits' required: false @@ -104,6 +104,11 @@ runs: echo "No Ansible files found. Skipping lint and fix." fi + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Cache Python Dependencies if: steps.check-files.outputs.files_found == 'true' id: cache-pip @@ -177,6 +182,6 @@ runs: - name: Upload SARIF Report if: steps.check-files.outputs.files_found == 'true' - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: ansible-lint.sarif diff --git a/biome-check/action.yml b/biome-check/action.yml index 6c50fe8..8b76b84 100644 --- a/biome-check/action.yml +++ b/biome-check/action.yml @@ -233,6 +233,6 @@ runs: - name: Upload Biome Results if: always() - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: biome-report.sarif diff --git a/codeql-analysis/action.yml b/codeql-analysis/action.yml index eede427..9ff21cb 100644 --- a/codeql-analysis/action.yml +++ b/codeql-analysis/action.yml @@ -189,7 +189,7 @@ runs: echo "Using build mode: $build_mode" - name: Initialize CodeQL - uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: languages: ${{ inputs.language }} queries: ${{ inputs.queries }} @@ -202,12 +202,12 @@ runs: threads: ${{ inputs.threads }} - name: Autobuild - uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 if: ${{ steps.set-build-mode.outputs.build-mode == 'autobuild' }} - name: Perform CodeQL Analysis id: analysis - uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: category: ${{ steps.set-category.outputs.category }} upload: ${{ inputs.upload-results }} diff --git a/csharp-build/README.md b/csharp-build/README.md index b11709d..5cf9521 100644 --- a/csharp-build/README.md +++ b/csharp-build/README.md @@ -12,6 +12,7 @@ Builds and tests C# projects. |------------------|-----------------------------------------------------------------------|----------|---------| | `dotnet-version` |

Version of .NET SDK to use.

| `false` | `""` | | `max-retries` |

Maximum number of retry attempts for dotnet restore operations

| `false` | `3` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -43,4 +44,10 @@ This action is a `composite` action. # # Required: false # Default: 3 + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/csharp-build/action.yml b/csharp-build/action.yml index a189023..7953b17 100644 --- a/csharp-build/action.yml +++ b/csharp-build/action.yml @@ -18,6 +18,10 @@ inputs: description: 'Maximum number of retry attempts for dotnet restore operations' required: false default: '3' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: build_status: @@ -39,6 +43,11 @@ outputs: runs: using: composite steps: + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Detect .NET SDK Version id: detect-dotnet-version uses: ./dotnet-version-detect diff --git a/csharp-build/rules.yml b/csharp-build/rules.yml index 370871c..105660b 100644 --- a/csharp-build/rules.yml +++ b/csharp-build/rules.yml @@ -2,7 +2,7 @@ # Validation rules for csharp-build action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (2/2 inputs) +# Coverage: 100% (3/3 inputs) # # This file defines validation rules for the csharp-build GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -17,13 +17,15 @@ required_inputs: [] optional_inputs: - dotnet-version - max-retries + - token conventions: dotnet-version: dotnet_version max-retries: numeric_range_1_10 + token: github_token overrides: {} statistics: - total_inputs: 2 - validated_inputs: 2 + total_inputs: 3 + validated_inputs: 3 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -31,7 +33,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: false - has_security_validation: false + has_security_validation: true diff --git a/csharp-lint-check/README.md b/csharp-lint-check/README.md index d05994e..49fb1b0 100644 --- a/csharp-lint-check/README.md +++ b/csharp-lint-check/README.md @@ -8,9 +8,10 @@ Runs linters like StyleCop or dotnet-format for C# code style checks. ### Inputs -| name | description | required | default | -|------------------|------------------------------------|----------|---------| -| `dotnet-version` |

Version of .NET SDK to use.

| `false` | `""` | +| name | description | required | default | +|------------------|----------------------------------------|----------|---------| +| `dotnet-version` |

Version of .NET SDK to use.

| `false` | `""` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -34,4 +35,10 @@ This action is a `composite` action. # # Required: false # Default: "" + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/csharp-lint-check/action.yml b/csharp-lint-check/action.yml index 7a57ee7..c5da0df 100644 --- a/csharp-lint-check/action.yml +++ b/csharp-lint-check/action.yml @@ -15,6 +15,10 @@ inputs: dotnet-version: description: 'Version of .NET SDK to use.' required: false + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: lint_status: @@ -55,6 +59,11 @@ runs: echo "Input validation completed successfully" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Detect .NET SDK Version id: detect-dotnet-version uses: ./dotnet-version-detect @@ -102,6 +111,6 @@ runs: fi - name: Upload SARIF Report - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: dotnet-format.sarif diff --git a/csharp-lint-check/rules.yml b/csharp-lint-check/rules.yml index 2ac9ccb..11966fe 100644 --- a/csharp-lint-check/rules.yml +++ b/csharp-lint-check/rules.yml @@ -2,7 +2,7 @@ # Validation rules for csharp-lint-check action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (1/1 inputs) +# Coverage: 100% (2/2 inputs) # # This file defines validation rules for the csharp-lint-check GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -16,12 +16,14 @@ generator_version: 1.0.0 required_inputs: [] optional_inputs: - dotnet-version + - token conventions: dotnet-version: dotnet_version + token: github_token overrides: {} statistics: - total_inputs: 1 - validated_inputs: 1 + total_inputs: 2 + validated_inputs: 2 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -29,7 +31,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: false - has_security_validation: false + has_security_validation: true diff --git a/csharp-publish/action.yml b/csharp-publish/action.yml index 674830e..d066352 100644 --- a/csharp-publish/action.yml +++ b/csharp-publish/action.yml @@ -44,6 +44,11 @@ runs: run: | echo "::add-mask::$API_KEY" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Validate Inputs id: validate uses: ./validate-inputs diff --git a/docker-build/action.yml b/docker-build/action.yml index 4b20f0f..0327dfe 100644 --- a/docker-build/action.yml +++ b/docker-build/action.yml @@ -140,6 +140,11 @@ outputs: runs: using: composite steps: + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Validate Inputs id: validate uses: ./validate-inputs @@ -523,7 +528,7 @@ runs: - name: Install Cosign if: inputs.sign-image == 'true' && inputs.push == 'true' && inputs.dry-run != 'true' - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - name: Sign Image id: sign diff --git a/docker-publish-gh/action.yml b/docker-publish-gh/action.yml index 9db68dc..04bcff6 100644 --- a/docker-publish-gh/action.yml +++ b/docker-publish-gh/action.yml @@ -213,7 +213,7 @@ runs: - name: Set up Cosign if: inputs.provenance == 'true' || inputs.sign-image == 'true' - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - name: Detect Available Platforms id: detect-platforms diff --git a/docker-publish-hub/action.yml b/docker-publish-hub/action.yml index 957111e..6d04da3 100644 --- a/docker-publish-hub/action.yml +++ b/docker-publish-hub/action.yml @@ -213,7 +213,7 @@ runs: - name: Set up Cosign if: inputs.provenance == 'true' - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - name: Update Docker Hub Description if: inputs.repository-description != '' || inputs.readme-file != '' @@ -409,7 +409,7 @@ runs: - name: Install Cosign if: inputs.sign-image == 'true' - uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0 + uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0 - name: Sign Published Image id: sign diff --git a/docker-publish/README.md b/docker-publish/README.md index 54361a8..81eff07 100644 --- a/docker-publish/README.md +++ b/docker-publish/README.md @@ -21,6 +21,7 @@ Publish a Docker image to GitHub Packages and Docker Hub. | `verbose` |

Enable verbose logging

| `false` | `false` | | `dockerhub-username` |

Docker Hub username for authentication

| `false` | `""` | | `dockerhub-password` |

Docker Hub password or access token for authentication

| `false` | `""` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -109,4 +110,10 @@ This action is a `composite` action. # # Required: false # Default: "" + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/docker-publish/action.yml b/docker-publish/action.yml index 4cd8c8d..47494fb 100644 --- a/docker-publish/action.yml +++ b/docker-publish/action.yml @@ -54,6 +54,10 @@ inputs: dockerhub-password: description: 'Docker Hub password or access token for authentication' required: false + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: registry: @@ -84,6 +88,18 @@ outputs: runs: using: composite steps: + - name: Mask Sensitive Inputs + shell: bash + env: + DOCKERHUB_PASSWORD: ${{ inputs.dockerhub-password }} + run: | + set -euo pipefail + + # Mask Docker Hub credentials to prevent exposure in logs + if [[ -n "${DOCKERHUB_PASSWORD}" ]]; then + echo "::add-mask::${DOCKERHUB_PASSWORD}" + fi + - name: Validate Inputs id: validate shell: bash @@ -147,6 +163,11 @@ runs: echo "Publishing to: $REGISTRY" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Build Multi-Arch Docker Image id: build uses: ./docker-build diff --git a/docker-publish/rules.yml b/docker-publish/rules.yml index 3f6190e..749504d 100644 --- a/docker-publish/rules.yml +++ b/docker-publish/rules.yml @@ -2,7 +2,7 @@ # Validation rules for docker-publish action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (11/11 inputs) +# Coverage: 100% (12/12 inputs) # # This file defines validation rules for the docker-publish GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -25,6 +25,7 @@ optional_inputs: - platforms - scan-image - sign-image + - token - verbose conventions: auto-detect-platforms: docker_architectures @@ -37,14 +38,15 @@ conventions: registry: registry scan-image: boolean sign-image: boolean + token: github_token verbose: boolean overrides: cache-mode: cache_mode platforms: null registry: registry_enum statistics: - total_inputs: 11 - validated_inputs: 11 + total_inputs: 12 + validated_inputs: 12 skipped_inputs: 1 coverage_percentage: 100 validation_coverage: 100 @@ -52,7 +54,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: true - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: false has_security_validation: true diff --git a/dotnet-version-detect/README.md b/dotnet-version-detect/README.md index 1b796d7..60fb21a 100644 --- a/dotnet-version-detect/README.md +++ b/dotnet-version-detect/README.md @@ -11,6 +11,7 @@ Detects .NET SDK version from global.json or defaults to a specified version. | name | description | required | default | |-------------------|---------------------------------------------------------------------|----------|---------| | `default-version` |

Default .NET SDK version to use if global.json is not found.

| `true` | `7.0` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -32,4 +33,10 @@ This action is a `composite` action. # # Required: true # Default: 7.0 + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/dotnet-version-detect/action.yml b/dotnet-version-detect/action.yml index f13bda2..7445a08 100644 --- a/dotnet-version-detect/action.yml +++ b/dotnet-version-detect/action.yml @@ -15,6 +15,10 @@ inputs: description: 'Default .NET SDK version to use if global.json is not found.' required: true default: '7.0' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: dotnet-version: @@ -47,6 +51,11 @@ runs: echo "Input validation completed successfully" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Parse .NET Version id: parse-version uses: ./version-file-parser diff --git a/dotnet-version-detect/rules.yml b/dotnet-version-detect/rules.yml index e96b0d3..65cad49 100644 --- a/dotnet-version-detect/rules.yml +++ b/dotnet-version-detect/rules.yml @@ -2,7 +2,7 @@ # Validation rules for dotnet-version-detect action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (1/1 inputs) +# Coverage: 100% (2/2 inputs) # # This file defines validation rules for the dotnet-version-detect GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -15,14 +15,16 @@ description: Detects .NET SDK version from global.json or defaults to a specifie generator_version: 1.0.0 required_inputs: - default-version -optional_inputs: [] +optional_inputs: + - token conventions: default-version: semantic_version + token: github_token overrides: default-version: dotnet_version statistics: - total_inputs: 1 - validated_inputs: 1 + total_inputs: 2 + validated_inputs: 2 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -30,7 +32,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: true - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: false - has_security_validation: false + has_security_validation: true diff --git a/eslint-check/README.md b/eslint-check/README.md index 77cedc8..05305b8 100644 --- a/eslint-check/README.md +++ b/eslint-check/README.md @@ -20,6 +20,7 @@ Run ESLint check on the repository with advanced configuration and reporting | `fail-on-error` |

Fail workflow if issues are found

| `false` | `true` | | `report-format` |

Output format (stylish, json, sarif)

| `false` | `sarif` | | `max-retries` |

Maximum number of retry attempts

| `false` | `3` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -98,4 +99,10 @@ This action is a `composite` action. # # Required: false # Default: 3 + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/eslint-check/action.yml b/eslint-check/action.yml index 435e1d8..a12f299 100644 --- a/eslint-check/action.yml +++ b/eslint-check/action.yml @@ -52,6 +52,10 @@ inputs: description: 'Maximum number of retry attempts' required: false default: '3' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: error-count: @@ -165,6 +169,11 @@ runs: exit 1 fi + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Setup Node.js id: node-setup uses: ./node-setup @@ -405,7 +414,7 @@ runs: - name: Upload ESLint Results if: always() && inputs.report-format == 'sarif' - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: ${{ inputs.working-directory }}/reports/eslint.sarif category: eslint diff --git a/eslint-check/rules.yml b/eslint-check/rules.yml index e1b6b78..48b08ba 100644 --- a/eslint-check/rules.yml +++ b/eslint-check/rules.yml @@ -2,7 +2,7 @@ # Validation rules for eslint-check action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (10/10 inputs) +# Coverage: 100% (11/11 inputs) # # This file defines validation rules for the eslint-check GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -24,6 +24,7 @@ optional_inputs: - max-retries - max-warnings - report-format + - token - working-directory conventions: cache: boolean @@ -35,11 +36,12 @@ conventions: max-retries: numeric_range_1_10 max-warnings: numeric_range_0_10000 report-format: report_format + token: github_token working-directory: file_path overrides: {} statistics: - total_inputs: 10 - validated_inputs: 10 + total_inputs: 11 + validated_inputs: 11 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -47,7 +49,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: true - has_security_validation: false + has_security_validation: true diff --git a/go-build/README.md b/go-build/README.md index 029dbf6..571cb45 100644 --- a/go-build/README.md +++ b/go-build/README.md @@ -13,6 +13,7 @@ Builds the Go project. | `go-version` |

Go version to use.

| `false` | `""` | | `destination` |

Build destination directory.

| `false` | `./bin` | | `max-retries` |

Maximum number of retry attempts for go mod download operations

| `false` | `3` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -50,4 +51,10 @@ This action is a `composite` action. # # Required: false # Default: 3 + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/go-build/action.yml b/go-build/action.yml index 5230f6f..c42906a 100644 --- a/go-build/action.yml +++ b/go-build/action.yml @@ -22,6 +22,10 @@ inputs: description: 'Maximum number of retry attempts for go mod download operations' required: false default: '3' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: build_status: @@ -43,6 +47,11 @@ outputs: runs: using: composite steps: + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Detect Go Version id: detect-go-version uses: ./go-version-detect diff --git a/go-build/rules.yml b/go-build/rules.yml index a61fbcb..1dd23b5 100644 --- a/go-build/rules.yml +++ b/go-build/rules.yml @@ -2,7 +2,7 @@ # Validation rules for go-build action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (3/3 inputs) +# Coverage: 100% (4/4 inputs) # # This file defines validation rules for the go-build GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -18,14 +18,16 @@ optional_inputs: - destination - go-version - max-retries + - token conventions: destination: file_path go-version: semantic_version max-retries: numeric_range_1_10 + token: github_token overrides: {} statistics: - total_inputs: 3 - validated_inputs: 3 + total_inputs: 4 + validated_inputs: 4 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -33,7 +35,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: true - has_security_validation: false + has_security_validation: true diff --git a/go-lint/README.md b/go-lint/README.md index 25f1799..bcdf8f4 100644 --- a/go-lint/README.md +++ b/go-lint/README.md @@ -23,6 +23,7 @@ Run golangci-lint with advanced configuration, caching, and reporting | `disable-all` |

Disable all linters (useful with --enable-\*)

| `false` | `false` | | `enable-linters` |

Comma-separated list of linters to enable

| `false` | `""` | | `disable-linters` |

Comma-separated list of linters to disable

| `false` | `""` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -119,4 +120,10 @@ This action is a `composite` action. # # Required: false # Default: "" + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/go-lint/action.yml b/go-lint/action.yml index 1372c1e..8571544 100644 --- a/go-lint/action.yml +++ b/go-lint/action.yml @@ -62,6 +62,10 @@ inputs: disable-linters: description: 'Comma-separated list of linters to disable' required: false + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: error-count: @@ -199,6 +203,11 @@ runs: go-version: ${{ inputs.go-version }} cache: true + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Set up Cache id: cache if: inputs.cache == 'true' @@ -394,7 +403,7 @@ runs: - name: Upload Lint Results if: always() && inputs.report-format == 'sarif' - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: ${{ inputs.working-directory }}/reports/golangci-lint.sarif category: golangci-lint diff --git a/go-lint/rules.yml b/go-lint/rules.yml index d4ce0f9..bfcd950 100644 --- a/go-lint/rules.yml +++ b/go-lint/rules.yml @@ -2,7 +2,7 @@ # Validation rules for go-lint action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (13/13 inputs) +# Coverage: 100% (14/14 inputs) # # This file defines validation rules for the go-lint GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -27,6 +27,7 @@ optional_inputs: - only-new-issues - report-format - timeout + - token - working-directory conventions: cache: boolean @@ -41,14 +42,15 @@ conventions: only-new-issues: branch_name report-format: report_format timeout: numeric_range_1_3600 + token: github_token working-directory: file_path overrides: go-version: go_version only-new-issues: boolean timeout: timeout_with_unit statistics: - total_inputs: 13 - validated_inputs: 13 + total_inputs: 14 + validated_inputs: 14 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -56,7 +58,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: true - has_security_validation: false + has_security_validation: true diff --git a/go-version-detect/README.md b/go-version-detect/README.md index 87262f4..1ea0720 100644 --- a/go-version-detect/README.md +++ b/go-version-detect/README.md @@ -11,6 +11,7 @@ Detects the Go version from the project's go.mod file or defaults to a specified | name | description | required | default | |-------------------|----------------------------------------------------------|----------|---------| | `default-version` |

Default Go version to use if go.mod is not found.

| `false` | `1.25` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -32,4 +33,10 @@ This action is a `composite` action. # # Required: false # Default: 1.25 + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/go-version-detect/action.yml b/go-version-detect/action.yml index cddb356..fa90bfc 100644 --- a/go-version-detect/action.yml +++ b/go-version-detect/action.yml @@ -15,6 +15,10 @@ inputs: description: 'Default Go version to use if go.mod is not found.' required: false default: '1.25' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: go-version: @@ -54,6 +58,11 @@ runs: echo "Input validation completed successfully" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Parse Go Version id: parse-version uses: ./version-file-parser diff --git a/go-version-detect/rules.yml b/go-version-detect/rules.yml index ec85f7b..8a96744 100644 --- a/go-version-detect/rules.yml +++ b/go-version-detect/rules.yml @@ -2,7 +2,7 @@ # Validation rules for go-version-detect action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (1/1 inputs) +# Coverage: 100% (2/2 inputs) # # This file defines validation rules for the go-version-detect GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -16,13 +16,15 @@ generator_version: 1.0.0 required_inputs: [] optional_inputs: - default-version + - token conventions: default-version: semantic_version + token: github_token overrides: default-version: go_version statistics: - total_inputs: 1 - validated_inputs: 1 + total_inputs: 2 + validated_inputs: 2 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -30,7 +32,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: false - has_security_validation: false + has_security_validation: true diff --git a/node-setup/action.yml b/node-setup/action.yml index 2686885..558b65c 100644 --- a/node-setup/action.yml +++ b/node-setup/action.yml @@ -169,6 +169,11 @@ runs: echo "Input validation completed successfully" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Parse Node.js Version id: version uses: ./version-file-parser diff --git a/npm-publish/README.md b/npm-publish/README.md index 46eb4ba..352a6f4 100644 --- a/npm-publish/README.md +++ b/npm-publish/README.md @@ -8,12 +8,13 @@ Publishes the package to the NPM registry with configurable scope and registry U ### Inputs -| name | description | required | default | -|-------------------|-------------------------------------|----------|----------------------------------------| -| `registry-url` |

Registry URL for publishing.

| `false` | `https://registry.npmjs.org/` | -| `scope` |

Package scope to use.

| `false` | `@ivuorinen` | -| `package-version` |

The version to publish.

| `false` | `${{ github.event.release.tag_name }}` | -| `npm_token` |

NPM token.

| `true` | `""` | +| name | description | required | default | +|-------------------|----------------------------------------|----------|----------------------------------------| +| `registry-url` |

Registry URL for publishing.

| `false` | `https://registry.npmjs.org/` | +| `scope` |

Package scope to use.

| `false` | `@ivuorinen` | +| `package-version` |

The version to publish.

| `false` | `${{ github.event.release.tag_name }}` | +| `npm_token` |

NPM token.

| `true` | `""` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -55,4 +56,10 @@ This action is a `composite` action. # # Required: true # Default: "" + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/npm-publish/action.yml b/npm-publish/action.yml index 3e33ccb..bea7c91 100644 --- a/npm-publish/action.yml +++ b/npm-publish/action.yml @@ -28,6 +28,10 @@ inputs: description: 'NPM token.' required: true default: '' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: registry-url: @@ -91,6 +95,11 @@ runs: exit 1 fi + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Setup Node.js uses: ./node-setup diff --git a/npm-publish/rules.yml b/npm-publish/rules.yml index 85cd9b7..678f1bf 100644 --- a/npm-publish/rules.yml +++ b/npm-publish/rules.yml @@ -2,7 +2,7 @@ # Validation rules for npm-publish action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (4/4 inputs) +# Coverage: 100% (5/5 inputs) # # This file defines validation rules for the npm-publish GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -19,16 +19,18 @@ optional_inputs: - package-version - registry-url - scope + - token conventions: npm_token: github_token package-version: semantic_version registry-url: url scope: scope + token: github_token overrides: package-version: strict_semantic_version statistics: - total_inputs: 4 - validated_inputs: 4 + total_inputs: 5 + validated_inputs: 5 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -36,7 +38,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: true - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: false has_security_validation: true diff --git a/php-composer/README.md b/php-composer/README.md index 76306f2..e579bcc 100644 --- a/php-composer/README.md +++ b/php-composer/README.md @@ -17,7 +17,7 @@ Runs Composer install on a repository with advanced caching and configuration. | `composer-version` |

Composer version to use (1 or 2)

| `false` | `2` | | `stability` |

Minimum stability (stable, RC, beta, alpha, dev)

| `false` | `stable` | | `cache-directories` |

Additional directories to cache (comma-separated)

| `false` | `""` | -| `token` |

GitHub token for private repository access

| `false` | `${{ github.token }}` | +| `token` |

GitHub token for private repository access

| `false` | `""` | | `max-retries` |

Maximum number of retry attempts for Composer commands

| `false` | `3` | ### Outputs @@ -84,7 +84,7 @@ This action is a `composite` action. # GitHub token for private repository access # # Required: false - # Default: ${{ github.token }} + # Default: "" max-retries: # Maximum number of retry attempts for Composer commands diff --git a/php-composer/action.yml b/php-composer/action.yml index 0fc9243..de21881 100644 --- a/php-composer/action.yml +++ b/php-composer/action.yml @@ -42,7 +42,7 @@ inputs: token: description: 'GitHub token for private repository access' required: false - default: ${{ github.token }} + default: '' max-retries: description: 'Maximum number of retry attempts for Composer commands' required: false @@ -68,10 +68,15 @@ runs: - name: Mask Secrets shell: bash env: - GITHUB_TOKEN: ${{ inputs.token }} + GITHUB_TOKEN: ${{ inputs.token || github.token }} run: | echo "::add-mask::$GITHUB_TOKEN" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Validate Inputs id: validate uses: ./validate-inputs @@ -129,7 +134,7 @@ runs: id: composer shell: bash env: - GITHUB_TOKEN: ${{ inputs.token }} + GITHUB_TOKEN: ${{ inputs.token || github.token }} STABILITY: ${{ inputs.stability }} COMPOSER_VERSION: ${{ inputs.composer-version }} run: | diff --git a/php-tests/action.yml b/php-tests/action.yml index 6c9f138..412d58d 100644 --- a/php-tests/action.yml +++ b/php-tests/action.yml @@ -80,6 +80,11 @@ runs: echo "Input validation completed successfully" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Set Git Config uses: ./set-git-config with: diff --git a/php-version-detect/README.md b/php-version-detect/README.md index a600b42..e0027c7 100644 --- a/php-version-detect/README.md +++ b/php-version-detect/README.md @@ -11,6 +11,7 @@ Detects the PHP version from the project's composer.json, phpunit.xml, or other | name | description | required | default | |-------------------|--------------------------------------------------------------|----------|---------| | `default-version` |

Default PHP version to use if no version is detected.

| `false` | `8.2` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -32,4 +33,10 @@ This action is a `composite` action. # # Required: false # Default: 8.2 + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/php-version-detect/action.yml b/php-version-detect/action.yml index 1fbedee..656ed34 100644 --- a/php-version-detect/action.yml +++ b/php-version-detect/action.yml @@ -15,6 +15,10 @@ inputs: description: 'Default PHP version to use if no version is detected.' required: false default: '8.2' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: php-version: @@ -56,6 +60,11 @@ runs: echo "Input validation completed successfully" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Parse PHP Version id: parse-version uses: ./version-file-parser diff --git a/php-version-detect/rules.yml b/php-version-detect/rules.yml index 4b76d0f..f6d0994 100644 --- a/php-version-detect/rules.yml +++ b/php-version-detect/rules.yml @@ -2,7 +2,7 @@ # Validation rules for php-version-detect action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (1/1 inputs) +# Coverage: 100% (2/2 inputs) # # This file defines validation rules for the php-version-detect GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -16,13 +16,15 @@ generator_version: 1.0.0 required_inputs: [] optional_inputs: - default-version + - token conventions: default-version: semantic_version + token: github_token overrides: default-version: php_version statistics: - total_inputs: 1 - validated_inputs: 1 + total_inputs: 2 + validated_inputs: 2 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -30,7 +32,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: false - has_security_validation: false + has_security_validation: true diff --git a/pr-lint/README.md b/pr-lint/README.md index e95c926..9d3a5e3 100644 --- a/pr-lint/README.md +++ b/pr-lint/README.md @@ -10,7 +10,7 @@ Runs MegaLinter against pull requests | name | description | required | default | |------------|----------------------------------------|----------|-----------------------------| -| `token` |

GitHub token for authentication

| `false` | `${{ github.token }}` | +| `token` |

GitHub token for authentication

| `false` | `""` | | `username` |

GitHub username for commits

| `false` | `github-actions` | | `email` |

GitHub email for commits

| `false` | `github-actions@github.com` | @@ -34,7 +34,7 @@ This action is a `composite` action. # GitHub token for authentication # # Required: false - # Default: ${{ github.token }} + # Default: "" username: # GitHub username for commits diff --git a/pr-lint/action.yml b/pr-lint/action.yml index 9e8a087..b266c30 100644 --- a/pr-lint/action.yml +++ b/pr-lint/action.yml @@ -17,7 +17,7 @@ inputs: token: description: 'GitHub token for authentication' required: false - default: ${{ github.token }} + default: '' username: description: 'GitHub username for commits' required: false @@ -53,7 +53,7 @@ runs: - name: Checkout Code uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - token: ${{ inputs.token }} + token: ${{ inputs.token || github.token }} # If you use VALIDATE_ALL_CODEBASE = true, you can remove this line to # improve performance diff --git a/pre-commit/README.md b/pre-commit/README.md index 09f717f..7c5960d 100644 --- a/pre-commit/README.md +++ b/pre-commit/README.md @@ -12,7 +12,7 @@ Runs pre-commit on the repository and pushes the fixes back to the repository |---------------------|----------------------------------------|----------|-----------------------------| | `pre-commit-config` |

pre-commit configuration file

| `false` | `.pre-commit-config.yaml` | | `base-branch` |

Base branch to compare against

| `false` | `""` | -| `token` |

GitHub token for authentication

| `false` | `${{ github.token }}` | +| `token` |

GitHub token for authentication

| `false` | `""` | | `commit_user` |

Commit user

| `false` | `GitHub Actions` | | `commit_email` |

Commit email

| `false` | `github-actions@github.com` | @@ -48,7 +48,7 @@ This action is a `composite` action. # GitHub token for authentication # # Required: false - # Default: ${{ github.token }} + # Default: "" commit_user: # Commit user diff --git a/pre-commit/action.yml b/pre-commit/action.yml index b11a18e..39ab50f 100644 --- a/pre-commit/action.yml +++ b/pre-commit/action.yml @@ -21,7 +21,7 @@ inputs: token: description: 'GitHub token for authentication' required: false - default: ${{ github.token }} + default: '' commit_user: description: 'Commit user' required: false @@ -42,6 +42,11 @@ outputs: runs: using: composite steps: + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Validate Inputs id: validate uses: ./validate-inputs diff --git a/prettier-check/README.md b/prettier-check/README.md index 6ac1fa4..cb39af3 100644 --- a/prettier-check/README.md +++ b/prettier-check/README.md @@ -21,6 +21,7 @@ Run Prettier check on the repository with advanced configuration and reporting | `max-retries` |

Maximum number of retry attempts

| `false` | `3` | | `plugins` |

Comma-separated list of Prettier plugins to install

| `false` | `""` | | `check-only` |

Only check for formatting issues without fixing

| `false` | `true` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -105,4 +106,10 @@ This action is a `composite` action. # # Required: false # Default: true + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/prettier-check/action.yml b/prettier-check/action.yml index 8a99075..394513c 100644 --- a/prettier-check/action.yml +++ b/prettier-check/action.yml @@ -56,6 +56,10 @@ inputs: description: 'Only check for formatting issues without fixing' required: false default: 'true' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: files-checked: @@ -191,6 +195,11 @@ runs: fi fi + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Setup Node.js id: node-setup uses: ./node-setup @@ -423,7 +432,7 @@ runs: - name: Upload Prettier Results if: always() && inputs.report-format == 'sarif' - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: ${{ inputs.working-directory }}/reports/prettier.sarif category: prettier diff --git a/prettier-check/rules.yml b/prettier-check/rules.yml index 0fda5d4..b2a990b 100644 --- a/prettier-check/rules.yml +++ b/prettier-check/rules.yml @@ -2,7 +2,7 @@ # Validation rules for prettier-check action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (11/11 inputs) +# Coverage: 100% (12/12 inputs) # # This file defines validation rules for the prettier-check GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -25,6 +25,7 @@ optional_inputs: - plugins - prettier-version - report-format + - token - working-directory conventions: cache: boolean @@ -37,11 +38,12 @@ conventions: plugins: plugin_list prettier-version: semantic_version report-format: report_format + token: github_token working-directory: file_path overrides: {} statistics: - total_inputs: 11 - validated_inputs: 11 + total_inputs: 12 + validated_inputs: 12 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -49,7 +51,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: true - has_security_validation: false + has_security_validation: true diff --git a/python-lint-fix/action.yml b/python-lint-fix/action.yml index 24ab1ee..0621bbf 100644 --- a/python-lint-fix/action.yml +++ b/python-lint-fix/action.yml @@ -147,6 +147,12 @@ runs: echo "::warning::GitHub token format may be invalid. Expected format: gh*_36characters" fi fi + + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Detect Python Version id: python-version uses: ./python-version-detect @@ -364,7 +370,7 @@ runs: - name: Upload SARIF Report if: steps.check-files.outputs.result == 'found' - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: ${{ inputs.working-directory }}/reports/flake8.sarif category: 'python-lint' diff --git a/python-version-detect-v2/README.md b/python-version-detect-v2/README.md index 0983760..535007d 100644 --- a/python-version-detect-v2/README.md +++ b/python-version-detect-v2/README.md @@ -11,6 +11,7 @@ Detects Python version from project configuration files using enhanced detection | name | description | required | default | |-------------------|-----------------------------------------------------------------|----------|---------| | `default-version` |

Default Python version to use if no version is detected.

| `false` | `3.12` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -33,4 +34,10 @@ This action is a `composite` action. # # Required: false # Default: 3.12 + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/python-version-detect-v2/action.yml b/python-version-detect-v2/action.yml index 6676c27..e2288f7 100644 --- a/python-version-detect-v2/action.yml +++ b/python-version-detect-v2/action.yml @@ -15,6 +15,10 @@ inputs: description: 'Default Python version to use if no version is detected.' required: false default: '3.12' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: python-version: @@ -57,6 +61,11 @@ runs: echo "Input validation completed successfully" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Parse Python Version id: parse-version uses: ./version-file-parser diff --git a/python-version-detect-v2/rules.yml b/python-version-detect-v2/rules.yml index ad0e6b9..2071ca3 100644 --- a/python-version-detect-v2/rules.yml +++ b/python-version-detect-v2/rules.yml @@ -2,7 +2,7 @@ # Validation rules for python-version-detect-v2 action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (1/1 inputs) +# Coverage: 100% (2/2 inputs) # # This file defines validation rules for the python-version-detect-v2 GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -16,13 +16,15 @@ generator_version: 1.0.0 required_inputs: [] optional_inputs: - default-version + - token conventions: default-version: semantic_version + token: github_token overrides: default-version: python_version statistics: - total_inputs: 1 - validated_inputs: 1 + total_inputs: 2 + validated_inputs: 2 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -30,7 +32,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: false - has_security_validation: false + has_security_validation: true diff --git a/python-version-detect/README.md b/python-version-detect/README.md index 9f73c07..d80c12e 100644 --- a/python-version-detect/README.md +++ b/python-version-detect/README.md @@ -11,6 +11,7 @@ Detects Python version from project configuration files or defaults to a specifi | name | description | required | default | |-------------------|-----------------------------------------------------------------|----------|---------| | `default-version` |

Default Python version to use if no version is detected.

| `false` | `3.12` | +| `token` |

GitHub token for authentication

| `false` | `""` | ### Outputs @@ -32,4 +33,10 @@ This action is a `composite` action. # # Required: false # Default: 3.12 + + token: + # GitHub token for authentication + # + # Required: false + # Default: "" ``` diff --git a/python-version-detect/action.yml b/python-version-detect/action.yml index d903bbe..e4f7e34 100644 --- a/python-version-detect/action.yml +++ b/python-version-detect/action.yml @@ -15,6 +15,10 @@ inputs: description: 'Default Python version to use if no version is detected.' required: false default: '3.12' + token: + description: 'GitHub token for authentication' + required: false + default: '' outputs: python-version: @@ -54,6 +58,11 @@ runs: echo "Input validation completed successfully" + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Parse Python Version id: parse-version uses: ./version-file-parser diff --git a/python-version-detect/rules.yml b/python-version-detect/rules.yml index cb80294..d1e7c18 100644 --- a/python-version-detect/rules.yml +++ b/python-version-detect/rules.yml @@ -2,7 +2,7 @@ # Validation rules for python-version-detect action # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Schema version: 1.0 -# Coverage: 100% (1/1 inputs) +# Coverage: 100% (2/2 inputs) # # This file defines validation rules for the python-version-detect GitHub Action. # Rules are automatically applied by validate-inputs action when this @@ -16,13 +16,15 @@ generator_version: 1.0.0 required_inputs: [] optional_inputs: - default-version + - token conventions: default-version: semantic_version + token: github_token overrides: default-version: python_version statistics: - total_inputs: 1 - validated_inputs: 1 + total_inputs: 2 + validated_inputs: 2 skipped_inputs: 0 coverage_percentage: 100 validation_coverage: 100 @@ -30,7 +32,7 @@ auto_detected: true manual_review_required: false quality_indicators: has_required_inputs: false - has_token_validation: false + has_token_validation: true has_version_validation: true has_file_validation: false - has_security_validation: false + has_security_validation: true diff --git a/stale/README.md b/stale/README.md index 95d3cdc..d4a1c5e 100644 --- a/stale/README.md +++ b/stale/README.md @@ -8,11 +8,11 @@ A GitHub Action to close stale issues and pull requests. ### Inputs -| name | description | required | default | -|---------------------|------------------------------------------------------------------------|----------|-----------------------| -| `token` |

GitHub token for authentication

| `false` | `${{ github.token }}` | -| `days-before-stale` |

Number of days of inactivity before an issue is marked as stale

| `false` | `30` | -| `days-before-close` |

Number of days of inactivity before a stale issue is closed

| `false` | `7` | +| name | description | required | default | +|---------------------|------------------------------------------------------------------------|----------|---------| +| `token` |

GitHub token for authentication

| `false` | `""` | +| `days-before-stale` |

Number of days of inactivity before an issue is marked as stale

| `false` | `30` | +| `days-before-close` |

Number of days of inactivity before a stale issue is closed

| `false` | `7` | ### Outputs @@ -34,7 +34,7 @@ This action is a `composite` action. # GitHub token for authentication # # Required: false - # Default: ${{ github.token }} + # Default: "" days-before-stale: # Number of days of inactivity before an issue is marked as stale diff --git a/stale/action.yml b/stale/action.yml index c23c570..57027b5 100644 --- a/stale/action.yml +++ b/stale/action.yml @@ -15,7 +15,7 @@ inputs: token: description: 'GitHub token for authentication' required: false - default: ${{ github.token }} + default: '' days-before-stale: description: 'Number of days of inactivity before an issue is marked as stale' required: false @@ -36,12 +36,17 @@ outputs: runs: using: composite steps: + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Validate Inputs id: validate uses: ./validate-inputs with: action: 'stale' - token: ${{ inputs.token }} + token: ${{ inputs.token || github.token }} days-before-stale: ${{ inputs.days-before-stale }} days-before-close: ${{ inputs.days-before-close }} @@ -49,7 +54,7 @@ runs: id: stale uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 with: - repo-token: ${{ inputs.token }} + repo-token: ${{ inputs.token || github.token }} days-before-stale: ${{ inputs.days-before-stale }} days-before-close: ${{ inputs.days-before-close }} remove-stale-when-updated: true diff --git a/terraform-lint-fix/README.md b/terraform-lint-fix/README.md index 9ec2b7a..1c12e5d 100644 --- a/terraform-lint-fix/README.md +++ b/terraform-lint-fix/README.md @@ -18,7 +18,7 @@ Lints and fixes Terraform files with advanced validation and security checks. | `auto-fix` |

Automatically fix issues when possible

| `false` | `true` | | `max-retries` |

Maximum number of retry attempts

| `false` | `3` | | `format` |

Output format (compact, json, checkstyle, junit, sarif)

| `false` | `sarif` | -| `token` |

GitHub token for authentication

| `false` | `${{ github.token }}` | +| `token` |

GitHub token for authentication

| `false` | `""` | | `username` |

GitHub username for commits

| `false` | `github-actions` | | `email` |

GitHub email for commits

| `false` | `github-actions@github.com` | @@ -91,7 +91,7 @@ This action is a `composite` action. # GitHub token for authentication # # Required: false - # Default: ${{ github.token }} + # Default: "" username: # GitHub username for commits diff --git a/terraform-lint-fix/action.yml b/terraform-lint-fix/action.yml index 3bcd0aa..cbb0f8a 100644 --- a/terraform-lint-fix/action.yml +++ b/terraform-lint-fix/action.yml @@ -47,7 +47,7 @@ inputs: token: description: 'GitHub token for authentication' required: false - default: ${{ github.token }} + default: '' username: description: 'GitHub username for commits' required: false @@ -71,12 +71,17 @@ outputs: runs: using: composite steps: + - name: Checkout Repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + token: ${{ inputs.token || github.token }} + - name: Validate Inputs id: validate uses: ./validate-inputs with: action-type: 'terraform-lint-fix' - token: ${{ inputs.token }} + token: ${{ inputs.token || github.token }} email: ${{ inputs.email }} username: ${{ inputs.username }} terraform-version: ${{ inputs.terraform-version }} @@ -267,7 +272,7 @@ runs: if: ${{ fromJSON(steps.fix.outputs.fixed_count) > 0 }} uses: ./set-git-config with: - token: ${{ inputs.token }} + token: ${{ inputs.token || github.token }} username: ${{ inputs.username }} email: ${{ inputs.email }} @@ -297,7 +302,7 @@ runs: - name: Upload SARIF Report if: steps.check-files.outputs.found == 'true' && inputs.format == 'sarif' - uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 + uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9 with: sarif_file: ${{ env.VALIDATED_WORKING_DIR }}/reports/tflint.sarif category: terraform-lint diff --git a/validate-inputs/CustomValidator.py b/validate-inputs/CustomValidator.py index 5fd89e5..e3dfd59 100755 --- a/validate-inputs/CustomValidator.py +++ b/validate-inputs/CustomValidator.py @@ -4,9 +4,9 @@ from __future__ import annotations -from pathlib import Path import re import sys +from pathlib import Path # Add validate-inputs directory to path to import validators validate_inputs_path = Path(__file__).parent diff --git a/validate-inputs/docs/ACTION_MAINTAINER.md b/validate-inputs/docs/ACTION_MAINTAINER.md index 5c62e60..11635b6 100644 --- a/validate-inputs/docs/ACTION_MAINTAINER.md +++ b/validate-inputs/docs/ACTION_MAINTAINER.md @@ -200,10 +200,10 @@ class CustomValidator(BaseValidator): return False return True - def get_validation_rules(self) -> dict: - """Get validation rules.""" - rules_path = Path(__file__).parent / "rules.yml" - return self.load_rules(rules_path) + def get_validation_rules(self) -> dict: + """Get validation rules.""" + rules_path = Path(__file__).parent / "rules.yml" + return self.load_rules(rules_path) ``` 1. **Test your validator** (optional but recommended): diff --git a/validate-inputs/docs/README_ARCHITECTURE.md b/validate-inputs/docs/README_ARCHITECTURE.md index b0dd8b7..4cf45db 100644 --- a/validate-inputs/docs/README_ARCHITECTURE.md +++ b/validate-inputs/docs/README_ARCHITECTURE.md @@ -5,7 +5,7 @@ A comprehensive, modular validation system for GitHub Actions inputs with automa ## Features - 🔍 **Automatic Validation** - Convention-based input detection -- 🧩 **Modular Architecture** - 11+ specialized validators +- 🧩 **Modular Architecture** - 9 specialized validators - 🛡️ **Security First** - Injection and traversal protection - 🎯 **Custom Validators** - Action-specific validation logic - 🧪 **Test Generation** - Automatic test scaffolding @@ -332,8 +332,8 @@ class CustomValidator(BaseValidator): ## Quality Metrics -- **Test Coverage**: 100% (303 tests) -- **Validators**: 11 core + unlimited custom +- **Test Coverage**: 100% (769 tests) +- **Validators**: 9 specialized + unlimited custom - **Performance**: < 10ms typical validation time - **Zero Dependencies**: Uses only Python stdlib + PyYAML - **Production Ready**: Zero defects policy diff --git a/validate-inputs/modular_validator.py b/validate-inputs/modular_validator.py index f706cc9..150c4ac 100755 --- a/validate-inputs/modular_validator.py +++ b/validate-inputs/modular_validator.py @@ -9,8 +9,8 @@ from __future__ import annotations import logging import os -from pathlib import Path import sys +from pathlib import Path # Add validators module to path sys.path.insert(0, str(Path(__file__).parent)) diff --git a/validate-inputs/pyproject.toml b/validate-inputs/pyproject.toml new file mode 100644 index 0000000..2c88be0 --- /dev/null +++ b/validate-inputs/pyproject.toml @@ -0,0 +1,131 @@ +[project] +name = "github-actions-validate-inputs" +version = "1.0.0" +description = "Modular input validation system for GitHub Actions with automatic convention-based detection" +readme = "README.md" +requires-python = ">=3.8" +authors = [ + {name = "Ismo Vuorinen", email = "ismo@ivuorinen.net"} +] +license = {text = "MIT"} +keywords = [ + "github-actions", + "validation", + "input-validation", + "security", + "ci-cd" +] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Quality Assurance", + "Topic :: Software Development :: Testing", +] + +dependencies = [ + "pyyaml>=6.0", +] + +[project.optional-dependencies] +dev = [ + "pytest>=7.0.0", + "pytest-cov>=4.0.0", + "ruff>=0.1.0", +] + +[project.urls] +Homepage = "https://github.com/ivuorinen/actions" +Repository = "https://github.com/ivuorinen/actions.git" +Issues = "https://github.com/ivuorinen/actions/issues" + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["validators", "tests"] + +[tool.pytest.ini_options] +minversion = "7.0" +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] +addopts = [ + "-ra", + "--strict-markers", + "--strict-config", + "--showlocals", +] +markers = [ + "slow: marks tests as slow (deselect with '-m \"not slow\"')", + "integration: marks tests as integration tests", +] + +[tool.coverage.run] +source = ["validators"] +omit = [ + "*/tests/*", + "*/test_*.py", + "*/__pycache__/*", +] + +[tool.coverage.report] +exclude_lines = [ + "pragma: no cover", + "def __repr__", + "raise AssertionError", + "raise NotImplementedError", + "if __name__ == .__main__.:", + "if TYPE_CHECKING:", + "@abstractmethod", +] +show_missing = true +precision = 2 + +[tool.ruff] +target-version = "py38" +line-length = 100 + +[tool.ruff.lint] +select = [ + "E", # pycodestyle errors + "W", # pycodestyle warnings + "F", # pyflakes + "I", # isort + "B", # flake8-bugbear + "C4", # flake8-comprehensions + "UP", # pyupgrade + "ARG", # flake8-unused-arguments + "SIM", # flake8-simplify +] +ignore = [ + "E402", # module level import not at top of file (intentional for sys.path manipulation) + "E501", # line too long (handled by formatter) + "B008", # do not perform function calls in argument defaults + "W191", # indentation contains tabs +] + +[tool.ruff.lint.per-file-ignores] +"tests/**/*.py" = [ + "ARG", # Unused function arguments + "S101", # Use of assert + "SIM117", # Nested with statements (stylistic preference in tests) +] + +[tool.ruff.lint.isort] +known-first-party = ["validators"] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "lf" diff --git a/validate-inputs/scripts/benchmark-validator.py b/validate-inputs/scripts/benchmark-validator.py index 3001a9c..2eb0073 100755 --- a/validate-inputs/scripts/benchmark-validator.py +++ b/validate-inputs/scripts/benchmark-validator.py @@ -8,10 +8,10 @@ from __future__ import annotations import argparse import json -from pathlib import Path import statistics import sys import time +from pathlib import Path from typing import Any # Add parent directory to path for imports @@ -171,8 +171,8 @@ class ValidatorBenchmark: inputs: Dictionary of inputs to validate """ import cProfile - from io import StringIO import pstats + from io import StringIO print(f"\nProfiling {action_type} validator...") print("-" * 70) diff --git a/validate-inputs/scripts/debug-validator.py b/validate-inputs/scripts/debug-validator.py index ec8bf89..ae2a1ac 100755 --- a/validate-inputs/scripts/debug-validator.py +++ b/validate-inputs/scripts/debug-validator.py @@ -13,8 +13,8 @@ from __future__ import annotations import argparse import json import logging -from pathlib import Path import sys +from pathlib import Path from typing import TYPE_CHECKING # Add parent directory to path for imports diff --git a/validate-inputs/scripts/generate-tests.py b/validate-inputs/scripts/generate-tests.py index 0d88a93..01dc493 100755 --- a/validate-inputs/scripts/generate-tests.py +++ b/validate-inputs/scripts/generate-tests.py @@ -10,9 +10,9 @@ from __future__ import annotations import argparse import logging -from pathlib import Path import re import sys +from pathlib import Path import yaml # pylint: disable=import-error diff --git a/validate-inputs/scripts/update-validators.py b/validate-inputs/scripts/update-validators.py index 68b77fb..92770f5 100755 --- a/validate-inputs/scripts/update-validators.py +++ b/validate-inputs/scripts/update-validators.py @@ -12,9 +12,9 @@ Usage: from __future__ import annotations import argparse -from pathlib import Path import re import sys +from pathlib import Path from typing import Any import yaml # pylint: disable=import-error diff --git a/validate-inputs/tests/test_base.py b/validate-inputs/tests/test_base.py index 6cea38b..cc78759 100644 --- a/validate-inputs/tests/test_base.py +++ b/validate-inputs/tests/test_base.py @@ -2,9 +2,9 @@ from __future__ import annotations -from pathlib import Path import sys import unittest +from pathlib import Path from unittest.mock import patch # Add parent directory to path diff --git a/validate-inputs/tests/test_boolean_validator.py b/validate-inputs/tests/test_boolean_validator.py index 9c1a1b8..e398b42 100644 --- a/validate-inputs/tests/test_boolean_validator.py +++ b/validate-inputs/tests/test_boolean_validator.py @@ -1,16 +1,15 @@ """Tests for the BooleanValidator module.""" -from pathlib import Path import sys +from pathlib import Path import pytest # pylint: disable=import-error # Add the parent directory to the path sys.path.insert(0, str(Path(__file__).parent.parent)) -from validators.boolean import BooleanValidator - from tests.fixtures.version_test_data import BOOLEAN_INVALID, BOOLEAN_VALID +from validators.boolean import BooleanValidator class TestBooleanValidator: diff --git a/validate-inputs/tests/test_codeql-analysis_custom.py b/validate-inputs/tests/test_codeql-analysis_custom.py index 4e06e10..df73c14 100644 --- a/validate-inputs/tests/test_codeql-analysis_custom.py +++ b/validate-inputs/tests/test_codeql-analysis_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "codeql-analysis" diff --git a/validate-inputs/tests/test_common-cache_custom.py b/validate-inputs/tests/test_common-cache_custom.py index 185e118..b4cc5e3 100644 --- a/validate-inputs/tests/test_common-cache_custom.py +++ b/validate-inputs/tests/test_common-cache_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "common-cache" diff --git a/validate-inputs/tests/test_common-file-check_custom.py b/validate-inputs/tests/test_common-file-check_custom.py index 5615b90..dbaf235 100644 --- a/validate-inputs/tests/test_common-file-check_custom.py +++ b/validate-inputs/tests/test_common-file-check_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "common-file-check" diff --git a/validate-inputs/tests/test_common-retry_custom.py b/validate-inputs/tests/test_common-retry_custom.py index 4256332..8b931b8 100644 --- a/validate-inputs/tests/test_common-retry_custom.py +++ b/validate-inputs/tests/test_common-retry_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "common-retry" diff --git a/validate-inputs/tests/test_compress-images_custom.py b/validate-inputs/tests/test_compress-images_custom.py index d8fee7e..803a09b 100644 --- a/validate-inputs/tests/test_compress-images_custom.py +++ b/validate-inputs/tests/test_compress-images_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "compress-images" diff --git a/validate-inputs/tests/test_convention_mapper.py b/validate-inputs/tests/test_convention_mapper.py index 6605c71..63c2506 100644 --- a/validate-inputs/tests/test_convention_mapper.py +++ b/validate-inputs/tests/test_convention_mapper.py @@ -1,7 +1,7 @@ """Tests for the ConventionMapper class.""" -from pathlib import Path import sys +from pathlib import Path # Add the parent directory to the path sys.path.insert(0, str(Path(__file__).parent.parent)) diff --git a/validate-inputs/tests/test_custom_validators.py b/validate-inputs/tests/test_custom_validators.py index d7a8578..d479aa9 100644 --- a/validate-inputs/tests/test_custom_validators.py +++ b/validate-inputs/tests/test_custom_validators.py @@ -1,7 +1,7 @@ """Tests for custom validators in action directories.""" -from pathlib import Path import sys +from pathlib import Path # Add parent directory to path sys.path.insert(0, str(Path(__file__).parent.parent)) diff --git a/validate-inputs/tests/test_docker-build_custom.py b/validate-inputs/tests/test_docker-build_custom.py index 91dcd21..4b7a09d 100644 --- a/validate-inputs/tests/test_docker-build_custom.py +++ b/validate-inputs/tests/test_docker-build_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "docker-build" diff --git a/validate-inputs/tests/test_docker-publish-gh_custom.py b/validate-inputs/tests/test_docker-publish-gh_custom.py index 919fb0a..b886d2b 100644 --- a/validate-inputs/tests/test_docker-publish-gh_custom.py +++ b/validate-inputs/tests/test_docker-publish-gh_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "docker-publish-gh" diff --git a/validate-inputs/tests/test_docker-publish-hub_custom.py b/validate-inputs/tests/test_docker-publish-hub_custom.py index 0e5da83..60a62af 100644 --- a/validate-inputs/tests/test_docker-publish-hub_custom.py +++ b/validate-inputs/tests/test_docker-publish-hub_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "docker-publish-hub" diff --git a/validate-inputs/tests/test_docker-publish_custom.py b/validate-inputs/tests/test_docker-publish_custom.py index 81e6b05..6e33320 100644 --- a/validate-inputs/tests/test_docker-publish_custom.py +++ b/validate-inputs/tests/test_docker-publish_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "docker-publish" diff --git a/validate-inputs/tests/test_docker_validator.py b/validate-inputs/tests/test_docker_validator.py index f7cc6e9..bb96a4c 100644 --- a/validate-inputs/tests/test_docker_validator.py +++ b/validate-inputs/tests/test_docker_validator.py @@ -1,7 +1,7 @@ """Tests for the DockerValidator module.""" -from pathlib import Path import sys +from pathlib import Path # Add the parent directory to the path sys.path.insert(0, str(Path(__file__).parent.parent)) diff --git a/validate-inputs/tests/test_eslint-check_custom.py b/validate-inputs/tests/test_eslint-check_custom.py index 3102c1c..67ba65e 100644 --- a/validate-inputs/tests/test_eslint-check_custom.py +++ b/validate-inputs/tests/test_eslint-check_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "eslint-check" diff --git a/validate-inputs/tests/test_file_validator.py b/validate-inputs/tests/test_file_validator.py index 9cba516..43983b3 100644 --- a/validate-inputs/tests/test_file_validator.py +++ b/validate-inputs/tests/test_file_validator.py @@ -1,16 +1,15 @@ """Tests for the FileValidator module.""" -from pathlib import Path import sys +from pathlib import Path import pytest # pylint: disable=import-error # Add the parent directory to the path sys.path.insert(0, str(Path(__file__).parent.parent)) -from validators.file import FileValidator - from tests.fixtures.version_test_data import FILE_PATH_INVALID, FILE_PATH_VALID +from validators.file import FileValidator class TestFileValidator: diff --git a/validate-inputs/tests/test_generate_tests.py b/validate-inputs/tests/test_generate_tests.py index 65b3de3..15ef675 100644 --- a/validate-inputs/tests/test_generate_tests.py +++ b/validate-inputs/tests/test_generate_tests.py @@ -2,9 +2,9 @@ # pylint: disable=protected-access # Testing private methods is intentional import importlib.util -from pathlib import Path import sys import tempfile +from pathlib import Path import yaml # pylint: disable=import-error diff --git a/validate-inputs/tests/test_go-lint_custom.py b/validate-inputs/tests/test_go-lint_custom.py index 326ead3..5edc21b 100644 --- a/validate-inputs/tests/test_go-lint_custom.py +++ b/validate-inputs/tests/test_go-lint_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "go-lint" diff --git a/validate-inputs/tests/test_go-version-detect_custom.py b/validate-inputs/tests/test_go-version-detect_custom.py index b997b03..7167e01 100644 --- a/validate-inputs/tests/test_go-version-detect_custom.py +++ b/validate-inputs/tests/test_go-version-detect_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "go-version-detect" diff --git a/validate-inputs/tests/test_integration.py b/validate-inputs/tests/test_integration.py index 5f3165b..70566d0 100644 --- a/validate-inputs/tests/test_integration.py +++ b/validate-inputs/tests/test_integration.py @@ -1,10 +1,10 @@ """Integration tests for the validator script execution.""" import os -from pathlib import Path import subprocess import sys import tempfile +from pathlib import Path import pytest # pylint: disable=import-error @@ -20,7 +20,8 @@ class TestValidatorIntegration: del os.environ[key] # Create temporary output file - self.temp_output = tempfile.NamedTemporaryFile(mode="w", delete=False) + # noqa: SIM115 - Need persistent file for teardown, can't use context manager + self.temp_output = tempfile.NamedTemporaryFile(mode="w", delete=False) # noqa: SIM115 os.environ["GITHUB_OUTPUT"] = self.temp_output.name self.temp_output.close() diff --git a/validate-inputs/tests/test_modular_validator.py b/validate-inputs/tests/test_modular_validator.py index b468a17..fc46d18 100644 --- a/validate-inputs/tests/test_modular_validator.py +++ b/validate-inputs/tests/test_modular_validator.py @@ -3,8 +3,8 @@ from __future__ import annotations import os -from pathlib import Path import sys +from pathlib import Path from unittest.mock import MagicMock, patch import pytest # pylint: disable=import-error @@ -24,14 +24,11 @@ class TestModularValidator: output_file = tmp_path / "github_output" output_file.touch() - with ( - patch.dict( - os.environ, - {"GITHUB_OUTPUT": str(output_file)}, - clear=True, - ), - pytest.raises(SystemExit) as exc_info, - ): + with patch.dict( + os.environ, + {"GITHUB_OUTPUT": str(output_file)}, + clear=True, + ), pytest.raises(SystemExit) as exc_info: main() assert exc_info.value.code == 1 @@ -45,19 +42,16 @@ class TestModularValidator: output_file.touch() # docker-build is a known action with a validator - with ( - patch.dict( - os.environ, - { - "GITHUB_OUTPUT": str(output_file), - "INPUT_ACTION_TYPE": "docker-build", - "INPUT_TAG": "v1.0.0", - "INPUT_IMAGE_NAME": "myapp", - }, - clear=True, - ), - patch("modular_validator.logger") as mock_logger, - ): + with patch.dict( + os.environ, + { + "GITHUB_OUTPUT": str(output_file), + "INPUT_ACTION_TYPE": "docker-build", + "INPUT_TAG": "v1.0.0", + "INPUT_IMAGE_NAME": "myapp", + }, + clear=True, + ), patch("modular_validator.logger") as mock_logger: main() content = output_file.read_text() @@ -69,20 +63,18 @@ class TestModularValidator: output_file = tmp_path / "github_output" output_file.touch() - with ( - patch.dict( - os.environ, - { - "GITHUB_OUTPUT": str(output_file), - "INPUT_ACTION_TYPE": "docker-build", - "INPUT_TAG": "invalid_tag!", # Invalid tag format - }, - clear=True, - ), - patch("modular_validator.logger") as mock_logger, - pytest.raises(SystemExit) as exc_info, + with patch.dict( + os.environ, + { + "GITHUB_OUTPUT": str(output_file), + "INPUT_ACTION_TYPE": "docker-build", + "INPUT_TAG": "invalid_tag!", # Invalid tag format + }, + clear=True, ): - main() + with patch("modular_validator.logger") as mock_logger: + with pytest.raises(SystemExit) as exc_info: + main() assert exc_info.value.code == 1 content = output_file.read_text() @@ -99,21 +91,18 @@ class TestModularValidator: mock_validator.validate_inputs.return_value = True mock_validator.errors = [] - with ( - patch.dict( - os.environ, - { - "GITHUB_OUTPUT": str(output_file), - "INPUT_ACTION_TYPE": "docker-build", - "INPUT_TAG": "v1.0.0", - "INPUT_IMAGE_NAME": "myapp", - "INPUT_BUILD_ARGS": "NODE_ENV=prod", - "NOT_AN_INPUT": "should_be_ignored", - }, - clear=True, - ), - patch("modular_validator.get_validator", return_value=mock_validator), - ): + with patch.dict( + os.environ, + { + "GITHUB_OUTPUT": str(output_file), + "INPUT_ACTION_TYPE": "docker-build", + "INPUT_TAG": "v1.0.0", + "INPUT_IMAGE_NAME": "myapp", + "INPUT_BUILD_ARGS": "NODE_ENV=prod", + "NOT_AN_INPUT": "should_be_ignored", + }, + clear=True, + ), patch("modular_validator.get_validator", return_value=mock_validator): main() # Check that validate_inputs was called with correct inputs @@ -133,18 +122,15 @@ class TestModularValidator: mock_validator.validate_inputs.return_value = True mock_validator.errors = [] - with ( - patch.dict( - os.environ, - { - "GITHUB_OUTPUT": str(output_file), - "INPUT_ACTION_TYPE": "docker-build", - "INPUT_BUILD_ARGS": "test=value", - }, - clear=True, - ), - patch("modular_validator.get_validator", return_value=mock_validator), - ): + with patch.dict( + os.environ, + { + "GITHUB_OUTPUT": str(output_file), + "INPUT_ACTION_TYPE": "docker-build", + "INPUT_BUILD_ARGS": "test=value", + }, + clear=True, + ), patch("modular_validator.get_validator", return_value=mock_validator): main() # Check that both underscore and dash versions are present @@ -160,17 +146,14 @@ class TestModularValidator: mock_validator.validate_inputs.return_value = True mock_validator.errors = [] - with ( - patch.dict( - os.environ, - { - "GITHUB_OUTPUT": str(output_file), - "INPUT_ACTION_TYPE": "docker-build", - }, - clear=True, - ), - patch("modular_validator.get_validator", return_value=mock_validator) as mock_get, - ): + with patch.dict( + os.environ, + { + "GITHUB_OUTPUT": str(output_file), + "INPUT_ACTION_TYPE": "docker-build", + }, + clear=True, + ), patch("modular_validator.get_validator", return_value=mock_validator) as mock_get: main() # get_validator should be called with underscore version @@ -181,19 +164,16 @@ class TestModularValidator: output_file = tmp_path / "github_output" output_file.touch() - with ( - patch.dict( - os.environ, - { - "GITHUB_OUTPUT": str(output_file), - "INPUT_ACTION_TYPE": "docker-build", - }, - clear=True, - ), - patch("modular_validator.get_validator", side_effect=ValueError("Test error")), - pytest.raises(SystemExit) as exc_info, - ): - main() + with patch.dict( + os.environ, + { + "GITHUB_OUTPUT": str(output_file), + "INPUT_ACTION_TYPE": "docker-build", + }, + clear=True, + ), patch("modular_validator.get_validator", side_effect=ValueError("Test error")): + with pytest.raises(SystemExit) as exc_info: + main() assert exc_info.value.code == 1 content = output_file.read_text() @@ -206,13 +186,11 @@ class TestModularValidator: fallback_path = Path.home() / "github_output" try: - with ( - patch.dict(os.environ, {"INPUT_ACTION_TYPE": "docker-build"}, clear=True), - patch("modular_validator.get_validator", side_effect=ValueError("Test error")), - patch("modular_validator.logger"), - pytest.raises(SystemExit) as exc_info, - ): - main() + with patch.dict(os.environ, {"INPUT_ACTION_TYPE": "docker-build"}, clear=True): + with patch("modular_validator.get_validator", side_effect=ValueError("Test error")): + with patch("modular_validator.logger"): + with pytest.raises(SystemExit) as exc_info: + main() assert exc_info.value.code == 1 @@ -235,19 +213,16 @@ class TestModularValidator: mock_validator.validate_inputs.return_value = False mock_validator.errors = ["Error 1", "Error 2"] - with ( - patch.dict( - os.environ, - { - "GITHUB_OUTPUT": str(output_file), - "INPUT_ACTION_TYPE": "docker-build", - }, - clear=True, - ), - patch("modular_validator.get_validator", return_value=mock_validator), - pytest.raises(SystemExit) as exc_info, - ): - main() + with patch.dict( + os.environ, + { + "GITHUB_OUTPUT": str(output_file), + "INPUT_ACTION_TYPE": "docker-build", + }, + clear=True, + ), patch("modular_validator.get_validator", return_value=mock_validator): + with pytest.raises(SystemExit) as exc_info: + main() assert exc_info.value.code == 1 content = output_file.read_text() @@ -260,18 +235,16 @@ class TestModularValidator: output_file = tmp_path / "github_output" output_file.touch() - with ( - patch.dict( - os.environ, - { - "GITHUB_OUTPUT": str(output_file), - "INPUT_ACTION_TYPE": " ", # Whitespace only - }, - clear=True, - ), - pytest.raises(SystemExit) as exc_info, + with patch.dict( + os.environ, + { + "GITHUB_OUTPUT": str(output_file), + "INPUT_ACTION_TYPE": " ", # Whitespace only + }, + clear=True, ): - main() + with pytest.raises(SystemExit) as exc_info: + main() assert exc_info.value.code == 1 content = output_file.read_text() diff --git a/validate-inputs/tests/test_network_validator.py b/validate-inputs/tests/test_network_validator.py index 60b344f..17eac49 100644 --- a/validate-inputs/tests/test_network_validator.py +++ b/validate-inputs/tests/test_network_validator.py @@ -1,21 +1,20 @@ """Tests for the NetworkValidator module.""" -from pathlib import Path import sys +from pathlib import Path import pytest # pylint: disable=import-error # Add the parent directory to the path sys.path.insert(0, str(Path(__file__).parent.parent)) -from validators.network import NetworkValidator - from tests.fixtures.version_test_data import ( EMAIL_INVALID, EMAIL_VALID, USERNAME_INVALID, USERNAME_VALID, ) +from validators.network import NetworkValidator class TestNetworkValidator: diff --git a/validate-inputs/tests/test_node-setup_custom.py b/validate-inputs/tests/test_node-setup_custom.py index fecb648..440f0ce 100644 --- a/validate-inputs/tests/test_node-setup_custom.py +++ b/validate-inputs/tests/test_node-setup_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "node-setup" diff --git a/validate-inputs/tests/test_npm-publish_custom.py b/validate-inputs/tests/test_npm-publish_custom.py index adfbca1..8c81b99 100644 --- a/validate-inputs/tests/test_npm-publish_custom.py +++ b/validate-inputs/tests/test_npm-publish_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "npm-publish" diff --git a/validate-inputs/tests/test_numeric_validator.py b/validate-inputs/tests/test_numeric_validator.py index 305b39c..f5c5304 100644 --- a/validate-inputs/tests/test_numeric_validator.py +++ b/validate-inputs/tests/test_numeric_validator.py @@ -1,7 +1,7 @@ """Tests for the NumericValidator module.""" -from pathlib import Path import sys +from pathlib import Path import pytest # pylint: disable=import-error @@ -9,9 +9,8 @@ import pytest # pylint: disable=import-error sys.path.insert(0, str(Path(__file__).parent.parent)) # pylint: disable=wrong-import-position -from validators.numeric import NumericValidator - from tests.fixtures.version_test_data import NUMERIC_RANGE_INVALID, NUMERIC_RANGE_VALID +from validators.numeric import NumericValidator class TestNumericValidator: diff --git a/validate-inputs/tests/test_php-composer_custom.py b/validate-inputs/tests/test_php-composer_custom.py index 3fd601a..8d22626 100644 --- a/validate-inputs/tests/test_php-composer_custom.py +++ b/validate-inputs/tests/test_php-composer_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "php-composer" diff --git a/validate-inputs/tests/test_php-laravel-phpunit_custom.py b/validate-inputs/tests/test_php-laravel-phpunit_custom.py index 7c265f7..39e6b5a 100644 --- a/validate-inputs/tests/test_php-laravel-phpunit_custom.py +++ b/validate-inputs/tests/test_php-laravel-phpunit_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "php-laravel-phpunit" diff --git a/validate-inputs/tests/test_php-tests_custom.py b/validate-inputs/tests/test_php-tests_custom.py index bc6ea76..670c4f9 100644 --- a/validate-inputs/tests/test_php-tests_custom.py +++ b/validate-inputs/tests/test_php-tests_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "php-tests" diff --git a/validate-inputs/tests/test_php-version-detect_custom.py b/validate-inputs/tests/test_php-version-detect_custom.py index c34c123..aaed1da 100644 --- a/validate-inputs/tests/test_php-version-detect_custom.py +++ b/validate-inputs/tests/test_php-version-detect_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "php-version-detect" diff --git a/validate-inputs/tests/test_pre-commit_custom.py b/validate-inputs/tests/test_pre-commit_custom.py index c1281bd..b7b2f80 100644 --- a/validate-inputs/tests/test_pre-commit_custom.py +++ b/validate-inputs/tests/test_pre-commit_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "pre-commit" diff --git a/validate-inputs/tests/test_prettier-check_custom.py b/validate-inputs/tests/test_prettier-check_custom.py index 977fa67..2a92937 100644 --- a/validate-inputs/tests/test_prettier-check_custom.py +++ b/validate-inputs/tests/test_prettier-check_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "prettier-check" diff --git a/validate-inputs/tests/test_prettier-fix_custom.py b/validate-inputs/tests/test_prettier-fix_custom.py index aa9b35f..3f7066e 100644 --- a/validate-inputs/tests/test_prettier-fix_custom.py +++ b/validate-inputs/tests/test_prettier-fix_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "prettier-fix" diff --git a/validate-inputs/tests/test_python-lint-fix_custom.py b/validate-inputs/tests/test_python-lint-fix_custom.py index c301e04..8d07912 100644 --- a/validate-inputs/tests/test_python-lint-fix_custom.py +++ b/validate-inputs/tests/test_python-lint-fix_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "python-lint-fix" diff --git a/validate-inputs/tests/test_python-version-detect-v2_custom.py b/validate-inputs/tests/test_python-version-detect-v2_custom.py index 52fa63c..6530515 100644 --- a/validate-inputs/tests/test_python-version-detect-v2_custom.py +++ b/validate-inputs/tests/test_python-version-detect-v2_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "python-version-detect-v2" diff --git a/validate-inputs/tests/test_python-version-detect_custom.py b/validate-inputs/tests/test_python-version-detect_custom.py index 21a0e32..e30b2b1 100644 --- a/validate-inputs/tests/test_python-version-detect_custom.py +++ b/validate-inputs/tests/test_python-version-detect_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "python-version-detect" diff --git a/validate-inputs/tests/test_registry.py b/validate-inputs/tests/test_registry.py index 46514f3..0b39c39 100644 --- a/validate-inputs/tests/test_registry.py +++ b/validate-inputs/tests/test_registry.py @@ -2,10 +2,10 @@ from __future__ import annotations -from pathlib import Path import sys import tempfile import unittest +from pathlib import Path from unittest.mock import MagicMock, patch # Add parent directory to path diff --git a/validate-inputs/tests/test_security_validator.py b/validate-inputs/tests/test_security_validator.py index b1413f6..0aa18f3 100644 --- a/validate-inputs/tests/test_security_validator.py +++ b/validate-inputs/tests/test_security_validator.py @@ -1,7 +1,7 @@ """Tests for the SecurityValidator module.""" -from pathlib import Path import sys +from pathlib import Path # Add the parent directory to the path sys.path.insert(0, str(Path(__file__).parent.parent)) diff --git a/validate-inputs/tests/test_set-git-config_custom.py b/validate-inputs/tests/test_set-git-config_custom.py index c3414ee..49de20d 100644 --- a/validate-inputs/tests/test_set-git-config_custom.py +++ b/validate-inputs/tests/test_set-git-config_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "set-git-config" diff --git a/validate-inputs/tests/test_sync-labels_custom.py b/validate-inputs/tests/test_sync-labels_custom.py index 469df62..551f1be 100644 --- a/validate-inputs/tests/test_sync-labels_custom.py +++ b/validate-inputs/tests/test_sync-labels_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "sync-labels" diff --git a/validate-inputs/tests/test_terraform-lint-fix_custom.py b/validate-inputs/tests/test_terraform-lint-fix_custom.py index dace1ff..334f143 100644 --- a/validate-inputs/tests/test_terraform-lint-fix_custom.py +++ b/validate-inputs/tests/test_terraform-lint-fix_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "terraform-lint-fix" diff --git a/validate-inputs/tests/test_token_validator.py b/validate-inputs/tests/test_token_validator.py index 535b415..ad7b8d3 100644 --- a/validate-inputs/tests/test_token_validator.py +++ b/validate-inputs/tests/test_token_validator.py @@ -1,7 +1,7 @@ """Tests for the TokenValidator module.""" -from pathlib import Path import sys +from pathlib import Path import pytest # pylint: disable=import-error @@ -9,9 +9,8 @@ import pytest # pylint: disable=import-error sys.path.insert(0, str(Path(__file__).parent.parent)) # pylint: disable=wrong-import-position -from validators.token import TokenValidator - from tests.fixtures.version_test_data import GITHUB_TOKEN_INVALID, GITHUB_TOKEN_VALID +from validators.token import TokenValidator class TestTokenValidator: diff --git a/validate-inputs/tests/test_update_validators.py b/validate-inputs/tests/test_update_validators.py index 3cdba90..3401f1d 100644 --- a/validate-inputs/tests/test_update_validators.py +++ b/validate-inputs/tests/test_update_validators.py @@ -2,9 +2,9 @@ import argparse import importlib.util -from pathlib import Path import sys import tempfile +from pathlib import Path from unittest.mock import patch import yaml # pylint: disable=import-error @@ -440,13 +440,10 @@ class TestCLIFunctionality: """Test main function with --dry-run flag.""" test_args = ["update-validators.py", "--dry-run"] - with ( - patch("sys.argv", test_args), - patch.object( - ValidationRuleGenerator, - "generate_rules", - ) as mock_generate, - ): + with patch("sys.argv", test_args), patch.object( + ValidationRuleGenerator, + "generate_rules", + ) as mock_generate: main() mock_generate.assert_called_once() @@ -454,13 +451,10 @@ class TestCLIFunctionality: """Test main function with --action flag.""" test_args = ["update-validators.py", "--action", "test-action"] - with ( - patch("sys.argv", test_args), - patch.object( - ValidationRuleGenerator, - "generate_rules", - ) as mock_generate, - ): + with patch("sys.argv", test_args), patch.object( + ValidationRuleGenerator, + "generate_rules", + ) as mock_generate: main() mock_generate.assert_called_once() @@ -468,15 +462,11 @@ class TestCLIFunctionality: """Test main function with --validate flag (success case).""" test_args = ["update-validators.py", "--validate"] - with ( - patch("sys.argv", test_args), - patch.object( - ValidationRuleGenerator, - "validate_rules_files", - return_value=True, - ), - patch("sys.exit") as mock_exit, - ): + with patch("sys.argv", test_args), patch.object( + ValidationRuleGenerator, + "validate_rules_files", + return_value=True, + ), patch("sys.exit") as mock_exit: main() mock_exit.assert_called_once_with(0) @@ -484,15 +474,11 @@ class TestCLIFunctionality: """Test main function with --validate flag (failure case).""" test_args = ["update-validators.py", "--validate"] - with ( - patch("sys.argv", test_args), - patch.object( - ValidationRuleGenerator, - "validate_rules_files", - return_value=False, - ), - patch("sys.exit") as mock_exit, - ): + with patch("sys.argv", test_args), patch.object( + ValidationRuleGenerator, + "validate_rules_files", + return_value=False, + ), patch("sys.exit") as mock_exit: main() mock_exit.assert_called_once_with(1) diff --git a/validate-inputs/tests/test_validate-inputs_custom.py b/validate-inputs/tests/test_validate-inputs_custom.py index a624a9e..0ec7b2e 100644 --- a/validate-inputs/tests/test_validate-inputs_custom.py +++ b/validate-inputs/tests/test_validate-inputs_custom.py @@ -1,8 +1,8 @@ """Tests for validate-inputs custom validator.""" # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "validate-inputs" diff --git a/validate-inputs/tests/test_validator.py b/validate-inputs/tests/test_validator.py index 83f2d63..b79565f 100644 --- a/validate-inputs/tests/test_validator.py +++ b/validate-inputs/tests/test_validator.py @@ -1,9 +1,9 @@ """Tests for the main validator entry point.""" import os -from pathlib import Path import sys import tempfile +from pathlib import Path from unittest.mock import MagicMock, patch import pytest # pylint: disable=import-error @@ -23,7 +23,8 @@ class TestValidatorScript: del os.environ[key] # Create temporary output file - self.temp_output = tempfile.NamedTemporaryFile(mode="w", delete=False) + # Need persistent file for teardown, can't use context manager + self.temp_output = tempfile.NamedTemporaryFile(mode="w", delete=False) # noqa: SIM115 os.environ["GITHUB_OUTPUT"] = self.temp_output.name self.temp_output.close() @@ -181,7 +182,8 @@ class TestValidatorIntegration: def setup_method(self): """Set up test environment.""" - self.temp_output = tempfile.NamedTemporaryFile(mode="w", delete=False) + # Need persistent file for teardown, can't use context manager + self.temp_output = tempfile.NamedTemporaryFile(mode="w", delete=False) # noqa: SIM115 os.environ["GITHUB_OUTPUT"] = self.temp_output.name self.temp_output.close() diff --git a/validate-inputs/tests/test_version-file-parser_custom.py b/validate-inputs/tests/test_version-file-parser_custom.py index e8945e0..a9254b6 100644 --- a/validate-inputs/tests/test_version-file-parser_custom.py +++ b/validate-inputs/tests/test_version-file-parser_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path 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" diff --git a/validate-inputs/tests/test_version-validator_custom.py b/validate-inputs/tests/test_version-validator_custom.py index 0960756..deababc 100644 --- a/validate-inputs/tests/test_version-validator_custom.py +++ b/validate-inputs/tests/test_version-validator_custom.py @@ -4,8 +4,8 @@ Generated by generate-tests.py - Do not edit manually. """ # pylint: disable=invalid-name # Test file name matches action name -from pathlib import Path import sys +from pathlib import Path # Add action directory to path to import custom validator action_path = Path(__file__).parent.parent.parent / "version-validator" diff --git a/validate-inputs/tests/test_version.py b/validate-inputs/tests/test_version.py index 4b3ac21..f0b56fa 100644 --- a/validate-inputs/tests/test_version.py +++ b/validate-inputs/tests/test_version.py @@ -1,7 +1,7 @@ """Tests for the VersionValidator module.""" -from pathlib import Path import sys +from pathlib import Path import pytest # pylint: disable=import-error @@ -9,14 +9,13 @@ import pytest # pylint: disable=import-error sys.path.insert(0, str(Path(__file__).parent.parent)) # pylint: disable=wrong-import-position -from validators.version import VersionValidator - from tests.fixtures.version_test_data import ( CALVER_INVALID, CALVER_VALID, SEMVER_INVALID, SEMVER_VALID, ) +from validators.version import VersionValidator class TestVersionValidator: # pylint: disable=too-many-public-methods diff --git a/validate-inputs/uv.lock b/validate-inputs/uv.lock new file mode 100644 index 0000000..70c74a2 --- /dev/null +++ b/validate-inputs/uv.lock @@ -0,0 +1,565 @@ +version = 1 +revision = 2 +requires-python = ">=3.8" +resolution-markers = [ + "python_full_version >= '3.9'", + "python_full_version < '3.9'", +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "coverage" +version = "7.6.1" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f7/08/7e37f82e4d1aead42a7443ff06a1e406aabf7302c4f00a546e4b320b994c/coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d", size = 798791, upload-time = "2024-08-04T19:45:30.9Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/61/eb7ce5ed62bacf21beca4937a90fe32545c91a3c8a42a30c6616d48fc70d/coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16", size = 206690, upload-time = "2024-08-04T19:43:07.695Z" }, + { url = "https://files.pythonhosted.org/packages/7d/73/041928e434442bd3afde5584bdc3f932fb4562b1597629f537387cec6f3d/coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36", size = 207127, upload-time = "2024-08-04T19:43:10.15Z" }, + { url = "https://files.pythonhosted.org/packages/c7/c8/6ca52b5147828e45ad0242388477fdb90df2c6cbb9a441701a12b3c71bc8/coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02", size = 235654, upload-time = "2024-08-04T19:43:12.405Z" }, + { url = "https://files.pythonhosted.org/packages/d5/da/9ac2b62557f4340270942011d6efeab9833648380109e897d48ab7c1035d/coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc", size = 233598, upload-time = "2024-08-04T19:43:14.078Z" }, + { url = "https://files.pythonhosted.org/packages/53/23/9e2c114d0178abc42b6d8d5281f651a8e6519abfa0ef460a00a91f80879d/coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23", size = 234732, upload-time = "2024-08-04T19:43:16.632Z" }, + { url = "https://files.pythonhosted.org/packages/0f/7e/a0230756fb133343a52716e8b855045f13342b70e48e8ad41d8a0d60ab98/coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34", size = 233816, upload-time = "2024-08-04T19:43:19.049Z" }, + { url = "https://files.pythonhosted.org/packages/28/7c/3753c8b40d232b1e5eeaed798c875537cf3cb183fb5041017c1fdb7ec14e/coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c", size = 232325, upload-time = "2024-08-04T19:43:21.246Z" }, + { url = "https://files.pythonhosted.org/packages/57/e3/818a2b2af5b7573b4b82cf3e9f137ab158c90ea750a8f053716a32f20f06/coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959", size = 233418, upload-time = "2024-08-04T19:43:22.945Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fb/4532b0b0cefb3f06d201648715e03b0feb822907edab3935112b61b885e2/coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232", size = 209343, upload-time = "2024-08-04T19:43:25.121Z" }, + { url = "https://files.pythonhosted.org/packages/5a/25/af337cc7421eca1c187cc9c315f0a755d48e755d2853715bfe8c418a45fa/coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0", size = 210136, upload-time = "2024-08-04T19:43:26.851Z" }, + { url = "https://files.pythonhosted.org/packages/ad/5f/67af7d60d7e8ce61a4e2ddcd1bd5fb787180c8d0ae0fbd073f903b3dd95d/coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93", size = 206796, upload-time = "2024-08-04T19:43:29.115Z" }, + { url = "https://files.pythonhosted.org/packages/e1/0e/e52332389e057daa2e03be1fbfef25bb4d626b37d12ed42ae6281d0a274c/coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3", size = 207244, upload-time = "2024-08-04T19:43:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/aa/cd/766b45fb6e090f20f8927d9c7cb34237d41c73a939358bc881883fd3a40d/coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff", size = 239279, upload-time = "2024-08-04T19:43:33.581Z" }, + { url = "https://files.pythonhosted.org/packages/70/6c/a9ccd6fe50ddaf13442a1e2dd519ca805cbe0f1fcd377fba6d8339b98ccb/coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d", size = 236859, upload-time = "2024-08-04T19:43:35.301Z" }, + { url = "https://files.pythonhosted.org/packages/14/6f/8351b465febb4dbc1ca9929505202db909c5a635c6fdf33e089bbc3d7d85/coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6", size = 238549, upload-time = "2024-08-04T19:43:37.578Z" }, + { url = "https://files.pythonhosted.org/packages/68/3c/289b81fa18ad72138e6d78c4c11a82b5378a312c0e467e2f6b495c260907/coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56", size = 237477, upload-time = "2024-08-04T19:43:39.92Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1c/aa1efa6459d822bd72c4abc0b9418cf268de3f60eeccd65dc4988553bd8d/coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234", size = 236134, upload-time = "2024-08-04T19:43:41.453Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c8/521c698f2d2796565fe9c789c2ee1ccdae610b3aa20b9b2ef980cc253640/coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133", size = 236910, upload-time = "2024-08-04T19:43:43.037Z" }, + { url = "https://files.pythonhosted.org/packages/7d/30/033e663399ff17dca90d793ee8a2ea2890e7fdf085da58d82468b4220bf7/coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c", size = 209348, upload-time = "2024-08-04T19:43:44.787Z" }, + { url = "https://files.pythonhosted.org/packages/20/05/0d1ccbb52727ccdadaa3ff37e4d2dc1cd4d47f0c3df9eb58d9ec8508ca88/coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6", size = 210230, upload-time = "2024-08-04T19:43:46.707Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d4/300fc921dff243cd518c7db3a4c614b7e4b2431b0d1145c1e274fd99bd70/coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778", size = 206983, upload-time = "2024-08-04T19:43:49.082Z" }, + { url = "https://files.pythonhosted.org/packages/e1/ab/6bf00de5327ecb8db205f9ae596885417a31535eeda6e7b99463108782e1/coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391", size = 207221, upload-time = "2024-08-04T19:43:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/92/8f/2ead05e735022d1a7f3a0a683ac7f737de14850395a826192f0288703472/coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8", size = 240342, upload-time = "2024-08-04T19:43:53.746Z" }, + { url = "https://files.pythonhosted.org/packages/0f/ef/94043e478201ffa85b8ae2d2c79b4081e5a1b73438aafafccf3e9bafb6b5/coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d", size = 237371, upload-time = "2024-08-04T19:43:55.993Z" }, + { url = "https://files.pythonhosted.org/packages/1f/0f/c890339dd605f3ebc269543247bdd43b703cce6825b5ed42ff5f2d6122c7/coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca", size = 239455, upload-time = "2024-08-04T19:43:57.618Z" }, + { url = "https://files.pythonhosted.org/packages/d1/04/7fd7b39ec7372a04efb0f70c70e35857a99b6a9188b5205efb4c77d6a57a/coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163", size = 238924, upload-time = "2024-08-04T19:44:00.012Z" }, + { url = "https://files.pythonhosted.org/packages/ed/bf/73ce346a9d32a09cf369f14d2a06651329c984e106f5992c89579d25b27e/coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a", size = 237252, upload-time = "2024-08-04T19:44:01.713Z" }, + { url = "https://files.pythonhosted.org/packages/86/74/1dc7a20969725e917b1e07fe71a955eb34bc606b938316bcc799f228374b/coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d", size = 238897, upload-time = "2024-08-04T19:44:03.898Z" }, + { url = "https://files.pythonhosted.org/packages/b6/e9/d9cc3deceb361c491b81005c668578b0dfa51eed02cd081620e9a62f24ec/coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5", size = 209606, upload-time = "2024-08-04T19:44:05.532Z" }, + { url = "https://files.pythonhosted.org/packages/47/c8/5a2e41922ea6740f77d555c4d47544acd7dc3f251fe14199c09c0f5958d3/coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb", size = 210373, upload-time = "2024-08-04T19:44:07.079Z" }, + { url = "https://files.pythonhosted.org/packages/8c/f9/9aa4dfb751cb01c949c990d136a0f92027fbcc5781c6e921df1cb1563f20/coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106", size = 207007, upload-time = "2024-08-04T19:44:09.453Z" }, + { url = "https://files.pythonhosted.org/packages/b9/67/e1413d5a8591622a46dd04ff80873b04c849268831ed5c304c16433e7e30/coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9", size = 207269, upload-time = "2024-08-04T19:44:11.045Z" }, + { url = "https://files.pythonhosted.org/packages/14/5b/9dec847b305e44a5634d0fb8498d135ab1d88330482b74065fcec0622224/coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c", size = 239886, upload-time = "2024-08-04T19:44:12.83Z" }, + { url = "https://files.pythonhosted.org/packages/7b/b7/35760a67c168e29f454928f51f970342d23cf75a2bb0323e0f07334c85f3/coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a", size = 237037, upload-time = "2024-08-04T19:44:15.393Z" }, + { url = "https://files.pythonhosted.org/packages/f7/95/d2fd31f1d638df806cae59d7daea5abf2b15b5234016a5ebb502c2f3f7ee/coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060", size = 239038, upload-time = "2024-08-04T19:44:17.466Z" }, + { url = "https://files.pythonhosted.org/packages/6e/bd/110689ff5752b67924efd5e2aedf5190cbbe245fc81b8dec1abaffba619d/coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862", size = 238690, upload-time = "2024-08-04T19:44:19.336Z" }, + { url = "https://files.pythonhosted.org/packages/d3/a8/08d7b38e6ff8df52331c83130d0ab92d9c9a8b5462f9e99c9f051a4ae206/coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388", size = 236765, upload-time = "2024-08-04T19:44:20.994Z" }, + { url = "https://files.pythonhosted.org/packages/d6/6a/9cf96839d3147d55ae713eb2d877f4d777e7dc5ba2bce227167d0118dfe8/coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155", size = 238611, upload-time = "2024-08-04T19:44:22.616Z" }, + { url = "https://files.pythonhosted.org/packages/74/e4/7ff20d6a0b59eeaab40b3140a71e38cf52547ba21dbcf1d79c5a32bba61b/coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a", size = 209671, upload-time = "2024-08-04T19:44:24.418Z" }, + { url = "https://files.pythonhosted.org/packages/35/59/1812f08a85b57c9fdb6d0b383d779e47b6f643bc278ed682859512517e83/coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129", size = 210368, upload-time = "2024-08-04T19:44:26.276Z" }, + { url = "https://files.pythonhosted.org/packages/9c/15/08913be1c59d7562a3e39fce20661a98c0a3f59d5754312899acc6cb8a2d/coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e", size = 207758, upload-time = "2024-08-04T19:44:29.028Z" }, + { url = "https://files.pythonhosted.org/packages/c4/ae/b5d58dff26cade02ada6ca612a76447acd69dccdbb3a478e9e088eb3d4b9/coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962", size = 208035, upload-time = "2024-08-04T19:44:30.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d7/62095e355ec0613b08dfb19206ce3033a0eedb6f4a67af5ed267a8800642/coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb", size = 250839, upload-time = "2024-08-04T19:44:32.412Z" }, + { url = "https://files.pythonhosted.org/packages/7c/1e/c2967cb7991b112ba3766df0d9c21de46b476d103e32bb401b1b2adf3380/coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704", size = 246569, upload-time = "2024-08-04T19:44:34.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/61/a7a6a55dd266007ed3b1df7a3386a0d760d014542d72f7c2c6938483b7bd/coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b", size = 248927, upload-time = "2024-08-04T19:44:36.313Z" }, + { url = "https://files.pythonhosted.org/packages/c8/fa/13a6f56d72b429f56ef612eb3bc5ce1b75b7ee12864b3bd12526ab794847/coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f", size = 248401, upload-time = "2024-08-04T19:44:38.155Z" }, + { url = "https://files.pythonhosted.org/packages/75/06/0429c652aa0fb761fc60e8c6b291338c9173c6aa0f4e40e1902345b42830/coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223", size = 246301, upload-time = "2024-08-04T19:44:39.883Z" }, + { url = "https://files.pythonhosted.org/packages/52/76/1766bb8b803a88f93c3a2d07e30ffa359467810e5cbc68e375ebe6906efb/coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3", size = 247598, upload-time = "2024-08-04T19:44:41.59Z" }, + { url = "https://files.pythonhosted.org/packages/66/8b/f54f8db2ae17188be9566e8166ac6df105c1c611e25da755738025708d54/coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f", size = 210307, upload-time = "2024-08-04T19:44:43.301Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b0/e0dca6da9170aefc07515cce067b97178cefafb512d00a87a1c717d2efd5/coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657", size = 211453, upload-time = "2024-08-04T19:44:45.677Z" }, + { url = "https://files.pythonhosted.org/packages/81/d0/d9e3d554e38beea5a2e22178ddb16587dbcbe9a1ef3211f55733924bf7fa/coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0", size = 206674, upload-time = "2024-08-04T19:44:47.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/cab2dc248d9f45b2b7f9f1f596a4d75a435cb364437c61b51d2eb33ceb0e/coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a", size = 207101, upload-time = "2024-08-04T19:44:49.32Z" }, + { url = "https://files.pythonhosted.org/packages/ca/6f/f82f9a500c7c5722368978a5390c418d2a4d083ef955309a8748ecaa8920/coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b", size = 236554, upload-time = "2024-08-04T19:44:51.631Z" }, + { url = "https://files.pythonhosted.org/packages/a6/94/d3055aa33d4e7e733d8fa309d9adf147b4b06a82c1346366fc15a2b1d5fa/coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3", size = 234440, upload-time = "2024-08-04T19:44:53.464Z" }, + { url = "https://files.pythonhosted.org/packages/e4/6e/885bcd787d9dd674de4a7d8ec83faf729534c63d05d51d45d4fa168f7102/coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de", size = 235889, upload-time = "2024-08-04T19:44:55.165Z" }, + { url = "https://files.pythonhosted.org/packages/f4/63/df50120a7744492710854860783d6819ff23e482dee15462c9a833cc428a/coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6", size = 235142, upload-time = "2024-08-04T19:44:57.269Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5d/9d0acfcded2b3e9ce1c7923ca52ccc00c78a74e112fc2aee661125b7843b/coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569", size = 233805, upload-time = "2024-08-04T19:44:59.033Z" }, + { url = "https://files.pythonhosted.org/packages/c4/56/50abf070cb3cd9b1dd32f2c88f083aab561ecbffbcd783275cb51c17f11d/coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989", size = 234655, upload-time = "2024-08-04T19:45:01.398Z" }, + { url = "https://files.pythonhosted.org/packages/25/ee/b4c246048b8485f85a2426ef4abab88e48c6e80c74e964bea5cd4cd4b115/coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7", size = 209296, upload-time = "2024-08-04T19:45:03.819Z" }, + { url = "https://files.pythonhosted.org/packages/5c/1c/96cf86b70b69ea2b12924cdf7cabb8ad10e6130eab8d767a1099fbd2a44f/coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8", size = 210137, upload-time = "2024-08-04T19:45:06.25Z" }, + { url = "https://files.pythonhosted.org/packages/19/d3/d54c5aa83268779d54c86deb39c1c4566e5d45c155369ca152765f8db413/coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255", size = 206688, upload-time = "2024-08-04T19:45:08.358Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/137d5dca72e4a258b1bc17bb04f2e0196898fe495843402ce826a7419fe3/coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8", size = 207120, upload-time = "2024-08-04T19:45:11.526Z" }, + { url = "https://files.pythonhosted.org/packages/78/5b/a0a796983f3201ff5485323b225d7c8b74ce30c11f456017e23d8e8d1945/coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2", size = 235249, upload-time = "2024-08-04T19:45:13.202Z" }, + { url = "https://files.pythonhosted.org/packages/4e/e1/76089d6a5ef9d68f018f65411fcdaaeb0141b504587b901d74e8587606ad/coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a", size = 233237, upload-time = "2024-08-04T19:45:14.961Z" }, + { url = "https://files.pythonhosted.org/packages/9a/6f/eef79b779a540326fee9520e5542a8b428cc3bfa8b7c8f1022c1ee4fc66c/coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc", size = 234311, upload-time = "2024-08-04T19:45:16.924Z" }, + { url = "https://files.pythonhosted.org/packages/75/e1/656d65fb126c29a494ef964005702b012f3498db1a30dd562958e85a4049/coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004", size = 233453, upload-time = "2024-08-04T19:45:18.672Z" }, + { url = "https://files.pythonhosted.org/packages/68/6a/45f108f137941a4a1238c85f28fd9d048cc46b5466d6b8dda3aba1bb9d4f/coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb", size = 231958, upload-time = "2024-08-04T19:45:20.63Z" }, + { url = "https://files.pythonhosted.org/packages/9b/e7/47b809099168b8b8c72ae311efc3e88c8d8a1162b3ba4b8da3cfcdb85743/coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36", size = 232938, upload-time = "2024-08-04T19:45:23.062Z" }, + { url = "https://files.pythonhosted.org/packages/52/80/052222ba7058071f905435bad0ba392cc12006380731c37afaf3fe749b88/coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c", size = 209352, upload-time = "2024-08-04T19:45:25.042Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d8/1b92e0b3adcf384e98770a00ca095da1b5f7b483e6563ae4eb5e935d24a1/coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca", size = 210153, upload-time = "2024-08-04T19:45:27.079Z" }, + { url = "https://files.pythonhosted.org/packages/a5/2b/0354ed096bca64dc8e32a7cbcae28b34cb5ad0b1fe2125d6d99583313ac0/coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df", size = 198926, upload-time = "2024-08-04T19:45:28.875Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version < '3.9'" }, +] + +[[package]] +name = "coverage" +version = "7.10.7" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/51/26/d22c300112504f5f9a9fd2297ce33c35f3d353e4aeb987c8419453b2a7c2/coverage-7.10.7.tar.gz", hash = "sha256:f4ab143ab113be368a3e9b795f9cd7906c5ef407d6173fe9675a902e1fffc239", size = 827704, upload-time = "2025-09-21T20:03:56.815Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/6c/3a3f7a46888e69d18abe3ccc6fe4cb16cccb1e6a2f99698931dafca489e6/coverage-7.10.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc04cc7a3db33664e0c2d10eb8990ff6b3536f6842c9590ae8da4c614b9ed05a", size = 217987, upload-time = "2025-09-21T20:00:57.218Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/952d30f180b1a916c11a56f5c22d3535e943aa22430e9e3322447e520e1c/coverage-7.10.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e201e015644e207139f7e2351980feb7040e6f4b2c2978892f3e3789d1c125e5", size = 218388, upload-time = "2025-09-21T20:01:00.081Z" }, + { url = "https://files.pythonhosted.org/packages/50/2b/9e0cf8ded1e114bcd8b2fd42792b57f1c4e9e4ea1824cde2af93a67305be/coverage-7.10.7-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:240af60539987ced2c399809bd34f7c78e8abe0736af91c3d7d0e795df633d17", size = 245148, upload-time = "2025-09-21T20:01:01.768Z" }, + { url = "https://files.pythonhosted.org/packages/19/20/d0384ac06a6f908783d9b6aa6135e41b093971499ec488e47279f5b846e6/coverage-7.10.7-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8421e088bc051361b01c4b3a50fd39a4b9133079a2229978d9d30511fd05231b", size = 246958, upload-time = "2025-09-21T20:01:03.355Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/5c283cff3d41285f8eab897651585db908a909c572bdc014bcfaf8a8b6ae/coverage-7.10.7-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6be8ed3039ae7f7ac5ce058c308484787c86e8437e72b30bf5e88b8ea10f3c87", size = 248819, upload-time = "2025-09-21T20:01:04.968Z" }, + { url = "https://files.pythonhosted.org/packages/60/22/02eb98fdc5ff79f423e990d877693e5310ae1eab6cb20ae0b0b9ac45b23b/coverage-7.10.7-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e28299d9f2e889e6d51b1f043f58d5f997c373cc12e6403b90df95b8b047c13e", size = 245754, upload-time = "2025-09-21T20:01:06.321Z" }, + { url = "https://files.pythonhosted.org/packages/b4/bc/25c83bcf3ad141b32cd7dc45485ef3c01a776ca3aa8ef0a93e77e8b5bc43/coverage-7.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c4e16bd7761c5e454f4efd36f345286d6f7c5fa111623c355691e2755cae3b9e", size = 246860, upload-time = "2025-09-21T20:01:07.605Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b7/95574702888b58c0928a6e982038c596f9c34d52c5e5107f1eef729399b5/coverage-7.10.7-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:b1c81d0e5e160651879755c9c675b974276f135558cf4ba79fee7b8413a515df", size = 244877, upload-time = "2025-09-21T20:01:08.829Z" }, + { url = "https://files.pythonhosted.org/packages/47/b6/40095c185f235e085df0e0b158f6bd68cc6e1d80ba6c7721dc81d97ec318/coverage-7.10.7-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:606cc265adc9aaedcc84f1f064f0e8736bc45814f15a357e30fca7ecc01504e0", size = 245108, upload-time = "2025-09-21T20:01:10.527Z" }, + { url = "https://files.pythonhosted.org/packages/c8/50/4aea0556da7a4b93ec9168420d170b55e2eb50ae21b25062513d020c6861/coverage-7.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:10b24412692df990dbc34f8fb1b6b13d236ace9dfdd68df5b28c2e39cafbba13", size = 245752, upload-time = "2025-09-21T20:01:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/6a/28/ea1a84a60828177ae3b100cb6723838523369a44ec5742313ed7db3da160/coverage-7.10.7-cp310-cp310-win32.whl", hash = "sha256:b51dcd060f18c19290d9b8a9dd1e0181538df2ce0717f562fff6cf74d9fc0b5b", size = 220497, upload-time = "2025-09-21T20:01:13.459Z" }, + { url = "https://files.pythonhosted.org/packages/fc/1a/a81d46bbeb3c3fd97b9602ebaa411e076219a150489bcc2c025f151bd52d/coverage-7.10.7-cp310-cp310-win_amd64.whl", hash = "sha256:3a622ac801b17198020f09af3eaf45666b344a0d69fc2a6ffe2ea83aeef1d807", size = 221392, upload-time = "2025-09-21T20:01:14.722Z" }, + { url = "https://files.pythonhosted.org/packages/d2/5d/c1a17867b0456f2e9ce2d8d4708a4c3a089947d0bec9c66cdf60c9e7739f/coverage-7.10.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a609f9c93113be646f44c2a0256d6ea375ad047005d7f57a5c15f614dc1b2f59", size = 218102, upload-time = "2025-09-21T20:01:16.089Z" }, + { url = "https://files.pythonhosted.org/packages/54/f0/514dcf4b4e3698b9a9077f084429681bf3aad2b4a72578f89d7f643eb506/coverage-7.10.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:65646bb0359386e07639c367a22cf9b5bf6304e8630b565d0626e2bdf329227a", size = 218505, upload-time = "2025-09-21T20:01:17.788Z" }, + { url = "https://files.pythonhosted.org/packages/20/f6/9626b81d17e2a4b25c63ac1b425ff307ecdeef03d67c9a147673ae40dc36/coverage-7.10.7-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5f33166f0dfcce728191f520bd2692914ec70fac2713f6bf3ce59c3deacb4699", size = 248898, upload-time = "2025-09-21T20:01:19.488Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ef/bd8e719c2f7417ba03239052e099b76ea1130ac0cbb183ee1fcaa58aaff3/coverage-7.10.7-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:35f5e3f9e455bb17831876048355dca0f758b6df22f49258cb5a91da23ef437d", size = 250831, upload-time = "2025-09-21T20:01:20.817Z" }, + { url = "https://files.pythonhosted.org/packages/a5/b6/bf054de41ec948b151ae2b79a55c107f5760979538f5fb80c195f2517718/coverage-7.10.7-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4da86b6d62a496e908ac2898243920c7992499c1712ff7c2b6d837cc69d9467e", size = 252937, upload-time = "2025-09-21T20:01:22.171Z" }, + { url = "https://files.pythonhosted.org/packages/0f/e5/3860756aa6f9318227443c6ce4ed7bf9e70bb7f1447a0353f45ac5c7974b/coverage-7.10.7-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6b8b09c1fad947c84bbbc95eca841350fad9cbfa5a2d7ca88ac9f8d836c92e23", size = 249021, upload-time = "2025-09-21T20:01:23.907Z" }, + { url = "https://files.pythonhosted.org/packages/26/0f/bd08bd042854f7fd07b45808927ebcce99a7ed0f2f412d11629883517ac2/coverage-7.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4376538f36b533b46f8971d3a3e63464f2c7905c9800db97361c43a2b14792ab", size = 250626, upload-time = "2025-09-21T20:01:25.721Z" }, + { url = "https://files.pythonhosted.org/packages/8e/a7/4777b14de4abcc2e80c6b1d430f5d51eb18ed1d75fca56cbce5f2db9b36e/coverage-7.10.7-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:121da30abb574f6ce6ae09840dae322bef734480ceafe410117627aa54f76d82", size = 248682, upload-time = "2025-09-21T20:01:27.105Z" }, + { url = "https://files.pythonhosted.org/packages/34/72/17d082b00b53cd45679bad682fac058b87f011fd8b9fe31d77f5f8d3a4e4/coverage-7.10.7-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:88127d40df529336a9836870436fc2751c339fbaed3a836d42c93f3e4bd1d0a2", size = 248402, upload-time = "2025-09-21T20:01:28.629Z" }, + { url = "https://files.pythonhosted.org/packages/81/7a/92367572eb5bdd6a84bfa278cc7e97db192f9f45b28c94a9ca1a921c3577/coverage-7.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ba58bbcd1b72f136080c0bccc2400d66cc6115f3f906c499013d065ac33a4b61", size = 249320, upload-time = "2025-09-21T20:01:30.004Z" }, + { url = "https://files.pythonhosted.org/packages/2f/88/a23cc185f6a805dfc4fdf14a94016835eeb85e22ac3a0e66d5e89acd6462/coverage-7.10.7-cp311-cp311-win32.whl", hash = "sha256:972b9e3a4094b053a4e46832b4bc829fc8a8d347160eb39d03f1690316a99c14", size = 220536, upload-time = "2025-09-21T20:01:32.184Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ef/0b510a399dfca17cec7bc2f05ad8bd78cf55f15c8bc9a73ab20c5c913c2e/coverage-7.10.7-cp311-cp311-win_amd64.whl", hash = "sha256:a7b55a944a7f43892e28ad4bc0561dfd5f0d73e605d1aa5c3c976b52aea121d2", size = 221425, upload-time = "2025-09-21T20:01:33.557Z" }, + { url = "https://files.pythonhosted.org/packages/51/7f/023657f301a276e4ba1850f82749bc136f5a7e8768060c2e5d9744a22951/coverage-7.10.7-cp311-cp311-win_arm64.whl", hash = "sha256:736f227fb490f03c6488f9b6d45855f8e0fd749c007f9303ad30efab0e73c05a", size = 220103, upload-time = "2025-09-21T20:01:34.929Z" }, + { url = "https://files.pythonhosted.org/packages/13/e4/eb12450f71b542a53972d19117ea5a5cea1cab3ac9e31b0b5d498df1bd5a/coverage-7.10.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7bb3b9ddb87ef7725056572368040c32775036472d5a033679d1fa6c8dc08417", size = 218290, upload-time = "2025-09-21T20:01:36.455Z" }, + { url = "https://files.pythonhosted.org/packages/37/66/593f9be12fc19fb36711f19a5371af79a718537204d16ea1d36f16bd78d2/coverage-7.10.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18afb24843cbc175687225cab1138c95d262337f5473512010e46831aa0c2973", size = 218515, upload-time = "2025-09-21T20:01:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/80/4c49f7ae09cafdacc73fbc30949ffe77359635c168f4e9ff33c9ebb07838/coverage-7.10.7-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:399a0b6347bcd3822be369392932884b8216d0944049ae22925631a9b3d4ba4c", size = 250020, upload-time = "2025-09-21T20:01:39.617Z" }, + { url = "https://files.pythonhosted.org/packages/a6/90/a64aaacab3b37a17aaedd83e8000142561a29eb262cede42d94a67f7556b/coverage-7.10.7-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:314f2c326ded3f4b09be11bc282eb2fc861184bc95748ae67b360ac962770be7", size = 252769, upload-time = "2025-09-21T20:01:41.341Z" }, + { url = "https://files.pythonhosted.org/packages/98/2e/2dda59afd6103b342e096f246ebc5f87a3363b5412609946c120f4e7750d/coverage-7.10.7-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c41e71c9cfb854789dee6fc51e46743a6d138b1803fab6cb860af43265b42ea6", size = 253901, upload-time = "2025-09-21T20:01:43.042Z" }, + { url = "https://files.pythonhosted.org/packages/53/dc/8d8119c9051d50f3119bb4a75f29f1e4a6ab9415cd1fa8bf22fcc3fb3b5f/coverage-7.10.7-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc01f57ca26269c2c706e838f6422e2a8788e41b3e3c65e2f41148212e57cd59", size = 250413, upload-time = "2025-09-21T20:01:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/98/b3/edaff9c5d79ee4d4b6d3fe046f2b1d799850425695b789d491a64225d493/coverage-7.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a6442c59a8ac8b85812ce33bc4d05bde3fb22321fa8294e2a5b487c3505f611b", size = 251820, upload-time = "2025-09-21T20:01:45.915Z" }, + { url = "https://files.pythonhosted.org/packages/11/25/9a0728564bb05863f7e513e5a594fe5ffef091b325437f5430e8cfb0d530/coverage-7.10.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:78a384e49f46b80fb4c901d52d92abe098e78768ed829c673fbb53c498bef73a", size = 249941, upload-time = "2025-09-21T20:01:47.296Z" }, + { url = "https://files.pythonhosted.org/packages/e0/fd/ca2650443bfbef5b0e74373aac4df67b08180d2f184b482c41499668e258/coverage-7.10.7-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:5e1e9802121405ede4b0133aa4340ad8186a1d2526de5b7c3eca519db7bb89fb", size = 249519, upload-time = "2025-09-21T20:01:48.73Z" }, + { url = "https://files.pythonhosted.org/packages/24/79/f692f125fb4299b6f963b0745124998ebb8e73ecdfce4ceceb06a8c6bec5/coverage-7.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d41213ea25a86f69efd1575073d34ea11aabe075604ddf3d148ecfec9e1e96a1", size = 251375, upload-time = "2025-09-21T20:01:50.529Z" }, + { url = "https://files.pythonhosted.org/packages/5e/75/61b9bbd6c7d24d896bfeec57acba78e0f8deac68e6baf2d4804f7aae1f88/coverage-7.10.7-cp312-cp312-win32.whl", hash = "sha256:77eb4c747061a6af8d0f7bdb31f1e108d172762ef579166ec84542f711d90256", size = 220699, upload-time = "2025-09-21T20:01:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/ca/f3/3bf7905288b45b075918d372498f1cf845b5b579b723c8fd17168018d5f5/coverage-7.10.7-cp312-cp312-win_amd64.whl", hash = "sha256:f51328ffe987aecf6d09f3cd9d979face89a617eacdaea43e7b3080777f647ba", size = 221512, upload-time = "2025-09-21T20:01:53.481Z" }, + { url = "https://files.pythonhosted.org/packages/5c/44/3e32dbe933979d05cf2dac5e697c8599cfe038aaf51223ab901e208d5a62/coverage-7.10.7-cp312-cp312-win_arm64.whl", hash = "sha256:bda5e34f8a75721c96085903c6f2197dc398c20ffd98df33f866a9c8fd95f4bf", size = 220147, upload-time = "2025-09-21T20:01:55.2Z" }, + { url = "https://files.pythonhosted.org/packages/9a/94/b765c1abcb613d103b64fcf10395f54d69b0ef8be6a0dd9c524384892cc7/coverage-7.10.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:981a651f543f2854abd3b5fcb3263aac581b18209be49863ba575de6edf4c14d", size = 218320, upload-time = "2025-09-21T20:01:56.629Z" }, + { url = "https://files.pythonhosted.org/packages/72/4f/732fff31c119bb73b35236dd333030f32c4bfe909f445b423e6c7594f9a2/coverage-7.10.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:73ab1601f84dc804f7812dc297e93cd99381162da39c47040a827d4e8dafe63b", size = 218575, upload-time = "2025-09-21T20:01:58.203Z" }, + { url = "https://files.pythonhosted.org/packages/87/02/ae7e0af4b674be47566707777db1aa375474f02a1d64b9323e5813a6cdd5/coverage-7.10.7-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:a8b6f03672aa6734e700bbcd65ff050fd19cddfec4b031cc8cf1c6967de5a68e", size = 249568, upload-time = "2025-09-21T20:01:59.748Z" }, + { url = "https://files.pythonhosted.org/packages/a2/77/8c6d22bf61921a59bce5471c2f1f7ac30cd4ac50aadde72b8c48d5727902/coverage-7.10.7-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:10b6ba00ab1132a0ce4428ff68cf50a25efd6840a42cdf4239c9b99aad83be8b", size = 252174, upload-time = "2025-09-21T20:02:01.192Z" }, + { url = "https://files.pythonhosted.org/packages/b1/20/b6ea4f69bbb52dac0aebd62157ba6a9dddbfe664f5af8122dac296c3ee15/coverage-7.10.7-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c79124f70465a150e89340de5963f936ee97097d2ef76c869708c4248c63ca49", size = 253447, upload-time = "2025-09-21T20:02:02.701Z" }, + { url = "https://files.pythonhosted.org/packages/f9/28/4831523ba483a7f90f7b259d2018fef02cb4d5b90bc7c1505d6e5a84883c/coverage-7.10.7-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:69212fbccdbd5b0e39eac4067e20a4a5256609e209547d86f740d68ad4f04911", size = 249779, upload-time = "2025-09-21T20:02:04.185Z" }, + { url = "https://files.pythonhosted.org/packages/a7/9f/4331142bc98c10ca6436d2d620c3e165f31e6c58d43479985afce6f3191c/coverage-7.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ea7c6c9d0d286d04ed3541747e6597cbe4971f22648b68248f7ddcd329207f0", size = 251604, upload-time = "2025-09-21T20:02:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/ce/60/bda83b96602036b77ecf34e6393a3836365481b69f7ed7079ab85048202b/coverage-7.10.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b9be91986841a75042b3e3243d0b3cb0b2434252b977baaf0cd56e960fe1e46f", size = 249497, upload-time = "2025-09-21T20:02:07.619Z" }, + { url = "https://files.pythonhosted.org/packages/5f/af/152633ff35b2af63977edd835d8e6430f0caef27d171edf2fc76c270ef31/coverage-7.10.7-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:b281d5eca50189325cfe1f365fafade89b14b4a78d9b40b05ddd1fc7d2a10a9c", size = 249350, upload-time = "2025-09-21T20:02:10.34Z" }, + { url = "https://files.pythonhosted.org/packages/9d/71/d92105d122bd21cebba877228990e1646d862e34a98bb3374d3fece5a794/coverage-7.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:99e4aa63097ab1118e75a848a28e40d68b08a5e19ce587891ab7fd04475e780f", size = 251111, upload-time = "2025-09-21T20:02:12.122Z" }, + { url = "https://files.pythonhosted.org/packages/a2/9e/9fdb08f4bf476c912f0c3ca292e019aab6712c93c9344a1653986c3fd305/coverage-7.10.7-cp313-cp313-win32.whl", hash = "sha256:dc7c389dce432500273eaf48f410b37886be9208b2dd5710aaf7c57fd442c698", size = 220746, upload-time = "2025-09-21T20:02:13.919Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b1/a75fd25df44eab52d1931e89980d1ada46824c7a3210be0d3c88a44aaa99/coverage-7.10.7-cp313-cp313-win_amd64.whl", hash = "sha256:cac0fdca17b036af3881a9d2729a850b76553f3f716ccb0360ad4dbc06b3b843", size = 221541, upload-time = "2025-09-21T20:02:15.57Z" }, + { url = "https://files.pythonhosted.org/packages/14/3a/d720d7c989562a6e9a14b2c9f5f2876bdb38e9367126d118495b89c99c37/coverage-7.10.7-cp313-cp313-win_arm64.whl", hash = "sha256:4b6f236edf6e2f9ae8fcd1332da4e791c1b6ba0dc16a2dc94590ceccb482e546", size = 220170, upload-time = "2025-09-21T20:02:17.395Z" }, + { url = "https://files.pythonhosted.org/packages/bb/22/e04514bf2a735d8b0add31d2b4ab636fc02370730787c576bb995390d2d5/coverage-7.10.7-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a0ec07fd264d0745ee396b666d47cef20875f4ff2375d7c4f58235886cc1ef0c", size = 219029, upload-time = "2025-09-21T20:02:18.936Z" }, + { url = "https://files.pythonhosted.org/packages/11/0b/91128e099035ece15da3445d9015e4b4153a6059403452d324cbb0a575fa/coverage-7.10.7-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd5e856ebb7bfb7672b0086846db5afb4567a7b9714b8a0ebafd211ec7ce6a15", size = 219259, upload-time = "2025-09-21T20:02:20.44Z" }, + { url = "https://files.pythonhosted.org/packages/8b/51/66420081e72801536a091a0c8f8c1f88a5c4bf7b9b1bdc6222c7afe6dc9b/coverage-7.10.7-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:f57b2a3c8353d3e04acf75b3fed57ba41f5c0646bbf1d10c7c282291c97936b4", size = 260592, upload-time = "2025-09-21T20:02:22.313Z" }, + { url = "https://files.pythonhosted.org/packages/5d/22/9b8d458c2881b22df3db5bb3e7369e63d527d986decb6c11a591ba2364f7/coverage-7.10.7-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ef2319dd15a0b009667301a3f84452a4dc6fddfd06b0c5c53ea472d3989fbf0", size = 262768, upload-time = "2025-09-21T20:02:24.287Z" }, + { url = "https://files.pythonhosted.org/packages/f7/08/16bee2c433e60913c610ea200b276e8eeef084b0d200bdcff69920bd5828/coverage-7.10.7-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:83082a57783239717ceb0ad584de3c69cf581b2a95ed6bf81ea66034f00401c0", size = 264995, upload-time = "2025-09-21T20:02:26.133Z" }, + { url = "https://files.pythonhosted.org/packages/20/9d/e53eb9771d154859b084b90201e5221bca7674ba449a17c101a5031d4054/coverage-7.10.7-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:50aa94fb1fb9a397eaa19c0d5ec15a5edd03a47bf1a3a6111a16b36e190cff65", size = 259546, upload-time = "2025-09-21T20:02:27.716Z" }, + { url = "https://files.pythonhosted.org/packages/ad/b0/69bc7050f8d4e56a89fb550a1577d5d0d1db2278106f6f626464067b3817/coverage-7.10.7-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2120043f147bebb41c85b97ac45dd173595ff14f2a584f2963891cbcc3091541", size = 262544, upload-time = "2025-09-21T20:02:29.216Z" }, + { url = "https://files.pythonhosted.org/packages/ef/4b/2514b060dbd1bc0aaf23b852c14bb5818f244c664cb16517feff6bb3a5ab/coverage-7.10.7-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2fafd773231dd0378fdba66d339f84904a8e57a262f583530f4f156ab83863e6", size = 260308, upload-time = "2025-09-21T20:02:31.226Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/7ba2175007c246d75e496f64c06e94122bdb914790a1285d627a918bd271/coverage-7.10.7-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:0b944ee8459f515f28b851728ad224fa2d068f1513ef6b7ff1efafeb2185f999", size = 258920, upload-time = "2025-09-21T20:02:32.823Z" }, + { url = "https://files.pythonhosted.org/packages/c0/b3/fac9f7abbc841409b9a410309d73bfa6cfb2e51c3fada738cb607ce174f8/coverage-7.10.7-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4b583b97ab2e3efe1b3e75248a9b333bd3f8b0b1b8e5b45578e05e5850dfb2c2", size = 261434, upload-time = "2025-09-21T20:02:34.86Z" }, + { url = "https://files.pythonhosted.org/packages/ee/51/a03bec00d37faaa891b3ff7387192cef20f01604e5283a5fabc95346befa/coverage-7.10.7-cp313-cp313t-win32.whl", hash = "sha256:2a78cd46550081a7909b3329e2266204d584866e8d97b898cd7fb5ac8d888b1a", size = 221403, upload-time = "2025-09-21T20:02:37.034Z" }, + { url = "https://files.pythonhosted.org/packages/53/22/3cf25d614e64bf6d8e59c7c669b20d6d940bb337bdee5900b9ca41c820bb/coverage-7.10.7-cp313-cp313t-win_amd64.whl", hash = "sha256:33a5e6396ab684cb43dc7befa386258acb2d7fae7f67330ebb85ba4ea27938eb", size = 222469, upload-time = "2025-09-21T20:02:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/49/a1/00164f6d30d8a01c3c9c48418a7a5be394de5349b421b9ee019f380df2a0/coverage-7.10.7-cp313-cp313t-win_arm64.whl", hash = "sha256:86b0e7308289ddde73d863b7683f596d8d21c7d8664ce1dee061d0bcf3fbb4bb", size = 220731, upload-time = "2025-09-21T20:02:40.939Z" }, + { url = "https://files.pythonhosted.org/packages/23/9c/5844ab4ca6a4dd97a1850e030a15ec7d292b5c5cb93082979225126e35dd/coverage-7.10.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b06f260b16ead11643a5a9f955bd4b5fd76c1a4c6796aeade8520095b75de520", size = 218302, upload-time = "2025-09-21T20:02:42.527Z" }, + { url = "https://files.pythonhosted.org/packages/f0/89/673f6514b0961d1f0e20ddc242e9342f6da21eaba3489901b565c0689f34/coverage-7.10.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:212f8f2e0612778f09c55dd4872cb1f64a1f2b074393d139278ce902064d5b32", size = 218578, upload-time = "2025-09-21T20:02:44.468Z" }, + { url = "https://files.pythonhosted.org/packages/05/e8/261cae479e85232828fb17ad536765c88dd818c8470aca690b0ac6feeaa3/coverage-7.10.7-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3445258bcded7d4aa630ab8296dea4d3f15a255588dd535f980c193ab6b95f3f", size = 249629, upload-time = "2025-09-21T20:02:46.503Z" }, + { url = "https://files.pythonhosted.org/packages/82/62/14ed6546d0207e6eda876434e3e8475a3e9adbe32110ce896c9e0c06bb9a/coverage-7.10.7-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bb45474711ba385c46a0bfe696c695a929ae69ac636cda8f532be9e8c93d720a", size = 252162, upload-time = "2025-09-21T20:02:48.689Z" }, + { url = "https://files.pythonhosted.org/packages/ff/49/07f00db9ac6478e4358165a08fb41b469a1b053212e8a00cb02f0d27a05f/coverage-7.10.7-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:813922f35bd800dca9994c5971883cbc0d291128a5de6b167c7aa697fcf59360", size = 253517, upload-time = "2025-09-21T20:02:50.31Z" }, + { url = "https://files.pythonhosted.org/packages/a2/59/c5201c62dbf165dfbc91460f6dbbaa85a8b82cfa6131ac45d6c1bfb52deb/coverage-7.10.7-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:93c1b03552081b2a4423091d6fb3787265b8f86af404cff98d1b5342713bdd69", size = 249632, upload-time = "2025-09-21T20:02:51.971Z" }, + { url = "https://files.pythonhosted.org/packages/07/ae/5920097195291a51fb00b3a70b9bbd2edbfe3c84876a1762bd1ef1565ebc/coverage-7.10.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cc87dd1b6eaf0b848eebb1c86469b9f72a1891cb42ac7adcfbce75eadb13dd14", size = 251520, upload-time = "2025-09-21T20:02:53.858Z" }, + { url = "https://files.pythonhosted.org/packages/b9/3c/a815dde77a2981f5743a60b63df31cb322c944843e57dbd579326625a413/coverage-7.10.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:39508ffda4f343c35f3236fe8d1a6634a51f4581226a1262769d7f970e73bffe", size = 249455, upload-time = "2025-09-21T20:02:55.807Z" }, + { url = "https://files.pythonhosted.org/packages/aa/99/f5cdd8421ea656abefb6c0ce92556709db2265c41e8f9fc6c8ae0f7824c9/coverage-7.10.7-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:925a1edf3d810537c5a3abe78ec5530160c5f9a26b1f4270b40e62cc79304a1e", size = 249287, upload-time = "2025-09-21T20:02:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/c3/7a/e9a2da6a1fc5d007dd51fca083a663ab930a8c4d149c087732a5dbaa0029/coverage-7.10.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2c8b9a0636f94c43cd3576811e05b89aa9bc2d0a85137affc544ae5cb0e4bfbd", size = 250946, upload-time = "2025-09-21T20:02:59.431Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5b/0b5799aa30380a949005a353715095d6d1da81927d6dbed5def2200a4e25/coverage-7.10.7-cp314-cp314-win32.whl", hash = "sha256:b7b8288eb7cdd268b0304632da8cb0bb93fadcfec2fe5712f7b9cc8f4d487be2", size = 221009, upload-time = "2025-09-21T20:03:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/da/b0/e802fbb6eb746de006490abc9bb554b708918b6774b722bb3a0e6aa1b7de/coverage-7.10.7-cp314-cp314-win_amd64.whl", hash = "sha256:1ca6db7c8807fb9e755d0379ccc39017ce0a84dcd26d14b5a03b78563776f681", size = 221804, upload-time = "2025-09-21T20:03:03.4Z" }, + { url = "https://files.pythonhosted.org/packages/9e/e8/71d0c8e374e31f39e3389bb0bd19e527d46f00ea8571ec7ec8fd261d8b44/coverage-7.10.7-cp314-cp314-win_arm64.whl", hash = "sha256:097c1591f5af4496226d5783d036bf6fd6cd0cbc132e071b33861de756efb880", size = 220384, upload-time = "2025-09-21T20:03:05.111Z" }, + { url = "https://files.pythonhosted.org/packages/62/09/9a5608d319fa3eba7a2019addeacb8c746fb50872b57a724c9f79f146969/coverage-7.10.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a62c6ef0d50e6de320c270ff91d9dd0a05e7250cac2a800b7784bae474506e63", size = 219047, upload-time = "2025-09-21T20:03:06.795Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6f/f58d46f33db9f2e3647b2d0764704548c184e6f5e014bef528b7f979ef84/coverage-7.10.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9fa6e4dd51fe15d8738708a973470f67a855ca50002294852e9571cdbd9433f2", size = 219266, upload-time = "2025-09-21T20:03:08.495Z" }, + { url = "https://files.pythonhosted.org/packages/74/5c/183ffc817ba68e0b443b8c934c8795553eb0c14573813415bd59941ee165/coverage-7.10.7-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:8fb190658865565c549b6b4706856d6a7b09302c797eb2cf8e7fe9dabb043f0d", size = 260767, upload-time = "2025-09-21T20:03:10.172Z" }, + { url = "https://files.pythonhosted.org/packages/0f/48/71a8abe9c1ad7e97548835e3cc1adbf361e743e9d60310c5f75c9e7bf847/coverage-7.10.7-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:affef7c76a9ef259187ef31599a9260330e0335a3011732c4b9effa01e1cd6e0", size = 262931, upload-time = "2025-09-21T20:03:11.861Z" }, + { url = "https://files.pythonhosted.org/packages/84/fd/193a8fb132acfc0a901f72020e54be5e48021e1575bb327d8ee1097a28fd/coverage-7.10.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e16e07d85ca0cf8bafe5f5d23a0b850064e8e945d5677492b06bbe6f09cc699", size = 265186, upload-time = "2025-09-21T20:03:13.539Z" }, + { url = "https://files.pythonhosted.org/packages/b1/8f/74ecc30607dd95ad50e3034221113ccb1c6d4e8085cc761134782995daae/coverage-7.10.7-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:03ffc58aacdf65d2a82bbeb1ffe4d01ead4017a21bfd0454983b88ca73af94b9", size = 259470, upload-time = "2025-09-21T20:03:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/0f/55/79ff53a769f20d71b07023ea115c9167c0bb56f281320520cf64c5298a96/coverage-7.10.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1b4fd784344d4e52647fd7857b2af5b3fbe6c239b0b5fa63e94eb67320770e0f", size = 262626, upload-time = "2025-09-21T20:03:17.673Z" }, + { url = "https://files.pythonhosted.org/packages/88/e2/dac66c140009b61ac3fc13af673a574b00c16efdf04f9b5c740703e953c0/coverage-7.10.7-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:0ebbaddb2c19b71912c6f2518e791aa8b9f054985a0769bdb3a53ebbc765c6a1", size = 260386, upload-time = "2025-09-21T20:03:19.36Z" }, + { url = "https://files.pythonhosted.org/packages/a2/f1/f48f645e3f33bb9ca8a496bc4a9671b52f2f353146233ebd7c1df6160440/coverage-7.10.7-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a2d9a3b260cc1d1dbdb1c582e63ddcf5363426a1a68faa0f5da28d8ee3c722a0", size = 258852, upload-time = "2025-09-21T20:03:21.007Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3b/8442618972c51a7affeead957995cfa8323c0c9bcf8fa5a027421f720ff4/coverage-7.10.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a3cc8638b2480865eaa3926d192e64ce6c51e3d29c849e09d5b4ad95efae5399", size = 261534, upload-time = "2025-09-21T20:03:23.12Z" }, + { url = "https://files.pythonhosted.org/packages/b2/dc/101f3fa3a45146db0cb03f5b4376e24c0aac818309da23e2de0c75295a91/coverage-7.10.7-cp314-cp314t-win32.whl", hash = "sha256:67f8c5cbcd3deb7a60b3345dffc89a961a484ed0af1f6f73de91705cc6e31235", size = 221784, upload-time = "2025-09-21T20:03:24.769Z" }, + { url = "https://files.pythonhosted.org/packages/4c/a1/74c51803fc70a8a40d7346660379e144be772bab4ac7bb6e6b905152345c/coverage-7.10.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e1ed71194ef6dea7ed2d5cb5f7243d4bcd334bfb63e59878519be558078f848d", size = 222905, upload-time = "2025-09-21T20:03:26.93Z" }, + { url = "https://files.pythonhosted.org/packages/12/65/f116a6d2127df30bcafbceef0302d8a64ba87488bf6f73a6d8eebf060873/coverage-7.10.7-cp314-cp314t-win_arm64.whl", hash = "sha256:7fe650342addd8524ca63d77b2362b02345e5f1a093266787d210c70a50b471a", size = 220922, upload-time = "2025-09-21T20:03:28.672Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/d1c25053764b4c42eb294aae92ab617d2e4f803397f9c7c8295caa77a260/coverage-7.10.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fff7b9c3f19957020cac546c70025331113d2e61537f6e2441bc7657913de7d3", size = 217978, upload-time = "2025-09-21T20:03:30.362Z" }, + { url = "https://files.pythonhosted.org/packages/52/2f/b9f9daa39b80ece0b9548bbb723381e29bc664822d9a12c2135f8922c22b/coverage-7.10.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bc91b314cef27742da486d6839b677b3f2793dfe52b51bbbb7cf736d5c29281c", size = 218370, upload-time = "2025-09-21T20:03:32.147Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6e/30d006c3b469e58449650642383dddf1c8fb63d44fdf92994bfd46570695/coverage-7.10.7-cp39-cp39-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:567f5c155eda8df1d3d439d40a45a6a5f029b429b06648235f1e7e51b522b396", size = 244802, upload-time = "2025-09-21T20:03:33.919Z" }, + { url = "https://files.pythonhosted.org/packages/b0/49/8a070782ce7e6b94ff6a0b6d7c65ba6bc3091d92a92cef4cd4eb0767965c/coverage-7.10.7-cp39-cp39-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2af88deffcc8a4d5974cf2d502251bc3b2db8461f0b66d80a449c33757aa9f40", size = 246625, upload-time = "2025-09-21T20:03:36.09Z" }, + { url = "https://files.pythonhosted.org/packages/6a/92/1c1c5a9e8677ce56d42b97bdaca337b2d4d9ebe703d8c174ede52dbabd5f/coverage-7.10.7-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c7315339eae3b24c2d2fa1ed7d7a38654cba34a13ef19fbcb9425da46d3dc594", size = 248399, upload-time = "2025-09-21T20:03:38.342Z" }, + { url = "https://files.pythonhosted.org/packages/c0/54/b140edee7257e815de7426d5d9846b58505dffc29795fff2dfb7f8a1c5a0/coverage-7.10.7-cp39-cp39-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:912e6ebc7a6e4adfdbb1aec371ad04c68854cd3bf3608b3514e7ff9062931d8a", size = 245142, upload-time = "2025-09-21T20:03:40.591Z" }, + { url = "https://files.pythonhosted.org/packages/e4/9e/6d6b8295940b118e8b7083b29226c71f6154f7ff41e9ca431f03de2eac0d/coverage-7.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f49a05acd3dfe1ce9715b657e28d138578bc40126760efb962322c56e9ca344b", size = 246284, upload-time = "2025-09-21T20:03:42.355Z" }, + { url = "https://files.pythonhosted.org/packages/db/e5/5e957ca747d43dbe4d9714358375c7546cb3cb533007b6813fc20fce37ad/coverage-7.10.7-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:cce2109b6219f22ece99db7644b9622f54a4e915dad65660ec435e89a3ea7cc3", size = 244353, upload-time = "2025-09-21T20:03:44.218Z" }, + { url = "https://files.pythonhosted.org/packages/9a/45/540fc5cc92536a1b783b7ef99450bd55a4b3af234aae35a18a339973ce30/coverage-7.10.7-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:f3c887f96407cea3916294046fc7dab611c2552beadbed4ea901cbc6a40cc7a0", size = 244430, upload-time = "2025-09-21T20:03:46.065Z" }, + { url = "https://files.pythonhosted.org/packages/75/0b/8287b2e5b38c8fe15d7e3398849bb58d382aedc0864ea0fa1820e8630491/coverage-7.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:635adb9a4507c9fd2ed65f39693fa31c9a3ee3a8e6dc64df033e8fdf52a7003f", size = 245311, upload-time = "2025-09-21T20:03:48.19Z" }, + { url = "https://files.pythonhosted.org/packages/0c/1d/29724999984740f0c86d03e6420b942439bf5bd7f54d4382cae386a9d1e9/coverage-7.10.7-cp39-cp39-win32.whl", hash = "sha256:5a02d5a850e2979b0a014c412573953995174743a3f7fa4ea5a6e9a3c5617431", size = 220500, upload-time = "2025-09-21T20:03:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/11/4b1e6b129943f905ca54c339f343877b55b365ae2558806c1be4f7476ed5/coverage-7.10.7-cp39-cp39-win_amd64.whl", hash = "sha256:c134869d5ffe34547d14e174c866fd8fe2254918cc0a95e99052903bc1543e07", size = 221408, upload-time = "2025-09-21T20:03:51.803Z" }, + { url = "https://files.pythonhosted.org/packages/ec/16/114df1c291c22cac3b0c127a73e0af5c12ed7bbb6558d310429a0ae24023/coverage-7.10.7-py3-none-any.whl", hash = "sha256:f7941f6f2fe6dd6807a1208737b8a0cbcf1cc6d7b07d24998ad2d63590868260", size = 209952, upload-time = "2025-09-21T20:03:53.918Z" }, +] + +[package.optional-dependencies] +toml = [ + { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version <= '3.11'" }, +] + +[[package]] +name = "exceptiongroup" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", version = "4.13.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "typing-extensions", version = "4.15.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9' and python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0b/9f/a65090624ecf468cdca03533906e7c69ed7588582240cfe7cc9e770b50eb/exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88", size = 29749, upload-time = "2025-05-10T17:42:51.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/36/f4/c6e662dade71f56cd2f3735141b265c3c79293c109549c1e6933b0651ffc/exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10", size = 16674, upload-time = "2025-05-10T17:42:49.33Z" }, +] + +[[package]] +name = "github-actions-validate-inputs" +version = "1.0.0" +source = { editable = "." } +dependencies = [ + { name = "pyyaml" }, +] + +[package.optional-dependencies] +dev = [ + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pytest-cov", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "pytest-cov", version = "7.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "ruff" }, +] + +[package.metadata] +requires-dist = [ + { name = "pytest", marker = "extra == 'dev'", specifier = ">=7.0.0" }, + { name = "pytest-cov", marker = "extra == 'dev'", specifier = ">=4.0.0" }, + { name = "pyyaml", specifier = ">=6.0" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.1.0" }, +] +provides-extras = ["dev"] + +[[package]] +name = "iniconfig" +version = "2.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, +] + +[[package]] +name = "packaging" +version = "25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955, upload-time = "2024-04-20T21:34:42.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556, upload-time = "2024-04-20T21:34:40.434Z" }, +] + +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + +[[package]] +name = "pygments" +version = "2.19.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, +] + +[[package]] +name = "pytest" +version = "8.3.5" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version < '3.9' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version < '3.9'" }, + { name = "iniconfig", marker = "python_full_version < '3.9'" }, + { name = "packaging", marker = "python_full_version < '3.9'" }, + { name = "pluggy", version = "1.5.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, + { name = "tomli", marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ae/3c/c9d525a414d506893f0cd8a8d0de7706446213181570cdbd766691164e40/pytest-8.3.5.tar.gz", hash = "sha256:f4efe70cc14e511565ac476b57c279e12a855b11f48f212af1080ef2263d3845", size = 1450891, upload-time = "2025-03-02T12:54:54.503Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/3d/64ad57c803f1fa1e963a7946b6e0fea4a70df53c1a7fed304586539c2bac/pytest-8.3.5-py3-none-any.whl", hash = "sha256:c69214aa47deac29fad6c2a4f590b9c4a9fdb16a403176fe154b79c0b4d4d820", size = 343634, upload-time = "2025-03-02T12:54:52.069Z" }, +] + +[[package]] +name = "pytest" +version = "8.4.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +dependencies = [ + { name = "colorama", marker = "python_full_version >= '3.9' and sys_platform == 'win32'" }, + { name = "exceptiongroup", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, + { name = "iniconfig", marker = "python_full_version >= '3.9'" }, + { name = "packaging", marker = "python_full_version >= '3.9'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pygments", marker = "python_full_version >= '3.9'" }, + { name = "tomli", marker = "python_full_version >= '3.9' and python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, +] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +dependencies = [ + { name = "coverage", version = "7.6.1", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version < '3.9'" }, + { name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/74/67/00efc8d11b630c56f15f4ad9c7f9223f1e5ec275aaae3fa9118c6a223ad2/pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857", size = 63042, upload-time = "2024-03-24T20:16:34.856Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/3a/af5b4fa5961d9a1e6237b530eb87dd04aea6eb83da09d2a4073d81b54ccf/pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652", size = 21990, upload-time = "2024-03-24T20:16:32.444Z" }, +] + +[[package]] +name = "pytest-cov" +version = "7.0.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +dependencies = [ + { name = "coverage", version = "7.10.7", source = { registry = "https://pypi.org/simple" }, extra = ["toml"], marker = "python_full_version >= '3.9'" }, + { name = "pluggy", version = "1.6.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, + { name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/a2/09f67a3589cb4320fb5ce90d3fd4c9752636b8b6ad8f34b54d76c5a54693/PyYAML-6.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:c2514fceb77bc5e7a2f7adfaa1feb2fb311607c9cb518dbc378688ec73d8292f", size = 186824, upload-time = "2025-09-29T20:27:35.918Z" }, + { url = "https://files.pythonhosted.org/packages/02/72/d972384252432d57f248767556ac083793292a4adf4e2d85dfe785ec2659/PyYAML-6.0.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c57bb8c96f6d1808c030b1687b9b5fb476abaa47f0db9c0101f5e9f394e97f4", size = 795069, upload-time = "2025-09-29T20:27:38.15Z" }, + { url = "https://files.pythonhosted.org/packages/a7/3b/6c58ac0fa7c4e1b35e48024eb03d00817438310447f93ef4431673c24138/PyYAML-6.0.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:efd7b85f94a6f21e4932043973a7ba2613b059c4a000551892ac9f1d11f5baf3", size = 862585, upload-time = "2025-09-29T20:27:39.715Z" }, + { url = "https://files.pythonhosted.org/packages/25/a2/b725b61ac76a75583ae7104b3209f75ea44b13cfd026aa535ece22b7f22e/PyYAML-6.0.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22ba7cfcad58ef3ecddc7ed1db3409af68d023b7f940da23c6c2a1890976eda6", size = 806018, upload-time = "2025-09-29T20:27:41.444Z" }, + { url = "https://files.pythonhosted.org/packages/6f/b0/b2227677b2d1036d84f5ee95eb948e7af53d59fe3e4328784e4d290607e0/PyYAML-6.0.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6344df0d5755a2c9a276d4473ae6b90647e216ab4757f8426893b5dd2ac3f369", size = 802822, upload-time = "2025-09-29T20:27:42.885Z" }, + { url = "https://files.pythonhosted.org/packages/99/a5/718a8ea22521e06ef19f91945766a892c5ceb1855df6adbde67d997ea7ed/PyYAML-6.0.3-cp38-cp38-win32.whl", hash = "sha256:3ff07ec89bae51176c0549bc4c63aa6202991da2d9a6129d7aef7f1407d3f295", size = 143744, upload-time = "2025-09-29T20:27:44.487Z" }, + { url = "https://files.pythonhosted.org/packages/76/b2/2b69cee94c9eb215216fc05778675c393e3aa541131dc910df8e52c83776/PyYAML-6.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:5cf4e27da7e3fbed4d6c3d8e797387aaad68102272f8f9752883bc32d61cb87b", size = 160082, upload-time = "2025-09-29T20:27:46.049Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, + { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, + { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, + { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, + { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, + { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, + { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, + { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, + { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, + { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, + { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, + { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, + { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, + { url = "https://files.pythonhosted.org/packages/9f/62/67fc8e68a75f738c9200422bf65693fb79a4cd0dc5b23310e5202e978090/pyyaml-6.0.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:b865addae83924361678b652338317d1bd7e79b1f4596f96b96c77a5a34b34da", size = 184450, upload-time = "2025-09-25T21:33:00.618Z" }, + { url = "https://files.pythonhosted.org/packages/ae/92/861f152ce87c452b11b9d0977952259aa7df792d71c1053365cc7b09cc08/pyyaml-6.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c3355370a2c156cffb25e876646f149d5d68f5e0a3ce86a5084dd0b64a994917", size = 174319, upload-time = "2025-09-25T21:33:02.086Z" }, + { url = "https://files.pythonhosted.org/packages/d0/cd/f0cfc8c74f8a030017a2b9c771b7f47e5dd702c3e28e5b2071374bda2948/pyyaml-6.0.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c5677e12444c15717b902a5798264fa7909e41153cdf9ef7ad571b704a63dd9", size = 737631, upload-time = "2025-09-25T21:33:03.25Z" }, + { url = "https://files.pythonhosted.org/packages/ef/b2/18f2bd28cd2055a79a46c9b0895c0b3d987ce40ee471cecf58a1a0199805/pyyaml-6.0.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5ed875a24292240029e4483f9d4a4b8a1ae08843b9c54f43fcc11e404532a8a5", size = 836795, upload-time = "2025-09-25T21:33:05.014Z" }, + { url = "https://files.pythonhosted.org/packages/73/b9/793686b2d54b531203c160ef12bec60228a0109c79bae6c1277961026770/pyyaml-6.0.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0150219816b6a1fa26fb4699fb7daa9caf09eb1999f3b70fb6e786805e80375a", size = 750767, upload-time = "2025-09-25T21:33:06.398Z" }, + { url = "https://files.pythonhosted.org/packages/a9/86/a137b39a611def2ed78b0e66ce2fe13ee701a07c07aebe55c340ed2a050e/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fa160448684b4e94d80416c0fa4aac48967a969efe22931448d853ada8baf926", size = 727982, upload-time = "2025-09-25T21:33:08.708Z" }, + { url = "https://files.pythonhosted.org/packages/dd/62/71c27c94f457cf4418ef8ccc71735324c549f7e3ea9d34aba50874563561/pyyaml-6.0.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:27c0abcb4a5dac13684a37f76e701e054692a9b2d3064b70f5e4eb54810553d7", size = 755677, upload-time = "2025-09-25T21:33:09.876Z" }, + { url = "https://files.pythonhosted.org/packages/29/3d/6f5e0d58bd924fb0d06c3a6bad00effbdae2de5adb5cda5648006ffbd8d3/pyyaml-6.0.3-cp39-cp39-win32.whl", hash = "sha256:1ebe39cb5fc479422b83de611d14e2c0d3bb2a18bbcb01f229ab3cfbd8fee7a0", size = 142592, upload-time = "2025-09-25T21:33:10.983Z" }, + { url = "https://files.pythonhosted.org/packages/f0/0c/25113e0b5e103d7f1490c0e947e303fe4a696c10b501dea7a9f49d4e876c/pyyaml-6.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:2e71d11abed7344e42a8849600193d15b6def118602c4c176f748e4583246007", size = 158777, upload-time = "2025-09-25T21:33:15.55Z" }, +] + +[[package]] +name = "ruff" +version = "0.14.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/41/b9/9bd84453ed6dd04688de9b3f3a4146a1698e8faae2ceeccce4e14c67ae17/ruff-0.14.0.tar.gz", hash = "sha256:62ec8969b7510f77945df916de15da55311fade8d6050995ff7f680afe582c57", size = 5452071, upload-time = "2025-10-07T18:21:55.763Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/4e/79d463a5f80654e93fa653ebfb98e0becc3f0e7cf6219c9ddedf1e197072/ruff-0.14.0-py3-none-linux_armv6l.whl", hash = "sha256:58e15bffa7054299becf4bab8a1187062c6f8cafbe9f6e39e0d5aface455d6b3", size = 12494532, upload-time = "2025-10-07T18:21:00.373Z" }, + { url = "https://files.pythonhosted.org/packages/ee/40/e2392f445ed8e02aa6105d49db4bfff01957379064c30f4811c3bf38aece/ruff-0.14.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:838d1b065f4df676b7c9957992f2304e41ead7a50a568185efd404297d5701e8", size = 13160768, upload-time = "2025-10-07T18:21:04.73Z" }, + { url = "https://files.pythonhosted.org/packages/75/da/2a656ea7c6b9bd14c7209918268dd40e1e6cea65f4bb9880eaaa43b055cd/ruff-0.14.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:703799d059ba50f745605b04638fa7e9682cc3da084b2092feee63500ff3d9b8", size = 12363376, upload-time = "2025-10-07T18:21:07.833Z" }, + { url = "https://files.pythonhosted.org/packages/42/e2/1ffef5a1875add82416ff388fcb7ea8b22a53be67a638487937aea81af27/ruff-0.14.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ba9a8925e90f861502f7d974cc60e18ca29c72bb0ee8bfeabb6ade35a3abde7", size = 12608055, upload-time = "2025-10-07T18:21:10.72Z" }, + { url = "https://files.pythonhosted.org/packages/4a/32/986725199d7cee510d9f1dfdf95bf1efc5fa9dd714d0d85c1fb1f6be3bc3/ruff-0.14.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e41f785498bd200ffc276eb9e1570c019c1d907b07cfb081092c8ad51975bbe7", size = 12318544, upload-time = "2025-10-07T18:21:13.741Z" }, + { url = "https://files.pythonhosted.org/packages/9a/ed/4969cefd53315164c94eaf4da7cfba1f267dc275b0abdd593d11c90829a3/ruff-0.14.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30a58c087aef4584c193aebf2700f0fbcfc1e77b89c7385e3139956fa90434e2", size = 14001280, upload-time = "2025-10-07T18:21:16.411Z" }, + { url = "https://files.pythonhosted.org/packages/ab/ad/96c1fc9f8854c37681c9613d825925c7f24ca1acfc62a4eb3896b50bacd2/ruff-0.14.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f8d07350bc7af0a5ce8812b7d5c1a7293cf02476752f23fdfc500d24b79b783c", size = 15027286, upload-time = "2025-10-07T18:21:19.577Z" }, + { url = "https://files.pythonhosted.org/packages/b3/00/1426978f97df4fe331074baf69615f579dc4e7c37bb4c6f57c2aad80c87f/ruff-0.14.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eec3bbbf3a7d5482b5c1f42d5fc972774d71d107d447919fca620b0be3e3b75e", size = 14451506, upload-time = "2025-10-07T18:21:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/58/d5/9c1cea6e493c0cf0647674cca26b579ea9d2a213b74b5c195fbeb9678e15/ruff-0.14.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16b68e183a0e28e5c176d51004aaa40559e8f90065a10a559176713fcf435206", size = 13437384, upload-time = "2025-10-07T18:21:25.758Z" }, + { url = "https://files.pythonhosted.org/packages/29/b4/4cd6a4331e999fc05d9d77729c95503f99eae3ba1160469f2b64866964e3/ruff-0.14.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb732d17db2e945cfcbbc52af0143eda1da36ca8ae25083dd4f66f1542fdf82e", size = 13447976, upload-time = "2025-10-07T18:21:28.83Z" }, + { url = "https://files.pythonhosted.org/packages/3b/c0/ac42f546d07e4f49f62332576cb845d45c67cf5610d1851254e341d563b6/ruff-0.14.0-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:c958f66ab884b7873e72df38dcabee03d556a8f2ee1b8538ee1c2bbd619883dd", size = 13682850, upload-time = "2025-10-07T18:21:31.842Z" }, + { url = "https://files.pythonhosted.org/packages/5f/c4/4b0c9bcadd45b4c29fe1af9c5d1dc0ca87b4021665dfbe1c4688d407aa20/ruff-0.14.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7eb0499a2e01f6e0c285afc5bac43ab380cbfc17cd43a2e1dd10ec97d6f2c42d", size = 12449825, upload-time = "2025-10-07T18:21:35.074Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a8/e2e76288e6c16540fa820d148d83e55f15e994d852485f221b9524514730/ruff-0.14.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:4c63b2d99fafa05efca0ab198fd48fa6030d57e4423df3f18e03aa62518c565f", size = 12272599, upload-time = "2025-10-07T18:21:38.08Z" }, + { url = "https://files.pythonhosted.org/packages/18/14/e2815d8eff847391af632b22422b8207704222ff575dec8d044f9ab779b2/ruff-0.14.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:668fce701b7a222f3f5327f86909db2bbe99c30877c8001ff934c5413812ac02", size = 13193828, upload-time = "2025-10-07T18:21:41.216Z" }, + { url = "https://files.pythonhosted.org/packages/44/c6/61ccc2987cf0aecc588ff8f3212dea64840770e60d78f5606cd7dc34de32/ruff-0.14.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a86bf575e05cb68dcb34e4c7dfe1064d44d3f0c04bbc0491949092192b515296", size = 13628617, upload-time = "2025-10-07T18:21:44.04Z" }, + { url = "https://files.pythonhosted.org/packages/73/e6/03b882225a1b0627e75339b420883dc3c90707a8917d2284abef7a58d317/ruff-0.14.0-py3-none-win32.whl", hash = "sha256:7450a243d7125d1c032cb4b93d9625dea46c8c42b4f06c6b709baac168e10543", size = 12367872, upload-time = "2025-10-07T18:21:46.67Z" }, + { url = "https://files.pythonhosted.org/packages/41/77/56cf9cf01ea0bfcc662de72540812e5ba8e9563f33ef3d37ab2174892c47/ruff-0.14.0-py3-none-win_amd64.whl", hash = "sha256:ea95da28cd874c4d9c922b39381cbd69cb7e7b49c21b8152b014bd4f52acddc2", size = 13464628, upload-time = "2025-10-07T18:21:50.318Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2a/65880dfd0e13f7f13a775998f34703674a4554906167dce02daf7865b954/ruff-0.14.0-py3-none-win_arm64.whl", hash = "sha256:f42c9495f5c13ff841b1da4cb3c2a42075409592825dada7c5885c2c844ac730", size = 12565142, upload-time = "2025-10-07T18:21:53.577Z" }, +] + +[[package]] +name = "tomli" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, + { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, + { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, + { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, + { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, + { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, + { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, + { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, + { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, + { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, + { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, + { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, + { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, + { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, + { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, + { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, + { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, + { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, + { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, + { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, + { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, + { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, + { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, + { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, + { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, + { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, + { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, + { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, + { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, + { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, + { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, + { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, + { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, + { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version < '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967, upload-time = "2025-04-10T14:19:05.416Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806, upload-time = "2025-04-10T14:19:03.967Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.9'", +] +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] diff --git a/validate-inputs/validator.py b/validate-inputs/validator.py index b25b2c1..f535d75 100755 --- a/validate-inputs/validator.py +++ b/validate-inputs/validator.py @@ -8,8 +8,8 @@ from __future__ import annotations import logging import os -from pathlib import Path import sys +from pathlib import Path # Add current directory to path sys.path.insert(0, str(Path(__file__).parent)) diff --git a/validate-inputs/validators/file.py b/validate-inputs/validators/file.py index 5a66085..3c0c734 100644 --- a/validate-inputs/validators/file.py +++ b/validate-inputs/validators/file.py @@ -2,8 +2,8 @@ from __future__ import annotations -from pathlib import Path import re +from pathlib import Path from .base import BaseValidator diff --git a/validate-inputs/validators/registry.py b/validate-inputs/validators/registry.py index bad9460..211455f 100644 --- a/validate-inputs/validators/registry.py +++ b/validate-inputs/validators/registry.py @@ -8,8 +8,8 @@ from __future__ import annotations import importlib import importlib.util import logging -from pathlib import Path import sys +from pathlib import Path from typing import TYPE_CHECKING from .convention_mapper import ConventionMapper