Compare commits

..

74 Commits

Author SHA1 Message Date
9aa16a8164 feat: use our own actions in our workflows (#377)
* feat: use our own actions in our workflows

* fix: add missing inputs to validate-inputs, refactor node

* chore: cr comment fixes

* fix: update-validators formatting

* chore: update validators, add tests, conventions

* feat: validate severity with severity_enum

* feat: add 10 generic validators to improve input validation coverage

Add comprehensive validation system improvements across multiple phases:

Phase 2A - Quick Wins:
- Add multi_value_enum validator for 2-10 value enumerations
- Add exit_code_list validator for Unix/Linux exit codes (0-255)
- Refactor coverage_driver to use multi_value_enum

Phase 2B - High-Value Validators:
- Add key_value_list validator with shell injection prevention
- Add path_list validator with path traversal and glob support

Quick Wins - Additional Enums:
- Add network_mode validator for Docker network modes
- Add language_enum validator for language detection
- Add framework_mode validator for PHP framework modes
- Update boolean pattern to include 'push'

Phase 2C - Specialized Validators:
- Add json_format validator for JSON syntax validation
- Add cache_config validator for Docker BuildKit cache configs

Improvements:
- All validators include comprehensive security checks
- Pattern-based validation with clear error messages
- 23 new test methods with edge case coverage
- Update special case mappings for 20+ inputs
- Fix build-args mapping test expectation

Coverage impact: 22 actions now at 100% validation (88% → 92%)
Test suite: 762 → 785 tests (+23 tests, all passing)

* chore: regenerate rules.yml with improved validator coverage

Regenerate validation rules for all actions with new validators:

- compress-images: 86% → 100% (+1 input: ignore-paths)
- docker-build: 63% → 100% (+4 inputs: cache configs, platform-build-args)
- docker-publish: 73% → 100% (+1 input: build-args)
- language-version-detect: 67% → 100% (+1 input: language)
- php-tests: 89% (fixed framework→framework_mode mapping)
- prettier-lint: 86% → 100% (+2 inputs: file-pattern, plugins)
- security-scan: 86% (maintained coverage)

Overall: 23 of 25 actions now at 100% validation coverage (92%)

* fix: address PR #377 review comments

- Add | None type annotations to 6 optional parameters (PEP 604)
- Standardize injection pattern: remove @# from comma_separated_list validator
  (@ and # are not shell injection vectors, allows npm scoped packages)
- Remove dead code: unused value expression in key_value_list validator
- Update tests to reflect injection pattern changes
2025-11-25 23:51:03 +02:00
e58465e5d3 chore(new-release): add prefix v, add security as type (#376)
* chore(new-release): add prefix v, add security as type

* fix(pr-lint): fix pr-lint workflow

* fix(lint): prettier format
2025-11-25 14:15:55 +02:00
9fe05efeec chore: update workflows (#375) 2025-11-25 13:31:36 +02:00
449669120c fix: pr-lint UID, use printf instead of echo, tweaks (#374) 2025-11-25 13:31:11 +02:00
github-actions[bot]
d9098ddead chore: update action references to v2025 (5cc7373a22) (#372)
This commit updates all internal action references to point to the latest

v2025 tag SHA.
2025-11-25 12:35:25 +02:00
f37d940c72 fix: remove --verbose flag from PHPUnit command (#373)
Root Cause:
- PHPUnit 10 and 11 (released 2024) removed the --verbose flag
- Running `vendor/bin/phpunit --verbose` results in "Unknown option" error
- Multiple projects (Debian packages, Laravel) encountered this in 2024

Evidence:
- GitHub issues #5647 in sebastianbergmann/phpunit
- Debian bug reports #1070508, #1070509, #1070510
- Laravel framework discussion #46672

Solution:
- Remove --verbose flag from line 457
- PHPUnit's default output is sufficient for parsing
- Output parsing logic (lines 465-486) works with standard format
- Tests confirm action designed for PHPUnit 10+ output format

Impact:
- Fixes compatibility with PHPUnit 10.x and 11.x
- Maintains backward compatibility (flag was informational only)
- No change to test result parsing behavior
2025-11-25 12:25:29 +02:00
renovate[bot]
eea547998d chore(deps): update docker/build-push-action action (v6.9.0 → v6.18.0) (#371)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 07:58:54 +02:00
renovate[bot]
49159fc895 chore(deps): update actions/setup-go action (v6.0.0 → v6.1.0) (#370)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 07:57:09 +02:00
renovate[bot]
89fd0f3627 chore(deps): update pre-commit hook rhysd/actionlint (v1.7.8 → v1.7.9) (#369)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 07:53:25 +02:00
renovate[bot]
83cf08ff76 chore(deps): update pre-commit hook renovatebot/pre-commit-hooks (42.19.0 → 42.19.3) (#368)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 07:53:07 +02:00
renovate[bot]
90ab7c645c chore(deps): update pre-commit hook gitleaks/gitleaks (v8.29.0 → v8.29.1) (#367)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 07:52:40 +02:00
renovate[bot]
d05e898ea9 chore(deps): update astral-sh/setup-uv action (v7.1.3 → v7.1.4) (#362)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 01:13:56 +02:00
renovate[bot]
650ebb87b8 chore(deps): update peter-evans/create-pull-request action (v7.0.8 → v7.0.9) (#363)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 01:13:38 +02:00
renovate[bot]
13316bd827 chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.14.5 → v0.14.6) (#364)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 01:13:04 +02:00
renovate[bot]
350fd30043 chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.494 → 3.2.495) (#365)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 01:12:46 +02:00
renovate[bot]
587853a9cd chore(deps): update pre-commit hook davidanson/markdownlint-cli2 (v0.19.0 → v0.19.1) (#366)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-24 01:12:28 +02:00
Copilot
6cde6d088d fix: disable MegaLinter autofixing in pr-lint action (#361)
* Initial plan

* fix: disable MegaLinter autofixing in pr-lint action

Co-authored-by: ivuorinen <11024+ivuorinen@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: ivuorinen <11024+ivuorinen@users.noreply.github.com>
2025-11-24 00:38:14 +02:00
5cc7373a22 chore: update action references for release v2025.11.23
This commit updates all internal action references to point to the current
commit SHA in preparation for release v2025.11.23.
2025-11-23 19:09:40 +02:00
renovate[bot]
8fb52522ab chore(deps)!: update stefanzweifel/git-auto-commit-action (v5.2.0 → v7.0.0) (#355)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 16:47:31 +02:00
renovate[bot]
bcf49f55b5 chore(deps): update github/codeql-action action (v4.31.3 → v4.31.4) (#357)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 14:08:28 +00:00
renovate[bot]
060afb8871 chore(deps): update pre-commit hook renovatebot/pre-commit-hooks (42.6.2 → 42.19.0) (#360)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 14:06:10 +00:00
renovate[bot]
a0ebb00853 chore(deps): update pre-commit hook astral-sh/uv-pre-commit (0.9.8 → 0.9.11) (#354)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 14:05:37 +00:00
renovate[bot]
227cf7f56f chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.489 → 3.2.494) (#345)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 14:05:20 +00:00
renovate[bot]
e28c56c7cf chore(deps)!: update terraform-linters/setup-tflint (v4.0.0 → v6.2.1) (#356)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 16:04:42 +02:00
renovate[bot]
504debcb8d chore(deps): update stefanzweifel/git-auto-commit-action action (v5.0.1 → v5.2.0) (#358)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-21 16:03:28 +02:00
dependabot[bot]
ed438428b2 chore(deps): bump glob from 10.4.5 to 10.5.0 (#352)
Bumps [glob](https://github.com/isaacs/node-glob) from 10.4.5 to 10.5.0.
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v10.4.5...v10.5.0)

---
updated-dependencies:
- dependency-name: glob
  dependency-version: 10.5.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-11-21 16:00:23 +02:00
a88bb34369 feature: inline actions (#359)
* refactor: make language-version-detect self-contained

Inline version-file-parser logic into language-version-detect to eliminate
external dependency and make the action fully self-contained.

Changes:
- Replace external call to version-file-parser with inline parsing script
- Use POSIX sh for maximum compatibility
- Streamlined version detection logic focusing on 4 supported languages
- Priority: .tool-versions > Dockerfile > devcontainer.json > version files > config files > default

Benefits:
- No external action dependencies
- Faster execution (no action setup overhead)
- Easier to maintain and test
- Reduced surface area for security issues

The action now handles all version detection inline while maintaining
the same outputs and functionality.

* refactor: inline Go detection into go-build

Make go-build self-contained by inlining Go version detection logic,
eliminating dependency on language-version-detect action.

Changes:
- Replace external language-version-detect call with inline script (~102 lines)
- Detect Go version from: .tool-versions, Dockerfile, devcontainer.json, .go-version, go.mod
- Use POSIX sh for maximum compatibility
- Maintain same output contract (detected-version)
- Fix sed to use POSIX-compliant extended regex (-E flag)
- Fix go.mod parsing to clean version before validation

Benefits:
- Faster execution (no external action overhead)
- Self-contained action
- Reduced attack surface
- Template for other language actions

This is part of Phase 1 of the inlining campaign to improve performance
and reduce internal dependencies.

* refactor: inline .NET detection into csharp actions

Replace language-version-detect dependency with inline version detection
for all three C# actions (csharp-build, csharp-lint-check, csharp-publish).

Detection logic checks (in priority order):
- .tool-versions file (dotnet key)
- Dockerfile (FROM dotnet: image)
- devcontainer.json (dotnet: image)
- global.json (.sdk.version field)

Implementation details:
- POSIX sh compliant with `set -eu`
- Validates version format: X, X.Y, or X.Y.Z
- Normalizes versions: strips 'v' prefix, whitespace, line endings
- Uses `sed -E` for portable extended regex
- Conditional jq usage with diagnostic messages
- Maintains output contract (detected-version)

Fixed issues from code review:
- devcontainer.json sed regex: malformed wildcard ('. */' → '.*')
- Dockerfile sed regex: removed unintended leading space (' \1' → '\1')
- Added stderr diagnostics when jq is not found
- Applied fixes to all three actions for consistency

Changes:
- csharp-build: ~100 lines of inline detection + jq diagnostics
- csharp-lint-check: ~100 lines of inline detection + jq diagnostics
- csharp-publish: ~100 lines of inline detection + jq diagnostics
- All READMEs regenerated with action-docs

Benefits:
- Eliminates external dependency for .NET version detection
- Reduces action initialization time
- Improved debugging (diagnostic messages, all logic in one file)
- Consistent with go-build pattern

* refactor: inline Python detection into python-lint-fix

Replace language-version-detect dependency with inline version detection
for the Python linting action.

Detection logic checks (in priority order):
- .tool-versions file (python key)
- Dockerfile (FROM python: image)
- devcontainer.json (python: image)
- .python-version file
- pyproject.toml (requires-python field)

Implementation details:
- POSIX sh compliant with `set -eu`
- Validates version format: X.Y or X.Y.Z
- Normalizes versions: strips 'v' prefix, whitespace, line endings
- Uses `sed -E` for portable extended regex (Dockerfile/devcontainer)
- Uses basic sed for pyproject.toml (POSIX-compatible backslash escapes)
- Conditional jq usage with diagnostic messages
- Maintains output contract (detected-version)

Changes:
- python-lint-fix: ~110 lines of inline detection + jq diagnostics
- README regenerated with action-docs

Benefits:
- Eliminates external dependency for Python version detection
- Reduces action initialization time
- Improved debugging (diagnostic messages, all logic in one file)
- Consistent with go-build and csharp pattern

* refactor: inline PHP detection into php-laravel-phpunit

Replace language-version-detect dependency with inline version detection
for the Laravel PHPUnit testing action.

Detection logic checks (in priority order):
- .tool-versions file (php key)
- Dockerfile (FROM php: image)
- devcontainer.json (php: image)
- .php-version file
- composer.json (require.php or config.platform.php fields)

Implementation details:
- POSIX sh compliant with `set -eu`
- Validates version format: X.Y or X.Y.Z
- Normalizes versions: strips 'v' prefix, whitespace, line endings
- Uses `sed -E` for portable extended regex (Dockerfile/devcontainer)
- Uses basic sed for composer.json (POSIX-compatible backslash escapes)
- Conditional jq usage with diagnostic messages
- Maintains output contract (detected-version)

Changes:
- php-laravel-phpunit: ~115 lines of inline detection + jq diagnostics
- README regenerated with action-docs

Benefits:
- Eliminates external dependency for PHP version detection
- Reduces action initialization time
- Improved debugging (diagnostic messages, all logic in one file)
- Consistent with go-build, csharp, and python-lint-fix pattern

* refactor: inline Node.js version detection into node-setup

Replace version-file-parser dependency with ~140 lines of inline detection:
- Detect from .nvmrc, package.json, .tool-versions, Dockerfile, devcontainer.json
- Detect package manager from lock files (bun, pnpm, yarn, npm)
- Use POSIX sh with set -eu for portability
- Include validate_version() and clean_version() helper functions
- Add diagnostic messages when jq unavailable

Detection priority: .nvmrc > package.json > .tool-versions > Dockerfile > devcontainer > default

Reduces external dependencies and improves initialization performance.

* refactor: remove deprecated version-file-parser action

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

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

Reduces external dependencies and improves initialization performance across all actions.

* refactor: inline language-version-detect in pr-lint

Inline version detection for PHP, Python, and Go directly into pr-lint
to eliminate dependency on language-version-detect action and improve
initialization performance.

Changes:
- PHP detection: .tool-versions, Dockerfile, devcontainer.json,
  .php-version, composer.json (default: 8.4)
- Python detection: .tool-versions, Dockerfile, devcontainer.json,
  .python-version, pyproject.toml (default: 3.11)
- Go detection: .tool-versions, Dockerfile, devcontainer.json,
  .go-version, go.mod (default: 1.24)

All detection logic follows POSIX sh standard with set -eu and uses
validate_version() and clean_version() helper functions for consistency.

* docs: deprecate language-version-detect action

Mark language-version-detect as deprecated now that all internal usages
have been inlined. Inline version detection provides better performance
by eliminating action initialization overhead.

Changes:
- Add DEPRECATED notice to action.yml description and metadata
- Add deprecation warning banner to README with migration guidance
- Reference existing actions with inline detection patterns

Users should migrate to inlining version detection logic directly into
their actions rather than using this composite action. See pr-lint,
php-laravel-phpunit, python-lint-fix, and go-build for examples.

This action will be removed in a future release.

* refactor(go): remove redundant caching from Go actions

Remove redundant common-cache usage in Go actions since setup-go with
cache:true already provides comprehensive caching.

Changes:
- go-build: Removed duplicate common-cache step (setup-go caches
  ~/go/pkg/mod and ~/.cache/go-build automatically)
- go-lint: Removed redundant ~/.cache/go-build from cache paths
  (kept ~/.cache/golangci-lint as it's linter-specific and not
  covered by setup-go)

Performance improvements:
- Eliminates duplicate caching operations
- Reduces action initialization overhead
- setup-go's native caching is more efficient and maintained

setup-go with cache:true caches:
- ~/go/pkg/mod (Go modules)
- ~/.cache/go-build (Go build cache)

* refactor(python): migrate to native setup-python caching

Replace common-cache with native caching in Python actions for better
performance and maintainability.

python-lint-fix changes:
- Add package manager detection (uv, poetry, pipenv, pip)
- Use setup-python's native cache parameter dynamically
- Remove redundant common-cache step
- Support uv with pip-compatible caching
- Enhanced cache-dependency-path to include all lock files

ansible-lint-fix changes:
- Add setup-python with native pip caching (Python 3.11)
- Remove redundant common-cache step
- Simplify dependency installation

Benefits:
- Native caching is more efficient and better maintained
- Supports modern Python tooling (uv, poetry, pipenv)
- Reduces common-cache dependencies from 11 to 7 actions
- setup-python handles cache invalidation automatically

setup-python cache types supported: pip, pipenv, poetry

* refactor(csharp): migrate to native setup-dotnet caching

Replace common-cache with native caching in C# actions for better
performance and maintainability.

csharp-build changes:
- Add cache: true and cache-dependency-path to setup-dotnet
- Remove redundant common-cache step
- Simplify restore logic, remove cache-hit conditionals

csharp-publish changes:
- Add cache: true and cache-dependency-path to setup-dotnet
- Remove redundant common-cache step
- Simplify restore logic, use step-security/retry for restore

Benefits:
- Native caching is more efficient and better maintained
- Reduces common-cache dependencies from 7 to 5 actions
- setup-dotnet handles NuGet package caching automatically
- Cleaner workflow without complex conditional logic

Phase 2 complete: Reduced common-cache usage from 11 to 5 actions.

* refactor(go-lint): replace common-cache with actions/cache

Replace common-cache wrapper with direct actions/cache for golangci-lint
caching. This simplifies the action and improves performance.

Changes:
- Replace ivuorinen/actions/common-cache with actions/cache@v4.3.0
- Use hashFiles() for cache key generation instead of manual SHA256
- Simplify from 10 lines to 9 lines of YAML

Benefits:
- Native GitHub Actions functionality (no wrapper overhead)
- Better performance (no extra action call)
- Matches official golangci-lint-action approach
- Less maintenance (GitHub-maintained action)
- Reduces common-cache usage from 5 to 4 actions

Trade-off:
- Cache key format changes (invalidates existing caches once)

* refactor: eliminate common-cache, use actions/cache directly

Replace common-cache wrapper with native actions/cache in npm-publish
and php-composer, completing the caching optimization campaign.

Changes:
1. npm-publish (lines 107-114):
   - Replace common-cache with actions/cache@v4.3.0
   - Use hashFiles() for node_modules cache key
   - Support multiple lock files (package-lock, yarn.lock, pnpm, bun)

2. php-composer (lines 177-190):
   - Replace common-cache with actions/cache@v4.3.0
   - Use multiline YAML for cleaner path configuration
   - Use hashFiles() for composer cache key
   - Support optional cache-directories input

Benefits:
- Native GitHub Actions functionality (no wrapper overhead)
- Better performance (no extra action call)
- Simpler maintenance (one less internal action)
- Standard approach used by official actions
- Built-in hashFiles() more efficient than manual sha256sum

Result:
- Eliminates all common-cache usage (reduced from 4 to 0 actions)
- common-cache action can now be deprecated/removed
- Completes caching optimization: 11 → 0 common-cache dependencies

Campaign summary:
- Phase 1: Inline language-version-detect
- Phase 2: Migrate 6 actions to setup-* native caching
- Phase 3: Replace go-lint common-cache with actions/cache
- Phase 4: Eliminate remaining common-cache (npm, php)

* refactor: migrate Node.js linters from common-cache to actions/cache

Replace common-cache wrapper with native actions/cache@v4.3.0 in all
Node.js linting actions.

Changes:
- biome-lint: Use actions/cache with direct hashFiles()
- eslint-lint: Use actions/cache with direct hashFiles()
- prettier-lint: Use actions/cache with direct hashFiles()
- pr-lint: Use actions/cache with direct hashFiles()

All actions now use:
- Native GitHub Actions cache functionality
- Multi-lock-file support (npm, yarn, pnpm, bun)
- Two-level restore-keys for graceful fallback
- OS-aware cache keys with runner.os

Benefits:
- No wrapper overhead
- Native hashFiles() instead of manual SHA256
- Consistent caching pattern across all Node.js actions

* refactor: remove common-cache action

Delete common-cache action and all associated test files. All actions
now use native actions/cache@v4.3.0 instead of the wrapper.

Deleted:
- common-cache/action.yml
- common-cache/README.md
- common-cache/rules.yml
- common-cache/CustomValidator.py
- _tests/unit/common-cache/validation.spec.sh
- _tests/integration/workflows/common-cache-test.yml
- validate-inputs/tests/test_common-cache_custom.py

Action count: 28 → 27

* fix: improve cache key quality across actions

Address cache key quality issues identified during code review.

php-composer:
- Remove unused cache-directories input and handling code
- Simplify cache paths to vendor + ~/.composer/cache only
- Eliminate empty path issue when cache-directories was default empty

npm-publish:
- Remove redundant -npm- segment from cache key
- Change: runner.os-npm-publish-{manager}-npm-{hash}
- To: runner.os-npm-publish-{manager}-{hash}

go-lint:
- Add ~/.cache/go-build to cached paths
- Now caches both golangci-lint and Go build artifacts
- Improves Go build performance

Result: Cleaner cache keys and better caching coverage

* docs: remove common-cache references from documentation and tooling

Remove all remaining references to common-cache from project documentation,
test workflows, and build tooling after action deletion.

Updated:
- CLAUDE.md: Remove from action catalog (28 → 27 actions)
- README.md: Regenerate catalog without common-cache
- SECURITY.md: Update caching optimization notes
- Test workflows: Remove common-cache test references
- spec_helper.sh: Remove common-cache test helpers
- generate_listing.cjs: Remove from category/language mappings
- update-validators.py: Remove custom validator entry

* refactor: inline node-setup across Node.js actions

Phase 6A: Remove node-setup abstraction layer and inline Node.js setup.

Changes:
- Replace node-setup calls with direct actions/setup-node@v6.0.0
- Inline package manager detection (lockfile-based)
- Add Corepack enablement and package manager installation
- Use Node.js 22 as default version

Actions migrated (5):
- prettier-lint: Inline Node.js setup + package manager detection
- biome-lint: Inline Node.js setup + package manager detection
- eslint-lint: Inline Node.js setup + package manager detection
- pr-lint: Inline Node.js setup (conditional on package.json)
- npm-publish: Inline Node.js setup + package manager detection

Removed:
- node-setup/action.yml (371 lines)
- node-setup/README.md, rules.yml, CustomValidator.py
- _tests/unit/node-setup/validation.spec.sh
- _tests/integration/workflows/node-setup-test.yml
- validate-inputs/tests/test_node-setup_custom.py

Documentation updates:
- CLAUDE.md: Remove node-setup from action list (26 actions)
- generate_listing.cjs: Remove node-setup mappings
- update-validators.py: Remove node-setup custom validator

Result: 26 actions (down from 27), eliminated internal dependency layer.

* refactor: consolidate PHP testing actions with Laravel detection

Merge php-tests, php-laravel-phpunit, and php-composer into single php-tests action:

Consolidation:
- Merge three PHP actions into one with framework auto-detection
- Add framework input (auto/laravel/generic) with artisan file detection
- Inline PHP version detection from multiple sources
- Inline Composer setup, caching, and dependency installation
- Add conditional Laravel-specific setup steps

Features:
- Auto-detect Laravel via artisan file presence
- PHP version detection from .tool-versions, Dockerfile, composer.json, etc.
- Composer dependency management with retry logic and caching
- Laravel setup: .env copy, key generation, permissions, SQLite database
- Smart test execution: composer test for Laravel, direct PHPUnit for generic

Outputs:
- framework: Detected framework (laravel/generic)
- php-version, composer-version, cache-hit: Setup metadata
- test-status, tests-run, tests-passed: Test results

Deleted:
- php-laravel-phpunit/: Laravel-specific testing action
- php-composer/: Composer dependency management action
- Related test files and custom validators

Updated:
- CLAUDE.md: 26 → 24 actions
- generate_listing.cjs: Remove php-laravel-phpunit, php-composer
- validate-inputs: Remove php-laravel-phpunit custom validator

Result: 3 actions → 1 action, maintained all functionality with simpler interface.

* fix: correct sed pattern in go-build Dockerfile parsing

Remove unintended space in sed replacement pattern that was extracting
golang version from Dockerfile.

Before: s/.*golang:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/ \1/p
After:  s/.*golang:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p

The leading space in the replacement would have caused version strings
to have unwanted whitespace, potentially breaking version validation.

* fix: convert bash-specific syntax to POSIX sh in php-tests

Replace bash-specific [[ ]] syntax with POSIX-compliant alternatives
to adhere to CLAUDE.md standards (all scripts must be POSIX sh).

Changes:
- PHP version validation: Replace regex =~ with case statement
  matching X.Y and X.Y.Z patterns
- Max retries validation: Replace regex =~ with case statement
  checking for non-digit characters
- Email validation: Replace glob patterns with case statement
  matching *@*.* pattern
- Username validation: Replace glob patterns with case statement
  detecting command injection characters (;, &&, |)

All validation logic preserved, error messages unchanged.

* fix: add missing max-retries input to csharp-publish

Add missing max-retries input declaration that was being used by the
step-security/retry step at line 171 but not defined in the inputs
section.

Changes:
- Add max-retries input with default value of '3'
- Add description for dependency restoration retry attempts
- Regenerate README.md with updated inputs documentation

This fixes undefined input reference in the Restore Dependencies step.

* fix: remove misleading 'Restore Complete' step in csharp-publish

Remove the 'Restore Complete' step that always printed 'Cache hit -
skipping dotnet restore' even though restore always runs via the retry
action.

The message was misleading because:
- Dependencies are always restored via step-security/retry
- The message claimed restore was skipped, which was false
- The step served no actual purpose

The 'Restore Dependencies' step already provides appropriate output
during execution, making this step redundant and confusing.

* fix(csharp-publish): use NuGet lock files for cache hashing

The cache-dependency-path was incorrectly targeting *.csproj files which
don't represent dependency state. Update to target **/packages.lock.json
for accurate cache key generation.

This ensures:
- Cache hits only when dependencies actually match
- No false cache hits from project file changes
- Correct behavior per setup-dotnet@v5 documentation

* fix: escape dots in shell case patterns for literal period matching

In shell case statements, unescaped dots match any character rather than
literal periods. Escape all dots in version pattern matching to ensure
correct semantic version validation (e.g., '8.3.1' not '8X3Y1').

Fixed in 9 actions:
- go-build: validate_version function
- csharp-build: validate_version function
- csharp-lint-check: validate_version function
- csharp-publish: validate_version function
- php-tests: PHP version validation + validate_version function
- python-lint-fix: validate_version function
- pr-lint: 3x validate_version functions (Go, Node.js, Python)
- language-version-detect: PHP, Python, Node.js, .NET, Go validation

Changed patterns: [0-9]*.[0-9]* → [0-9]*\.[0-9]*
Impact: More accurate version validation, prevents false matches

* fix(csharp-build): use NuGet lock files for cache hashing

The cache-dependency-path was incorrectly targeting *.csproj files which
don't represent dependency state. Update to target **/packages.lock.json
for accurate cache key generation, matching csharp-publish configuration.

This ensures:
- Cache hits only when dependencies actually match
- No false cache hits from project file changes
- Consistent caching behavior across C# actions

* fix(php-tests): replace GNU grep with POSIX-compatible sed

The Composer version detection used 'grep -oP' with \K which is GNU-specific
and breaks portability on BSD/macOS systems. Replace with POSIX-compliant
sed pattern that extracts version numbers from 'Composer version X.Y.Z'.

Changed:
- grep -oP 'Composer version \K[0-9]+\.[0-9]+\.[0-9]+'
+ sed -n 's/.*Composer version \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*/\1/p'

Maintains same behavior with empty string fallback on match failure.

* fix: remove misleading 'Restore Complete' step in csharp-build

The 'Restore Complete' step always printed 'Cache hit - skipping dotnet
restore' even when no cache hit occurred and restore ran unconditionally
via the retry action. Remove the step entirely to eliminate misleading
log messages.

Matches fix already applied to csharp-publish (821aef0). The 'Restore
Dependencies' step already provides appropriate output.

* fix(python-lint-fix): use literal cache values for setup-python@v6

The setup-python@v6 action requires literal cache values, not dynamic
expressions. Split the single Setup Python step into three conditional
steps with literal cache values ('pip', 'pipenv', 'poetry').

Changed:
- Single step with 'cache: ${{ steps.package-manager.outputs.package-manager }}'
+ Three conditional steps each with literal cache values
+ Each step includes only relevant cache-dependency-path patterns

Benefits:
- Compatible with setup-python@v6 requirements
- More precise cache-dependency-path per package manager
- Maintains full functionality across pip, pipenv, poetry, and uv

* fix(python-lint-fix): remove unreachable venv activation step

The 'Activate Virtual Environment (Cache Hit)' step references non-existent
steps.cache-pip.outputs.cache-hit, making its condition always false and
the step unreachable dead code.

Additionally, this step is redundant as all subsequent steps (Run flake8,
Run autopep8, etc.) already activate the venv directly with
'. .venv/bin/activate' in their run blocks.

Removed lines 298-312 (15 lines of dead code).

* fix: correct invalid step references and unsafe conditions

prettier-lint/action.yml:
- Fix 3 invalid references to steps.node-setup.outputs.package-manager
- Should reference steps.detect-pm.outputs.package-manager (correct step ID)
- Lines 327, 360, 417

terraform-lint-fix/action.yml:
- Add safety checks to commit condition to prevent fromJSON failures
- Now verifies: files found + auto-fix enabled + fixes made
- Line 249

Note: eslint-lint/action.yml already has correct references (no changes needed)

* fix(php-tests): improve PHPUnit output parsing robustness

Address fragile test result parsing that fails with common PHPUnit formats:

Before:
- Line 467 pattern 'OK.*[0-9]+ tests' required plural, failing on single test
- Pattern only matched success case, silently defaulting to 0 on failures
- Didn't handle ERRORED!/FAILED! output or skipped tests

After:
- Pattern 1: Match 'OK (N test(s), M assertions)' - handles singular/plural
- Pattern 2: Parse 'Tests: N' line for failures/errors, calculate passed
- Handles: single test, failures, errors, mixed, skipped tests
- Exit code still determines final status (line 489)

* test: add comprehensive PHPUnit output parsing tests

Add 20 unit tests covering all PHPUnit output format variations:

Success cases (3):
- Single test (singular 'test')
- Multiple tests (plural 'tests')
- Large test counts

Failure cases (5):
- Failures only
- Errors only
- Mixed failures and errors
- All tests failing
- Prevents negative passed count

Edge cases (7):
- Skipped tests (with/without OK prefix)
- No parseable output (fallback to 0/0)
- Empty output
- Verbose output with noise
- Full failure details
- Risky tests

Status tests (2):
- Exit code 0 → success
- Exit code non-zero → failure

Helper function parse_phpunit_output() replicates action parsing logic
for isolated testing.

Also fix pre-existing test expecting underscores in output names:
- test_status → test-status
- tests_run → tests-run
- tests_passed → tests-passed
- coverage_path → framework (output doesn't exist)

All 62 tests now pass (42 existing + 20 new)

* fix(python-lint-fix): make pyproject.toml parsing POSIX/BSD compatible

Fix portability issues in pyproject.toml version parsing:

Line 173:
- Before: grep -q '^\\[project\\]' (double-escaped, incorrect)
- After: grep -q '^\[project\]' (single-escaped, correct)

Line 174:
- Before: '^\\s*' and sed with GNU-only '\+' quantifier
- After: '^[[:space:]]*' (POSIX character class) and sed -E with '+' (extended regex)

Changes ensure compatibility with:
- macOS (BSD sed)
- Linux (GNU sed)
- Any POSIX-compliant shell environment

sed -E enables extended regex mode where:
- No backslashes needed for grouping ()
- '+' works directly (not '\+')
- More readable and portable

* fix(posix): Phase 1 - convert bash-specific syntax to POSIX sh

Convert three simpler action files from bash to POSIX sh:

terraform-lint-fix/action.yml:
- Line 216: Change [[ to [ and == to =

codeql-analysis/action.yml:
- Change shell: bash to shell: sh (4 steps)
- Add set -eu to all shell blocks
- Convert [[ to [ and == to = (5 locations)
- Fix $GITHUB_OUTPUT quoting

sync-labels/action.yml:
- Change shell: bash to shell: sh (2 steps)
- Convert bash [[ tests to POSIX [ tests
- Replace regex =~ with case pattern matching
- Convert pipefail (bash-only) to POSIX set -eu

All changes verified:
- make lint: passed
- Unit tests: 25/25 passed (100% coverage)
- POSIX compliance: Confirmed for all three actions

Part of comprehensive POSIX compatibility review.
Next: Phase 2 (validation-heavy files)

* fix(posix): Phase 2A - convert bash to POSIX sh (3 validation-heavy files)

Convert three validation-heavy action files from bash to POSIX sh:

release-monthly/action.yml:
- Replace shell: bash → shell: sh (3 steps)
- Replace set -euo pipefail → set -eu
- Replace regex =~ with case pattern matching for version validation
- Replace [[ ]] tests with [ ] tests and == with =
- Use case statements for prefix validation
- Fix &>/dev/null → >/dev/null 2>&1 (POSIX)

compress-images/action.yml:
- Replace shell: bash → shell: sh
- Replace set -euo pipefail → set -eu
- Convert all [[ ]] wildcard tests to case pattern matching
- Convert regex =~ to case statements for numeric validation
- Consolidated validation patterns

biome-lint/action.yml:
- Replace shell: bash → shell: sh (4 steps)
- Replace set -euo pipefail → set -eu
- Convert [[ ]] tests to case pattern matching
- Convert regex =~ to case statements
- Handle GitHub token expression validation with case
- Email validation with case patterns
- Username validation with case patterns for:
  - Invalid characters
  - Leading/trailing hyphens
  - Consecutive hyphens

All changes maintain identical validation logic while using POSIX-compliant syntax.

Next: eslint-lint and prettier-lint (similar patterns)

* fix: convert eslint-lint and prettier-lint to POSIX sh (Phase 2B)

Convert shell scripts from bash to POSIX sh for maximum compatibility:

eslint-lint/action.yml:
- Change all shell: bash → shell: sh
- Replace set -euo pipefail with set -eu (pipefail not POSIX)
- Convert [[ ]] tests to [ ] with = instead of ==
- Replace regex validation with case pattern matching
- Convert boolean validation to case-insensitive patterns
- Update version/path/email validation with case patterns

prettier-lint/action.yml:
- Change all shell: bash → shell: sh
- Replace set -euo pipefail with set -eu
- Convert [[ ]] tests to [ ] with = instead of ==
- Replace regex validation with case pattern matching
- Convert boolean validation to case-insensitive patterns
- Update version/path/email validation with case patterns

All changes maintain identical functionality while ensuring
compatibility with POSIX sh on all platforms.

Part of comprehensive POSIX compatibility effort. Phase 2B completes
5/5 validation-heavy action files.

* fix: convert docker-build and php-tests to POSIX sh (Phase 3)

Convert complex shell scripts from bash to POSIX sh for maximum compatibility:

docker-build/action.yml:
- Convert all 12 shell blocks from bash to sh
- Replace set -euo pipefail with set -eu (pipefail not POSIX)
- Convert bash array parsing to POSIX positional parameters
  * Parse Build Arguments: IFS + set -- pattern
  * Parse Build Contexts: IFS + set -- pattern
  * Parse Secrets: IFS + set -- with case validation
- Replace [[ wildcard tests with case patterns
  * Line 277: secret contains '=' check
  * Line 376: cache_to contains 'type=local' check
- Convert [[ -z || -z ]] to separate [ ] tests
- Change == to = in string comparisons

php-tests/action.yml:
- Convert all 11 shell blocks from bash to sh
- Replace set -euo pipefail with set -eu
- No bash-specific constructs (already POSIX-compatible logic)

All changes maintain identical functionality while ensuring
compatibility with POSIX sh on all platforms. Complex array
handling in docker-build required careful conversion using
positional parameters with IFS manipulation.

Part of comprehensive POSIX compatibility effort. Phase 3 completes
2/2 complex action files with array handling.

* fix: convert remaining actions to POSIX sh (Phase 4 - Final)

Convert final shell scripts from bash to POSIX sh for maximum compatibility:

csharp-build/action.yml (2 blocks):
- Change shell: bash → shell: sh
- Replace set -euo pipefail with set -eu

csharp-lint-check/action.yml (3 blocks):
- Change shell: bash → shell: sh
- Replace set -euo pipefail with set -eu

csharp-publish/action.yml (6 blocks):
- Change shell: bash → shell: sh
- Replace set -euo pipefail with set -eu

go-build/action.yml (2 blocks):
- Change shell: bash → shell: sh
- Replace set -euo pipefail with set -eu

python-lint-fix/action.yml:
- Already using shell: sh (POSIX compliant)
- No changes needed

All Phase 4 files contained only isolated bash usage without
bash-specific features ([[, =~, arrays, etc.), making conversion
straightforward. All changes maintain identical functionality while
ensuring compatibility with POSIX sh on all platforms.

POSIX COMPATIBILITY PROJECT COMPLETE:
- Phase 1: 3 files (terraform-lint-fix, codeql-analysis, sync-labels)
- Phase 2: 5 files (release-monthly, compress-images, biome-lint, eslint-lint, prettier-lint)
- Phase 3: 2 files (docker-build, php-tests) - complex array handling
- Phase 4: 4 files (csharp-build, csharp-lint-check, csharp-publish, go-build)

Total: 14/14 files converted (100% complete)
All shell scripts now POSIX sh compatible across all platforms.

* fix: address PR #359 review comments

- Fix eslint-lint file extensions regex to support 1+ extensions
  (critical bug: default .js,.jsx,.ts,.tsx was rejected by validation)
- Add NuGet caching to csharp-lint-check Setup .NET SDK step
  (matches csharp-build and csharp-publish configuration)

* fix: address additional PR #359 review comments

POSIX Compatibility:
- csharp-lint-check: replace bash [[ ]] and =~ with POSIX [ ] and grep -qE
  (fixes validation block incompatible with sh shell declaration)

ESLint Consistency:
- eslint-lint: use detected package manager in check mode
  (adds pnpm/yarn/bun support, matching fix mode behavior)
- eslint-lint: replace case pattern with grep-based validation
  (fixes regex that rejected valid multi-extension inputs like .js,.jsx,.ts,.tsx)

* fix: use file-extensions input in eslint-lint commands

The file-extensions input was defined, validated, and passed to env
but never used in ESLint commands, causing ESLint to only lint .js
files by default.

Changes:
- Add --ext flag to all check mode ESLint commands (pnpm/yarn/bun/npm)
- Add FILE_EXTENSIONS to fix mode env section
- Add --ext flag to all fix mode ESLint commands (pnpm/yarn/bun/npm)

Now ESLint correctly lints all configured extensions (.js,.jsx,.ts,.tsx)

* fix: strengthen eslint-lint version validation

The previous case pattern allowed invalid versions like "1.0.0-"
(trailing hyphen with no pre-release identifier) and didn't treat
dots as literal characters.

Changes:
- Replace case pattern with grep-based regex validation
- Pattern: ^[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9]+([.-][a-zA-Z0-9]+)*)?$
- Requires at least one alphanumeric character after hyphen if present
- Supports dot/dash-separated pre-release identifiers
- Treats dots as literal characters throughout

Valid: 8.57.0, 8.57.0-rc.1, 8.57.0-alpha.beta.1
Invalid: 8.57.0-, 8.57.0--, 8.57.0-., 8.57.0-alpha..1

* fix: strengthen eslint-lint email validation

The previous case pattern *@*.* was overly permissive and would
accept invalid emails like @@@, a@b., or user@@domain.com.

Changes:
- Replace case pattern with grep-based regex validation
- Pattern: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
- Requires proper structure: local-part@domain.tld
- Local part: alphanumeric plus common email special chars
- Domain: alphanumeric, dots, hyphens
- TLD: at least 2 letters

Valid: user@example.com, first.last@domain.co.uk
Invalid: @@@, a@b., user@@domain.com, test@domain
2025-11-21 15:46:33 +02:00
ab371bdebf feat: simplify actions (#353)
* feat: first pass simplification

* refactor: simplify actions repository structure

Major simplification reducing actions from 44 to 30:

Consolidations:
- Merge biome-check + biome-fix → biome-lint (mode: check/fix)
- Merge eslint-check + eslint-fix → eslint-lint (mode: check/fix)
- Merge prettier-check + prettier-fix → prettier-lint (mode: check/fix)
- Merge 5 version-detect actions → language-version-detect (language param)

Removals:
- common-file-check, common-retry (better served by external tools)
- docker-publish-gh, docker-publish-hub (consolidated into docker-publish)
- github-release (redundant with existing tooling)
- set-git-config (no longer needed)
- version-validator (functionality moved to language-version-detect)

Fixes:
- Rewrite docker-publish to use official Docker actions directly
- Update validate-inputs example (eslint-fix → eslint-lint)
- Update tests and documentation for new structure

Result: ~6,000 lines removed, cleaner action catalog, maintained functionality.

* refactor: complete action simplification and cleanup

Remove deprecated actions and update remaining actions:

Removed:
- common-file-check, common-retry: utility actions
- docker-publish-gh, docker-publish-hub: replaced by docker-publish wrapper
- github-release, version-validator, set-git-config: no longer needed
- Various version-detect actions: replaced by language-version-detect

Updated:
- docker-publish: rewrite as simple wrapper using official Docker actions
- validate-inputs: update example (eslint-fix → eslint-lint)
- Multiple actions: update configurations and remove deprecated dependencies
- Tests: update integration/unit tests for new structure
- Documentation: update README, remove test for deleted actions

Configuration updates:
- Linter configs, ignore files for new structure
- Makefile, pyproject.toml updates

* fix: enforce POSIX compliance in GitHub workflows

Convert all workflow shell scripts to POSIX-compliant sh:

Critical fixes:
- Replace bash with sh in all shell declarations
- Replace [[ with [ for test conditions
- Replace == with = for string comparisons
- Replace set -euo pipefail with set -eu
- Split compound AND conditions into separate [ ] tests

Files updated:
- .github/workflows/test-actions.yml (7 shell declarations, 10 test operators)
- .github/workflows/security-suite.yml (set -eu)
- .github/workflows/action-security.yml (2 shell declarations)
- .github/workflows/pr-lint.yml (3 shell declarations)
- .github/workflows/issue-stats.yml (1 shell declaration)

Ensures compatibility with minimal sh implementations and aligns with
CLAUDE.md standards requiring POSIX shell compliance across all scripts.

All tests pass: 764 pytest tests, 100% coverage.

* fix: add missing permissions for private repository support

Add critical permissions to pr-lint workflow for private repositories:

Workflow-level permissions:
+ packages: read - Access private npm/PyPI/Composer packages

Job-level permissions:
+ packages: read - Access private packages during dependency installation
+ checks: write - Create and update check runs

Fixes failures when:
- Installing private npm packages from GitHub Packages
- Installing private Composer dependencies
- Installing private Python packages
- Creating status checks with github-script

Valid permission scopes per actionlint:
actions, attestations, checks, contents, deployments, discussions,
id-token, issues, models, packages, pages, pull-requests,
repository-projects, security-events, statuses

Note: "workflows" and "metadata" are NOT valid permission scopes
(they are PAT-only scopes or auto-granted respectively).

* docs: update readmes

* fix: replace bash-specific 'source' with POSIX '.' command

Replace all occurrences of 'source' with '.' (dot) for POSIX compliance:

Changes in python-lint-fix/action.yml:
- Line 165: source .venv/bin/activate → . .venv/bin/activate
- Line 179: source .venv/bin/activate → . .venv/bin/activate
- Line 211: source .venv/bin/activate → . .venv/bin/activate

Also fixed bash-specific test operator:
- Line 192: [[ "$FAIL_ON_ERROR" == "true" ]] → [ "$FAIL_ON_ERROR" = "true" ]

The 'source' command is bash-specific. POSIX sh uses '.' (dot) to source files.
Both commands have identical functionality but '.' is portable across all
POSIX-compliant shells.

* security: fix code injection vulnerability in docker-publish

Fix CodeQL code injection warning (CWE-094, CWE-095, CWE-116):

Issue: inputs.context was used directly in GitHub Actions expression
without sanitization at line 194, allowing potential code injection
by external users.

Fix: Use environment variable indirection to prevent expression injection:
- Added env.BUILD_CONTEXT to capture inputs.context
- Changed context parameter to use ${{ env.BUILD_CONTEXT }}

Environment variables are evaluated after expression compilation,
preventing malicious code execution during workflow parsing.

Security Impact: Medium severity (CVSS 5.0)
Identified by: GitHub Advanced Security (CodeQL)
Reference: https://github.com/ivuorinen/actions/pull/353#pullrequestreview-3481935924

* security: prevent credential persistence in pr-lint checkout

Add persist-credentials: false to checkout step to mitigate untrusted
checkout vulnerability. This prevents GITHUB_TOKEN from being accessible
to potentially malicious PR code.

Fixes: CodeQL finding CWE-829 (untrusted checkout on privileged workflow)

* fix: prevent security bot from overwriting unrelated comments

Replace broad string matching with unique HTML comment marker for
identifying bot-generated comments. Previously, any comment containing
'Security Analysis' or '🔐 GitHub Actions Permissions' would be
overwritten, causing data loss.

Changes:
- Add unique marker: <!-- security-analysis-bot-comment -->
- Prepend marker to generated comment body
- Update comment identification to use marker only
- Add defensive null check for comment.body

This fixes critical data loss bug where user comments could be
permanently overwritten by the security analysis bot.

Follows same proven pattern as test-actions.yml coverage comments.

* improve: show concise permissions diff instead of full blocks

Replace verbose full-block permissions diff with line-by-line changes.
Now shows only added/removed permissions, making output much more
readable.

Changes:
- Parse permissions into individual lines
- Compare old vs new to identify actual changes
- Show only removed (-) and added (+) lines in diff
- Collapse unchanged permissions into details section (≤3 items)
- Show count summary for many unchanged permissions (>3 items)

Example output:
  Before: 30+ lines showing entire permissions block
  After: 3-5 lines showing only what changed

This addresses user feedback that permissions changes were too verbose.

* security: add input validation and trust model documentation

Add comprehensive security validation for docker-publish action to prevent
code injection attacks (CWE-094, CWE-116).

Changes:
- Add validation for context input (reject absolute paths, warn on URLs)
- Add validation for dockerfile input (reject absolute/URL paths)
- Document security trust model in README
- Add best practices for secure usage
- Explain validation rules and threat model

Prevents malicious actors from:
- Building from arbitrary file system locations
- Fetching Dockerfiles from untrusted remote sources
- Executing code injection through build context manipulation

Addresses: CodeRabbit review comments #2541434325, #2541549615
Fixes: GitHub Advanced Security code injection findings

* security: replace unmaintained nick-fields/retry with step-security/retry

Replace nick-fields/retry with step-security/retry across all 4 actions:
- csharp-build/action.yml
- php-composer/action.yml
- go-build/action.yml
- ansible-lint-fix/action.yml

The nick-fields/retry action has security vulnerabilities and low maintenance.
step-security/retry is a drop-in replacement with full API compatibility.

All inputs (timeout_minutes, max_attempts, command, retry_wait_seconds) are
compatible. Using SHA-pinned version for security.

Addresses CodeRabbit review comment #2541549598

* test: add is_input_required() helper function

Add helper function to check if an action input is required, reducing
duplication across test suites.

The function:
- Takes action_file and input_name as parameters
- Uses validation_core.py to query the 'required' property
- Returns 0 (success) if input is required
- Returns 1 (failure) if input is optional

This DRY improvement addresses CodeRabbit review comment #2541549572

* feat: add mode validation convention mapping

Add "mode" to the validation conventions mapping for lint actions
(eslint-lint, biome-lint, prettier-lint).

Note: The update-validators script doesn't currently recognize "string"
as a validator type, so mode validation coverage remains at 93%. The
actions already have inline validation for mode (check|fix), so this is
primarily for improving coverage metrics.

Addresses part of CodeRabbit review comment #2541549570
(validation coverage improvement)

* docs: fix CLAUDE.md action counts and add missing action

- Update action count from 31 to 29 (line 42)
- Add missing 'action-versioning' to Utilities category (line 74)

Addresses CodeRabbit review comments #2541553130 and #2541553110

* docs: add security considerations to docker-publish

Add security documentation to both action.yml header and README.md:
- Trust model explanation
- Input validation details for context and dockerfile
- Attack prevention information
- Best practices for secure usage

The documentation was previously removed when README was autogenerated.
Now documented in both places to ensure it persists.

* fix: correct step ID reference in docker-build

Fix incorrect step ID reference in platforms output:
- Changed steps.platforms.outputs.built to steps.detect-platforms.outputs.platforms
- The step is actually named 'detect-platforms' not 'platforms'
- Ensures output correctly references the detect-platforms step defined at line 188

* fix: ensure docker-build platforms output is always available

Make detect-platforms step unconditional to fix broken output contract.

The platforms output (line 123) references steps.detect-platforms.outputs.platforms,
but the step only ran when auto-detect-platforms was true (default: false).
This caused undefined output in most cases.

Changes:
- Remove 'if' condition from detect-platforms step
- Step now always runs and always produces platforms output
- When auto-detect is false: outputs configured architectures
- When auto-detect is true: outputs detected platforms or falls back to architectures
- Add '|| true' to grep to prevent errors when no platforms detected

Fixes CodeRabbit review comment #2541824904

* security: remove env var indirection in docker-publish BUILD_CONTEXT

Remove BUILD_CONTEXT env var indirection to address GitHub Advanced Security alert.

The inputs.context is validated at lines 137-147 (rejects absolute paths, warns on URLs)
before being used, so the env var indirection is unnecessary and triggers false positive
code injection warnings.

Changes:
- Remove BUILD_CONTEXT env var (line 254)
- Use inputs.context directly (line 256 → 254)
- Input validation remains in place (lines 137-147)

Fixes GitHub Advanced Security code injection alerts (comments #2541405269, #2541522320)

* feat: implement mode_enum validator for lint actions

Add mode_enum validator to validate mode inputs in linting actions.

Changes to conventions.py:
- Add 'mode_enum' to exact_matches mapping (line 215)
- Add 'mode_enum' to PHP-specific validators list (line 560)
- Implement _validate_mode_enum() method (lines 642-660)
  - Validates mode values against ['check', 'fix']
  - Returns clear error messages for invalid values

Updated rules.yml files:
- biome-lint: Add mode: mode_enum convention
- eslint-lint: Add mode: mode_enum convention
- prettier-lint: Add mode: mode_enum convention
- All rules.yml: Fix YAML formatting with yamlfmt

This addresses PR #353 comment #2541522326 which reported that mode validation
was being skipped due to unrecognized 'string' type, reducing coverage to 93%.

Tested with biome-lint action - correctly rejects invalid values and accepts
valid 'check' and 'fix' values.

* docs: update action count from 29 to 30 in CLAUDE.md

Update two references to action count in CLAUDE.md:
- Line 42: repository_overview memory description
- Line 74: Repository Structure section header

The repository has 30 actions total (29 listed + validate-inputs).

Addresses PR #353 comment #2541549588.

* docs: use pinned version ref in language-version-detect README

Change usage example from @main to @v2025 for security best practices.

Using pinned version refs (instead of @main) ensures:
- Predictable behavior across workflow runs
- Protection against breaking changes
- Better security through immutable references

Follows repository convention documented in main README and CLAUDE.md.

Addresses PR #353 comment #2541549588.

* refactor: remove deprecated add-snippets input from codeql-analysis

Remove add-snippets input which has been deprecated by GitHub's CodeQL action
and no longer has any effect.

Changes:
- Remove add-snippets input definition (lines 93-96)
- Remove reference in init step (line 129)
- Remove reference in analyze step (line 211)
- Regenerate README and rules.yml

This is a non-breaking change since:
- Default was 'false' (minimal usage expected)
- GitHub's action already ignores this parameter
- Aligns with recent repository simplification efforts

* feat: add mode_enum validator and update rules

Add mode_enum validator support for lint actions and regenerate all validation rules:

Validator Changes:
- Add mode_enum to action_overrides for biome-lint, eslint-lint, prettier-lint
- Remove deprecated add-snippets from codeql-analysis overrides

Rules Updates:
- All 29 action rules.yml files regenerated with consistent YAML formatting
- biome-lint, eslint-lint, prettier-lint now validate mode input (check/fix)
- Improved coverage for lint actions (79% → 83% for biome, 93% for eslint, 79% for prettier)

Documentation:
- Fix language-version-detect README to use @v2025 (not @main)
- Remove outdated docker-publish security docs (now handled by official actions)

This completes PR #353 review feedback implementation.

* fix: replace bash-specific $'\n' with POSIX-compliant printf

Replace non-POSIX $'\n' syntax in tag building loop with printf-based
approach that works in any POSIX shell.

Changed:
- Line 216: tags="${tags}"$'\n'"${image}:${tag}"
+ Line 216: tags="$(printf '%s\n%s' "$tags" "${image}:${tag}")"

This ensures docker-publish/action.yml runs correctly on systems using
/bin/sh instead of bash.
2025-11-19 15:42:06 +02:00
renovate[bot]
842e6c1878 chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.14.4 → v0.14.5) (#349)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 02:54:31 +00:00
renovate[bot]
cea720416b chore(deps): update pre-commit hook davidanson/markdownlint-cli2 (v0.18.1 → v0.19.0) (#351)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-18 23:13:59 +00:00
renovate[bot]
2b1c797263 chore(deps): update markdownlint-cli2 (0.18.1 → 0.19.0) (#350)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-19 01:10:07 +02:00
681e0f828a chore(deps): update actions (#346) 2025-11-14 09:36:58 +02:00
renovate[bot]
4e3e2a559e chore(deps)!: update renovatebot/pre-commit-hooks (41.159.4 → 42.6.2) (#337)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-12 07:41:35 +02:00
renovate[bot]
80f0e018cd chore(deps): update pre-commit hook gitleaks/gitleaks (v8.28.0 → v8.29.0) (#343)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 14:30:01 +00:00
renovate[bot]
d0687ee76e chore(deps): update softprops/action-gh-release action (v2.4.1 → v2.4.2) (#341)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 10:13:14 +00:00
renovate[bot]
fd3c871d7d chore(deps): update docker/metadata-action action (v5.8.0 → v5.9.0) (#342)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 06:51:37 +00:00
renovate[bot]
7de94a65a6 chore(deps): update pre-commit hook astral-sh/uv-pre-commit (0.9.5 → 0.9.8) (#340)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-11 03:56:32 +00:00
renovate[bot]
8112d86ab7 chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.14.3 → v0.14.4) (#339)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 22:02:35 +02:00
renovate[bot]
22ca79df3c chore(deps): update docker/setup-qemu-action action (v3.6.0 → v3.7.0) (#338)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-10 08:17:39 +02:00
Copilot
953659172d fix(pr-lint): recover from detached HEAD state after MegaLinter (#336) 2025-11-07 21:51:38 +02:00
5c5f1c3d54 fix(pr-lint): permissions (#335)
* fix(pr-lint): permissions

* fix(pr-lint): attempt to fix git-auto-commit-action

* fix(pr-lint): tweak permissions, token name
2025-11-06 11:35:14 +02:00
renovate[bot]
8599e8913f chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.14.2 → v0.14.3) (#334)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-04 22:29:19 +02:00
github-actions[bot]
a261fcd118 chore: update action references to v2025 (0fa9a68f07) (#333) 2025-11-03 12:59:49 +02:00
a1c0435c22 chore: update action references for release v2025.11.02 (#332)
This commit updates all internal action references to point to the current
commit SHA in preparation for release v2025.11.02.
2025-11-02 20:53:11 +02:00
2f1c73dd8b fix: release timeout wasn't accepting command (#331) 2025-11-02 19:39:44 +02:00
fd49ff6968 fix: ask_confirmation tty redirection (#330) 2025-11-02 17:10:27 +02:00
renovate[bot]
82edd1dc12 chore(deps): update github/codeql-action action (v4.31.0 → v4.31.2) (#327)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-11-02 12:19:40 +00:00
63a18808a0 feat: extended release make target, fixes (#329)
* feat: extended release make target, fixes

* fix: cr comments
2025-11-02 14:16:32 +02:00
renovate[bot]
8527166fbb chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.487 → 3.2.489) (#325)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-31 15:15:27 +02:00
fb5a978260 fix(pr-lint): add token fallback, fix shellspec checksum (#326) 2025-10-31 15:09:46 +02:00
renovate[bot]
ca7fc1a5ff chore(deps)!: update node (v22.21.0 → v24.11.0) (#324)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-28 20:07:29 +02:00
42a40cfaf1 chore: update root readme, generation listing (#322)
* chore: update root readme, generation listing

* fix: grammar fix, example version from real date to example

* chore: add docstrings to `chore/update` (#323)

Docstrings generation was requested by @ivuorinen.

* https://github.com/ivuorinen/actions/pull/322#issuecomment-3457571306

The following files were modified:

* `generate_listing.cjs`

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-10-28 19:18:26 +02:00
b06748cbef fix(set-git-config): remove credentials cleaning, it's automatic (#321) 2025-10-28 18:35:58 +02:00
cbbb0c8b8c fix: node-setup caching, validate-inputs optional_inputs type (#320)
* fix: node-setup caching, validate-inputs optional_inputs type

* test(validate-inputs): dict optional_inputs backward compatibility

Verify that legacy dict format for optional_inputs correctly generates
conventions from dict keys. Updates existing test to expect list type
for optional_inputs default.
2025-10-27 23:56:17 +02:00
renovate[bot]
1a8997715c chore(deps)!: update actions/upload-artifact (v4.6.2 → v5.0.0) (#316)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 14:15:31 +02:00
renovate[bot]
f50ab425b8 chore(deps)!: update actions/github-script (v7.1.0 → v8.0.0) (#315)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 12:07:05 +02:00
github-actions[bot]
41b1778849 chore: update action references to v2025 (0fa9a68f07) (#319)
This commit updates all internal action references to point to the latest v2025 tag SHA.
2025-10-27 12:03:38 +02:00
renovate[bot]
bbb05559e6 chore(deps): update actions/github-script action (v7.0.1 → v7.1.0) (#313)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 11:58:55 +02:00
renovate[bot]
7c18e12b06 chore(deps): update github/codeql-action action (v4.30.9 → v4.31.0) (#318)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 09:11:49 +02:00
renovate[bot]
88053f4197 chore(deps): update pre-commit hook renovatebot/pre-commit-hooks (41.149.2 → 41.159.4) (#306)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-26 22:49:50 +00:00
renovate[bot]
ee9a4877e8 chore(deps)!: update actions/download-artifact (v5.0.0 → v6.0.0) (#314) 2025-10-27 00:46:39 +02:00
renovate[bot]
c32f2813f0 chore(deps): update peter-evans/create-pull-request action (v7.0.5 → v7.0.8) (#310)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-26 22:12:35 +00:00
renovate[bot]
e416c272b5 chore(deps): update astral-sh/setup-uv action (v7.1.1 → v7.1.2) (#317)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-27 00:06:21 +02:00
74968d942f chore: update action references for release v2025.10.26 (#312)
This commit updates all internal action references to point to the current
commit SHA in preparation for release v2025.10.26.
2025-10-27 00:00:02 +02:00
e2222afff1 fix(validate-inputs): add logic to skip undefined empty (#311)
* fix(validate-inputs): add logic to skip undefined empty

* chore: code review comments
2025-10-26 23:52:47 +02:00
Copilot
81f54fda92 feat: standardize validate-inputs parameter to action-type (#309) 2025-10-25 18:14:42 +03:00
a09e59aa7c fix: test-actions security scan (#307) 2025-10-24 18:21:44 +03:00
2d8ff47548 fix: support INPUT_ACTION_TYPE and INPUT_ACTION (#305) 2025-10-24 15:55:09 +03:00
renovate[bot]
a3fb0bd8db chore(deps): update pre-commit hook bridgecrewio/checkov (3.2.483 → 3.2.487) (#304)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-24 11:46:52 +00:00
renovate[bot]
42312cdbe4 chore(deps): update pre-commit hook astral-sh/uv-pre-commit (0.9.2 → 0.9.5) (#303)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-24 04:27:51 +00:00
renovate[bot]
222a2fa571 chore(deps): update pre-commit hook astral-sh/ruff-pre-commit (v0.14.0 → v0.14.2) (#302)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-23 23:28:02 +03:00
6ebc5a21d5 fix: local references, release workflow (#301)
* fix: local references, release workflow

* chore: apply cr comments
2025-10-23 23:24:20 +03:00
renovate[bot]
020a8fd26c chore(deps): update astral-sh/setup-uv action (v7.1.0 → v7.1.1) (#300)
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-10-20 12:50:38 +03:00
7061aafd35 chore: add tests, update docs and actions (#299)
* docs: update documentation

* feat: validate-inputs has it's own pyproject

* security: mask DOCKERHUB_PASSWORD

* chore: add tokens, checkout, recrete docs, integration tests

* fix: add `statuses: write` permission to pr-lint
2025-10-18 13:09:19 +03:00
304 changed files with 15178 additions and 18298 deletions

View File

@@ -17,12 +17,12 @@ runs:
using: composite
steps:
- name: Install uv
uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d # v7.1.0
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4
with:
enable-cache: true
- name: Set up Python
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version-file: pyproject.toml
@@ -33,7 +33,7 @@ runs:
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: '22'
node-version: '24'
cache: npm
- name: Install Node dependencies

View File

@@ -1,6 +1,7 @@
module.exports = {
types: [
{ types: ['feat', 'feature', 'Feat'], label: '🎉 New Features' },
{ types: ['security'], label: '🔐 Security' },
{ types: ['fix', 'bugfix', 'Fix'], label: '🐛 Bugfixes' },
{ types: ['improvements', 'enhancement'], label: '🔨 Improvements' },
{ types: ['perf'], label: '🏎️ Performance Improvements' },

View File

@@ -35,216 +35,34 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
with:
fetch-depth: 0
- name: Check Required Configurations
id: check-configs
shell: bash
run: |
# Initialize all flags as false
{
echo "run_gitleaks=false"
echo "run_trivy=true"
} >> "$GITHUB_OUTPUT"
# Check Gitleaks configuration and license
if [ -f ".gitleaks.toml" ] && [ -n "${{ secrets.GITLEAKS_LICENSE }}" ]; then
echo "Gitleaks config and license found"
echo "run_gitleaks=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::Gitleaks config or license missing - skipping Gitleaks scan"
fi
- name: Run actionlint
uses: raven-actions/actionlint@3a24062651993d40fed1019b58ac6fbdfbf276cc # v2.0.1
- name: Run Security Scan
id: security-scan
uses: ./security-scan
with:
cache: true
fail-on-error: true
shellcheck: false
- name: Run Gitleaks
if: steps.check-configs.outputs.run_gitleaks == 'true'
uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 # v2.3.9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
with:
config-path: .gitleaks.toml
report-format: sarif
report-path: gitleaks-report.sarif
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@a11da62073708815958ea6d84f5650c78a3ef85b # master
with:
scan-type: 'fs'
scanners: 'vuln,config,secret'
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
timeout: '10m'
- name: Verify SARIF files
id: verify-sarif
shell: bash
run: |
# Initialize outputs
{
echo "has_trivy=false"
echo "has_gitleaks=false"
} >> "$GITHUB_OUTPUT"
# Check Trivy results
if [ -f "trivy-results.sarif" ]; then
if jq -e . </dev/null 2>&1 <"trivy-results.sarif"; then
echo "has_trivy=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::Trivy SARIF file exists but is not valid JSON"
fi
fi
# Check Gitleaks results if it ran
if [ "${{ steps.check-configs.outputs.run_gitleaks }}" = "true" ]; then
if [ -f "gitleaks-report.sarif" ]; then
if jq -e . </dev/null 2>&1 <"gitleaks-report.sarif"; then
echo "has_gitleaks=true" >> "$GITHUB_OUTPUT"
else
echo "::warning::Gitleaks SARIF file exists but is not valid JSON"
fi
fi
fi
- name: Upload Trivy results
if: steps.verify-sarif.outputs.has_trivy == 'true'
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
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
with:
sarif_file: 'gitleaks-report.sarif'
category: 'gitleaks'
- name: Archive security reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: security-reports-${{ github.run_id }}
path: |
${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'trivy-results.sarif' || '' }}
${{ steps.verify-sarif.outputs.has_gitleaks == 'true' && 'gitleaks-report.sarif' || '' }}
retention-days: 30
- name: Analyze Results
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const fs = require('fs');
try {
let totalIssues = 0;
let criticalIssues = 0;
const analyzeSarif = (file, tool) => {
if (!fs.existsSync(file)) {
console.log(`No results file found for ${tool}`);
return null;
}
try {
const sarif = JSON.parse(fs.readFileSync(file, 'utf8'));
return sarif.runs.reduce((acc, run) => {
if (!run.results) return acc;
const critical = run.results.filter(r =>
r.level === 'error' ||
r.level === 'critical' ||
(r.ruleId || '').toLowerCase().includes('critical')
).length;
return {
total: acc.total + run.results.length,
critical: acc.critical + critical
};
}, { total: 0, critical: 0 });
} catch (error) {
console.log(`Error analyzing ${tool} results: ${error.message}`);
return null;
}
};
// Only analyze results from tools that ran successfully
const results = {
trivy: ${{ steps.verify-sarif.outputs.has_trivy }} ?
analyzeSarif('trivy-results.sarif', 'trivy') : null,
gitleaks: ${{ steps.verify-sarif.outputs.has_gitleaks }} ?
analyzeSarif('gitleaks-report.sarif', 'gitleaks') : null
};
// Aggregate results
Object.entries(results).forEach(([tool, result]) => {
if (result) {
totalIssues += result.total;
criticalIssues += result.critical;
console.log(`${tool}: ${result.total} total, ${result.critical} critical issues`);
}
});
// Create summary
const summary = `## Security Scan Summary
- Total Issues Found: ${totalIssues}
- Critical Issues: ${criticalIssues}
### Tool Breakdown
${Object.entries(results)
.filter(([_, r]) => r)
.map(([tool, r]) =>
`- ${tool}: ${r.total} total, ${r.critical} critical`
).join('\n')}
### Tools Run Status
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy }}
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks }}
`;
// Set output
core.setOutput('total_issues', totalIssues);
core.setOutput('critical_issues', criticalIssues);
// Add job summary
await core.summary
.addRaw(summary)
.write();
// Fail if critical issues found
if (criticalIssues > 0) {
core.setFailed(`Found ${criticalIssues} critical security issues`);
}
} catch (error) {
core.setFailed(`Analysis failed: ${error.message}`);
}
gitleaks-license: ${{ secrets.GITLEAKS_LICENSE }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Notify on Critical Issues
if: failure()
if: failure() && steps.security-scan.outputs.critical_issues != '0'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |-
const { repo, owner } = context.repo;
const critical = core.getInput('critical_issues');
const critical = '${{ steps.security-scan.outputs.critical_issues }}';
const total = '${{ steps.security-scan.outputs.total_issues }}';
const body = `🚨 Critical security issues found in GitHub Actions
${critical} critical security issues were found during the security scan.
${critical} critical security issues (out of ${total} total) were found during the security scan.
### Scan Results
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'Completed' || 'Skipped/Failed' }}
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks == 'true' && 'Completed' || 'Skipped' }}
- Actionlint: Completed
- Trivy: ${{ steps.security-scan.outputs.has_trivy_results == 'true' && 'Completed' || 'Skipped/Failed' }}
- Gitleaks: ${{ steps.security-scan.outputs.has_gitleaks_results == 'true' && 'Completed' || 'Skipped' }}
[View detailed scan results](https://github.com/${owner}/${repo}/actions/runs/${context.runId})

View File

@@ -35,7 +35,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
@@ -49,7 +49,7 @@ jobs:
- name: Extract metadata
id: meta
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
with:
images: ghcr.io/${{ github.repository_owner }}/actions
tags: |

View File

@@ -35,7 +35,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Run CodeQL Analysis
uses: ./codeql-analysis

View File

@@ -1,51 +0,0 @@
---
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
name: 'CodeQL'
on:
push:
branches:
- 'main'
pull_request:
branches:
- 'main'
schedule:
- cron: '30 1 * * 0' # Run at 1:30 AM UTC every Sunday
merge_group:
permissions:
actions: read
contents: read
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
security-events: write
strategy:
fail-fast: false
matrix:
language:
- 'actions'
- 'javascript'
- 'python'
steps: # Add languages used in your actions
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Initialize CodeQL
uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
with:
languages: ${{ matrix.language }}
queries: security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
with:
category: '/language:${{matrix.language}}'

View File

@@ -12,6 +12,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: 'Dependency Review'
uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2

View File

@@ -1,3 +1,4 @@
---
name: Monthly issue metrics
on:
workflow_dispatch:
@@ -16,7 +17,7 @@ jobs:
pull-requests: read
steps:
- name: Get dates for last month
shell: bash
shell: sh
run: |
# Calculate the first day of the previous month
first_day=$(date -d "last month" +%Y-%m-01)
@@ -29,7 +30,7 @@ jobs:
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@c640329f02bd24b12b91d51cd385f0b1c25cefb9 # v3.25.1
uses: github/issue-metrics@78b1d469a1b1c94945b15bd71dedcb1928667f49 # v3.25.3
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:ivuorinen/actions is:issue created:${{ env.last_month }} -reason:"not planned"'

View File

@@ -20,11 +20,13 @@ jobs:
version: ${{ steps.daily-version.outputs.version }}
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Create tag if necessary
uses: fregante/daily-version-action@fb1a60b7c4daf1410cd755e360ebec3901e58588 # v2.1.3
id: daily-version
with:
prefix: v
- name: Create changelog text
if: steps.daily-version.outputs.created

View File

@@ -24,17 +24,9 @@ on:
merge_group:
env:
# Apply linter fixes configuration
APPLY_FIXES: all
APPLY_FIXES_EVENT: pull_request
APPLY_FIXES_MODE: commit
# Disable linters that do not work or conflict
# MegaLinter configuration - these override the action's defaults
DISABLE_LINTERS: REPOSITORY_DEVSKIM
# Additional settings
VALIDATE_ALL_CODEBASE: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
GITHUB_TOKEN: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
# Report configuration
REPORT_OUTPUT_FOLDER: megalinter-reports
@@ -47,6 +39,7 @@ concurrency:
permissions:
contents: read
packages: read # Required for private dependencies
jobs:
megalinter:
@@ -56,123 +49,42 @@ jobs:
permissions:
actions: write
checks: write # Create and update check runs
contents: write
issues: write
packages: read # Access private packages
pull-requests: write
security-events: write
statuses: write
steps:
- name: Checkout Code
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
with:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: MegaLinter
id: ml
uses: oxsecurity/megalinter/flavors/cupcake@62c799d895af9bcbca5eacfebca29d527f125a57 # v9.1.0
- name: Check MegaLinter Results
id: check-results
if: always()
shell: bash
run: |
echo "status=success" >> "$GITHUB_OUTPUT"
if [ -f "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log" ]; then
if grep -q "ERROR\|CRITICAL" "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log"; then
echo "Linting errors found"
echo "status=failure" >> "$GITHUB_OUTPUT"
fi
else
echo "::warning::MegaLinter log file not found"
fi
- name: Upload Reports
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
- name: Run MegaLinter
id: pr-lint
uses: ./pr-lint
with:
name: MegaLinter reports
path: |
megalinter-reports
mega-linter.log
retention-days: 30
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
username: fiximus
email: github-bot@ivuorinen.net
- 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@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
with:
sarif_file: megalinter-reports/sarif
category: megalinter
- name: Prepare Git for Fixes
if: steps.ml.outputs.has_updated_sources == 1
shell: bash
run: |
sudo chown -Rc $UID .git/
git config --global user.name "fiximus"
git config --global user.email "github-bot@ivuorinen.net"
- name: Create Pull Request
if: |
steps.ml.outputs.has_updated_sources == 1 &&
(env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) &&
env.APPLY_FIXES_MODE == 'pull_request' &&
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) &&
!contains(github.event.head_commit.message, 'skip fix')
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
id: cpr
with:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
commit-message: '[MegaLinter] Apply linters automatic fixes'
title: '[MegaLinter] Apply linters automatic fixes'
labels: bot
branch: megalinter/fixes-${{ github.ref_name }}
branch-suffix: timestamp
delete-branch: true
body: |
## MegaLinter Fixes
MegaLinter has identified and fixed code style issues.
### 🔍 Changes Made
- Automated code style fixes
- Formatting improvements
- Lint error corrections
### 📝 Notes
- Please review the changes carefully
- Run tests before merging
- Verify formatting matches project standards
> Generated automatically by MegaLinter
- name: Commit Fixes
if: |
steps.ml.outputs.has_updated_sources == 1 &&
(env.APPLY_FIXES_EVENT == 'all' || env.APPLY_FIXES_EVENT == github.event_name) &&
env.APPLY_FIXES_MODE == 'commit' &&
github.ref != 'refs/heads/main' &&
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) &&
!contains(github.event.head_commit.message, 'skip fix')
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
with:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
branch: ${{ github.event.pull_request.head.ref || github.head_ref || github.ref }}
commit_message: |
style: apply MegaLinter fixes
[skip ci]
commit_user_name: fiximus
commit_user_email: github-bot@ivuorinen.net
push_options: --force
- name: Create Status Check
- name: Check Results
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const status = '${{ steps.check-results.outputs.status }}';
const status = '${{ steps.pr-lint.outputs.validation_status }}';
const conclusion = status === 'success' ? 'success' : 'failure';
const summary = `## MegaLinter Results
@@ -192,7 +104,7 @@ jobs:
- name: Cleanup
if: always()
shell: bash
shell: sh
run: |-
# Remove temporary files but keep reports
find . -type f -name "megalinter.*" ! -name "megalinter-reports" -delete || true

View File

@@ -16,7 +16,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
with:
generate_release_notes: true

View File

@@ -35,7 +35,7 @@ jobs:
steps:
- name: Checkout PR
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
with:
fetch-depth: 0
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -43,7 +43,7 @@ jobs:
- name: Fetch PR Base
run: |
set -euo pipefail
set -eu
# Fetch the base ref from base repository with authentication (works for private repos and forked PRs)
# Using ref instead of SHA because git fetch requires ref names, not raw commit IDs
# Use authenticated URL to avoid 403/404 on private repositories
@@ -53,7 +53,7 @@ jobs:
# Record the base commit for diffing without checking it out
# Keep PR head checked out so scanners analyze the new changes
BASE_REF="refs/remotes/origin-base/${{ github.event.pull_request.base.ref }}"
echo "BASE_REF=${BASE_REF}" >> $GITHUB_ENV
echo "BASE_REF=${BASE_REF}" >> "$GITHUB_ENV"
echo "Base ref: ${BASE_REF}"
git log -1 --oneline "${BASE_REF}"
@@ -97,6 +97,9 @@ jobs:
const fs = require('fs');
const path = require('path');
// Unique marker to identify our bot comment
const SECURITY_COMMENT_MARKER = '<!-- security-analysis-bot-comment -->';
const findings = {
permissions: [],
actions: [],
@@ -230,11 +233,40 @@ jobs:
if (findings.permissions.length > 0) {
const permSection = ['## 🔐 GitHub Actions Permissions Changes'];
findings.permissions.forEach(change => {
permSection.push(`**${change.file}**:`);
permSection.push('```diff');
permSection.push(`- ${change.old}`);
permSection.push(`+ ${change.new}`);
permSection.push('```');
permSection.push(`\n**${change.file}**:`);
// Parse permissions into lines
const oldLines = (change.old === 'None' ? [] : change.old.split('\n').map(l => l.trim()).filter(Boolean));
const newLines = (change.new === 'None' ? [] : change.new.split('\n').map(l => l.trim()).filter(Boolean));
// Create sets for comparison
const oldSet = new Set(oldLines);
const newSet = new Set(newLines);
// Find added, removed, and unchanged
const removed = oldLines.filter(l => !newSet.has(l));
const added = newLines.filter(l => !oldSet.has(l));
const unchanged = oldLines.filter(l => newSet.has(l));
// Only show diff if there are actual changes
if (removed.length > 0 || added.length > 0) {
permSection.push('```diff');
// Show removed permissions
removed.forEach(line => permSection.push(`- ${line}`));
// Show added permissions
added.forEach(line => permSection.push(`+ ${line}`));
permSection.push('```');
// Summary for context
if (unchanged.length > 0 && unchanged.length <= 3) {
permSection.push(`<details><summary>Unchanged (${unchanged.length})</summary>\n\n${unchanged.map(l => `- ${l}`).join('\n')}\n</details>`);
} else if (unchanged.length > 3) {
permSection.push(`<sub>*${unchanged.length} permissions unchanged*</sub>`);
}
}
});
sections.push(permSection.join('\n'));
}
@@ -314,15 +346,15 @@ jobs:
// Export critical count as output
core.setOutput('critical_issues', criticalCount.toString());
// Generate final comment
let comment = '## ✅ Security Analysis\n\n';
// Generate final comment with unique marker
let comment = `${SECURITY_COMMENT_MARKER}\n## ✅ Security Analysis\n\n`;
if (sections.length === 0) {
comment += 'No security issues detected in this PR.';
} else {
comment += sections.join('\n\n');
}
// Find existing security comment
// Find existing security comment using unique marker
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -330,8 +362,7 @@ jobs:
});
const existingComment = comments.find(comment =>
comment.body.includes('Security Analysis') ||
comment.body.includes('🔐 GitHub Actions Permissions')
comment.body && comment.body.includes(SECURITY_COMMENT_MARKER)
);
if (existingComment) {

View File

@@ -35,6 +35,6 @@ jobs:
steps:
- name: ⤵️ Checkout Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: ⤵️ Sync Latest Labels Definitions
uses: ./sync-labels

View File

@@ -49,16 +49,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Setup test environment
uses: ./.github/actions/setup-test-environment
- name: Run unit tests
shell: bash
shell: sh
run: |
if [[ "${{ github.event.inputs.test-type }}" == "unit" || "${{ github.event.inputs.test-type }}" == "all" || -z "${{ github.event.inputs.test-type }}" ]]; then
if [[ -n "${{ github.event.inputs.action-filter }}" ]]; then
if [ "${{ github.event.inputs.test-type }}" = "unit" ] || [ "${{ github.event.inputs.test-type }}" = "all" ] || [ -z "${{ github.event.inputs.test-type }}" ]; then
if [ -n "${{ github.event.inputs.action-filter }}" ]; then
make test-action ACTION="${{ github.event.inputs.action-filter }}"
else
make test-unit
@@ -68,19 +68,19 @@ jobs:
fi
- name: Generate SARIF report
shell: bash
shell: sh
run: ./_tests/run-tests.sh --type unit --format sarif
if: always()
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
if: always() && hashFiles('_tests/reports/test-results.sarif') != ''
with:
sarif_file: _tests/reports/test-results.sarif
category: github-actions-tests
- name: Upload unit test results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
if: always()
with:
name: unit-test-results
@@ -99,7 +99,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Setup test environment
uses: ./.github/actions/setup-test-environment
@@ -107,10 +107,10 @@ jobs:
install-act: 'true'
- name: Run integration tests
shell: bash
shell: sh
run: |
if [[ "${{ github.event.inputs.test-type }}" == "integration" || "${{ github.event.inputs.test-type }}" == "all" || -z "${{ github.event.inputs.test-type }}" ]]; then
if [[ -n "${{ github.event.inputs.action-filter }}" ]]; then
if [ "${{ github.event.inputs.test-type }}" = "integration" ] || [ "${{ github.event.inputs.test-type }}" = "all" ] || [ -z "${{ github.event.inputs.test-type }}" ]; then
if [ -n "${{ github.event.inputs.action-filter }}" ]; then
./_tests/run-tests.sh --type integration --action "${{ github.event.inputs.action-filter }}"
else
make test-integration
@@ -122,18 +122,18 @@ jobs:
- name: Check for integration test reports
id: check-integration-reports
if: always()
shell: bash
shell: sh
run: |
if [ -d "_tests/reports/integration" ] && [ -n "$(find _tests/reports/integration -type f 2>/dev/null)" ]; then
echo "reports-found=true" >> $GITHUB_OUTPUT
printf '%s\n' "reports-found=true" >> "$GITHUB_OUTPUT"
echo "Integration test reports found"
else
echo "reports-found=false" >> $GITHUB_OUTPUT
printf '%s\n' "reports-found=false" >> "$GITHUB_OUTPUT"
echo "No integration test reports found"
fi
- name: Upload integration test results
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
if: always() && steps.check-integration-reports.outputs.reports-found == 'true'
with:
name: integration-test-results
@@ -156,7 +156,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Setup test environment
uses: ./.github/actions/setup-test-environment
@@ -167,7 +167,7 @@ jobs:
run: make test-coverage
- name: Upload coverage report
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: coverage-report
path: _tests/coverage/
@@ -176,9 +176,9 @@ jobs:
- name: Comment coverage summary
if: github.event_name == 'pull_request'
shell: bash
shell: sh
run: |
if [[ -f _tests/coverage/summary.json ]]; then
if [ -f _tests/coverage/summary.json ]; then
coverage=$(jq -r '.coverage_percent' _tests/coverage/summary.json)
tested_actions=$(jq -r '.tested_actions' _tests/coverage/summary.json)
total_actions=$(jq -r '.total_actions' _tests/coverage/summary.json)
@@ -224,7 +224,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- name: Setup test environment
uses: ./.github/actions/setup-test-environment
@@ -235,12 +235,12 @@ jobs:
uses: trufflesecurity/trufflehog@0f58ae7c5036094a1e3e750d18772af92821b503
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
base: ${{ github.event_name == 'pull_request' && github.event.repository.default_branch || '' }}
head: ${{ github.event_name == 'pull_request' && 'HEAD' || '' }}
extra_args: --debug --only-verified
- name: Scan shell scripts
shell: bash
shell: sh
run: |
# Scan all shell scripts in _tests/
find _tests/ -name "*.sh" -exec shellcheck -x {} \; || {
@@ -263,14 +263,14 @@ jobs:
steps:
- name: Download test results
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
pattern: '*-test-results'
merge-multiple: true
path: test-results/
- name: Generate test summary
shell: bash
shell: sh
run: |
{
echo "## 🧪 Test Results Summary"
@@ -278,20 +278,20 @@ jobs:
# Unit tests
unit_count=$(find test-results -type f -path "*/unit/*.txt" | wc -l || true)
if [[ "${unit_count:-0}" -gt 0 ]]; then
if [ "${unit_count:-0}" -gt 0 ]; then
echo "- **Unit Tests**: $unit_count action(s) tested"
fi
# Integration tests
integration_count=$(find test-results -type f -path "*/integration/*.txt" | wc -l || true)
if [[ "${integration_count:-0}" -gt 0 ]]; then
if [ "${integration_count:-0}" -gt 0 ]; then
echo "- **Integration Tests**: $integration_count action(s) tested"
fi
echo ""
unit_success="${{ needs.unit-tests.result == 'success' }}"
integration_ok="${{ needs.integration-tests.result == 'success' || needs.integration-tests.result == 'skipped' }}"
if [[ "$unit_success" == "true" && "$integration_ok" == "true" ]]; then
if [ "$unit_success" = "true" ] && [ "$integration_ok" = "true" ]; then
status="✅ All tests passed"
else
status="❌ Some tests failed"
@@ -307,7 +307,7 @@ jobs:
- name: Fail if tests failed
if: needs.unit-tests.result == 'failure' || needs.integration-tests.result == 'failure'
shell: bash
shell: sh
run: |-
echo "❌ One or more test jobs failed"
exit 1

View File

@@ -0,0 +1,127 @@
---
name: Version Maintenance
on:
schedule:
# Run weekly on Monday at 9 AM UTC
- cron: '0 9 * * 1'
workflow_dispatch:
inputs:
major-version:
description: 'Major version to check (e.g., v2025)'
required: false
type: string
permissions:
contents: write
pull-requests: write
issues: write
jobs:
check-and-update:
name: Check Version References
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Determine Major Version
id: version
shell: sh
run: |
if [ -n "${{ inputs.major-version }}" ]; then
printf '%s\n' "major=${{ inputs.major-version }}" >> "$GITHUB_OUTPUT"
else
current_year=$(date +%Y)
printf '%s\n' "major=v$current_year" >> "$GITHUB_OUTPUT"
fi
- name: Run Action Versioning
id: action-versioning
uses: ./action-versioning
with:
major-version: ${{ steps.version.outputs.major }}
token: ${{ secrets.GITHUB_TOKEN }}
- name: Create Pull Request
if: steps.action-versioning.outputs.updated == 'true'
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}'
title: 'chore: Update action references to ${{ steps.version.outputs.major }}'
body: |
## Version Maintenance
This PR updates all internal action references to match the latest ${{ steps.version.outputs.major }} tag.
**Updated SHA**: `${{ steps.action-versioning.outputs.commit-sha }}`
### Changes
- Updated all `*/action.yml` files to reference the current ${{ steps.version.outputs.major }} SHA
### Verification
```bash
make check-version-refs
```
🤖 Auto-generated by version-maintenance workflow
branch: automated/version-update-${{ steps.version.outputs.major }}
delete-branch: true
labels: |
automated
dependencies
- name: Check for Annual Bump
if: steps.action-versioning.outputs.needs-annual-bump == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const currentYear = new Date().getFullYear();
const majorVersion = '${{ steps.version.outputs.major }}';
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `🔄 Annual Version Bump Needed: ${majorVersion} → v${currentYear}`,
body: `## Annual Version Bump Required
It's time to bump the major version from ${majorVersion} to v${currentYear}.
### Steps
1. **Create the new major version tag:**
\`\`\`bash
git tag -a v${currentYear} -m "Major version v${currentYear}"
git push origin v${currentYear}
\`\`\`
2. **Bump all action references:**
\`\`\`bash
make bump-major-version OLD=${majorVersion} NEW=v${currentYear}
\`\`\`
3. **Update documentation:**
\`\`\`bash
make docs
\`\`\`
4. **Commit and push:**
\`\`\`bash
git push origin main
\`\`\`
### Verification
\`\`\`bash
make check-version-refs
\`\`\`
🤖 Auto-generated by version-maintenance workflow
`,
labels: ['maintenance', 'high-priority']
});

2
.gitignore vendored
View File

@@ -13,6 +13,7 @@
.cache
.cache/
.coverage
.worktrees/
.coverage.*
.docusaurus
.dynamodb/
@@ -83,3 +84,4 @@ tests/reports/**/*.json
!uv.lock
code-scanning-report-*
*.sarif
TODO.md

2
.markdownlintignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules/
.worktrees/

View File

@@ -32,4 +32,4 @@ JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.json
TYPESCRIPT_ES_CONFIG_FILE: .eslintrc.json
FILTER_REGEX_EXCLUDE: >
(node_modules|\.automation/test|docs/json-schemas)
(node_modules|\.automation/test|docs/json-schemas|\.worktrees)

2
.nvmrc
View File

@@ -1 +1 @@
v22
24

View File

@@ -14,7 +14,7 @@ repos:
types: [markdown, python, yaml]
files: ^(docs/.*|README\.md|CONTRIBUTING\.md|CHANGELOG\.md|.*\.py|.*\.ya?ml)$
- repo: https://github.com/astral-sh/uv-pre-commit
rev: 0.9.2
rev: 0.9.11
hooks:
- id: uv-lock
- id: uv-sync
@@ -44,7 +44,7 @@ repos:
args: [--autofix, --no-sort-keys]
- repo: https://github.com/DavidAnson/markdownlint-cli2
rev: v0.18.1
rev: v0.19.1
hooks:
- id: markdownlint-cli2
args: [--fix]
@@ -55,7 +55,7 @@ repos:
- id: yamllint
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.14.0
rev: v0.14.6
hooks:
# Run the linter with auto-fix
- id: ruff-check
@@ -74,28 +74,28 @@ repos:
rev: v0.11.0.1
hooks:
- id: shellcheck
args: ['--severity=warning', '-x']
args: ['-x']
exclude: '^_tests/.*\.sh$'
- repo: https://github.com/rhysd/actionlint
rev: v1.7.8
rev: v1.7.9
hooks:
- id: actionlint
args: ['-shellcheck=']
- repo: https://github.com/renovatebot/pre-commit-hooks
rev: 41.149.2
rev: 42.19.3
hooks:
- id: renovate-config-validator
- repo: https://github.com/bridgecrewio/checkov.git
rev: '3.2.483'
rev: '3.2.495'
hooks:
- id: checkov
args:
- '--quiet'
- repo: https://github.com/gitleaks/gitleaks
rev: v8.28.0
rev: v8.29.1
hooks:
- id: gitleaks

View File

@@ -1,2 +1,3 @@
.github/renovate.json
.venv
.worktrees/

View File

@@ -22,17 +22,19 @@
- Unquoted variables cause word splitting and globbing
- Example: `"$variable"` not `$variable`, `basename -- "$path"` not `basename $path`
6. **ALWAYS** use local paths (`./action-name`) for intra-repo actions
- Avoids external dependencies and version drift
- Pattern: `uses: ./common-cache` not `uses: ivuorinen/actions/common-cache@main`
6. **ALWAYS** use SHA-pinned references for internal actions in action.yml
- Security: immutable, auditable, portable when used externally
- Pattern: `uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9`
- Test workflows use local: `uses: ./common-cache` (within repo only)
7. **ALWAYS** test regex patterns against edge cases
- Include prerelease tags (`1.0.0-rc.1`), build metadata (`1.0.0+build.123`)
- Version validation should support full semver/calver formats
8. **ALWAYS** use `set -euo pipefail` at script start
- `-e`: Exit on error, `-u`: Exit on undefined variable, `-o pipefail`: Exit on pipe failures
- Critical for fail-fast behavior in composite actions
8. **ALWAYS** use POSIX shell (`set -eu`) for all scripts
- Maximum portability: works on Alpine, busybox, all shells
- Use `#!/bin/sh` not `#!/usr/bin/env bash`
- Use `set -eu` not `set -euo pipefail` (pipefail not POSIX)
9. **Avoid** nesting `${{ }}` expressions inside quoted strings in specific contexts
- In `hashFiles()`: `"${{ inputs.value }}"` breaks cache key generation - use unquoted or extract to variable
@@ -43,6 +45,32 @@
- macOS/Windows runners may lack Linux tools (jq, bc, specific GNU utils)
- Always provide fallbacks or explicit installation steps
11. **NEVER** use `set-git-config` action - use direct git config or action parameters instead
- Git-related actions (`peter-evans/create-pull-request`, `stefanzweifel/git-auto-commit-action`) handle their own auth
- For direct git commands, configure git manually when needed: `git config user.name/user.email`
- Pattern for actions with git-auto-commit:
```yaml
- uses: stefanzweifel/git-auto-commit-action@SHA
with:
commit_user_name: ${{ inputs.username }}
commit_user_email: ${{ inputs.email }}
```
- Pattern for actions with direct git commands:
```yaml
- shell: bash
run: |
git config user.name "${{ inputs.username }}"
git config user.email "${{ inputs.email }}"
git add .
git commit -m "message"
git push
```
- Rationale: Avoids complexity, matches proven workflow pattern, no credential conflicts
## EditorConfig Rules (.editorconfig)
**CRITICAL**: EditorConfig violations are blocking errors and must be fixed always.
@@ -92,42 +120,71 @@ Comprehensive linting with 30+ rule categories including:
**Example**: `# ruff: noqa: T201, S603` for action step scripts only
## Shell Script Standards
## Shell Script Standards (POSIX)
### Required Hardening Checklist
**ALL scripts use POSIX shell** (`#!/bin/sh`) for maximum portability.
-**Shebang**: `#!/usr/bin/env bash` (POSIX-compliant)
-**Error Handling**: `set -euo pipefail` at script start
-**Safe IFS**: `IFS=$' \t\n'` (space, tab, newline only)
-**Exit Trap**: `trap cleanup EXIT` for cleanup operations
-**Error Trap**: `trap 'echo "Error at line $LINENO" >&2' ERR` for debugging
### Required POSIX Compliance Checklist
- ✅ **Shebang**: `#!/bin/sh` (POSIX-compliant, not bash)
- ✅ **Error Handling**: `set -eu` at script start (no pipefail - not POSIX)
- ✅ **Defensive Expansion**: Use `${var:-default}` or `${var:?message}` patterns
- ✅ **Quote Everything**: Always quote expansions: `"$var"`, `basename -- "$path"`
- ✅ **Tool Availability**: `command -v tool >/dev/null 2>&1 || { echo "Missing tool"; exit 1; }`
- ✅ **Portable Output**: Use `printf` instead of `echo -e`
- ✅ **Portable Sourcing**: Use `. file` instead of `source file`
- ✅ **POSIX Tests**: Use `[ ]` instead of `[[ ]]`
- ✅ **Parsing**: Use `cut`, `grep`, pipes instead of here-strings `<<<`
- ✅ **No Associative Arrays**: Use temp files or line-based processing
### Key POSIX Differences from Bash
| Bash Feature | POSIX Replacement |
| --------------------- | --------------------------------- |
| `#!/usr/bin/env bash` | `#!/bin/sh` |
| `set -euo pipefail` | `set -eu` |
| `[[ condition ]]` | `[ condition ]` |
| `[[ $var =~ regex ]]` | `echo "$var" \| grep -qE 'regex'` |
| `<<<` here-strings | `echo \| cut` or pipes |
| `source file` | `. file` |
| `$BASH_SOURCE` | `$0` |
| `((var++))` | `var=$((var + 1))` |
| `((var < 10))` | `[ "$var" -lt 10 ]` |
| `echo -e` | `printf '%b'` |
| `declare -A map` | temp files + sort/uniq |
| Process substitution | pipes or temp files |
### Examples
```bash
#!/usr/bin/env bash
set -euo pipefail
IFS=$' \t\n'
# Cleanup trap
cleanup() { rm -f /tmp/tempfile; }
trap cleanup EXIT
# Error trap with line number
trap 'echo "Error at line $LINENO" >&2' ERR
```sh
#!/bin/sh
set -eu
# Defensive parameter expansion
config_file="${CONFIG_FILE:-config.yml}" # Use default if unset
required_param="${REQUIRED_PARAM:?Missing value}" # Error if unset
required_param="${REQUIRED_PARAM:?Missing value}" # Error if unset
# Always quote expansions
echo "Processing: $config_file"
printf 'Processing: %s\n' "$config_file"
result=$(basename -- "$file_path")
# POSIX test conditions
if [ -f "$config_file" ]; then
printf 'Found config\n'
fi
# Portable output
printf '%b' "Color: ${GREEN}text${NC}\n"
```
### Why POSIX Shell
- **Portability**: Works on Alpine Linux, busybox, minimal containers, all POSIX shells
- **Performance**: POSIX shells are lighter and faster than bash
- **CI-Friendly**: Minimal dependencies, works everywhere
- **Standards**: Follows POSIX best practices
- **Compatibility**: Works with sh, dash, ash, bash, zsh
### Additional Requirements
- **Security**: All external actions SHA-pinned
@@ -189,48 +246,49 @@ if: github.event_name == 'push'
- Don't quote in `with:`, `env:`, `if:` - GitHub evaluates these
- Never nest expressions: `"${{ inputs.value }}"` inside hashFiles breaks caching
### **Local Action References**
### Internal Action References (SHA-Pinned)
**CRITICAL**: When referencing actions within the same repository:
**CRITICAL**: Action files (`*/action.yml`) use SHA-pinned references for security:
-**CORRECT**: `uses: ./action-name` (relative to workspace root)
-**INCORRECT**: `uses: ../action-name` (relative paths that assume directory structure)
-**INCORRECT**: `uses: owner/repo/action-name@main` (floating branch reference)
- ✅ **CORRECT**: `uses: ivuorinen/actions/action-name@7061aafd35a2f21b57653e34f2b634b2a19334a9`
- ❌ **INCORRECT**: `uses: ./action-name` (security risk, not portable when used externally)
- ❌ **INCORRECT**: `uses: ivuorinen/actions/action-name@main` (floating reference)
**Rationale**:
- Uses GitHub workspace root (`$GITHUB_WORKSPACE`) as reference point
- Clear and unambiguous regardless of where action is called from
- Follows GitHub's recommended pattern for same-repository references
- Avoids issues if action checks out repository to different location
- Eliminates external dependencies and supply chain risks
- **Security**: Immutable, auditable references
- **Reproducibility**: Exact version control
- **Portability**: Works when actions used externally (e.g., `ivuorinen/f2b` using `ivuorinen/actions/pr-lint`)
- **Prevention**: No accidental version drift
**Examples**:
**Test Workflows Exception**:
Test workflows in `_tests/` use local references since they run within the repo:
```yaml
# ✅ Correct - relative to workspace root
- uses: ./validate-inputs
- uses: ./common-cache
- uses: ./node-setup
# ❌ Incorrect - relative directory navigation
- uses: ../validate-inputs
- uses: ../common-cache
- uses: ../node-setup
# ❌ Incorrect - external reference to same repo
- uses: ivuorinen/actions/validate-inputs@main
- uses: ivuorinen/actions/common-cache@v1
# ✅ Test workflows only
uses: ./validate-inputs
```
### **Step Output References**
### External Action References (SHA-Pinned)
```yaml
# ✅ Correct - SHA-pinned
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
# ❌ Incorrect - floating reference
uses: actions/checkout@main
uses: actions/checkout@v4
```
### Step Output References
**CRITICAL**: Steps must have `id:` to reference their outputs:
```yaml
# ❌ INCORRECT - missing id
- name: Detect Version
uses: ./version-detect
uses: ivuorinen/actions/version-detect@<SHA>
- name: Setup
with:
@@ -239,7 +297,7 @@ if: github.event_name == 'push'
# ✅ CORRECT - id present
- name: Detect Version
id: detect-version # Required for output reference
uses: ./version-detect
uses: ivuorinen/actions/version-detect@<SHA>
- name: Setup
with:
@@ -250,7 +308,7 @@ if: github.event_name == 'push'
- **No Secrets**: Never commit secrets or keys to repository
- **No Logging**: Never expose or log secrets/keys in code
- **SHA Pinning**: All external actions use SHA commits, not tags
- **SHA Pinning**: All action references (internal + external) use SHA commits, not tags
- **Input Validation**: All actions import from shared validation library (`validate-inputs/`) - stateless validation functions, no inter-action dependencies
- **Output Sanitization**: Use `printf` or heredoc for `$GITHUB_OUTPUT` writes
- **Injection Prevention**: Validate inputs for command injection patterns (`;`, `&&`, `|`, backticks)
@@ -276,6 +334,7 @@ if: github.event_name == 'push'
- **Convention-Based**: Automatic rule generation based on input naming patterns
- **Error Handling**: Comprehensive error messages and proper exit codes
- **Defensive Programming**: Check tool availability, validate inputs, handle edge cases
- **POSIX Compliance**: All scripts portable across POSIX shells
## Pre-commit and Security Configuration

View File

@@ -0,0 +1,201 @@
# 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
```
### Version Management
```bash
make release [VERSION=vYYYY.MM.DD] # Create new release (auto-generates version from date if omitted)
make update-version-refs MAJOR=vYYYY # Update refs to version
make bump-major-version OLD=vYYYY NEW=vYYYY # Annual bump
make check-version-refs # Verify current refs
```
See `versioning_system` memory for complete details.
## 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 (POSIX REQUIRED)
**ALL scripts use POSIX shell** (`#!/bin/sh`) for maximum portability:
```bash
#!/bin/sh
set -eu # MANDATORY (no pipefail - not POSIX)
# Quote everything: "$variable", basename -- "$path"
# Check tools: command -v jq >/dev/null 2>&1
# Use printf instead of echo -e for portability
```
**Why POSIX:**
- Works on Alpine Linux, busybox, minimal containers
- Faster than bash
- Maximum compatibility (sh, dash, ash, bash, zsh)
- CI-friendly, minimal dependencies
**Key Differences from Bash:**
- Use `#!/bin/sh` not `#!/usr/bin/env bash`
- Use `set -eu` not `set -euo pipefail` (pipefail not POSIX)
- Use `[ ]` not `[[ ]]`
- Use `printf` not `echo -e`
- Use `. file` not `source file`
- Use `cut`/`grep` for parsing, not here-strings `<<<`
- Use temp files instead of associative arrays
- Use `$0` not `$BASH_SOURCE`
### Python (Ruff)
- **Line Length**: 100 chars
- **Indent**: 4 spaces
- **Quotes**: Double
- **Docstrings**: Google style
- **Type Hints**: Required
### YAML/Actions
- **Indent**: 2 spaces
- **Internal Actions (action.yml)**: `ivuorinen/actions/action-name@<SHA>` (SHA-pinned, security)
- **Test Workflows**: `./action-name` (local reference, runs within repo)
- **Internal Workflows**: `./action-name` (local reference for sync-labels.yml etc)
- **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`
## Versioning System
### Internal References (SHA-Pinned)
All `*/action.yml` files use SHA-pinned references for security and reproducibility:
```yaml
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
```
**Why SHA-pinned internally:**
- Security: immutable, auditable references
- Reproducibility: exact version control
- Portability: works when actions used externally
- Prevention: no accidental version drift
### Test Workflows (Local References)
Test workflows in `_tests/` use local references:
```yaml
uses: ./validate-inputs
```
**Why local in tests:** Tests run within the repo, faster, simpler
### External User References
Users reference with version tags:
```yaml
uses: ivuorinen/actions/validate-inputs@v2025
```
### Version Format (CalVer)
- Major: `v2025` (year)
- Minor: `v2025.10` (year.month)
- Patch: `v2025.10.18` (year.month.day)
All three tags point to the same commit SHA.
### Creating Releases
```bash
make release # Auto-generates vYYYY.MM.DD from today's date
make release VERSION=v2025.10.18 # Specific version
git push origin main --tags --force-with-lease
```
## Security Requirements
1. **SHA Pinning**: All action references use commit SHAs (not moving tags)
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)
- Never use `@main` for internal action references (use SHA-pinned)
- Never use bash-specific features (scripts must be POSIX sh)
## Preferred Patterns
- POSIX shell for all scripts (not bash)
- SHA-pinned internal action references (security)
- 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

View File

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

View File

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

View File

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

View File

@@ -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 `<action-name>/CustomValidator.py` files
- Searches project root for `<action-dir>/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 `<action-name>/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.

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,115 @@
# GitHub Actions Monorepo - Overview
## Repository Info
- **Path**: /Users/ivuorinen/Code/ivuorinen/actions
- **Branch**: main
- **External Usage**: `ivuorinen/actions/<action-name>@main`
- **Total Actions**: 44 self-contained actions
- **Dogfooding**: Workflows use local actions (pr-lint, codeql-analysis, security-scan)
## Structure
```text
/
├── <action-dirs>/ # 44 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 (44 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
**Security (1)**: security-scan (actionlint, Gitleaks, Trivy scanning)
**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
## Dogfooding Strategy
The repository actively dogfoods its own actions in workflows:
**Fully Dogfooded Workflows**:
- **pr-lint.yml**: Uses `./pr-lint` (was 204 lines, now 112 lines - 45% reduction)
- **action-security.yml**: Uses `./security-scan` (was 264 lines, now 82 lines - 69% reduction)
- **codeql-new.yml**: Uses `./codeql-analysis`
- **sync-labels.yml**: Uses `./sync-labels`
- **version-maintenance.yml**: Uses `./action-versioning`
**Intentionally External**:
- **build-testing-image.yml**: Uses docker/\* actions directly (needs metadata extraction)
- Core GitHub actions (checkout, upload-artifact, setup-\*) kept for standardization
**Benefits**:
- Early detection of action issues
- Real-world testing of actions
- Reduced workflow duplication
- Improved maintainability
- Better documentation through usage examples

View File

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

View File

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

View File

@@ -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 `<action>/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
<action>/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)

View File

@@ -0,0 +1,219 @@
# Version System Architecture
## Overview
This repository uses a CalVer-based SHA-pinned versioning system for all internal action references.
## Version Format
### CalVer: vYYYY.MM.DD
- **Major**: `v2025` (year, updated annually)
- **Minor**: `v2025.10` (year.month)
- **Patch**: `v2025.10.18` (year.month.day)
Example: Release `v2025.10.18` creates three tags pointing to the same commit:
- `v2025.10.18` (patch - specific release)
- `v2025.10` (minor - latest October 2025 release)
- `v2025` (major - latest 2025 release)
## Internal vs External References
### Internal (action.yml files)
- **Format**: `ivuorinen/actions/validate-inputs@<40-char-SHA>`
- **Purpose**: Security, reproducibility, precise control
- **Example**: `ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9`
### External (user consumption)
- **Format**: `ivuorinen/actions/validate-inputs@v2025`
- **Purpose**: Convenience, always gets latest release
- **Options**: `@v2025`, `@v2025.10`, or `@v2025.10.18`
### Test Workflows
- **Format**: `uses: ./action-name` (local reference)
- **Location**: `_tests/integration/workflows/*.yml`
- **Reason**: Tests run within the actions repo context
### Internal Workflows
- **Format**: `uses: ./sync-labels` (local reference)
- **Location**: `.github/workflows/sync-labels.yml`
- **Reason**: Runs within the actions repo, local is sufficient
## Release Process
### Creating a Release
```bash
# 1. Create release with version tags
make release VERSION=v2025.10.18
# This automatically:
# - Updates all action.yml SHA refs to current HEAD
# - Commits the changes
# - Creates tags: v2025.10.18, v2025.10, v2025
# - All tags point to the same commit SHA
# 2. Push to remote
git push origin main --tags --force-with-lease
```
### After Each Release
Tags are force-pushed to ensure `v2025` and `v2025.10` always point to latest:
```bash
git push origin v2025 --force
git push origin v2025.10 --force
git push origin v2025.10.18
```
Or use `--tags --force-with-lease` to push all at once.
## Makefile Targets
### `make release VERSION=v2025.10.18`
Creates new release with version tags and updates all action references.
### `make update-version-refs MAJOR=v2025`
Updates all action.yml files to reference the SHA of the specified major version tag.
### `make bump-major-version OLD=v2025 NEW=v2026`
Annual version bump - replaces all references from one major version to another.
### `make check-version-refs`
Lists all current SHA-pinned references grouped by SHA. Useful for verification.
## Helper Scripts (\_tools/)
### release.sh
Main release script - validates version, updates refs, creates tags.
### validate-version.sh
Validates CalVer format (vYYYY.MM.DD, vYYYY.MM, vYYYY).
### update-action-refs.sh
Updates all action references to a specific SHA or version tag.
### bump-major-version.sh
Handles annual version bumps with commit creation.
### check-version-refs.sh
Displays current SHA-pinned references with tag information.
### get-action-sha.sh
Retrieves SHA for a specific version tag.
## Action Versioning Action
**Location**: `action-versioning/action.yml`
Automatically checks if major version tag has moved and updates all action references.
**Usage in CI**:
```yaml
- uses: ./action-versioning
with:
major-version: v2025
```
**Outputs**:
- `updated`: true/false
- `commit-sha`: SHA of created commit (if any)
- `needs-annual-bump`: true/false (year mismatch)
## CI Workflow
**File**: `.github/workflows/version-maintenance.yml`
**Triggers**:
- Weekly (Monday 9 AM UTC)
- Manual (workflow_dispatch)
**Actions**:
1. Checks if `v2025` tag has moved
2. Updates action references if needed
3. Creates PR with changes
4. Creates issue if annual bump needed
## Annual Version Bump
**When**: Start of each new year
**Process**:
```bash
# 1. Create new major version tag
git tag -a v2026 -m "Major version v2026"
git push origin v2026
# 2. Bump all references
make bump-major-version OLD=v2025 NEW=v2026
# 3. Update documentation
make docs
# 4. Push changes
git push origin main
```
## Verification
### Check Current Refs
```bash
make check-version-refs
```
### Verify All Refs Match
All action references should point to the same SHA after a release.
### Test External Usage
Create a test repo and use:
```yaml
uses: ivuorinen/actions/pr-lint@v2025
```
## Migration from @main
All action.yml files have been migrated from:
- `uses: ./action-name`
- `uses: ivuorinen/actions/action-name@main`
To:
- `uses: ivuorinen/actions/action-name@<SHA>`
Test workflows still use `./action-name` for local testing.
## Security Considerations
**SHA Pinning**: Prevents supply chain attacks by ensuring exact commit is used.
**Version Tags**: Provide user-friendly references while maintaining security internally.
**Tag Verification**: Always verify tags point to expected commits before force-pushing.
**Annual Review**: Each year requires conscious version bump, preventing accidental drift.

View File

@@ -4,10 +4,6 @@
# * For JavaScript, use typescript
# Special requirements:
# * csharp: Requires the presence of a .sln file in the project folder.
language: bash
# whether to use the project's gitignore file to ignore files
# Added on 2025-04-07
ignore_all_files_in_gitignore: true
# list of additional paths to ignore
# same syntax as gitignore, so you can use * and **
@@ -66,3 +62,8 @@ excluded_tools: []
initial_prompt: ''
project_name: 'actions'
languages:
- bash
- python
included_optional_tools: []
encoding: utf-8

View File

@@ -1 +1,2 @@
.venv
.worktrees/

View File

@@ -1,6 +1,10 @@
---
extends: default
ignore: |
node_modules/
.worktrees/
rules:
line-length:
max: 200

View File

@@ -31,15 +31,51 @@
- `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` 30 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`.
**43 Actions**: Setup (node-setup, set-git-config, php-version-detect, python-version-detect, python-version-detect-v2, go-version-detect, dotnet-version-detect), Utilities (version-file-parser, version-validator),
Linting (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),
Testing (php-tests, php-laravel-phpunit, php-composer), Build (csharp-build, go-build, docker-build),
Publishing (npm-publish, docker-publish, docker-publish-gh, docker-publish-hub, csharp-publish),
Repository (github-release, release-monthly, sync-labels, stale, compress-images, common-cache, common-file-check, common-retry, codeql-analysis),
**24 Actions**: Setup (language-version-detect), Utilities (action-versioning, version-file-parser),
Linting (ansible-lint-fix, biome-lint, csharp-lint-check, eslint-lint, go-lint, pr-lint, pre-commit, prettier-lint, python-lint-fix, terraform-lint-fix),
Testing (php-tests), Build (csharp-build, go-build, docker-build),
Publishing (npm-publish, docker-publish, csharp-publish),
Repository (release-monthly, sync-labels, stale, compress-images, codeql-analysis),
Validation (validate-inputs)
## Commands
@@ -50,7 +86,12 @@ Validation (validate-inputs)
**Validation**: `make update-validators`, `make update-validators-dry`
**References**: `make check-local-refs`, `make fix-local-refs`, `make fix-local-refs-dry`
**Versioning**:
- `make release [VERSION=vYYYY.MM.DD]` - Create release (auto-generates version from date if omitted)
- `make update-version-refs MAJOR=vYYYY` - Update action refs to version
- `make bump-major-version OLD=vYYYY NEW=vYYYY` - Annual version bump
- `make check-version-refs` - Verify current action references
### Linters
@@ -69,24 +110,38 @@ Violations cause runtime failures:
3. Sanitize `$GITHUB_OUTPUT`: use `printf '%s\n' "$val"` not `echo "$val"`
4. Pin external actions to SHA commits (not `@main`/`@v1`)
5. Quote shell vars: `"$var"`, `basename -- "$path"` (handles spaces)
6. Use local paths: `./action-name` (not `owner/repo/action@main`)
6. Use SHA-pinned refs for internal actions: `ivuorinen/actions/action-name@<SHA>`
(security, not `./` or `@main`)
7. Test regex edge cases (support `1.0.0-rc.1`, `1.0.0+build`)
8. Use `set -euo pipefail` at script start
8. Use `set -eu` (POSIX) in shell scripts (all scripts are POSIX sh, not bash)
9. Never nest `${{ }}` in quoted YAML strings (breaks hashFiles)
10. Provide tool fallbacks (macOS/Windows lack Linux tools)
### Core Requirements
- External actions SHA-pinned, use `${{ github.token }}`, `set -euo pipefail`
- All actions SHA-pinned (external + internal), use `${{ github.token }}`, POSIX shell (`set -eu`)
- EditorConfig: 2-space indent, UTF-8, LF, max 200 chars (120 for MD)
- Auto-gen README via `action-docs` (note: `npx action-docs --update-readme` doesn't work)
- Required error handling
- Required error handling, POSIX-compliant scripts
### Action References
`./action-name` | ❌ `../action-name` | ❌ `owner/repo/action@main`
**Internal actions (in action.yml)**: SHA-pinned full references
Check: `make check-local-refs`, `make fix-local-refs`
-`ivuorinen/actions/action-name@7061aafd35a2f21b57653e34f2b634b2a19334a9`
-`./action-name` (security risk, not portable when used externally)
-`owner/repo/action@main` (floating reference)
**Test workflows**: Local references
-`./action-name` (tests run within repo)
-`../action-name` (ambiguous paths)
**External users**: Version tags
-`ivuorinen/actions/action-name@v2025` (CalVer major version)
Check: `make check-version-refs`
## Validation System

View File

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

View File

@@ -1,7 +1,7 @@
# Makefile for GitHub Actions repository
# Provides organized task management with parallel execution capabilities
.PHONY: help all docs lint format check clean install-tools test test-unit test-integration test-coverage generate-tests generate-tests-dry test-generate-tests docker-build docker-push docker-test docker-login docker-all
.PHONY: help all docs update-catalog lint format check clean install-tools test test-unit test-integration test-coverage generate-tests generate-tests-dry test-generate-tests docker-build docker-push docker-test docker-login docker-all release release-dry release-prep release-tag release-undo update-version-refs bump-major-version check-version-refs
.DEFAULT_GOAL := help
# Colors for output
@@ -43,7 +43,7 @@ help: ## Show this help message
@echo " make check # Quick syntax checks"
# Main targets
all: install-tools update-validators docs format lint precommit ## Generate docs, format, lint, and run pre-commit
all: install-tools update-validators docs update-catalog format lint precommit ## Generate docs, format, lint, and run pre-commit
@echo "$(GREEN)✅ All tasks completed successfully$(RESET)"
docs: ## Generate documentation for all actions
@@ -66,6 +66,16 @@ docs: ## Generate documentation for all actions
done; \
[ $$failed -eq 0 ] && echo "$(GREEN)✅ All documentation updated successfully$(RESET)" || { echo "$(RED)$$failed documentation updates failed$(RESET)"; exit 1; }
update-catalog: ## Update action catalog in README.md
@echo "$(BLUE)📚 Updating action catalog...$(RESET)"
@if command -v npm >/dev/null 2>&1; then \
npm run update-catalog; \
else \
echo "$(RED)❌ npm not found. Please install Node.js$(RESET)"; \
exit 1; \
fi
@echo "$(GREEN)✅ Action catalog updated$(RESET)"
update-validators: ## Update validation rules for all actions
@echo "$(BLUE)🔧 Updating validation rules...$(RESET)"
@if command -v uv >/dev/null 2>&1; then \
@@ -145,10 +155,69 @@ fix-local-refs-dry: ## Preview local action reference fixes (dry run)
exit 1; \
fi
# Version management targets
release: ## Create a new release with version tags (usage: make release [VERSION=v2025.10.18])
@VERSION_TO_USE=$$(if [ -n "$(VERSION)" ]; then echo "$(VERSION)"; else date +v%Y.%m.%d; fi); \
echo "$(BLUE)🚀 Creating release $$VERSION_TO_USE...$(RESET)"; \
sh _tools/release.sh "$$VERSION_TO_USE"
release-dry: ## Preview release without making changes (usage: make release-dry VERSION=v2025.11.01)
@if [ -z "$(VERSION)" ]; then \
VERSION_TO_USE=$$(date +v%Y.%m.%d); \
else \
VERSION_TO_USE="$(VERSION)"; \
fi; \
echo "$(BLUE)🔍 Previewing release $$VERSION_TO_USE (dry run)...$(RESET)"; \
sh _tools/release.sh --dry-run "$$VERSION_TO_USE"
release-prep: ## Update action refs and commit (no tags) (usage: make release-prep [VERSION=v2025.11.01])
@VERSION_TO_USE=$$(if [ -n "$(VERSION)" ]; then echo "$(VERSION)"; else date +v%Y.%m.%d; fi); \
echo "$(BLUE)🔧 Preparing release $$VERSION_TO_USE...$(RESET)"; \
sh _tools/release.sh --prep-only "$$VERSION_TO_USE"; \
echo "$(GREEN)✅ Preparation complete$(RESET)"; \
echo "$(YELLOW)Next: make release-tag VERSION=$$VERSION_TO_USE$(RESET)"
release-tag: ## Create tags only (assumes prep done) (usage: make release-tag VERSION=v2025.11.01)
@if [ -z "$(VERSION)" ]; then \
echo "$(RED)❌ Error: VERSION parameter required for release-tag$(RESET)"; \
echo "Usage: make release-tag VERSION=v2025.11.01"; \
exit 1; \
fi; \
echo "$(BLUE)🏷️ Creating tags for release $(VERSION)...$(RESET)"; \
sh _tools/release.sh --tag-only "$(VERSION)"
release-undo: ## Rollback the most recent release (delete tags and reset HEAD)
@echo "$(BLUE)🔙 Rolling back release...$(RESET)"; \
sh _tools/release-undo.sh
update-version-refs: ## Update all action references to a specific version tag (usage: make update-version-refs MAJOR=v2025)
@if [ -z "$(MAJOR)" ]; then \
echo "$(RED)❌ Error: MAJOR parameter required$(RESET)"; \
echo "Usage: make update-version-refs MAJOR=v2025"; \
exit 1; \
fi
@echo "$(BLUE)🔧 Updating action references to $(MAJOR)...$(RESET)"
@sh _tools/update-action-refs.sh "$(MAJOR)"
@echo "$(GREEN)✅ Action references updated$(RESET)"
bump-major-version: ## Replace one major version with another (usage: make bump-major-version OLD=v2025 NEW=v2026)
@if [ -z "$(OLD)" ] || [ -z "$(NEW)" ]; then \
echo "$(RED)❌ Error: OLD and NEW parameters required$(RESET)"; \
echo "Usage: make bump-major-version OLD=v2025 NEW=v2026"; \
exit 1; \
fi
@echo "$(BLUE)🔄 Bumping version from $(OLD) to $(NEW)...$(RESET)"
@sh _tools/bump-major-version.sh "$(OLD)" "$(NEW)"
@echo "$(GREEN)✅ Major version bumped$(RESET)"
check-version-refs: ## List all current SHA-pinned action references
@echo "$(BLUE)🔍 Checking action references...$(RESET)"
@sh _tools/check-version-refs.sh
# Formatting targets
format-markdown: ## Format markdown files
@echo "$(BLUE)📝 Formatting markdown...$(RESET)"
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" 2>/dev/null; then \
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" "#.worktrees" 2>/dev/null; then \
echo "$(GREEN)✅ Markdown formatted$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Markdown formatting issues found$(RESET)" | tee -a $(LOG_FILE); \
@@ -200,7 +269,7 @@ format-python: ## Format Python files with ruff
# Linting targets
lint-markdown: ## Lint markdown files
@echo "$(BLUE)🔍 Linting markdown...$(RESET)"
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules"; then \
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" "#.worktrees"; then \
echo "$(GREEN)✅ Markdown linting passed$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Markdown linting issues found$(RESET)" | tee -a $(LOG_FILE); \
@@ -216,14 +285,17 @@ lint-yaml: ## Lint YAML files
lint-shell: ## Lint shell scripts
@echo "$(BLUE)🔍 Linting shell scripts...$(RESET)"
@if command -v shellcheck >/dev/null 2>&1; then \
if find . -name "*.sh" -not -path "./_tests/*" -exec shellcheck -x {} + 2>/dev/null; then \
echo "$(GREEN)✅ Shell linting passed$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Shell linting issues found$(RESET)" | tee -a $(LOG_FILE); \
fi; \
@if ! command -v shellcheck >/dev/null 2>&1; then \
echo "$(RED)❌ shellcheck not found. Please install shellcheck:$(RESET)"; \
echo " brew install shellcheck"; \
echo " or: apt-get install shellcheck"; \
exit 1; \
fi
@if find . -name "*.sh" -not -path "./_tests/*" -not -path "./.worktrees/*" -exec shellcheck -x {} +; then \
echo "$(GREEN)✅ Shell linting passed$(RESET)"; \
else \
echo "$(BLUE) shellcheck not available, skipping shell script linting$(RESET)"; \
echo "$(RED)❌ Shell linting issues found$(RESET)"; \
exit 1; \
fi
lint-python: ## Lint Python files with ruff and pyright
@@ -268,7 +340,7 @@ check-tools: ## Check if required tools are available
check-syntax: ## Check syntax of shell scripts and YAML files
@echo "$(BLUE)🔍 Checking syntax...$(RESET)"
@failed=0; \
find . -name "*.sh" -print0 | while IFS= read -r -d '' file; do \
find . -name "*.sh" -not -path "./_tests/*" -not -path "./.worktrees/*" -print0 | while IFS= read -r -d '' file; do \
if ! bash -n "$$file" 2>&1; then \
echo "$(RED)❌ Syntax error in $$file$(RESET)" >&2; \
failed=1; \
@@ -649,7 +721,8 @@ docker-all: docker-build docker-test docker-push ## Build, test, and push Docker
watch: ## Watch files and auto-format on changes (requires entr)
@if command -v entr >/dev/null 2>&1; then \
echo "$(BLUE)👀 Watching for changes... (press Ctrl+C to stop)$(RESET)"; \
find . -name "*.yml" -o -name "*.yaml" -o -name "*.md" -o -name "*.sh" | \
find . \( -name "*.yml" -o -name "*.yaml" -o -name "*.md" -o -name "*.sh" \) \
-not -path "./_tests/*" -not -path "./.worktrees/*" -not -path "./node_modules/*" | \
entr -c $(MAKE) format; \
else \
echo "$(RED)❌ Error: entr not found. Install with: brew install entr$(RESET)"; \

332
README.md
View File

@@ -22,203 +22,174 @@ Each action is fully self-contained and can be used independently in any GitHub
## 📚 Action Catalog
This repository contains **43 reusable GitHub Actions** for CI/CD automation.
This repository contains **26 reusable GitHub Actions** for CI/CD automation.
### Quick Reference (43 Actions)
### Quick Reference (26 Actions)
| Icon | Action | Category | Description | Key Features |
|:----:|:-------------------------------------------------------|:-----------|:----------------------------------------------------------------|:---------------------------------------------|
| 📦 | [`ansible-lint-fix`][ansible-lint-fix] | Linting | Lints and fixes Ansible playbooks, commits changes, and uplo... | Token auth, Outputs |
| | [`biome-check`][biome-check] | Linting | Run Biome check on the repository | Token auth, Outputs |
| ✅ | [`biome-fix`][biome-fix] | Linting | Run Biome fix on the repository | Token auth, Outputs |
| 🛡️ | [`codeql-analysis`][codeql-analysis] | Other | Run CodeQL security analysis for a single language with conf... | Auto-detection, Token auth, Outputs |
| 💾 | [`common-cache`][common-cache] | Repository | Standardized caching strategy for all actions | Caching, Outputs |
| 📦 | [`common-file-check`][common-file-check] | Repository | A reusable action to check if a specific file or type of fil... | Outputs |
| 🔄 | [`common-retry`][common-retry] | Repository | Standardized retry utility for network operations and flaky ... | Outputs |
| 🖼️ | [`compress-images`][compress-images] | Repository | Compress images on demand (workflow_dispatch), and at 11pm e... | Token auth, Outputs |
| 📝 | [`csharp-build`][csharp-build] | Build | Builds and tests C# projects. | Auto-detection, Outputs |
| 📝 | [`csharp-lint-check`][csharp-lint-check] | Linting | Runs linters like StyleCop or dotnet-format for C# code styl... | Auto-detection, Outputs |
| 📦 | [`csharp-publish`][csharp-publish] | Publishing | Publishes a C# project to GitHub Packages. | Auto-detection, Token auth, Outputs |
| 📦 | [`docker-build`][docker-build] | Build | Builds a Docker image for multiple architectures with enhanc... | Caching, Auto-detection, Token auth, Outputs |
| ☁️ | [`docker-publish`][docker-publish] | Publishing | Publish a Docker image to GitHub Packages and Docker Hub. | Auto-detection, Outputs |
| 📦 | [`docker-publish-gh`][docker-publish-gh] | Publishing | Publishes a Docker image to GitHub Packages with advanced se... | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`docker-publish-hub`][docker-publish-hub] | Publishing | Publishes a Docker image to Docker Hub with enhanced securit... | Caching, Auto-detection, Outputs |
| 📝 | [`dotnet-version-detect`][dotnet-version-detect] | Setup | Detects .NET SDK version from global.json or defaults to a s... | Auto-detection, Outputs |
| ✅ | [`eslint-check`][eslint-check] | Linting | Run ESLint check on the repository with advanced configurati... | Caching, Outputs |
| 📝 | [`eslint-fix`][eslint-fix] | Linting | Fixes ESLint violations in a project. | Token auth, Outputs |
| 🏷️ | [`github-release`][github-release] | Repository | Creates a GitHub release with a version and changelog. | Outputs |
| 📦 | [`go-build`][go-build] | Build | Builds the Go project. | Caching, Auto-detection, Outputs |
| 📝 | [`go-lint`][go-lint] | Linting | Run golangci-lint with advanced configuration, caching, and ... | Caching, Outputs |
| 📝 | [`go-version-detect`][go-version-detect] | Setup | Detects the Go version from the project's go.mod file or def... | Auto-detection, Outputs |
| 🖥️ | [`node-setup`][node-setup] | Setup | Sets up Node.js env with advanced version management, cachin... | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`npm-publish`][npm-publish] | Publishing | Publishes the package to the NPM registry with configurable ... | Outputs |
| 🖥️ | [`php-composer`][php-composer] | Testing | Runs Composer install on a repository with advanced caching ... | Auto-detection, Token auth, Outputs |
| 💻 | [`php-laravel-phpunit`][php-laravel-phpunit] | Testing | Setup PHP, install dependencies, generate key, create databa... | Auto-detection, Token auth, Outputs |
| ✅ | [`php-tests`][php-tests] | Testing | Run PHPUnit tests on the repository | Token auth, Outputs |
| 📝 | [`php-version-detect`][php-version-detect] | Setup | Detects the PHP version from the project's composer.json, ph... | Auto-detection, Outputs |
| ✅ | [`pr-lint`][pr-lint] | Linting | Runs MegaLinter against pull requests | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`pre-commit`][pre-commit] | Linting | Runs pre-commit on the repository and pushes the fixes back ... | Auto-detection, Token auth, Outputs |
| ✅ | [`prettier-check`][prettier-check] | Linting | Run Prettier check on the repository with advanced configura... | Caching, Outputs |
| 📝 | [`prettier-fix`][prettier-fix] | Linting | Run Prettier to fix code style violations | Token auth, Outputs |
| 📝 | [`python-lint-fix`][python-lint-fix] | Linting | Lints and fixes Python files, commits changes, and uploads S... | Caching, Auto-detection, Token auth, Outputs |
| 📝 | [`python-version-detect`][python-version-detect] | Setup | Detects Python version from project configuration files or d... | Auto-detection, Outputs |
| 📝 | [`python-version-detect-v2`][python-version-detect-v2] | Setup | Detects Python version from project configuration files usin... | Auto-detection, Outputs |
| 📦 | [`release-monthly`][release-monthly] | Repository | Creates a release for the current month, incrementing patch ... | Token auth, Outputs |
| 🔀 | [`set-git-config`][set-git-config] | Setup | Sets Git configuration for actions. | Token auth, Outputs |
| 📦 | [`stale`][stale] | Repository | A GitHub Action to close stale issues and pull requests. | Token auth, Outputs |
| 🏷️ | [`sync-labels`][sync-labels] | Repository | Sync labels from a YAML file to a GitHub repository | Token auth, Outputs |
| 🖥️ | [`terraform-lint-fix`][terraform-lint-fix] | Linting | Lints and fixes Terraform files with advanced validation and... | Token auth, Outputs |
| 🛡️ | [`validate-inputs`][validate-inputs] | Other | Centralized Python-based input validation for GitHub Actions... | Token auth, Outputs |
| 📦 | [`version-file-parser`][version-file-parser] | Utilities | Universal parser for common version detection files (.tool-v... | Auto-detection, Outputs |
| ✅ | [`version-validator`][version-validator] | Utilities | Validates and normalizes version strings using customizable ... | Auto-detection, Outputs |
| Icon | Action | Category | Description | Key Features |
|:----:|:-----------------------------------------------------|:-----------|:----------------------------------------------------------------|:---------------------------------------------|
| 🔀 | [`action-versioning`][action-versioning] | Utilities | Automatically update SHA-pinned action references to match l... | Token auth, Outputs |
| 📦 | [`ansible-lint-fix`][ansible-lint-fix] | Linting | Lints and fixes Ansible playbooks, commits changes, and uplo... | Caching, Token auth, Outputs |
| ✅ | [`biome-lint`][biome-lint] | Linting | Run Biome linter in check or fix mode | Caching, Auto-detection, Token auth, Outputs |
| 🛡️ | [`codeql-analysis`][codeql-analysis] | Repository | Run CodeQL security analysis for a single language with conf... | Auto-detection, Token auth, Outputs |
| 🖼️ | [`compress-images`][compress-images] | Repository | Compress images on demand (workflow_dispatch), and at 11pm e... | Token auth, Outputs |
| 📝 | [`csharp-build`][csharp-build] | Build | Builds and tests C# projects. | Caching, Auto-detection, Token auth, Outputs |
| 📝 | [`csharp-lint-check`][csharp-lint-check] | Linting | Runs linters like StyleCop or dotnet-format for C# code styl... | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`csharp-publish`][csharp-publish] | Publishing | Publishes a C# project to GitHub Packages. | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`docker-build`][docker-build] | Build | Builds a Docker image for multiple architectures with enhanc... | Caching, Auto-detection, Token auth, Outputs |
| ☁️ | [`docker-publish`][docker-publish] | Publishing | Simple wrapper to publish Docker images to GitHub Packages a... | Token auth, Outputs |
| | [`eslint-lint`][eslint-lint] | Linting | Run ESLint in check or fix mode with advanced configuration ... | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`go-build`][go-build] | Build | Builds the Go project. | Caching, Auto-detection, Token auth, Outputs |
| 📝 | [`go-lint`][go-lint] | Linting | Run golangci-lint with advanced configuration, caching, and ... | Caching, Token auth, Outputs |
| 📝 | [`language-version-detect`][language-version-detect] | Setup | DEPRECATED: This action is deprecated. Inline version detect... | Auto-detection, Token auth, Outputs |
| 📦 | [`npm-publish`][npm-publish] | Publishing | Publishes the package to the NPM registry with configurable ... | Caching, Auto-detection, Token auth, Outputs |
| | [`php-tests`][php-tests] | Testing | Run PHPUnit tests with optional Laravel setup and Composer d... | Caching, Auto-detection, Token auth, Outputs |
| ✅ | [`pr-lint`][pr-lint] | Linting | Runs MegaLinter against pull requests | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`pre-commit`][pre-commit] | Linting | Runs pre-commit on the repository and pushes the fixes back ... | Auto-detection, Token auth, Outputs |
| | [`prettier-lint`][prettier-lint] | Linting | Run Prettier in check or fix mode with advanced configuratio... | Caching, Auto-detection, Token auth, Outputs |
| 📝 | [`python-lint-fix`][python-lint-fix] | Linting | Lints and fixes Python files, commits changes, and uploads S... | Caching, Auto-detection, Token auth, Outputs |
| 📦 | [`release-monthly`][release-monthly] | Repository | Creates a release for the current month, incrementing patch ... | Token auth, Outputs |
| 🛡️ | [`security-scan`][security-scan] | Security | Comprehensive security scanning for GitHub Actions including... | Caching, Token auth, Outputs |
| 📦 | [`stale`][stale] | Repository | A GitHub Action to close stale issues and pull requests. | Token auth, Outputs |
| 🏷️ | [`sync-labels`][sync-labels] | Repository | Sync labels from a YAML file to a GitHub repository | Token auth, Outputs |
| 🖥️ | [`terraform-lint-fix`][terraform-lint-fix] | Linting | Lints and fixes Terraform files with advanced validation and... | Token auth, Outputs |
| 🛡️ | [`validate-inputs`][validate-inputs] | Validation | Centralized Python-based input validation for GitHub Actions... | Token auth, Outputs |
### Actions by Category
#### 🔧 Setup (7 actions)
#### 🔧 Setup (1 action)
| Action | Description | Languages | Features |
|:----------------------------------------------------------|:------------------------------------------------------|:--------------------------------|:---------------------------------------------|
| 📝 [`dotnet-version-detect`][dotnet-version-detect] | Detects .NET SDK version from global.json or defau... | C#, .NET | Auto-detection, Outputs |
| 📝 [`go-version-detect`][go-version-detect] | Detects the Go version from the project's go.mod f... | Go | Auto-detection, Outputs |
| 🖥️ [`node-setup`][node-setup] | Sets up Node.js env with advanced version manageme... | Node.js, JavaScript, TypeScript | Caching, Auto-detection, Token auth, Outputs |
| 📝 [`php-version-detect`][php-version-detect] | Detects the PHP version from the project's compose... | PHP | Auto-detection, Outputs |
| 📝 [`python-version-detect`][python-version-detect] | Detects Python version from project configuration ... | Python | Auto-detection, Outputs |
| 📝 [`python-version-detect-v2`][python-version-detect-v2] | Detects Python version from project configuration ... | Python | Auto-detection, Outputs |
| 🔀 [`set-git-config`][set-git-config] | Sets Git configuration for actions. | - | Token auth, Outputs |
| Action | Description | Languages | Features |
|:--------------------------------------------------------|:------------------------------------------------------|:-------------------------------|:------------------------------------|
| 📝 [`language-version-detect`][language-version-detect] | DEPRECATED: This action is deprecated. Inline vers... | PHP, Python, Go, .NET, Node.js | Auto-detection, Token auth, Outputs |
#### 🛠️ Utilities (2 actions)
#### 🛠️ Utilities (1 action)
| Action | Description | Languages | Features |
|:------------------------------------------------|:------------------------------------------------------|:----------|:------------------------|
| 📦 [`version-file-parser`][version-file-parser] | Universal parser for common version detection file... | - | Auto-detection, Outputs |
| ✅ [`version-validator`][version-validator] | Validates and normalizes version strings using cus... | - | Auto-detection, Outputs |
| Action | Description | Languages | Features |
|:--------------------------------------------|:------------------------------------------------------|:---------------|:--------------------|
| 🔀 [`action-versioning`][action-versioning] | Automatically update SHA-pinned action references ... | GitHub Actions | Token auth, Outputs |
#### 📝 Linting (13 actions)
#### 📝 Linting (10 actions)
| Action | Description | Languages | Features |
|:-----------------------------------------------|:------------------------------------------------------|:---------------------------------------------|:---------------------------------------------|
| 📦 [`ansible-lint-fix`][ansible-lint-fix] | Lints and fixes Ansible playbooks, commits changes... | Ansible, YAML | Token auth, Outputs |
| ✅ [`biome-check`][biome-check] | Run Biome check on the repository | JavaScript, TypeScript, JSON | Token auth, Outputs |
| [`biome-fix`][biome-fix] | Run Biome fix on the repository | JavaScript, TypeScript, JSON | Token auth, Outputs |
| 📝 [`csharp-lint-check`][csharp-lint-check] | Runs linters like StyleCop or dotnet-format for C#... | C#, .NET | Auto-detection, Outputs |
| [`eslint-check`][eslint-check] | Run ESLint check on the repository with advanced c... | JavaScript, TypeScript | Caching, Outputs |
| 📝 [`eslint-fix`][eslint-fix] | Fixes ESLint violations in a project. | JavaScript, TypeScript | Token auth, Outputs |
| 📝 [`go-lint`][go-lint] | Run golangci-lint with advanced configuration, cac... | Go | Caching, Outputs |
| ✅ [`pr-lint`][pr-lint] | Runs MegaLinter against pull requests | - | Caching, Auto-detection, Token auth, Outputs |
| 📦 [`pre-commit`][pre-commit] | Runs pre-commit on the repository and pushes the f... | - | Auto-detection, Token auth, Outputs |
| ✅ [`prettier-check`][prettier-check] | Run Prettier check on the repository with advanced... | JavaScript, TypeScript, Markdown, YAML, JSON | Caching, Outputs |
| 📝 [`prettier-fix`][prettier-fix] | Run Prettier to fix code style violations | JavaScript, TypeScript, Markdown, YAML, JSON | Token auth, Outputs |
| 📦 [`ansible-lint-fix`][ansible-lint-fix] | Lints and fixes Ansible playbooks, commits changes... | Ansible, YAML | Caching, Token auth, Outputs |
| ✅ [`biome-lint`][biome-lint] | Run Biome linter in check or fix mode | JavaScript, TypeScript, JSON | Caching, Auto-detection, Token auth, Outputs |
| 📝 [`csharp-lint-check`][csharp-lint-check] | Runs linters like StyleCop or dotnet-format for C#... | C#, .NET | Caching, Auto-detection, Token auth, Outputs |
| [`eslint-lint`][eslint-lint] | Run ESLint in check or fix mode with advanced conf... | JavaScript, TypeScript | Caching, Auto-detection, Token auth, Outputs |
| 📝 [`go-lint`][go-lint] | Run golangci-lint with advanced configuration, cac... | Go | Caching, Token auth, Outputs |
| [`pr-lint`][pr-lint] | Runs MegaLinter against pull requests | Conventional Commits | Caching, Auto-detection, Token auth, Outputs |
| 📦 [`pre-commit`][pre-commit] | Runs pre-commit on the repository and pushes the f... | Python, Multiple Languages | Auto-detection, Token auth, Outputs |
| ✅ [`prettier-lint`][prettier-lint] | Run Prettier in check or fix mode with advanced co... | JavaScript, TypeScript, Markdown, YAML, JSON | Caching, Auto-detection, Token auth, Outputs |
| 📝 [`python-lint-fix`][python-lint-fix] | Lints and fixes Python files, commits changes, and... | Python | Caching, Auto-detection, Token auth, Outputs |
| 🖥️ [`terraform-lint-fix`][terraform-lint-fix] | Lints and fixes Terraform files with advanced vali... | Terraform, HCL | Token auth, Outputs |
#### 🧪 Testing (3 actions)
#### 🧪 Testing (1 action)
| Action | Description | Languages | Features |
|:------------------------------------------------|:------------------------------------------------------|:-------------|:------------------------------------|
| 🖥️ [`php-composer`][php-composer] | Runs Composer install on a repository with advance... | PHP | Auto-detection, Token auth, Outputs |
| 💻 [`php-laravel-phpunit`][php-laravel-phpunit] | Setup PHP, install dependencies, generate key, cre... | PHP, Laravel | Auto-detection, Token auth, Outputs |
| ✅ [`php-tests`][php-tests] | Run PHPUnit tests on the repository | PHP | Token auth, Outputs |
| Action | Description | Languages | Features |
|:---------------------------|:------------------------------------------------------|:-------------|:---------------------------------------------|
| [`php-tests`][php-tests] | Run PHPUnit tests with optional Laravel setup and ... | PHP, Laravel | Caching, Auto-detection, Token auth, Outputs |
#### 🏗️ Build (3 actions)
| Action | Description | Languages | Features |
|:----------------------------------|:------------------------------------------------------|:----------|:---------------------------------------------|
| 📝 [`csharp-build`][csharp-build] | Builds and tests C# projects. | C#, .NET | Auto-detection, Outputs |
| 📝 [`csharp-build`][csharp-build] | Builds and tests C# projects. | C#, .NET | Caching, Auto-detection, Token auth, Outputs |
| 📦 [`docker-build`][docker-build] | Builds a Docker image for multiple architectures w... | Docker | Caching, Auto-detection, Token auth, Outputs |
| 📦 [`go-build`][go-build] | Builds the Go project. | Go | Caching, Auto-detection, Outputs |
| 📦 [`go-build`][go-build] | Builds the Go project. | Go | Caching, Auto-detection, Token auth, Outputs |
#### 🚀 Publishing (5 actions)
#### 🚀 Publishing (3 actions)
| Action | Description | Languages | Features |
|:----------------------------------------------|:------------------------------------------------------|:-------------|:---------------------------------------------|
| 📦 [`csharp-publish`][csharp-publish] | Publishes a C# project to GitHub Packages. | C#, .NET | Auto-detection, Token auth, Outputs |
| ☁️ [`docker-publish`][docker-publish] | Publish a Docker image to GitHub Packages and Dock... | Docker | Auto-detection, Outputs |
| 📦 [`docker-publish-gh`][docker-publish-gh] | Publishes a Docker image to GitHub Packages with a... | Docker | Caching, Auto-detection, Token auth, Outputs |
| 📦 [`docker-publish-hub`][docker-publish-hub] | Publishes a Docker image to Docker Hub with enhanc... | Docker | Caching, Auto-detection, Outputs |
| 📦 [`npm-publish`][npm-publish] | Publishes the package to the NPM registry with con... | Node.js, npm | Outputs |
| Action | Description | Languages | Features |
|:--------------------------------------|:------------------------------------------------------|:-------------|:---------------------------------------------|
| 📦 [`csharp-publish`][csharp-publish] | Publishes a C# project to GitHub Packages. | C#, .NET | Caching, Auto-detection, Token auth, Outputs |
| ☁️ [`docker-publish`][docker-publish] | Simple wrapper to publish Docker images to GitHub ... | Docker | Token auth, Outputs |
| 📦 [`npm-publish`][npm-publish] | Publishes the package to the NPM registry with con... | Node.js, npm | Caching, Auto-detection, Token auth, Outputs |
#### 📦 Repository (8 actions)
#### 📦 Repository (5 actions)
| Action | Description | Languages | Features |
|:--------------------------------------------|:------------------------------------------------------|:----------|:--------------------|
| 💾 [`common-cache`][common-cache] | Standardized caching strategy for all actions | - | Caching, Outputs |
| 📦 [`common-file-check`][common-file-check] | A reusable action to check if a specific file or t... | - | Outputs |
| 🔄 [`common-retry`][common-retry] | Standardized retry utility for network operations ... | - | Outputs |
| 🖼️ [`compress-images`][compress-images] | Compress images on demand (workflow_dispatch), and... | - | Token auth, Outputs |
| 🏷️ [`github-release`][github-release] | Creates a GitHub release with a version and change... | - | Outputs |
| 📦 [`release-monthly`][release-monthly] | Creates a release for the current month, increment... | - | Token auth, Outputs |
| 📦 [`stale`][stale] | A GitHub Action to close stale issues and pull req... | - | Token auth, Outputs |
| 🏷️ [`sync-labels`][sync-labels] | Sync labels from a YAML file to a GitHub repositor... | - | Token auth, Outputs |
| Action | Description | Languages | Features |
|:-----------------------------------------|:------------------------------------------------------|:--------------------------------------------------------|:------------------------------------|
| 🛡️ [`codeql-analysis`][codeql-analysis] | Run CodeQL security analysis for a single language... | JavaScript, TypeScript, Python, Java, C#, C++, Go, Ruby | Auto-detection, Token auth, Outputs |
| 🖼️ [`compress-images`][compress-images] | Compress images on demand (workflow_dispatch), and... | Images, PNG, JPEG | Token auth, Outputs |
| 📦 [`release-monthly`][release-monthly] | Creates a release for the current month, increment... | GitHub Actions | Token auth, Outputs |
| 📦 [`stale`][stale] | A GitHub Action to close stale issues and pull req... | GitHub Actions | Token auth, Outputs |
| 🏷️ [`sync-labels`][sync-labels] | Sync labels from a YAML file to a GitHub repositor... | YAML, GitHub | Token auth, Outputs |
#### 🛡️ Security (1 action)
| Action | Description | Languages | Features |
|:-------------------------------------|:------------------------------------------------------|:----------|:-----------------------------|
| 🛡️ [`security-scan`][security-scan] | Comprehensive security scanning for GitHub Actions... | - | Caching, Token auth, Outputs |
#### ✅ Validation (1 action)
| Action | Description | Languages | Features |
|:-----------------------------------------|:------------------------------------------------------|:---------------------|:--------------------|
| 🛡️ [`validate-inputs`][validate-inputs] | Centralized Python-based input validation for GitH... | YAML, GitHub Actions | Token auth, Outputs |
### Feature Matrix
| Action | Caching | Auto-detection | Token auth | Outputs |
|:-------------------------------------------------------|:-------:|:--------------:|:----------:|:-------:|
| [`ansible-lint-fix`][ansible-lint-fix] | - | - | ✅ | ✅ |
| [`biome-check`][biome-check] | - | - | ✅ | ✅ |
| [`biome-fix`][biome-fix] | - | - | ✅ | ✅ |
| [`codeql-analysis`][codeql-analysis] | - | ✅ | ✅ | ✅ |
| [`common-cache`][common-cache] | | - | - | ✅ |
| [`common-file-check`][common-file-check] | - | - | - | ✅ |
| [`common-retry`][common-retry] | - | - | - | ✅ |
| [`compress-images`][compress-images] | - | - | ✅ | ✅ |
| [`csharp-build`][csharp-build] | - | ✅ | - | ✅ |
| [`csharp-lint-check`][csharp-lint-check] | - | | - | ✅ |
| [`csharp-publish`][csharp-publish] | - | ✅ | ✅ | ✅ |
| [`docker-build`][docker-build] | ✅ | ✅ | ✅ | ✅ |
| [`docker-publish`][docker-publish] | - | | - | ✅ |
| [`docker-publish-gh`][docker-publish-gh] | | ✅ | ✅ | ✅ |
| [`docker-publish-hub`][docker-publish-hub] | ✅ | ✅ | - | ✅ |
| [`dotnet-version-detect`][dotnet-version-detect] | - | ✅ | - | ✅ |
| [`eslint-check`][eslint-check] | ✅ | - | - | ✅ |
| [`eslint-fix`][eslint-fix] | - | - | ✅ | ✅ |
| [`github-release`][github-release] | - | - | - | ✅ |
| [`go-build`][go-build] | ✅ | ✅ | - | ✅ |
| [`go-lint`][go-lint] | | - | - | ✅ |
| [`go-version-detect`][go-version-detect] | - | | - | ✅ |
| [`node-setup`][node-setup] | | | ✅ | ✅ |
| [`npm-publish`][npm-publish] | - | - | - | ✅ |
| [`php-composer`][php-composer] | - | | ✅ | ✅ |
| [`php-laravel-phpunit`][php-laravel-phpunit] | - | | ✅ | ✅ |
| [`php-tests`][php-tests] | - | - | ✅ | ✅ |
| [`php-version-detect`][php-version-detect] | - | ✅ | - | ✅ |
| [`pr-lint`][pr-lint] | ✅ | ✅ | ✅ | ✅ |
| [`pre-commit`][pre-commit] | - | ✅ | ✅ | ✅ |
| [`prettier-check`][prettier-check] | ✅ | - | - | ✅ |
| [`prettier-fix`][prettier-fix] | - | - | ✅ | ✅ |
| [`python-lint-fix`][python-lint-fix] | ✅ | ✅ | ✅ | ✅ |
| [`python-version-detect`][python-version-detect] | - | ✅ | - | ✅ |
| [`python-version-detect-v2`][python-version-detect-v2] | - | ✅ | - | ✅ |
| [`release-monthly`][release-monthly] | - | - | ✅ | ✅ |
| [`set-git-config`][set-git-config] | - | - | ✅ | ✅ |
| [`stale`][stale] | - | - | ✅ | ✅ |
| [`sync-labels`][sync-labels] | - | - | ✅ | ✅ |
| [`terraform-lint-fix`][terraform-lint-fix] | - | - | ✅ | ✅ |
| [`validate-inputs`][validate-inputs] | - | - | ✅ | ✅ |
| [`version-file-parser`][version-file-parser] | - | ✅ | - | ✅ |
| [`version-validator`][version-validator] | - | ✅ | - | ✅ |
| Action | Caching | Auto-detection | Token auth | Outputs |
|:-----------------------------------------------------|:-------:|:--------------:|:----------:|:-------:|
| [`action-versioning`][action-versioning] | - | - | ✅ | ✅ |
| [`ansible-lint-fix`][ansible-lint-fix] | | - | ✅ | ✅ |
| [`biome-lint`][biome-lint] | | | ✅ | ✅ |
| [`codeql-analysis`][codeql-analysis] | - | ✅ | ✅ | ✅ |
| [`compress-images`][compress-images] | - | - | | ✅ |
| [`csharp-build`][csharp-build] | | | | ✅ |
| [`csharp-lint-check`][csharp-lint-check] | | | | ✅ |
| [`csharp-publish`][csharp-publish] | | | ✅ | ✅ |
| [`docker-build`][docker-build] | | ✅ | | ✅ |
| [`docker-publish`][docker-publish] | - | - | | ✅ |
| [`eslint-lint`][eslint-lint] | | ✅ | ✅ | ✅ |
| [`go-build`][go-build] | ✅ | ✅ | ✅ | ✅ |
| [`go-lint`][go-lint] | | - | | ✅ |
| [`language-version-detect`][language-version-detect] | - | ✅ | ✅ | ✅ |
| [`npm-publish`][npm-publish] | ✅ | ✅ | | ✅ |
| [`php-tests`][php-tests] | | ✅ | | ✅ |
| [`pr-lint`][pr-lint] | ✅ | | | ✅ |
| [`pre-commit`][pre-commit] | - | | ✅ | ✅ |
| [`prettier-lint`][prettier-lint] | | | | ✅ |
| [`python-lint-fix`][python-lint-fix] | ✅ | ✅ | | ✅ |
| [`release-monthly`][release-monthly] | - | - | | ✅ |
| [`security-scan`][security-scan] | | - | | ✅ |
| [`stale`][stale] | - | - | ✅ | ✅ |
| [`sync-labels`][sync-labels] | - | - | | ✅ |
| [`terraform-lint-fix`][terraform-lint-fix] | - | - | ✅ | ✅ |
| [`validate-inputs`][validate-inputs] | - | - | ✅ | ✅ |
### Language Support
| Language | Actions |
|:-----------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| .NET | [`csharp-build`][csharp-build], [`csharp-lint-check`][csharp-lint-check], [`csharp-publish`][csharp-publish], [`dotnet-version-detect`][dotnet-version-detect] |
| Ansible | [`ansible-lint-fix`][ansible-lint-fix] |
| C# | [`csharp-build`][csharp-build], [`csharp-lint-check`][csharp-lint-check], [`csharp-publish`][csharp-publish], [`dotnet-version-detect`][dotnet-version-detect] |
| Docker | [`docker-build`][docker-build], [`docker-publish`][docker-publish], [`docker-publish-gh`][docker-publish-gh], [`docker-publish-hub`][docker-publish-hub] |
| Go | [`go-build`][go-build], [`go-lint`][go-lint], [`go-version-detect`][go-version-detect] |
| HCL | [`terraform-lint-fix`][terraform-lint-fix] |
| JSON | [`biome-check`][biome-check], [`biome-fix`][biome-fix], [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
| JavaScript | [`biome-check`][biome-check], [`biome-fix`][biome-fix], [`eslint-check`][eslint-check], [`eslint-fix`][eslint-fix], [`node-setup`][node-setup], [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
| Laravel | [`php-laravel-phpunit`][php-laravel-phpunit] |
| Markdown | [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
| Node.js | [`node-setup`][node-setup], [`npm-publish`][npm-publish] |
| PHP | [`php-composer`][php-composer], [`php-laravel-phpunit`][php-laravel-phpunit], [`php-tests`][php-tests], [`php-version-detect`][php-version-detect] |
| Python | [`python-lint-fix`][python-lint-fix], [`python-version-detect`][python-version-detect], [`python-version-detect-v2`][python-version-detect-v2] |
| Terraform | [`terraform-lint-fix`][terraform-lint-fix] |
| TypeScript | [`biome-check`][biome-check], [`biome-fix`][biome-fix], [`eslint-check`][eslint-check], [`eslint-fix`][eslint-fix], [`node-setup`][node-setup], [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
| YAML | [`ansible-lint-fix`][ansible-lint-fix], [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
| npm | [`npm-publish`][npm-publish] |
| Language | Actions |
|:---------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| .NET | [`csharp-build`][csharp-build], [`csharp-lint-check`][csharp-lint-check], [`csharp-publish`][csharp-publish], [`language-version-detect`][language-version-detect] |
| Ansible | [`ansible-lint-fix`][ansible-lint-fix] |
| C# | [`codeql-analysis`][codeql-analysis], [`csharp-build`][csharp-build], [`csharp-lint-check`][csharp-lint-check], [`csharp-publish`][csharp-publish] |
| C++ | [`codeql-analysis`][codeql-analysis] |
| Conventional Commits | [`pr-lint`][pr-lint] |
| Docker | [`docker-build`][docker-build], [`docker-publish`][docker-publish] |
| GitHub | [`sync-labels`][sync-labels] |
| GitHub Actions | [`action-versioning`][action-versioning], [`release-monthly`][release-monthly], [`stale`][stale], [`validate-inputs`][validate-inputs] |
| Go | [`codeql-analysis`][codeql-analysis], [`go-build`][go-build], [`go-lint`][go-lint], [`language-version-detect`][language-version-detect] |
| HCL | [`terraform-lint-fix`][terraform-lint-fix] |
| Images | [`compress-images`][compress-images] |
| JPEG | [`compress-images`][compress-images] |
| JSON | [`biome-lint`][biome-lint], [`prettier-lint`][prettier-lint] |
| Java | [`codeql-analysis`][codeql-analysis] |
| JavaScript | [`biome-lint`][biome-lint], [`codeql-analysis`][codeql-analysis], [`eslint-lint`][eslint-lint], [`prettier-lint`][prettier-lint] |
| Laravel | [`php-tests`][php-tests] |
| Markdown | [`prettier-lint`][prettier-lint] |
| Multiple Languages | [`pre-commit`][pre-commit] |
| Node.js | [`language-version-detect`][language-version-detect], [`npm-publish`][npm-publish] |
| PHP | [`language-version-detect`][language-version-detect], [`php-tests`][php-tests] |
| PNG | [`compress-images`][compress-images] |
| Python | [`codeql-analysis`][codeql-analysis], [`language-version-detect`][language-version-detect], [`pre-commit`][pre-commit], [`python-lint-fix`][python-lint-fix] |
| Ruby | [`codeql-analysis`][codeql-analysis] |
| Terraform | [`terraform-lint-fix`][terraform-lint-fix] |
| TypeScript | [`biome-lint`][biome-lint], [`codeql-analysis`][codeql-analysis], [`eslint-lint`][eslint-lint], [`prettier-lint`][prettier-lint] |
| YAML | [`ansible-lint-fix`][ansible-lint-fix], [`prettier-lint`][prettier-lint], [`sync-labels`][sync-labels], [`validate-inputs`][validate-inputs] |
| npm | [`npm-publish`][npm-publish] |
### Action Usage
@@ -226,7 +197,7 @@ All actions can be used independently in your workflows:
```yaml
# Recommended: Use pinned refs for supply-chain security
- uses: ivuorinen/actions/action-name@2025-01-15 # Date-based tag
- uses: ivuorinen/actions/action-name@vYYYY-MM-DD # Date-based tag (example)
with:
# action-specific inputs
@@ -240,49 +211,32 @@ All actions can be used independently in your workflows:
<!-- Reference Links -->
[action-versioning]: action-versioning/README.md
[ansible-lint-fix]: ansible-lint-fix/README.md
[biome-check]: biome-check/README.md
[biome-fix]: biome-fix/README.md
[biome-lint]: biome-lint/README.md
[codeql-analysis]: codeql-analysis/README.md
[common-cache]: common-cache/README.md
[common-file-check]: common-file-check/README.md
[common-retry]: common-retry/README.md
[compress-images]: compress-images/README.md
[csharp-build]: csharp-build/README.md
[csharp-lint-check]: csharp-lint-check/README.md
[csharp-publish]: csharp-publish/README.md
[docker-build]: docker-build/README.md
[docker-publish]: docker-publish/README.md
[docker-publish-gh]: docker-publish-gh/README.md
[docker-publish-hub]: docker-publish-hub/README.md
[dotnet-version-detect]: dotnet-version-detect/README.md
[eslint-check]: eslint-check/README.md
[eslint-fix]: eslint-fix/README.md
[github-release]: github-release/README.md
[eslint-lint]: eslint-lint/README.md
[go-build]: go-build/README.md
[go-lint]: go-lint/README.md
[go-version-detect]: go-version-detect/README.md
[node-setup]: node-setup/README.md
[language-version-detect]: language-version-detect/README.md
[npm-publish]: npm-publish/README.md
[php-composer]: php-composer/README.md
[php-laravel-phpunit]: php-laravel-phpunit/README.md
[php-tests]: php-tests/README.md
[php-version-detect]: php-version-detect/README.md
[pr-lint]: pr-lint/README.md
[pre-commit]: pre-commit/README.md
[prettier-check]: prettier-check/README.md
[prettier-fix]: prettier-fix/README.md
[prettier-lint]: prettier-lint/README.md
[python-lint-fix]: python-lint-fix/README.md
[python-version-detect]: python-version-detect/README.md
[python-version-detect-v2]: python-version-detect-v2/README.md
[release-monthly]: release-monthly/README.md
[set-git-config]: set-git-config/README.md
[security-scan]: security-scan/README.md
[stale]: stale/README.md
[sync-labels]: sync-labels/README.md
[terraform-lint-fix]: terraform-lint-fix/README.md
[validate-inputs]: validate-inputs/README.md
[version-file-parser]: version-file-parser/README.md
[version-validator]: version-validator/README.md
---

View File

@@ -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,12 +226,12 @@ 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
- Optimized file hashing in common-cache
- Added secret masking to 7 critical actions (including docker-publish)
- Migrated from custom common-cache to official actions/cache
- Status: ✅ Complete
### Phase 3: Documentation & Policy (2024)

View File

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

View File

@@ -6,8 +6,8 @@ set -euo pipefail
# Source setup utilities
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
# shellcheck source=_tests/framework/setup.sh
# shellcheck disable=SC1091
source "${SCRIPT_DIR}/setup.sh"
# Action testing utilities
@@ -57,6 +57,28 @@ get_action_name() {
uv run "$script_dir/../shared/validation_core.py" --name "$action_file"
}
get_action_runs_using() {
local action_file="$1"
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
uv run "$script_dir/../shared/validation_core.py" --runs-using "$action_file"
}
# Check if an input is required in an action.yml file
is_input_required() {
local action_file="$1"
local input_name="$2"
local script_dir
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Get the 'required' property for the input
local required_status
required_status=$(uv run "$script_dir/../shared/validation_core.py" --property "$action_file" "$input_name" "required")
# Return 0 (success) if input is required, 1 (failure) if optional
[[ "$required_status" == "required" ]]
}
# Test input validation using Python validation module
test_input_validation() {
local action_dir="$1"
@@ -348,5 +370,5 @@ run_action_tests() {
}
# Export all functions
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name get_action_runs_using is_input_required
export -f test_input_validation test_action_outputs test_external_usage measure_action_time run_action_tests

View File

@@ -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 <<EOF
FROM alpine:3.19
RUN apk add --no-cache bash
COPY test.sh /test.sh
RUN chmod +x /test.sh
CMD ["/test.sh"]
EOF
cat > test.sh <<EOF
#!/bin/bash
echo "Test container is running"
EOF
- name: Test docker-build action
id: build
uses: ./docker-build
with:
image-name: 'test-image'
tag: 'test-tag'
dockerfile: './Dockerfile'
context: '.'
platforms: 'linux/amd64'
push: 'false'
scan-image: 'false'
- name: Validate build outputs
run: |
echo "Build outputs:"
echo " Image Digest: ${{ steps.build.outputs.image-digest }}"
echo " Build Time: ${{ steps.build.outputs.build-time }}"
echo " Platforms: ${{ steps.build.outputs.platforms }}"
# Validate that we got a digest
if [[ -z "${{ steps.build.outputs.image-digest }}" ]]; then
echo "❌ ERROR: No image digest output"
exit 1
fi
# Validate digest format (sha256:...)
if ! echo "${{ steps.build.outputs.image-digest }}" | grep -E '^sha256:[a-f0-9]{64}'; then
echo "❌ ERROR: Invalid digest format: ${{ steps.build.outputs.image-digest }}"
exit 1
fi
echo "✅ Docker build validation passed"
test-docker-inputs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create test Dockerfile
run: |
cat > Dockerfile <<EOF
FROM alpine:3.19
CMD ["echo", "test"]
EOF
- name: Test with build-args
id: build-with-args
uses: ./docker-build
with:
image-name: 'test-build-args'
tag: 'latest'
dockerfile: './Dockerfile'
context: '.'
build-args: |
ARG1=value1
ARG2=value2
platforms: 'linux/amd64'
push: 'false'
scan-image: 'false'
- name: Validate build-args handling
run: |
if [[ -z "${{ steps.build-with-args.outputs.image-digest }}" ]]; then
echo "❌ ERROR: Build with build-args failed"
exit 1
fi
echo "✅ Build-args handling validated"
test-platform-detection:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create test Dockerfile
run: |
cat > Dockerfile <<EOF
FROM alpine:3.19
CMD ["echo", "multi-platform test"]
EOF
- name: Test multi-platform build
id: multi-platform
uses: ./docker-build
with:
image-name: 'test-multiplatform'
tag: 'latest'
dockerfile: './Dockerfile'
context: '.'
platforms: 'linux/amd64,linux/arm64'
push: 'false'
scan-image: 'false'
- name: Validate platform matrix output
run: |
echo "Platform Matrix: ${{ steps.multi-platform.outputs.platform-matrix }}"
# Check that we got platform results
if [[ -z "${{ steps.multi-platform.outputs.platform-matrix }}" ]]; then
echo "⚠️ WARNING: No platform matrix output (may be expected for local builds)"
else
echo "✅ Platform matrix generated"
fi
echo "✅ Multi-platform build validated"
test-input-validation:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test invalid tag format
id: invalid-tag
uses: ./docker-build
with:
image-name: 'test-image'
tag: 'INVALID TAG WITH SPACES'
dockerfile: './Dockerfile'
context: '.'
platforms: 'linux/amd64'
push: 'false'
continue-on-error: true
- name: Validate tag validation
run: |
if [ "${{ steps.invalid-tag.outcome }}" != "failure" ]; then
echo "❌ ERROR: Invalid tag should have failed validation"
exit 1
fi
echo "✅ Tag validation works correctly"
- name: Test invalid image name
id: invalid-image
uses: ./docker-build
with:
image-name: 'UPPERCASE_NOT_ALLOWED'
tag: 'latest'
dockerfile: './Dockerfile'
context: '.'
platforms: 'linux/amd64'
push: 'false'
continue-on-error: true
- name: Validate image name validation
run: |
if [ "${{ steps.invalid-image.outcome }}" != "failure" ]; then
echo "❌ ERROR: Invalid image name should have failed validation"
exit 1
fi
echo "✅ Image name validation works correctly"

View File

@@ -0,0 +1,321 @@
---
name: Test Lint & Fix Action Chains
on:
workflow_dispatch:
push:
paths:
- 'eslint-lint/**'
- 'prettier-lint/**'
- 'node-setup/**'
- '_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 <<EOF
{
"name": "test-project",
"version": "1.0.0",
"devDependencies": {
"eslint": "^8.0.0"
}
}
EOF
# Create .eslintrc.json
cat > test-project/.eslintrc.json <<EOF
{
"env": {
"node": true,
"es2021": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12
},
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "single"]
}
}
EOF
# Create test file with linting issues
cat > test-project/src/test.js <<EOF
const x = "double quotes"
console.log(x)
EOF
- name: Setup Node.js
uses: ./node-setup
with:
node-version: '18'
working-directory: './test-project'
- name: Test eslint-lint check mode (should find errors)
id: eslint-check
uses: ./eslint-lint
with:
mode: 'check'
working-directory: './test-project'
continue-on-error: true
- name: Validate eslint-lint check found issues
run: |
echo "ESLint check outcome: ${{ steps.eslint-check.outcome }}"
echo "Error count: ${{ steps.eslint-check.outputs.error-count }}"
echo "Warning count: ${{ steps.eslint-check.outputs.warning-count }}"
# Check should fail or find issues
if [[ "${{ steps.eslint-check.outcome }}" == "success" ]]; then
if [[ "${{ steps.eslint-check.outputs.error-count }}" == "0" ]]; then
echo "⚠️ WARNING: Expected to find linting errors but found none"
fi
fi
echo "✅ ESLint check validated"
- name: Test eslint-lint fix mode (should fix issues)
id: eslint-fix
uses: ./eslint-lint
with:
mode: 'fix'
working-directory: './test-project'
token: ${{ github.token }}
email: 'test@example.com'
username: 'test-user'
- name: Validate eslint-lint fix ran
run: |
echo "Errors fixed: ${{ steps.eslint-fix.outputs.errors-fixed }}"
echo "Files changed: ${{ steps.eslint-fix.outputs.files-changed }}"
# Check that fixes were attempted
if [[ -n "${{ steps.eslint-fix.outputs.errors-fixed }}" ]]; then
echo "✅ ESLint fixed ${{ steps.eslint-fix.outputs.errors-fixed }} issues"
else
echo "⚠️ No fix count reported (may be expected if no fixable issues)"
fi
echo "✅ ESLint fix validated"
test-prettier-chain:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create test files for Prettier
run: |
mkdir -p test-prettier
# Create package.json
cat > test-prettier/package.json <<EOF
{
"name": "test-prettier",
"version": "1.0.0",
"devDependencies": {
"prettier": "^3.0.0"
}
}
EOF
# Create .prettierrc
cat > test-prettier/.prettierrc <<EOF
{
"semi": true,
"singleQuote": true,
"printWidth": 80
}
EOF
# Create badly formatted file
cat > test-prettier/test.js <<EOF
const x={"key":"value","another":"data"}
console.log(x)
EOF
# Create badly formatted JSON
cat > test-prettier/test.json <<EOF
{"key":"value","nested":{"data":"here"}}
EOF
- name: Setup Node.js for Prettier
uses: ./node-setup
with:
node-version: '18'
working-directory: './test-prettier'
- name: Test prettier-lint check mode (should find issues)
id: prettier-check
uses: ./prettier-lint
with:
mode: 'check'
working-directory: './test-prettier'
continue-on-error: true
- name: Validate prettier-check found issues
run: |
echo "Prettier check outcome: ${{ steps.prettier-check.outcome }}"
# Check should find formatting issues
if [[ "${{ steps.prettier-check.outcome }}" == "failure" ]]; then
echo "✅ Prettier correctly found formatting issues"
else
echo "⚠️ WARNING: Expected Prettier to find formatting issues"
fi
- name: Test prettier-lint fix mode (should fix issues)
id: prettier-fix
uses: ./prettier-lint
with:
mode: 'fix'
working-directory: './test-prettier'
token: ${{ github.token }}
email: 'test@example.com'
username: 'test-user'
- name: Validate prettier-lint fix ran
run: |
echo "Prettier fix completed"
# Check that files exist and have been processed
if [[ -f "test-prettier/test.js" ]]; then
echo "✅ Test file exists after Prettier fix"
else
echo "❌ ERROR: Test file missing after Prettier fix"
exit 1
fi
echo "✅ Prettier fix validated"
test-action-chain-integration:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create comprehensive test project
run: |
mkdir -p test-chain/src
# Create package.json with both ESLint and Prettier
cat > test-chain/package.json <<EOF
{
"name": "test-chain",
"version": "1.0.0",
"devDependencies": {
"eslint": "^8.0.0",
"prettier": "^3.0.0"
}
}
EOF
# Create .eslintrc.json
cat > test-chain/.eslintrc.json <<EOF
{
"env": {
"node": true,
"es2021": true
},
"extends": "eslint:recommended",
"parserOptions": {
"ecmaVersion": 12
},
"rules": {
"semi": ["error", "always"],
"quotes": ["error", "single"]
}
}
EOF
# Create .prettierrc
cat > test-chain/.prettierrc <<EOF
{
"semi": true,
"singleQuote": true,
"printWidth": 80
}
EOF
# Create test file with both linting and formatting issues
cat > test-chain/src/app.js <<EOF
const message="hello world"
function greet(){console.log(message)}
greet()
EOF
- name: Setup Node.js
uses: ./node-setup
with:
node-version: '18'
working-directory: './test-chain'
- name: Run ESLint check
id: lint-check
uses: ./eslint-lint
with:
mode: 'check'
working-directory: './test-chain'
continue-on-error: true
- name: Run Prettier check
id: format-check
uses: ./prettier-lint
with:
mode: 'check'
working-directory: './test-chain'
continue-on-error: true
- name: Run ESLint fix
id: lint-fix
uses: ./eslint-lint
with:
mode: 'fix'
working-directory: './test-chain'
token: ${{ github.token }}
email: 'test@example.com'
username: 'test-user'
- name: Run Prettier fix
id: format-fix
uses: ./prettier-lint
with:
mode: 'fix'
working-directory: './test-chain'
token: ${{ github.token }}
email: 'test@example.com'
username: 'test-user'
- name: Validate complete chain
run: |
echo "=== Action Chain Results ==="
echo "Lint Check: ${{ steps.lint-check.outcome }}"
echo "Format Check: ${{ steps.format-check.outcome }}"
echo "Lint Fix: ${{ steps.lint-fix.outcome }}"
echo "Format Fix: ${{ steps.format-fix.outcome }}"
# Validate that all steps ran
steps_run=0
[[ "${{ steps.lint-check.outcome }}" != "skipped" ]] && ((steps_run++))
[[ "${{ steps.format-check.outcome }}" != "skipped" ]] && ((steps_run++))
[[ "${{ steps.lint-fix.outcome }}" != "skipped" ]] && ((steps_run++))
[[ "${{ steps.format-fix.outcome }}" != "skipped" ]] && ((steps_run++))
if [[ $steps_run -eq 4 ]]; then
echo "✅ Complete action chain executed successfully"
else
echo "❌ ERROR: Not all steps in chain executed (ran: $steps_run/4)"
exit 1
fi
echo "✅ Action chain integration validated"

View File

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

View File

@@ -0,0 +1,434 @@
---
name: Integration Test - Pre-commit
on:
workflow_dispatch:
push:
paths:
- 'pre-commit/**'
- '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!"

View File

@@ -1,241 +0,0 @@
---
name: Test version-file-parser Integration
on:
workflow_dispatch:
push:
paths:
- 'version-file-parser/**'
- '_tests/integration/workflows/version-file-parser-test.yml'
jobs:
test-version-file-parser:
runs-on: ubuntu-latest
strategy:
matrix:
test-case:
- name: 'Node.js project'
language: 'node'
tool-versions-key: 'nodejs'
dockerfile-image: 'node'
expected-version: '18.0.0'
setup-files: |
echo "18.17.0" > .nvmrc
cat > package.json <<EOF
{
"name": "test-project",
"engines": { "node": ">=18.0.0" }
}
EOF
touch package-lock.json
- name: 'PHP project'
language: 'php'
tool-versions-key: 'php'
dockerfile-image: 'php'
expected-version: '8.1'
setup-files: |
cat > composer.json <<EOF
{
"require": { "php": "^8.1" }
}
EOF
- name: 'Python project'
language: 'python'
tool-versions-key: 'python'
dockerfile-image: 'python'
expected-version: '3.9'
setup-files: |
echo "3.9.0" > .python-version
cat > pyproject.toml <<EOF
[tool.poetry.dependencies]
python = "^3.9"
EOF
- name: 'Go project'
language: 'go'
tool-versions-key: 'golang'
dockerfile-image: 'golang'
expected-version: '1.21'
setup-files: |
cat > go.mod <<EOF
module test-project
go 1.21
EOF
- name: '.tool-versions file'
language: 'node'
tool-versions-key: 'nodejs'
dockerfile-image: 'node'
expected-version: '18.16.0'
setup-files: |
echo "nodejs 18.16.0" > .tool-versions
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Clean up test files from previous runs
run: |
rm -f .nvmrc package.json package-lock.json composer.json .python-version pyproject.toml go.mod .tool-versions
- name: Setup test files
run: ${{ matrix.test-case.setup-files }}
- name: Test version-file-parser
id: test-action
uses: ./version-file-parser
with:
language: ${{ matrix.test-case.language }}
tool-versions-key: ${{ matrix.test-case.tool-versions-key }}
dockerfile-image: ${{ matrix.test-case.dockerfile-image }}
default-version: '1.0.0'
- name: Validate outputs
run: |
echo "Test case: ${{ matrix.test-case.name }}"
echo "Expected version: ${{ matrix.test-case.expected-version }}"
echo "Detected version: ${{ steps.test-action.outputs.detected-version }}"
echo "Package manager: ${{ steps.test-action.outputs.package-manager }}"
# Validate that we got some version
if [[ -z "${{ steps.test-action.outputs.detected-version }}" ]]; then
echo "❌ ERROR: No version detected"
exit 1
fi
# Validate version format (basic semver check)
if ! echo "${{ steps.test-action.outputs.detected-version }}" | grep -E '^[0-9]+\.[0-9]+(\.[0-9]+)?'; then
echo "❌ ERROR: Invalid version format: ${{ steps.test-action.outputs.detected-version }}"
exit 1
fi
# Validate detected version matches expected version (not the fallback)
if [[ "${{ steps.test-action.outputs.detected-version }}" != "${{ matrix.test-case.expected-version }}" ]]; then
echo "❌ ERROR: Version mismatch"
echo "Expected: ${{ matrix.test-case.expected-version }}"
echo "Got: ${{ steps.test-action.outputs.detected-version }}"
exit 1
fi
echo "✅ Version validation passed"
# Skip external reference test in local/CI environment to avoid auth issues
- name: Test external reference (info only)
run: |
echo "External reference test would use: ivuorinen/actions/version-file-parser@main"
echo "Skipping to avoid authentication issues in local testing"
test-edge-cases:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Clean up test files from previous runs
run: |
rm -f .nvmrc package.json package-lock.json composer.json .python-version pyproject.toml go.mod .tool-versions
- name: Setup test files (package.json engines)
shell: bash
run: |
set -Eeuo pipefail
cat > package.json <<'EOF'
{
"name": "edge-case",
"engines": { "node": ">=18.0.0" }
}
EOF
echo "18.17.0" > .nvmrc
- name: Test version detection from existing files
id: existing-version
uses: ./version-file-parser
with:
language: 'node'
tool-versions-key: 'nodejs'
dockerfile-image: 'node'
default-version: '20.0.0'
- name: Validate existing version detection
run: |
# The action detects Node.js version from package.json engines field
# package.json >=18.0.0 is parsed as 18.0.0
# Note: .nvmrc exists but package.json takes precedence in this implementation
expected_version="18.0.0"
detected_version="${{ steps.existing-version.outputs.detected-version }}"
if [[ "$detected_version" != "$expected_version" ]]; then
echo "❌ ERROR: Version mismatch"
echo "Expected: $expected_version"
echo "Got: $detected_version"
exit 1
fi
echo "✅ Existing version detection works correctly"
- name: Clean up before invalid regex test
run: |
rm -f .nvmrc package.json package-lock.json
- name: Test with invalid regex
id: invalid-regex
uses: ./version-file-parser
with:
language: 'node'
tool-versions-key: 'nodejs'
dockerfile-image: 'node'
validation-regex: 'invalid[regex'
default-version: '18.0.0'
continue-on-error: true
- name: Validate regex error handling
run: |
echo "Testing regex error handling completed"
# Action should handle invalid regex gracefully
if [ "${{ steps.invalid-regex.outcome }}" != "failure" ]; then
echo "::error::Expected invalid-regex step to fail, but it was: ${{ steps.invalid-regex.outcome }}"
exit 1
fi
echo "✅ Invalid regex properly failed as expected"
test-dockerfile-parsing:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Clean up test files from previous runs
run: |
rm -f .nvmrc package.json package-lock.json composer.json .python-version pyproject.toml go.mod .tool-versions Dockerfile
- name: Create Dockerfile with Node.js
run: |
cat > Dockerfile <<EOF
FROM node:18.17.0-alpine
WORKDIR /app
COPY . .
EOF
- name: Test Dockerfile parsing
id: dockerfile-test
uses: ./version-file-parser
with:
language: 'node'
tool-versions-key: 'nodejs'
dockerfile-image: 'node'
- name: Validate Dockerfile parsing
run: |
expected_version="18.17.0"
detected_version="${{ steps.dockerfile-test.outputs.dockerfile-version }}"
echo "Expected version: $expected_version"
echo "Detected version: $detected_version"
if [[ "$detected_version" != "$expected_version" ]]; then
echo "❌ ERROR: Version mismatch"
echo "Expected: $expected_version"
echo "Got: $detected_version"
exit 1
fi
echo "✅ Dockerfile parsing successful"

View File

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

View File

@@ -151,14 +151,14 @@ discover_actions() {
if [[ $action_name == *"$ACTION_FILTER"* ]]; then
actions+=("$action_name")
fi
done < <(find "${TEST_ROOT}/.." -mindepth 1 -maxdepth 1 -type d -name "*-*" | sort)
done < <(find "${TEST_ROOT}/.." -mindepth 2 -maxdepth 2 -type f -name "action.yml" -exec dirname {} \; | sort)
else
# All actions
while IFS= read -r action_dir; do
local action_name
action_name=$(basename "$action_dir")
actions+=("$action_name")
done < <(find "${TEST_ROOT}/.." -mindepth 1 -maxdepth 1 -type d -name "*-*" | sort)
done < <(find "${TEST_ROOT}/.." -mindepth 2 -maxdepth 2 -type f -name "action.yml" -exec dirname {} \; | sort)
fi
log_info "Discovered ${#actions[@]} actions to test: ${actions[*]}"
@@ -203,7 +203,7 @@ install_shellspec() {
# Pinned SHA256 checksum for ShellSpec 0.28.1
# Source: https://github.com/shellspec/shellspec/archive/refs/tags/0.28.1.tar.gz
local checksum="351e7a63b8df47c07b022c19d21a167b85693f5eb549fa96e64f64844b680024"
local checksum="400d835466429a5fe6c77a62775a9173729d61dd43e05dfa893e8cf6cb511783"
# Ensure cleanup of the downloaded file
# Use ${tarball:-} to handle unbound variable when trap fires after function returns

View File

@@ -521,6 +521,16 @@ class ActionFileParser:
except (OSError, ValueError, yaml.YAMLError, AttributeError):
return []
@staticmethod
def get_action_runs_using(action_file: str) -> str:
"""Get the runs.using value from an action.yml file."""
try:
data = ActionFileParser.load_action_file(action_file)
runs = data.get("runs", {})
return runs.get("using", "unknown")
except (OSError, ValueError, yaml.YAMLError, AttributeError):
return "unknown"
@staticmethod
def _get_required_property(input_data: dict, property_name: str) -> str:
"""Get the required/optional property."""
@@ -787,6 +797,11 @@ Examples:
mode_group.add_argument("--inputs", metavar="ACTION_FILE", help="List action inputs")
mode_group.add_argument("--outputs", metavar="ACTION_FILE", help="List action outputs")
mode_group.add_argument("--name", metavar="ACTION_FILE", help="Get action name")
mode_group.add_argument(
"--runs-using",
metavar="ACTION_FILE",
help="Get action runs.using value",
)
mode_group.add_argument(
"--validate-yaml",
metavar="YAML_FILE",
@@ -834,6 +849,12 @@ def _handle_name_command(args):
print(name)
def _handle_runs_using_command(args):
"""Handle the runs-using command."""
runs_using = ActionFileParser.get_action_runs_using(args.runs_using)
print(runs_using)
def _handle_validate_yaml_command(args):
"""Handle the validate-yaml command."""
try:
@@ -853,6 +874,7 @@ def _execute_command(args):
"inputs": _handle_inputs_command,
"outputs": _handle_outputs_command,
"name": _handle_name_command,
"runs_using": _handle_runs_using_command,
"validate_yaml": _handle_validate_yaml_command,
}

View File

@@ -0,0 +1,142 @@
#!/usr/bin/env shellspec
# Unit tests for action-versioning action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "action-versioning action"
ACTION_DIR="action-versioning"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating major-version input"
It "accepts valid year-based version (vYYYY)"
When call validate_input_python "action-versioning" "major-version" "v2025"
The status should be success
End
It "accepts valid semantic version (v1)"
When call validate_input_python "action-versioning" "major-version" "v1"
The status should be success
End
It "accepts valid semantic version (v2)"
When call validate_input_python "action-versioning" "major-version" "v2"
The status should be success
End
It "accepts year-based version from 2020"
When call validate_input_python "action-versioning" "major-version" "v2020"
The status should be success
End
It "accepts year-based version for 2030"
When call validate_input_python "action-versioning" "major-version" "v2030"
The status should be success
End
It "rejects version without v prefix"
When call validate_input_python "action-versioning" "major-version" "2025"
The status should be failure
End
It "rejects invalid version format"
When call validate_input_python "action-versioning" "major-version" "invalid"
The status should be failure
End
It "rejects empty version"
When call validate_input_python "action-versioning" "major-version" ""
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "action-versioning" "major-version" "v2025; rm -rf /"
The status should be failure
End
End
Context "when validating token input"
It "accepts valid GitHub token (classic)"
When call validate_input_python "action-versioning" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "accepts valid GitHub fine-grained token"
When call validate_input_python "action-versioning" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
The status should be success
End
It "accepts empty token (optional input)"
When call validate_input_python "action-versioning" "token" ""
The status should be success
End
It "rejects invalid token format"
When call validate_input_python "action-versioning" "token" "invalid-token"
The status should be failure
End
It "rejects token with command injection"
When call validate_input_python "action-versioning" "token" "ghp_123456789012345678901234567890123456; rm -rf /"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Action Versioning"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "major-version"
The output should include "token"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "updated"
The output should include "commit-sha"
The output should include "needs-annual-bump"
End
End
Context "when testing input requirements"
It "requires major-version input"
When call is_input_required "$ACTION_FILE" "major-version"
The status should be success
End
It "has token as optional input"
When call is_input_required "$ACTION_FILE" "token"
The status should be failure
End
End
Context "when testing security validations"
It "validates against path traversal in major-version"
When call validate_input_python "action-versioning" "major-version" "v../../etc"
The status should be failure
End
It "validates against shell metacharacters in major-version"
When call validate_input_python "action-versioning" "major-version" "v2025|echo"
The status should be failure
End
It "validates against command substitution in major-version"
When call validate_input_python "action-versioning" "major-version" "v\$(whoami)"
The status should be failure
End
It "validates against path traversal in token"
When call validate_input_python "action-versioning" "token" "../../../etc/passwd"
The status should be failure
End
End
End

View File

@@ -1,149 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for biome-check action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "biome-check action"
ACTION_DIR="biome-check"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating token input"
It "accepts personal access token"
When call validate_input_python "biome-check" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "accepts organization token"
When call validate_input_python "biome-check" "token" "gho_123456789012345678901234567890123456"
The status should be success
End
It "accepts user token"
When call validate_input_python "biome-check" "token" "ghu_123456789012345678901234567890123456"
The status should be success
End
It "accepts server token"
When call validate_input_python "biome-check" "token" "ghs_123456789012345678901234567890123456"
The status should be success
End
It "accepts refresh token"
When call validate_input_python "biome-check" "token" "ghr_123456789012345678901234567890123456"
The status should be success
End
End
Context "when validating email input"
It "accepts valid email"
When call validate_input_python "biome-check" "email" "test@example.com"
The status should be success
End
It "rejects invalid email without @"
When call validate_input_python "biome-check" "email" "testexample.com"
The status should be failure
End
It "rejects invalid email without domain"
When call validate_input_python "biome-check" "email" "test@"
The status should be failure
End
End
Context "when validating username input"
It "accepts valid username"
When call validate_input_python "biome-check" "username" "github-actions"
The status should be success
End
It "rejects semicolon injection"
When call validate_input_python "biome-check" "username" "user;rm -rf /"
The status should be failure
End
It "rejects ampersand injection"
When call validate_input_python "biome-check" "username" "user&&malicious"
The status should be failure
End
It "rejects pipe injection"
When call validate_input_python "biome-check" "username" "user|dangerous"
The status should be failure
End
It "rejects overly long username"
When call validate_input_python "biome-check" "username" "this-username-is-definitely-too-long-for-github-maximum-length-limit"
The status should be failure
End
End
Context "when validating max-retries input"
It "accepts valid retry count"
When call validate_input_python "biome-check" "max-retries" "5"
The status should be success
End
It "rejects zero retries"
When call validate_input_python "biome-check" "max-retries" "0"
The status should be failure
End
It "rejects negative retries"
When call validate_input_python "biome-check" "max-retries" "-1"
The status should be failure
End
It "rejects retries above limit"
When call validate_input_python "biome-check" "max-retries" "15"
The status should be failure
End
It "rejects non-numeric retries"
When call validate_input_python "biome-check" "max-retries" "invalid"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Biome Check"
End
It "defines expected inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "token"
The output should include "username"
The output should include "email"
The output should include "max-retries"
End
It "defines expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "check_status"
The output should include "errors_count"
The output should include "warnings_count"
End
End
Context "when validating security"
It "rejects command injection in token"
When call validate_input_python "biome-check" "token" "ghp_123;rm -rf /"
The status should be failure
End
It "rejects command injection in email"
When call validate_input_python "biome-check" "email" "user@domain.com;rm -rf /"
The status should be failure
End
It "validates all inputs for injection patterns"
When call validate_input_python "biome-check" "max-retries" "3;malicious"
The status should be failure
End
End
Context "when testing outputs"
It "produces all expected outputs consistently"
When call test_action_outputs "$ACTION_DIR" "token" "ghp_123456789012345678901234567890123456" "username" "github-actions" "email" "test@example.com" "max-retries" "3"
The status should be success
The stderr should include "Testing action outputs for: biome-check"
The stderr should include "Output test passed for: biome-check"
End
End
End

View File

@@ -1,148 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for biome-fix action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "biome-fix action"
ACTION_DIR="biome-fix"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating token input"
It "accepts personal access token"
When call validate_input_python "biome-fix" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "accepts organization token"
When call validate_input_python "biome-fix" "token" "gho_123456789012345678901234567890123456"
The status should be success
End
It "accepts user token"
When call validate_input_python "biome-fix" "token" "ghu_123456789012345678901234567890123456"
The status should be success
End
It "accepts server token"
When call validate_input_python "biome-fix" "token" "ghs_123456789012345678901234567890123456"
The status should be success
End
It "accepts refresh token"
When call validate_input_python "biome-fix" "token" "ghr_123456789012345678901234567890123456"
The status should be success
End
End
Context "when validating email input"
It "accepts valid email"
When call validate_input_python "biome-fix" "email" "test@example.com"
The status should be success
End
It "rejects invalid email without @"
When call validate_input_python "biome-fix" "email" "testexample.com"
The status should be failure
End
It "rejects invalid email without domain"
When call validate_input_python "biome-fix" "email" "test@"
The status should be failure
End
End
Context "when validating username input"
It "accepts valid username"
When call validate_input_python "biome-fix" "username" "github-actions"
The status should be success
End
It "rejects semicolon injection"
When call validate_input_python "biome-fix" "username" "user;rm -rf /"
The status should be failure
End
It "rejects ampersand injection"
When call validate_input_python "biome-fix" "username" "user&&malicious"
The status should be failure
End
It "rejects pipe injection"
When call validate_input_python "biome-fix" "username" "user|dangerous"
The status should be failure
End
It "rejects overly long username"
When call validate_input_python "biome-fix" "username" "this-username-is-definitely-too-long-for-github-maximum-length-limit"
The status should be failure
End
End
Context "when validating max-retries input"
It "accepts valid retry count"
When call validate_input_python "biome-fix" "max-retries" "5"
The status should be success
End
It "rejects zero retries"
When call validate_input_python "biome-fix" "max-retries" "0"
The status should be failure
End
It "rejects negative retries"
When call validate_input_python "biome-fix" "max-retries" "-1"
The status should be failure
End
It "rejects retries above limit"
When call validate_input_python "biome-fix" "max-retries" "15"
The status should be failure
End
It "rejects non-numeric retries"
When call validate_input_python "biome-fix" "max-retries" "invalid"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Biome Fix"
End
It "defines expected inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "token"
The output should include "username"
The output should include "email"
The output should include "max-retries"
End
It "defines expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "files_changed"
The output should include "fix_status"
End
End
Context "when validating security"
It "rejects command injection in token"
When call validate_input_python "biome-fix" "token" "ghp_123;rm -rf /"
The status should be failure
End
It "rejects command injection in email"
When call validate_input_python "biome-fix" "email" "user@domain.com;rm -rf /"
The status should be failure
End
It "validates all inputs for injection patterns"
When call validate_input_python "biome-fix" "max-retries" "3;malicious"
The status should be failure
End
End
Context "when testing outputs"
It "produces all expected outputs consistently"
When call test_action_outputs "$ACTION_DIR" "token" "ghp_123456789012345678901234567890123456" "username" "github-actions" "email" "test@example.com" "max-retries" "3"
The status should be success
The stderr should include "Testing action outputs for: biome-fix"
The stderr should include "Output test passed for: biome-fix"
End
End
End

View File

@@ -0,0 +1,230 @@
#!/usr/bin/env shellspec
# Unit tests for biome-lint action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "biome-lint action"
ACTION_DIR="biome-lint"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating mode input"
It "accepts check mode"
When call validate_input_python "biome-lint" "mode" "check"
The status should be success
End
It "accepts fix mode"
When call validate_input_python "biome-lint" "mode" "fix"
The status should be success
End
It "accepts empty mode (uses default)"
When call validate_input_python "biome-lint" "mode" ""
The status should be success
End
It "rejects invalid mode"
When call validate_input_python "biome-lint" "mode" "invalid"
The status should be failure
End
It "rejects mode with command injection"
When call validate_input_python "biome-lint" "mode" "check; rm -rf /"
The status should be failure
End
End
Context "when validating token input"
It "accepts valid GitHub token (classic)"
When call validate_input_python "biome-lint" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "accepts valid GitHub fine-grained token"
When call validate_input_python "biome-lint" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
The status should be success
End
It "accepts empty token (optional)"
When call validate_input_python "biome-lint" "token" ""
The status should be success
End
It "rejects invalid token format"
When call validate_input_python "biome-lint" "token" "invalid-token"
The status should be failure
End
It "rejects token with command injection"
When call validate_input_python "biome-lint" "token" "ghp_123456789012345678901234567890123456; echo"
The status should be failure
End
End
Context "when validating username input"
It "accepts valid username"
When call validate_input_python "biome-lint" "username" "github-actions"
The status should be success
End
It "accepts username with hyphens"
When call validate_input_python "biome-lint" "username" "my-bot-user"
The status should be success
End
It "accepts empty username (uses default)"
When call validate_input_python "biome-lint" "username" ""
The status should be success
End
It "rejects username with command injection"
When call validate_input_python "biome-lint" "username" "user; rm -rf /"
The status should be failure
End
End
Context "when validating email input"
It "accepts valid email"
When call validate_input_python "biome-lint" "email" "github-actions@github.com"
The status should be success
End
It "accepts email with plus sign"
When call validate_input_python "biome-lint" "email" "user+bot@example.com"
The status should be success
End
It "accepts email with subdomain"
When call validate_input_python "biome-lint" "email" "bot@ci.example.com"
The status should be success
End
It "accepts empty email (uses default)"
When call validate_input_python "biome-lint" "email" ""
The status should be success
End
It "rejects invalid email format"
When call validate_input_python "biome-lint" "email" "not-an-email"
The status should be failure
End
It "rejects email with command injection"
When call validate_input_python "biome-lint" "email" "user@example.com; rm -rf /"
The status should be failure
End
End
Context "when validating max-retries input"
It "accepts valid retry count (default)"
When call validate_input_python "biome-lint" "max-retries" "3"
The status should be success
End
It "accepts retry count of 1"
When call validate_input_python "biome-lint" "max-retries" "1"
The status should be success
End
It "accepts retry count of 10"
When call validate_input_python "biome-lint" "max-retries" "10"
The status should be success
End
It "accepts empty max-retries (uses default)"
When call validate_input_python "biome-lint" "max-retries" ""
The status should be success
End
It "rejects negative retry count"
When call validate_input_python "biome-lint" "max-retries" "-1"
The status should be failure
End
It "rejects non-numeric retry count"
When call validate_input_python "biome-lint" "max-retries" "abc"
The status should be failure
End
It "rejects retry count with command injection"
When call validate_input_python "biome-lint" "max-retries" "3; echo"
The status should be failure
End
End
Context "when validating fail-on-error input"
It "accepts true"
When call validate_input_python "biome-lint" "fail-on-error" "true"
The status should be success
End
It "accepts false"
When call validate_input_python "biome-lint" "fail-on-error" "false"
The status should be success
End
It "accepts empty (uses default)"
When call validate_input_python "biome-lint" "fail-on-error" ""
The status should be success
End
It "rejects invalid boolean value"
When call validate_input_python "biome-lint" "fail-on-error" "maybe"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Biome Lint"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "mode"
The output should include "token"
The output should include "username"
The output should include "email"
The output should include "max-retries"
The output should include "fail-on-error"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "status"
The output should include "errors_count"
The output should include "warnings_count"
The output should include "files_changed"
End
End
Context "when testing input requirements"
It "has all inputs as optional (with defaults)"
When call is_input_required "$ACTION_FILE" "mode"
The status should be failure
End
End
Context "when testing security validations"
It "validates against path traversal"
When call validate_input_python "biome-lint" "username" "../../../etc"
The status should be failure
End
It "validates against shell metacharacters"
When call validate_input_python "biome-lint" "email" "user@example.com|echo"
The status should be failure
End
It "validates against command substitution"
When call validate_input_python "biome-lint" "username" "\$(whoami)"
The status should be failure
End
End
End

View File

@@ -1,168 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for common-cache action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "common-cache action"
ACTION_DIR="common-cache"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating cache type input"
It "accepts npm cache type"
When call validate_input_python "common-cache" "type" "npm"
The status should be success
End
It "accepts composer cache type"
When call validate_input_python "common-cache" "type" "composer"
The status should be success
End
It "accepts go cache type"
When call validate_input_python "common-cache" "type" "go"
The status should be success
End
It "accepts pip cache type"
When call validate_input_python "common-cache" "type" "pip"
The status should be success
End
It "accepts maven cache type"
When call validate_input_python "common-cache" "type" "maven"
The status should be success
End
It "accepts gradle cache type"
When call validate_input_python "common-cache" "type" "gradle"
The status should be success
End
It "rejects empty cache type"
When call validate_input_python "common-cache" "type" ""
The status should be failure
End
It "rejects invalid cache type"
Pending "TODO: Implement enum validation for cache type"
When call validate_input_python "common-cache" "type" "invalid-type"
The status should be failure
End
End
Context "when validating paths input"
It "accepts single path"
When call validate_input_python "common-cache" "paths" "node_modules"
The status should be success
End
It "accepts multiple paths"
When call validate_input_python "common-cache" "paths" "node_modules,dist,build"
The status should be success
End
It "rejects empty paths"
When call validate_input_python "common-cache" "paths" ""
The status should be failure
End
It "rejects path traversal"
When call validate_input_python "common-cache" "paths" "../../../etc/passwd"
The status should be failure
End
It "rejects command injection in paths"
When call validate_input_python "common-cache" "paths" "node_modules;rm -rf /"
The status should be failure
End
End
Context "when validating key-prefix input"
It "accepts valid key prefix"
When call validate_input_python "common-cache" "key-prefix" "v2-build"
The status should be success
End
It "rejects command injection in key-prefix"
When call validate_input_python "common-cache" "key-prefix" "v2&&malicious"
The status should be failure
End
End
Context "when validating key-files input"
It "accepts single key file"
When call validate_input_python "common-cache" "key-files" "package.json"
The status should be success
End
It "accepts multiple key files"
When call validate_input_python "common-cache" "key-files" "package.json,package-lock.json,yarn.lock"
The status should be success
End
It "rejects path traversal in key-files"
When call validate_input_python "common-cache" "key-files" "../../../sensitive.json"
The status should be failure
End
End
Context "when validating restore-keys input"
It "accepts valid restore keys format"
When call validate_input_python "common-cache" "restore-keys" "Linux-npm-,Linux-"
The status should be success
End
It "rejects malicious restore keys"
When call validate_input_python "common-cache" "restore-keys" "Linux-npm-;rm -rf /"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Common Cache"
End
It "defines required inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "type"
The output should include "paths"
End
It "defines optional inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "key-prefix"
The output should include "key-files"
The output should include "restore-keys"
The output should include "env-vars"
End
It "defines expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "cache-hit"
The output should include "cache-key"
The output should include "cache-paths"
End
End
Context "when validating security"
It "rejects injection in all input types"
When call validate_input_python "common-cache" "type" "npm;malicious"
The status should be failure
End
It "validates environment variable names safely"
When call validate_input_python "common-cache" "env-vars" "NODE_ENV,CI"
The status should be success
End
It "rejects injection in environment variables"
When call validate_input_python "common-cache" "env-vars" "NODE_ENV;rm -rf /"
The status should be failure
End
End
Context "when testing outputs"
It "produces all expected outputs consistently"
When call test_action_outputs "$ACTION_DIR" "type" "npm" "paths" "node_modules"
The status should be success
The stderr should include "Testing action outputs for: common-cache"
The stderr should include "Output test passed for: common-cache"
End
End
End

View File

@@ -1,99 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for common-file-check action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "common-file-check action"
ACTION_DIR="common-file-check"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating file-pattern input"
It "accepts simple file pattern"
When call validate_input_python "common-file-check" "file-pattern" "package.json"
The status should be success
End
It "accepts glob pattern with wildcard"
When call validate_input_python "common-file-check" "file-pattern" "*.json"
The status should be success
End
It "accepts glob pattern with question mark"
When call validate_input_python "common-file-check" "file-pattern" "test?.js"
The status should be success
End
It "accepts nested path pattern"
When call validate_input_python "common-file-check" "file-pattern" "src/**/*.ts"
The status should be success
End
It "accepts pattern with braces"
When call validate_input_python "common-file-check" "file-pattern" "*.{js,ts}"
The status should be success
End
It "accepts pattern with brackets"
When call validate_input_python "common-file-check" "file-pattern" "[A-Z]*.txt"
The status should be success
End
It "rejects empty file pattern"
When call validate_input_python "common-file-check" "file-pattern" ""
The status should be failure
End
It "rejects path traversal"
When call validate_input_python "common-file-check" "file-pattern" "../../../etc/passwd"
The status should be failure
End
It "rejects command injection"
When call validate_input_python "common-file-check" "file-pattern" "*.json;rm -rf /"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Common File Check"
End
It "defines expected inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "file-pattern"
End
It "defines expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "found"
End
End
Context "when validating security"
It "validates glob patterns safely"
When call validate_input_python "common-file-check" "file-pattern" "**/*.{js,ts,json}"
The status should be success
End
It "rejects injection in glob patterns"
When call validate_input_python "common-file-check" "file-pattern" "*.js&&malicious"
The status should be failure
End
It "rejects pipe injection in patterns"
When call validate_input_python "common-file-check" "file-pattern" "*.js|dangerous"
The status should be failure
End
End
Context "when testing outputs"
It "produces all expected outputs consistently"
When call test_action_outputs "$ACTION_DIR" "file-pattern" "*.json"
The status should be success
The stderr should include "Testing action outputs for: common-file-check"
The stderr should include "Output test passed for: common-file-check"
End
End
End

View File

@@ -1,165 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for common-retry action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "common-retry action"
ACTION_DIR="common-retry"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating max-retries input"
It "accepts minimum value (1)"
When call validate_input_python "common-retry" "max-retries" "1"
The status should be success
End
It "accepts maximum value (10)"
When call validate_input_python "common-retry" "max-retries" "10"
The status should be success
End
It "rejects below minimum"
When call validate_input_python "common-retry" "max-retries" "0"
The status should be failure
End
It "rejects above maximum"
When call validate_input_python "common-retry" "max-retries" "11"
The status should be failure
End
It "rejects non-numeric"
When call validate_input_python "common-retry" "max-retries" "invalid"
The status should be failure
End
End
Context "when validating retry-delay input"
It "accepts minimum value (1)"
When call validate_input_python "common-retry" "retry-delay" "1"
The status should be success
End
It "accepts maximum value (300)"
When call validate_input_python "common-retry" "retry-delay" "300"
The status should be success
End
It "rejects below minimum"
When call validate_input_python "common-retry" "retry-delay" "0"
The status should be failure
End
It "rejects above maximum"
When call validate_input_python "common-retry" "retry-delay" "301"
The status should be failure
End
End
Context "when validating backoff-strategy input"
It "accepts linear strategy"
When call validate_input_python "common-retry" "backoff-strategy" "linear"
The status should be success
End
It "accepts exponential strategy"
When call validate_input_python "common-retry" "backoff-strategy" "exponential"
The status should be success
End
It "accepts fixed strategy"
When call validate_input_python "common-retry" "backoff-strategy" "fixed"
The status should be success
End
It "rejects invalid strategy"
When call validate_input_python "common-retry" "backoff-strategy" "invalid"
The status should be failure
End
End
Context "when validating timeout input"
It "accepts minimum value (1)"
When call validate_input_python "common-retry" "timeout" "1"
The status should be success
End
It "accepts maximum value (3600)"
When call validate_input_python "common-retry" "timeout" "3600"
The status should be success
End
It "rejects below minimum"
When call validate_input_python "common-retry" "timeout" "0"
The status should be failure
End
It "rejects above maximum"
When call validate_input_python "common-retry" "timeout" "3601"
The status should be failure
End
End
Context "when validating working-directory input"
It "accepts current directory"
When call validate_input_python "common-retry" "working-directory" "."
The status should be success
End
It "accepts relative path"
When call validate_input_python "common-retry" "working-directory" "src/app"
The status should be success
End
It "rejects path traversal"
When call validate_input_python "common-retry" "working-directory" "../../../etc"
The status should be failure
End
End
Context "when validating shell input"
It "accepts bash shell"
When call validate_input_python "common-retry" "shell" "bash"
The status should be success
End
It "accepts sh shell"
When call validate_input_python "common-retry" "shell" "sh"
The status should be success
End
It "rejects zsh shell"
When call validate_input_python "common-retry" "shell" "zsh"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Common Retry"
End
End
Context "when validating security"
It "rejects command injection with semicolon"
When call validate_input_python "common-retry" "command" "value; rm -rf /"
The status should be failure
End
It "rejects command injection with ampersand"
When call validate_input_python "common-retry" "command" "value && malicious"
The status should be failure
End
It "accepts valid success codes"
When call validate_input_python "common-retry" "success-codes" "0,1,2"
The status should be success
End
It "rejects success codes with injection"
When call validate_input_python "common-retry" "success-codes" "0;rm -rf /"
The status should be failure
End
It "accepts valid retry codes"
When call validate_input_python "common-retry" "retry-codes" "1,126,127"
The status should be success
End
It "rejects retry codes with injection"
When call validate_input_python "common-retry" "retry-codes" "1;rm -rf /"
The status should be failure
End
End
End

View File

@@ -1,40 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for docker-publish-gh action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "docker-publish-gh action"
ACTION_DIR="docker-publish-gh"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating inputs"
It "accepts valid image name"
When call validate_input_python "docker-publish-gh" "image-name" "myapp"
The status should be success
End
It "accepts valid GitHub token"
When call validate_input_python "docker-publish-gh" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "accepts valid tags"
When call validate_input_python "docker-publish-gh" "tags" "v1.0.0,latest"
The status should be success
End
It "rejects injection in token"
When call validate_input_python "docker-publish-gh" "token" "ghp_123;malicious"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should match pattern "*Docker*"
End
End
End

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for docker-publish-hub action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "docker-publish-hub action"
ACTION_DIR="docker-publish-hub"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating inputs"
It "accepts valid image name"
When call validate_input_python "docker-publish-hub" "image-name" "myapp"
The status should be success
End
It "accepts valid username"
When call validate_input_python "docker-publish-hub" "username" "dockeruser"
The status should be success
End
It "accepts valid password"
When call validate_input_python "docker-publish-hub" "password" "secretpassword123"
The status should be success
End
It "accepts valid tags"
When call validate_input_python "docker-publish-hub" "tags" "v1.0.0,latest"
The status should be success
End
It "rejects injection in username"
When call validate_input_python "docker-publish-hub" "username" "user;malicious"
The status should be failure
End
It "rejects injection in password"
When call validate_input_python "docker-publish-hub" "password" "pass;rm -rf /"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should match pattern "*Docker*"
End
End
End

View File

@@ -1,85 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for dotnet-version-detect action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "dotnet-version-detect action"
ACTION_DIR="dotnet-version-detect"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating default-version input"
It "accepts valid dotnet version"
When call validate_input_python "dotnet-version-detect" "default-version" "8.0"
The status should be success
End
It "accepts full semantic version"
When call validate_input_python "dotnet-version-detect" "default-version" "8.0.0"
The status should be success
End
It "accepts dotnet 6 version"
When call validate_input_python "dotnet-version-detect" "default-version" "6.0.0"
The status should be success
End
It "accepts dotnet 7 version"
When call validate_input_python "dotnet-version-detect" "default-version" "7.0.0"
The status should be success
End
It "rejects invalid version format"
When call validate_input_python "dotnet-version-detect" "default-version" "invalid"
The status should be failure
End
It "rejects version with leading zeros"
When call validate_input_python "dotnet-version-detect" "default-version" "08.0.0"
The status should be failure
End
It "rejects unsupported version"
When call validate_input_python "dotnet-version-detect" "default-version" "2.0.0"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Dotnet Version Detect"
End
It "defines expected inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "default-version"
End
It "defines expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "dotnet-version"
End
End
Context "when validating security"
It "rejects injection in version"
When call validate_input_python "dotnet-version-detect" "default-version" "8.0;malicious"
The status should be failure
End
It "validates version security"
When call validate_input_python "dotnet-version-detect" "default-version" "8.0&&malicious"
The status should be failure
End
End
Context "when testing outputs"
It "produces all expected outputs consistently"
When call test_action_outputs "$ACTION_DIR" "default-version" "8.0"
The status should be success
The stderr should include "Testing action outputs for: dotnet-version-detect"
The stderr should include "Output test passed for: dotnet-version-detect"
End
End
End

View File

@@ -1,355 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for eslint-check action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "eslint-check action"
ACTION_DIR="eslint-check"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating working-directory input"
It "accepts current directory"
When call validate_input_python "eslint-check" "working-directory" "."
The status should be success
End
It "accepts relative path"
When call validate_input_python "eslint-check" "working-directory" "src/frontend"
The status should be success
End
It "accepts nested directory"
When call validate_input_python "eslint-check" "working-directory" "packages/ui"
The status should be success
End
It "rejects path traversal"
When call validate_input_python "eslint-check" "working-directory" "../../../etc/passwd"
The status should be failure
End
It "rejects absolute paths"
When call validate_input_python "eslint-check" "working-directory" "/etc/passwd"
The status should be failure
End
It "rejects injection attempts"
When call validate_input_python "eslint-check" "working-directory" "src; rm -rf /"
The status should be failure
End
End
Context "when validating eslint-version input"
It "accepts latest version"
When call validate_input_python "eslint-check" "eslint-version" "latest"
The status should be success
End
It "accepts semantic version"
When call validate_input_python "eslint-check" "eslint-version" "8.57.0"
The status should be success
End
It "accepts version with prerelease"
When call validate_input_python "eslint-check" "eslint-version" "9.0.0-alpha.0"
The status should be success
End
It "accepts older stable version"
When call validate_input_python "eslint-check" "eslint-version" "7.32.0"
The status should be success
End
It "rejects invalid version format"
When call validate_input_python "eslint-check" "eslint-version" "8.57"
The status should be failure
End
It "rejects version with letters"
When call validate_input_python "eslint-check" "eslint-version" "8.57.0a"
The status should be failure
End
It "rejects empty version"
When call validate_input_python "eslint-check" "eslint-version" ""
The status should be failure
End
End
Context "when validating config-file input"
It "accepts default eslintrc"
When call validate_input_python "eslint-check" "config-file" ".eslintrc"
The status should be success
End
It "accepts eslintrc.json"
When call validate_input_python "eslint-check" "config-file" ".eslintrc.json"
The status should be success
End
It "accepts eslint.config.js"
When call validate_input_python "eslint-check" "config-file" "eslint.config.js"
The status should be success
End
It "accepts relative path config"
When call validate_input_python "eslint-check" "config-file" "config/eslint.json"
The status should be success
End
It "rejects path traversal"
When call validate_input_python "eslint-check" "config-file" "../../../malicious.js"
The status should be failure
End
It "rejects injection in config path"
When call validate_input_python "eslint-check" "config-file" "config.js;rm -rf /"
The status should be failure
End
End
Context "when validating ignore-file input"
It "accepts default eslintignore"
When call validate_input_python "eslint-check" "ignore-file" ".eslintignore"
The status should be success
End
It "accepts custom ignore file"
When call validate_input_python "eslint-check" "ignore-file" "eslint-ignore.txt"
The status should be success
End
It "accepts relative path ignore file"
When call validate_input_python "eslint-check" "ignore-file" "config/.eslintignore"
The status should be success
End
It "rejects path traversal"
When call validate_input_python "eslint-check" "ignore-file" "../../sensitive.txt"
The status should be failure
End
End
Context "when validating file-extensions input"
It "accepts default extensions"
When call validate_input_python "eslint-check" "file-extensions" ".js,.jsx,.ts,.tsx"
The status should be success
End
It "accepts single extension"
When call validate_input_python "eslint-check" "file-extensions" ".js"
The status should be success
End
It "accepts TypeScript extensions only"
When call validate_input_python "eslint-check" "file-extensions" ".ts,.tsx"
The status should be success
End
It "accepts Vue and JavaScript extensions"
When call validate_input_python "eslint-check" "file-extensions" ".js,.vue,.ts"
The status should be success
End
It "rejects extensions without dots"
When call validate_input_python "eslint-check" "file-extensions" "js,ts"
The status should be failure
End
It "rejects invalid extension format"
When call validate_input_python "eslint-check" "file-extensions" ".js;.ts"
The status should be failure
End
It "rejects extensions with special characters"
When call validate_input_python "eslint-check" "file-extensions" ".js,.t$"
The status should be failure
End
End
Context "when validating boolean inputs"
It "accepts cache as true"
When call validate_input_python "eslint-check" "cache" "true"
The status should be success
End
It "accepts cache as false"
When call validate_input_python "eslint-check" "cache" "false"
The status should be success
End
It "accepts fail-on-error as true"
When call validate_input_python "eslint-check" "fail-on-error" "true"
The status should be success
End
It "accepts fail-on-error as false"
When call validate_input_python "eslint-check" "fail-on-error" "false"
The status should be success
End
It "rejects invalid boolean value"
When call validate_input_python "eslint-check" "cache" "maybe"
The status should be failure
End
It "rejects numeric boolean"
When call validate_input_python "eslint-check" "fail-on-error" "1"
The status should be failure
End
End
Context "when validating numeric inputs"
It "accepts zero max-warnings"
When call validate_input_python "eslint-check" "max-warnings" "0"
The status should be success
End
It "accepts reasonable max-warnings"
When call validate_input_python "eslint-check" "max-warnings" "10"
The status should be success
End
It "accepts large max-warnings"
When call validate_input_python "eslint-check" "max-warnings" "1000"
The status should be success
End
It "accepts valid max-retries"
When call validate_input_python "eslint-check" "max-retries" "3"
The status should be success
End
It "accepts minimum retries"
When call validate_input_python "eslint-check" "max-retries" "1"
The status should be success
End
It "accepts maximum retries"
When call validate_input_python "eslint-check" "max-retries" "10"
The status should be success
End
It "rejects negative max-warnings"
When call validate_input_python "eslint-check" "max-warnings" "-1"
The status should be failure
End
It "rejects non-numeric max-warnings"
When call validate_input_python "eslint-check" "max-warnings" "many"
The status should be failure
End
It "rejects zero retries"
When call validate_input_python "eslint-check" "max-retries" "0"
The status should be failure
End
It "rejects retries above limit"
When call validate_input_python "eslint-check" "max-retries" "15"
The status should be failure
End
End
Context "when validating report-format input"
It "accepts stylish format"
When call validate_input_python "eslint-check" "report-format" "stylish"
The status should be success
End
It "accepts json format"
When call validate_input_python "eslint-check" "report-format" "json"
The status should be success
End
It "accepts sarif format"
When call validate_input_python "eslint-check" "report-format" "sarif"
The status should be success
End
It "accepts checkstyle format"
When call validate_input_python "eslint-check" "report-format" "checkstyle"
The status should be success
End
It "accepts compact format"
When call validate_input_python "eslint-check" "report-format" "compact"
The status should be success
End
It "accepts html format"
When call validate_input_python "eslint-check" "report-format" "html"
The status should be success
End
It "accepts junit format"
When call validate_input_python "eslint-check" "report-format" "junit"
The status should be success
End
It "accepts tap format"
When call validate_input_python "eslint-check" "report-format" "tap"
The status should be success
End
It "accepts unix format"
When call validate_input_python "eslint-check" "report-format" "unix"
The status should be success
End
It "rejects invalid format"
When call validate_input_python "eslint-check" "report-format" "invalid"
The status should be failure
End
It "rejects empty format"
When call validate_input_python "eslint-check" "report-format" ""
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "ESLint Check"
End
It "defines required inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "working-directory"
The output should include "eslint-version"
The output should include "max-retries"
End
It "defines optional inputs with defaults"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "config-file"
The output should include "ignore-file"
The output should include "file-extensions"
The output should include "cache"
The output should include "max-warnings"
The output should include "fail-on-error"
The output should include "report-format"
End
It "defines expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "error-count"
The output should include "warning-count"
The output should include "sarif-file"
The output should include "files-checked"
End
It "has composite run type"
When call grep -q "using: composite" "$ACTION_FILE"
The status should be success
End
It "includes input validation step"
When call grep -q "Validate Inputs" "$ACTION_FILE"
The status should be success
End
It "uses node-setup action"
When call grep -q "./node-setup" "$ACTION_FILE"
The status should be success
End
It "uses common-cache action"
When call grep -q "./common-cache" "$ACTION_FILE"
The status should be success
End
End
Context "when validating security"
It "validates input paths to prevent injection"
When call validate_input_python "eslint-check" "working-directory" "../../../etc"
The status should be failure
End
It "validates config file paths"
When call validate_input_python "eslint-check" "config-file" "../../malicious.js"
The status should be failure
End
It "sanitizes file extensions input"
When call validate_input_python "eslint-check" "file-extensions" ".js;rm -rf /"
The status should be failure
End
End
Context "when testing outputs"
It "produces all expected outputs"
When call test_action_outputs "$ACTION_DIR" "working-directory" "." "eslint-version" "latest" "max-retries" "3"
The status should be success
The stderr should include "Testing action outputs for: eslint-check"
The stderr should include "Output test passed for: eslint-check"
End
It "outputs consistent error and warning counts"
When call test_action_outputs "$ACTION_DIR" "max-warnings" "0" "report-format" "sarif"
The status should be success
The stderr should include "Testing action outputs for: eslint-check"
The stderr should include "Output test passed for: eslint-check"
End
End
End

View File

@@ -1,115 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for eslint-fix action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "eslint-fix action"
ACTION_DIR="eslint-fix"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating token input"
It "accepts valid GitHub token"
When call validate_input_python "eslint-fix" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "rejects injection in token"
When call validate_input_python "eslint-fix" "token" "token; rm -rf /"
The status should be failure
End
End
Context "when validating username input"
It "accepts valid username"
When call validate_input_python "eslint-fix" "username" "github-actions"
The status should be success
End
It "rejects injection in username"
When call validate_input_python "eslint-fix" "username" "user; rm -rf /"
The status should be failure
End
End
Context "when validating email input"
It "accepts valid email"
When call validate_input_python "eslint-fix" "email" "test@example.com"
The status should be success
End
It "rejects invalid email format"
When call validate_input_python "eslint-fix" "email" "invalid-email"
The status should be failure
End
End
Context "when validating numeric inputs"
It "accepts valid max-retries"
When call validate_input_python "eslint-fix" "max-retries" "3"
The status should be success
End
It "accepts minimum retries"
When call validate_input_python "eslint-fix" "max-retries" "1"
The status should be success
End
It "accepts maximum retries"
When call validate_input_python "eslint-fix" "max-retries" "10"
The status should be success
End
It "rejects zero retries"
When call validate_input_python "eslint-fix" "max-retries" "0"
The status should be failure
End
It "rejects retries above limit"
When call validate_input_python "eslint-fix" "max-retries" "15"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "ESLint Fix"
End
It "defines required inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "token"
The output should include "username"
The output should include "email"
The output should include "max-retries"
End
It "defines expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "files_changed"
The output should include "lint_status"
The output should include "errors_fixed"
End
End
Context "when validating security"
It "validates token format"
When call validate_input_python "eslint-fix" "token" "invalid-token;rm -rf /"
The status should be failure
End
It "validates email format"
When call validate_input_python "eslint-fix" "email" "invalid@email"
The status should be failure
End
End
Context "when testing outputs"
It "produces all expected outputs"
When call test_action_outputs "$ACTION_DIR" "token" "ghp_test" "username" "test" "email" "test@example.com" "max-retries" "3"
The status should be success
The stderr should include "Testing action outputs for: eslint-fix"
The stderr should include "Output test passed for: eslint-fix"
End
End
End

View File

@@ -0,0 +1,527 @@
#!/usr/bin/env shellspec
# Unit tests for eslint-lint action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "eslint-lint action"
ACTION_DIR="eslint-lint"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating mode input"
It "accepts check mode"
When call validate_input_python "eslint-lint" "mode" "check"
The status should be success
End
It "accepts fix mode"
When call validate_input_python "eslint-lint" "mode" "fix"
The status should be success
End
It "accepts empty mode (uses default)"
When call validate_input_python "eslint-lint" "mode" ""
The status should be success
End
It "rejects invalid mode"
When call validate_input_python "eslint-lint" "mode" "invalid"
The status should be failure
End
It "rejects mode with command injection"
When call validate_input_python "eslint-lint" "mode" "check; rm -rf /"
The status should be failure
End
End
Context "when validating working-directory input"
It "accepts default directory"
When call validate_input_python "eslint-lint" "working-directory" "."
The status should be success
End
It "accepts valid subdirectory"
When call validate_input_python "eslint-lint" "working-directory" "src"
The status should be success
End
It "accepts empty working-directory (uses default)"
When call validate_input_python "eslint-lint" "working-directory" ""
The status should be success
End
It "rejects path traversal"
When call validate_input_python "eslint-lint" "working-directory" "../../../etc"
The status should be failure
End
It "rejects directory with command injection"
When call validate_input_python "eslint-lint" "working-directory" "src; rm -rf /"
The status should be failure
End
End
Context "when validating eslint-version input"
It "accepts latest version"
When call validate_input_python "eslint-lint" "eslint-version" "latest"
The status should be success
End
It "accepts semantic version"
When call validate_input_python "eslint-lint" "eslint-version" "8.57.0"
The status should be success
End
It "accepts major.minor version"
When call validate_input_python "eslint-lint" "eslint-version" "8.57"
The status should be success
End
It "accepts major version"
When call validate_input_python "eslint-lint" "eslint-version" "8"
The status should be success
End
It "accepts version with pre-release"
When call validate_input_python "eslint-lint" "eslint-version" "9.0.0-beta.1"
The status should be success
End
It "accepts empty version (uses default)"
When call validate_input_python "eslint-lint" "eslint-version" ""
The status should be success
End
It "rejects invalid version format"
When call validate_input_python "eslint-lint" "eslint-version" "invalid"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "eslint-lint" "eslint-version" "8.57.0; echo"
The status should be failure
End
End
Context "when validating config-file input"
It "accepts default config file"
When call validate_input_python "eslint-lint" "config-file" ".eslintrc"
The status should be success
End
It "accepts custom config file"
When call validate_input_python "eslint-lint" "config-file" ".eslintrc.js"
The status should be success
End
It "accepts config file in subdirectory"
When call validate_input_python "eslint-lint" "config-file" "config/eslint.config.js"
The status should be success
End
It "accepts empty config-file (uses default)"
When call validate_input_python "eslint-lint" "config-file" ""
The status should be success
End
It "rejects config file with path traversal"
When call validate_input_python "eslint-lint" "config-file" "../../../.eslintrc"
The status should be failure
End
It "rejects config file with command injection"
When call validate_input_python "eslint-lint" "config-file" ".eslintrc; rm -rf /"
The status should be failure
End
End
Context "when validating ignore-file input"
It "accepts default ignore file"
When call validate_input_python "eslint-lint" "ignore-file" ".eslintignore"
The status should be success
End
It "accepts custom ignore file"
When call validate_input_python "eslint-lint" "ignore-file" ".eslintignore.custom"
The status should be success
End
It "accepts empty ignore-file (uses default)"
When call validate_input_python "eslint-lint" "ignore-file" ""
The status should be success
End
It "rejects ignore file with path traversal"
When call validate_input_python "eslint-lint" "ignore-file" "../../../.eslintignore"
The status should be failure
End
It "rejects ignore file with command injection"
When call validate_input_python "eslint-lint" "ignore-file" ".eslintignore; echo"
The status should be failure
End
End
Context "when validating file-extensions input"
It "accepts default extensions"
When call validate_input_python "eslint-lint" "file-extensions" ".js,.jsx,.ts,.tsx"
The status should be success
End
It "accepts single extension"
When call validate_input_python "eslint-lint" "file-extensions" ".js"
The status should be success
End
It "accepts multiple extensions"
When call validate_input_python "eslint-lint" "file-extensions" ".js,.ts,.mjs"
The status should be success
End
It "accepts empty file-extensions (uses default)"
When call validate_input_python "eslint-lint" "file-extensions" ""
The status should be success
End
It "rejects extensions without leading dot"
When call validate_input_python "eslint-lint" "file-extensions" "js,jsx"
The status should be failure
End
It "rejects extensions with command injection"
When call validate_input_python "eslint-lint" "file-extensions" ".js; rm -rf /"
The status should be failure
End
End
Context "when validating cache input"
It "accepts true"
When call validate_input_python "eslint-lint" "cache" "true"
The status should be success
End
It "accepts false"
When call validate_input_python "eslint-lint" "cache" "false"
The status should be success
End
It "accepts empty cache (uses default)"
When call validate_input_python "eslint-lint" "cache" ""
The status should be success
End
It "rejects invalid boolean value"
When call validate_input_python "eslint-lint" "cache" "maybe"
The status should be failure
End
End
Context "when validating max-warnings input"
It "accepts default value 0"
When call validate_input_python "eslint-lint" "max-warnings" "0"
The status should be success
End
It "accepts positive integer"
When call validate_input_python "eslint-lint" "max-warnings" "10"
The status should be success
End
It "accepts large number"
When call validate_input_python "eslint-lint" "max-warnings" "1000"
The status should be success
End
It "accepts empty max-warnings (uses default)"
When call validate_input_python "eslint-lint" "max-warnings" ""
The status should be success
End
It "rejects negative number"
When call validate_input_python "eslint-lint" "max-warnings" "-1"
The status should be failure
End
It "rejects non-numeric value"
When call validate_input_python "eslint-lint" "max-warnings" "abc"
The status should be failure
End
It "rejects max-warnings with command injection"
When call validate_input_python "eslint-lint" "max-warnings" "0; echo"
The status should be failure
End
End
Context "when validating fail-on-error input"
It "accepts true"
When call validate_input_python "eslint-lint" "fail-on-error" "true"
The status should be success
End
It "accepts false"
When call validate_input_python "eslint-lint" "fail-on-error" "false"
The status should be success
End
It "accepts empty fail-on-error (uses default)"
When call validate_input_python "eslint-lint" "fail-on-error" ""
The status should be success
End
It "rejects invalid boolean value"
When call validate_input_python "eslint-lint" "fail-on-error" "yes"
The status should be failure
End
End
Context "when validating report-format input"
It "accepts stylish format"
When call validate_input_python "eslint-lint" "report-format" "stylish"
The status should be success
End
It "accepts json format"
When call validate_input_python "eslint-lint" "report-format" "json"
The status should be success
End
It "accepts sarif format"
When call validate_input_python "eslint-lint" "report-format" "sarif"
The status should be success
End
It "accepts empty report-format (uses default)"
When call validate_input_python "eslint-lint" "report-format" ""
The status should be success
End
It "rejects invalid format"
When call validate_input_python "eslint-lint" "report-format" "invalid"
The status should be failure
End
It "rejects format with command injection"
When call validate_input_python "eslint-lint" "report-format" "json; rm -rf /"
The status should be failure
End
End
Context "when validating max-retries input"
It "accepts default value 3"
When call validate_input_python "eslint-lint" "max-retries" "3"
The status should be success
End
It "accepts retry count of 1"
When call validate_input_python "eslint-lint" "max-retries" "1"
The status should be success
End
It "accepts retry count of 10"
When call validate_input_python "eslint-lint" "max-retries" "10"
The status should be success
End
It "accepts empty max-retries (uses default)"
When call validate_input_python "eslint-lint" "max-retries" ""
The status should be success
End
It "rejects zero retries"
When call validate_input_python "eslint-lint" "max-retries" "0"
The status should be failure
End
It "rejects negative retry count"
When call validate_input_python "eslint-lint" "max-retries" "-1"
The status should be failure
End
It "rejects retry count above 10"
When call validate_input_python "eslint-lint" "max-retries" "11"
The status should be failure
End
It "rejects non-numeric retry count"
When call validate_input_python "eslint-lint" "max-retries" "abc"
The status should be failure
End
It "rejects retry count with command injection"
When call validate_input_python "eslint-lint" "max-retries" "3; echo"
The status should be failure
End
End
Context "when validating token input"
It "accepts valid GitHub token (classic)"
When call validate_input_python "eslint-lint" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "accepts valid GitHub fine-grained token"
When call validate_input_python "eslint-lint" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
The status should be success
End
It "accepts empty token (optional)"
When call validate_input_python "eslint-lint" "token" ""
The status should be success
End
It "rejects invalid token format"
When call validate_input_python "eslint-lint" "token" "invalid-token"
The status should be failure
End
It "rejects token with command injection"
When call validate_input_python "eslint-lint" "token" "ghp_123456789012345678901234567890123456; rm -rf /"
The status should be failure
End
End
Context "when validating username input"
It "accepts valid username"
When call validate_input_python "eslint-lint" "username" "github-actions"
The status should be success
End
It "accepts username with hyphens"
When call validate_input_python "eslint-lint" "username" "my-bot-user"
The status should be success
End
It "accepts alphanumeric username"
When call validate_input_python "eslint-lint" "username" "user123"
The status should be success
End
It "accepts empty username (uses default)"
When call validate_input_python "eslint-lint" "username" ""
The status should be success
End
It "rejects username with command injection"
When call validate_input_python "eslint-lint" "username" "user; rm -rf /"
The status should be failure
End
It "rejects username with special characters"
When call validate_input_python "eslint-lint" "username" "user@bot"
The status should be failure
End
End
Context "when validating email input"
It "accepts valid email"
When call validate_input_python "eslint-lint" "email" "github-actions@github.com"
The status should be success
End
It "accepts email with plus sign"
When call validate_input_python "eslint-lint" "email" "user+bot@example.com"
The status should be success
End
It "accepts email with subdomain"
When call validate_input_python "eslint-lint" "email" "bot@ci.example.com"
The status should be success
End
It "accepts empty email (uses default)"
When call validate_input_python "eslint-lint" "email" ""
The status should be success
End
It "rejects invalid email format"
When call validate_input_python "eslint-lint" "email" "not-an-email"
The status should be failure
End
It "rejects email with command injection"
When call validate_input_python "eslint-lint" "email" "user@example.com; rm -rf /"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "ESLint Lint"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "mode"
The output should include "working-directory"
The output should include "eslint-version"
The output should include "config-file"
The output should include "ignore-file"
The output should include "file-extensions"
The output should include "cache"
The output should include "max-warnings"
The output should include "fail-on-error"
The output should include "report-format"
The output should include "max-retries"
The output should include "token"
The output should include "username"
The output should include "email"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "status"
The output should include "error-count"
The output should include "warning-count"
The output should include "sarif-file"
The output should include "files-checked"
The output should include "files-changed"
The output should include "errors-fixed"
End
End
Context "when testing input requirements"
It "has all inputs as optional (with defaults)"
When call is_input_required "$ACTION_FILE" "mode"
The status should be failure
End
End
Context "when testing security validations"
It "validates against path traversal in working-directory"
When call validate_input_python "eslint-lint" "working-directory" "../../../etc"
The status should be failure
End
It "validates against shell metacharacters in mode"
When call validate_input_python "eslint-lint" "mode" "check|echo"
The status should be failure
End
It "validates against command substitution in config-file"
When call validate_input_python "eslint-lint" "config-file" "\$(whoami)"
The status should be failure
End
It "validates against path traversal in token"
When call validate_input_python "eslint-lint" "token" "../../../etc/passwd"
The status should be failure
End
It "validates against shell metacharacters in username"
When call validate_input_python "eslint-lint" "username" "user&whoami"
The status should be failure
End
It "validates against command injection in email"
When call validate_input_python "eslint-lint" "email" "user@example.com\`whoami\`"
The status should be failure
End
End
End

View File

@@ -1,141 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for github-release action validation and logic
# Framework is automatically loaded via spec_helper.sh
# Using the centralized validate_input_python function from spec_helper.sh
Describe "github-release action"
ACTION_DIR="github-release"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating version input"
It "accepts valid semantic version"
When call validate_input_python "github-release" "version" "1.2.3"
The status should be success
End
It "accepts semantic version with v prefix"
When call validate_input_python "github-release" "version" "v1.2.3"
The status should be success
End
It "accepts prerelease version"
When call validate_input_python "github-release" "version" "1.2.3-alpha"
The status should be success
End
It "accepts build metadata version"
When call validate_input_python "github-release" "version" "1.2.3+build.1"
The status should be success
End
It "accepts prerelease with build metadata"
When call validate_input_python "github-release" "version" "1.2.3-alpha.1+build.1"
The status should be success
End
It "accepts CalVer format"
When call validate_input_python "github-release" "version" "2024.3.1"
The status should be success
End
It "rejects invalid version format"
When call validate_input_python "github-release" "version" "invalid-version"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "github-release" "version" "1.2.3; rm -rf /"
The status should be failure
End
It "rejects empty version"
When call validate_input_python "github-release" "version" ""
The status should be failure
End
End
Context "when validating changelog input"
It "accepts empty changelog"
When call validate_input_python "github-release" "changelog" ""
The status should be success
End
It "accepts normal changelog content"
When call validate_input_python "github-release" "changelog" "## What's Changed\n- Fixed bug #123\n- Added feature X"
The status should be success
End
It "accepts changelog with special characters"
When call validate_input_python "github-release" "changelog" "Version 1.2.3\n\n- Bug fixes & improvements\n- Added @mention support"
The status should be success
End
It "rejects changelog with command injection"
When call validate_input_python "github-release" "changelog" "Release notes; rm -rf /"
The status should be failure
End
It "rejects changelog with shell expansion"
When call validate_input_python "github-release" "changelog" "Release \$(whoami) notes"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "GitHub Release"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "version"
The output should include "changelog"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "release_url"
The output should include "release_id"
The output should include "upload_url"
End
End
Context "when testing input requirements"
It "requires version input"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "version"
End
It "has changelog as optional input"
# Test that changelog has a default value in action.yml
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "changelog" "optional"
The output should equal "optional"
End
End
Context "when testing security validations"
It "validates against path traversal in version"
When call validate_input_python "github-release" "version" "../1.2.3"
The status should be failure
End
It "validates against shell metacharacters in version"
When call validate_input_python "github-release" "version" "1.2.3|echo"
The status should be failure
End
It "validates against shell metacharacters in changelog"
When call validate_input_python "github-release" "changelog" "Release notes|echo test"
The status should be failure
End
End
End

View File

@@ -1,171 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for go-version-detect action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "go-version-detect action"
ACTION_DIR="go-version-detect"
ACTION_FILE="$ACTION_DIR/action.yml"
# Test version constants (update these when Go releases new versions)
CURRENT_STABLE_GO_VERSION="1.25"
CURRENT_STABLE_GO_PATCH="1.25.0"
PREVIOUS_GO_VERSION="1.24.0"
MIN_SUPPORTED_GO_VERSION="1.18"
MAX_SUPPORTED_GO_VERSION="1.30"
TOO_OLD_GO_VERSION="1.17"
TOO_NEW_GO_VERSION="1.31"
Context "when validating default-version input"
It "accepts valid semantic version"
When call validate_input_python "go-version-detect" "default-version" "$CURRENT_STABLE_GO_VERSION"
The status should be success
End
It "accepts semantic version with patch"
When call validate_input_python "go-version-detect" "default-version" "$PREVIOUS_GO_VERSION"
The status should be success
End
It "accepts minimum supported Go version"
When call validate_input_python "go-version-detect" "default-version" "$MIN_SUPPORTED_GO_VERSION"
The status should be success
End
It "accepts current stable Go version"
When call validate_input_python "go-version-detect" "default-version" "$CURRENT_STABLE_GO_PATCH"
The status should be success
End
It "rejects version without minor"
When call validate_input_python "go-version-detect" "default-version" "1"
The status should be failure
End
It "rejects invalid version format"
When call validate_input_python "go-version-detect" "default-version" "invalid-version"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}; rm -rf /"
The status should be failure
End
It "rejects version with shell expansion"
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}\$(echo test)"
The status should be failure
End
It "rejects major version other than 1"
When call validate_input_python "go-version-detect" "default-version" "2.0"
The status should be failure
End
It "rejects too old minor version"
When call validate_input_python "go-version-detect" "default-version" "$TOO_OLD_GO_VERSION"
The status should be failure
End
It "rejects too new minor version"
When call validate_input_python "go-version-detect" "default-version" "$TOO_NEW_GO_VERSION"
The status should be failure
End
It "rejects empty version"
When call validate_input_python "go-version-detect" "default-version" ""
The status should be failure
End
It "rejects version with leading v"
When call validate_input_python "go-version-detect" "default-version" "v${CURRENT_STABLE_GO_VERSION}"
The status should be failure
End
It "rejects version with prerelease"
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}-beta"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Go Version Detect"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "default-version"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "go-version"
End
End
Context "when testing input requirements"
It "has default-version as optional input"
# Test that default-version has a default value in action.yml
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "optional"
The output should equal "optional"
End
It "has correct default version"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "default"
The output should equal "$CURRENT_STABLE_GO_VERSION"
End
End
Context "when testing security validations"
It "validates against path traversal in version"
When call validate_input_python "go-version-detect" "default-version" "../${CURRENT_STABLE_GO_VERSION}"
The status should be failure
End
It "validates against shell metacharacters in version"
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}|echo"
The status should be failure
End
It "validates against backtick injection"
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}\`whoami\`"
The status should be failure
End
It "validates against variable expansion"
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}\${HOME}"
The status should be failure
End
End
Context "when testing version range validation"
It "validates reasonable Go version range boundaries"
# Test boundary conditions for Go version validation
When call validate_input_python "go-version-detect" "default-version" "$TOO_OLD_GO_VERSION"
The status should be failure
End
It "validates upper boundary"
When call validate_input_python "go-version-detect" "default-version" "$TOO_NEW_GO_VERSION"
The status should be failure
End
It "validates exact boundary valid values"
When call validate_input_python "go-version-detect" "default-version" "$MIN_SUPPORTED_GO_VERSION"
The status should be success
End
It "validates exact boundary valid values upper"
When call validate_input_python "go-version-detect" "default-version" "$MAX_SUPPORTED_GO_VERSION"
The status should be success
End
End
End

View File

@@ -0,0 +1,297 @@
#!/usr/bin/env shellspec
# Unit tests for language-version-detect action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "language-version-detect action"
ACTION_DIR="language-version-detect"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating language input"
It "accepts php language"
When call validate_input_python "language-version-detect" "language" "php"
The status should be success
End
It "accepts python language"
When call validate_input_python "language-version-detect" "language" "python"
The status should be success
End
It "accepts go language"
When call validate_input_python "language-version-detect" "language" "go"
The status should be success
End
It "accepts dotnet language"
When call validate_input_python "language-version-detect" "language" "dotnet"
The status should be success
End
It "rejects invalid language"
When call validate_input_python "language-version-detect" "language" "javascript"
The status should be failure
End
It "rejects empty language (required)"
When call validate_input_python "language-version-detect" "language" ""
The status should be failure
End
It "rejects language with command injection"
When call validate_input_python "language-version-detect" "language" "php; rm -rf /"
The status should be failure
End
It "rejects language with shell metacharacters"
When call validate_input_python "language-version-detect" "language" "php|echo"
The status should be failure
End
End
Context "when validating default-version input for PHP"
It "accepts valid PHP version 8.4"
When call validate_input_python "language-version-detect" "default-version" "8.4"
The status should be success
End
It "accepts valid PHP version 8.3"
When call validate_input_python "language-version-detect" "default-version" "8.3"
The status should be success
End
It "accepts valid PHP version 7.4"
When call validate_input_python "language-version-detect" "default-version" "7.4"
The status should be success
End
It "accepts valid PHP version with patch 8.3.1"
When call validate_input_python "language-version-detect" "default-version" "8.3.1"
The status should be success
End
It "accepts empty default-version (uses language default)"
When call validate_input_python "language-version-detect" "default-version" ""
The status should be success
End
It "rejects invalid PHP version format"
When call validate_input_python "language-version-detect" "default-version" "invalid"
The status should be failure
End
End
Context "when validating default-version input for Python"
It "accepts valid Python version 3.12"
When call validate_input_python "language-version-detect" "default-version" "3.12"
The status should be success
End
It "accepts valid Python version 3.11"
When call validate_input_python "language-version-detect" "default-version" "3.11"
The status should be success
End
It "accepts valid Python version 3.10"
When call validate_input_python "language-version-detect" "default-version" "3.10"
The status should be success
End
It "accepts valid Python version with patch 3.12.1"
When call validate_input_python "language-version-detect" "default-version" "3.12.1"
The status should be success
End
It "accepts valid Python version 3.9"
When call validate_input_python "language-version-detect" "default-version" "3.9"
The status should be success
End
It "accepts valid Python version 3.8"
When call validate_input_python "language-version-detect" "default-version" "3.8"
The status should be success
End
End
Context "when validating default-version input for Go"
It "accepts valid Go version 1.21"
When call validate_input_python "language-version-detect" "default-version" "1.21"
The status should be success
End
It "accepts valid Go version 1.20"
When call validate_input_python "language-version-detect" "default-version" "1.20"
The status should be success
End
It "accepts valid Go version with patch 1.21.5"
When call validate_input_python "language-version-detect" "default-version" "1.21.5"
The status should be success
End
It "accepts valid Go version 1.22"
When call validate_input_python "language-version-detect" "default-version" "1.22"
The status should be success
End
End
Context "when validating default-version input for .NET"
It "accepts valid .NET version 7.0"
When call validate_input_python "language-version-detect" "default-version" "7.0"
The status should be success
End
It "accepts valid .NET version 8.0"
When call validate_input_python "language-version-detect" "default-version" "8.0"
The status should be success
End
It "accepts valid .NET version 6.0"
When call validate_input_python "language-version-detect" "default-version" "6.0"
The status should be success
End
It "accepts valid .NET version with patch 7.0.1"
When call validate_input_python "language-version-detect" "default-version" "7.0.1"
The status should be success
End
It "accepts valid .NET major version 7"
When call validate_input_python "language-version-detect" "default-version" "7"
The status should be success
End
End
Context "when validating default-version input edge cases"
It "rejects version with v prefix"
When call validate_input_python "language-version-detect" "default-version" "v3.12"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "language-version-detect" "default-version" "3.12; rm -rf /"
The status should be failure
End
It "rejects version with shell metacharacters"
When call validate_input_python "language-version-detect" "default-version" "3.12|echo"
The status should be failure
End
It "rejects version with command substitution"
When call validate_input_python "language-version-detect" "default-version" "\$(whoami)"
The status should be failure
End
It "rejects alphabetic version"
When call validate_input_python "language-version-detect" "default-version" "latest"
The status should be failure
End
End
Context "when validating token input"
It "accepts valid GitHub token (classic)"
When call validate_input_python "language-version-detect" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "accepts valid GitHub fine-grained token"
When call validate_input_python "language-version-detect" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
The status should be success
End
It "accepts empty token (optional)"
When call validate_input_python "language-version-detect" "token" ""
The status should be success
End
It "rejects invalid token format"
When call validate_input_python "language-version-detect" "token" "invalid-token"
The status should be failure
End
It "rejects token with command injection"
When call validate_input_python "language-version-detect" "token" "ghp_123456789012345678901234567890123456; rm -rf /"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Language Version Detect"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "language"
The output should include "default-version"
The output should include "token"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "detected-version"
The output should include "package-manager"
End
End
Context "when testing input requirements"
It "requires language input"
When call is_input_required "$ACTION_FILE" "language"
The status should be success
End
It "has default-version as optional input"
When call is_input_required "$ACTION_FILE" "default-version"
The status should be failure
End
It "has token as optional input"
When call is_input_required "$ACTION_FILE" "token"
The status should be failure
End
End
Context "when testing security validations"
It "validates against path traversal in language"
When call validate_input_python "language-version-detect" "language" "../../../etc"
The status should be failure
End
It "validates against shell metacharacters in language"
When call validate_input_python "language-version-detect" "language" "php&whoami"
The status should be failure
End
It "validates against command substitution in language"
When call validate_input_python "language-version-detect" "language" "php\`whoami\`"
The status should be failure
End
It "validates against path traversal in default-version"
When call validate_input_python "language-version-detect" "default-version" "../../../etc"
The status should be failure
End
It "validates against shell metacharacters in default-version"
When call validate_input_python "language-version-detect" "default-version" "3.12&echo"
The status should be failure
End
It "validates against command substitution in default-version"
When call validate_input_python "language-version-detect" "default-version" "3.12\$(whoami)"
The status should be failure
End
It "validates against path traversal in token"
When call validate_input_python "language-version-detect" "token" "../../../etc/passwd"
The status should be failure
End
End
End

View File

@@ -1,242 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for node-setup action
# Framework is automatically loaded via spec_helper.sh
Describe "node-setup action"
ACTION_DIR="node-setup"
ACTION_FILE="$ACTION_DIR/action.yml"
# Framework is automatically initialized via spec_helper.sh
Context "when validating inputs"
It "accepts valid Node.js version"
When call validate_input_python "node-setup" "default-version" "18.17.0"
The status should be success
End
It "accepts valid package manager"
When call validate_input_python "node-setup" "package-manager" "npm"
The status should be success
End
It "accepts yarn as package manager"
When call validate_input_python "node-setup" "package-manager" "yarn"
The status should be success
End
It "accepts pnpm as package manager"
When call validate_input_python "node-setup" "package-manager" "pnpm"
The status should be success
End
It "accepts bun as package manager"
When call validate_input_python "node-setup" "package-manager" "bun"
The status should be success
End
It "rejects invalid package manager"
When call validate_input_python "node-setup" "package-manager" "invalid-manager"
The status should be failure
End
It "rejects malformed Node.js version"
When call validate_input_python "node-setup" "default-version" "not-a-version"
The status should be failure
End
It "rejects command injection in inputs"
When call validate_input_python "node-setup" "default-version" "18.0.0; rm -rf /"
The status should be failure
End
End
Context "when checking action structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
When call get_action_name "$ACTION_FILE"
The output should equal "Node Setup"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "default-version"
The output should include "package-manager"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "node-version"
The output should include "package-manager"
The output should include "cache-hit"
End
End
Context "when testing Node.js version detection"
BeforeEach "shellspec_setup_test_env 'node-version-detection'"
AfterEach "shellspec_cleanup_test_env 'node-version-detection'"
It "detects version from package.json engines field"
create_mock_node_repo
# Mock action output based on package.json
echo "node-version=18.0.0" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "node-version" "18.0.0"
The status should be success
End
It "detects version from .nvmrc file"
create_mock_node_repo
echo "18.17.1" >.nvmrc
# Mock action output
echo "node-version=18.17.1" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "node-version" "18.17.1"
The status should be success
End
It "uses default version when none specified"
create_mock_node_repo
# Remove engines field simulation
# Mock default version output
echo "node-version=20.0.0" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "node-version" "20.0.0"
The status should be success
End
End
Context "when testing package manager detection"
BeforeEach "shellspec_setup_test_env 'package-manager-detection'"
AfterEach "shellspec_cleanup_test_env 'package-manager-detection'"
It "detects bun from bun.lockb"
create_mock_node_repo
touch bun.lockb
echo "package-manager=bun" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "package-manager" "bun"
The status should be success
End
It "detects pnpm from pnpm-lock.yaml"
create_mock_node_repo
touch pnpm-lock.yaml
echo "package-manager=pnpm" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "package-manager" "pnpm"
The status should be success
End
It "detects yarn from yarn.lock"
create_mock_node_repo
touch yarn.lock
echo "package-manager=yarn" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "package-manager" "yarn"
The status should be success
End
It "detects npm from package-lock.json"
create_mock_node_repo
touch package-lock.json
echo "package-manager=npm" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "package-manager" "npm"
The status should be success
End
It "detects packageManager field from package.json"
create_mock_node_repo
# Add packageManager field to package.json
cat >package.json <<EOF
{
"name": "test-project",
"version": "1.0.0",
"packageManager": "pnpm@8.0.0",
"engines": {
"node": ">=18.0.0"
}
}
EOF
echo "package-manager=pnpm" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "package-manager" "pnpm"
The status should be success
End
End
Context "when testing Corepack integration"
BeforeEach "shellspec_setup_test_env 'corepack-test'"
AfterEach "shellspec_cleanup_test_env 'corepack-test'"
It "enables Corepack when packageManager is specified"
create_mock_node_repo
# Simulate packageManager field
cat >package.json <<EOF
{
"name": "test-project",
"version": "1.0.0",
"packageManager": "yarn@3.6.0"
}
EOF
# Mock Corepack enabled output
echo "corepack-enabled=true" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "corepack-enabled" "true"
The status should be success
End
End
Context "when testing cache functionality"
BeforeEach "shellspec_setup_test_env 'cache-test'"
AfterEach "shellspec_cleanup_test_env 'cache-test'"
It "reports cache hit when dependencies are cached"
create_mock_node_repo
touch package-lock.json
mkdir -p node_modules
# Mock cache hit
echo "cache-hit=true" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "cache-hit" "true"
The status should be success
End
It "reports cache miss when no cache exists"
create_mock_node_repo
touch package-lock.json
# Mock cache miss
echo "cache-hit=false" >>"$GITHUB_OUTPUT"
When call shellspec_validate_action_output "cache-hit" "false"
The status should be success
End
End
Context "when testing output consistency"
It "produces all expected outputs"
When call test_action_outputs "$ACTION_DIR" "node-version" "18.0.0" "package-manager" "npm"
The status should be success
The stderr should include "Testing action outputs for: node-setup"
The stderr should include "Output test passed for: node-setup"
End
End
End

View File

@@ -1,407 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for php-composer action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "php-composer action"
ACTION_DIR="php-composer"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating php input"
It "accepts valid PHP version"
When call validate_input_python "php-composer" "php" "8.4"
The status should be success
End
It "accepts PHP version with patch"
When call validate_input_python "php-composer" "php" "8.4.1"
The status should be success
End
It "accepts PHP 7.4"
When call validate_input_python "php-composer" "php" "7.4"
The status should be success
End
It "accepts PHP 8.0"
When call validate_input_python "php-composer" "php" "8.0"
The status should be success
End
It "accepts PHP 8.1"
When call validate_input_python "php-composer" "php" "8.1"
The status should be success
End
It "rejects PHP version too old"
When call validate_input_python "php-composer" "php" "5.5"
The status should be failure
End
It "rejects invalid version format"
When call validate_input_python "php-composer" "php" "php8.4"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "php-composer" "php" "8.4; rm -rf /"
The status should be failure
End
It "rejects empty version"
When call validate_input_python "php-composer" "php" ""
The status should be failure
End
End
Context "when validating extensions input"
It "accepts valid PHP extensions"
When call validate_input_python "php-composer" "extensions" "mbstring, xml, zip"
The status should be success
End
It "accepts single extension"
When call validate_input_python "php-composer" "extensions" "mbstring"
The status should be success
End
It "accepts extensions without spaces"
When call validate_input_python "php-composer" "extensions" "mbstring,xml,zip"
The status should be success
End
It "accepts extensions with underscores"
When call validate_input_python "php-composer" "extensions" "pdo_mysql, gd_jpeg"
The status should be success
End
It "rejects extensions with special characters"
When call validate_input_python "php-composer" "extensions" "mbstring@xml"
The status should be failure
End
It "rejects extensions with command injection"
When call validate_input_python "php-composer" "extensions" "mbstring; rm -rf /"
The status should be failure
End
It "rejects empty extensions"
When call validate_input_python "php-composer" "extensions" ""
The status should be failure
End
End
Context "when validating tools input"
It "accepts valid Composer tools"
When call validate_input_python "php-composer" "tools" "composer:v2"
The status should be success
End
It "accepts multiple tools"
When call validate_input_python "php-composer" "tools" "composer:v2, phpunit:^9.0"
The status should be success
End
It "accepts tools with version constraints"
When call validate_input_python "php-composer" "tools" "phpcs, phpstan:1.10"
The status should be success
End
It "accepts tools with stability flags (@ allowed)"
When call validate_input_python "php-composer" "tools" "dev-master@dev"
The status should be success
End
It "accepts tools with version and stability flag"
When call validate_input_python "php-composer" "tools" "monolog/monolog@dev"
The status should be success
End
It "rejects tools with backticks"
When call validate_input_python "php-composer" "tools" "composer\`whoami\`"
The status should be failure
End
It "rejects tools with command injection"
When call validate_input_python "php-composer" "tools" "composer; rm -rf /"
The status should be failure
End
It "rejects empty tools"
When call validate_input_python "php-composer" "tools" ""
The status should be failure
End
End
Context "when validating composer-version input"
It "accepts composer version 1"
When call validate_input_python "php-composer" "composer-version" "1"
The status should be success
End
It "accepts composer version 2"
When call validate_input_python "php-composer" "composer-version" "2"
The status should be success
End
It "rejects invalid composer version"
When call validate_input_python "php-composer" "composer-version" "3"
The status should be failure
End
It "rejects non-numeric composer version"
When call validate_input_python "php-composer" "composer-version" "latest"
The status should be failure
End
It "rejects empty composer version"
When call validate_input_python "php-composer" "composer-version" ""
The status should be failure
End
End
Context "when validating stability input"
It "accepts stable"
When call validate_input_python "php-composer" "stability" "stable"
The status should be success
End
It "accepts RC"
When call validate_input_python "php-composer" "stability" "RC"
The status should be success
End
It "accepts beta"
When call validate_input_python "php-composer" "stability" "beta"
The status should be success
End
It "accepts alpha"
When call validate_input_python "php-composer" "stability" "alpha"
The status should be success
End
It "accepts dev"
When call validate_input_python "php-composer" "stability" "dev"
The status should be success
End
It "rejects invalid stability"
When call validate_input_python "php-composer" "stability" "unstable"
The status should be failure
End
It "rejects stability with injection"
When call validate_input_python "php-composer" "stability" "stable; rm -rf /"
The status should be failure
End
End
Context "when validating cache-directories input"
It "accepts valid cache directory"
When call validate_input_python "php-composer" "cache-directories" "vendor/cache"
The status should be success
End
It "accepts multiple cache directories"
When call validate_input_python "php-composer" "cache-directories" "vendor/cache, .cache"
The status should be success
End
It "accepts directories with underscores and hyphens"
When call validate_input_python "php-composer" "cache-directories" "cache_dir, cache-dir"
The status should be success
End
It "rejects path traversal"
When call validate_input_python "php-composer" "cache-directories" "../malicious"
The status should be failure
End
It "rejects absolute paths"
When call validate_input_python "php-composer" "cache-directories" "/etc/passwd"
The status should be failure
End
It "rejects directories with command injection"
When call validate_input_python "php-composer" "cache-directories" "cache; rm -rf /"
The status should be failure
End
It "rejects empty cache directories"
When call validate_input_python "php-composer" "cache-directories" ""
The status should be failure
End
End
Context "when validating token input"
It "accepts GitHub token expression"
When call validate_input_python "php-composer" "token" "\${{ github.token }}"
The status should be success
End
It "accepts GitHub fine-grained token"
When call validate_input_python "php-composer" "token" "ghp_abcdefghijklmnopqrstuvwxyz1234567890"
The status should be success
End
It "accepts GitHub app token"
When call validate_input_python "php-composer" "token" "ghs_abcdefghijklmnopqrstuvwxyz1234567890"
The status should be success
End
It "rejects invalid token format"
When call validate_input_python "php-composer" "token" "invalid-token"
The status should be failure
End
It "rejects empty token"
When call validate_input_python "php-composer" "token" ""
The status should be failure
End
End
Context "when validating max-retries input"
It "accepts valid retry count"
When call validate_input_python "php-composer" "max-retries" "3"
The status should be success
End
It "accepts minimum retries"
When call validate_input_python "php-composer" "max-retries" "1"
The status should be success
End
It "accepts maximum retries"
When call validate_input_python "php-composer" "max-retries" "10"
The status should be success
End
It "rejects zero retries"
When call validate_input_python "php-composer" "max-retries" "0"
The status should be failure
End
It "rejects too many retries"
When call validate_input_python "php-composer" "max-retries" "11"
The status should be failure
End
It "rejects non-numeric retries"
When call validate_input_python "php-composer" "max-retries" "many"
The status should be failure
End
It "rejects negative retries"
When call validate_input_python "php-composer" "max-retries" "-1"
The status should be failure
End
End
Context "when validating args input"
It "accepts valid Composer arguments"
When call validate_input_python "php-composer" "args" "--no-progress --prefer-dist"
The status should be success
End
It "rejects empty args"
When call validate_input_python "php-composer" "args" ""
The status should be failure
End
It "rejects args with command injection"
When call validate_input_python "php-composer" "args" "--no-progress; rm -rf /"
The status should be failure
End
It "rejects args with pipe"
When call validate_input_python "php-composer" "args" "--no-progress | cat /etc/passwd"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Run Composer Install"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "php"
The output should include "extensions"
The output should include "tools"
The output should include "args"
The output should include "composer-version"
The output should include "stability"
The output should include "cache-directories"
The output should include "token"
The output should include "max-retries"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "lock"
The output should include "php-version"
The output should include "composer-version"
The output should include "cache-hit"
End
End
Context "when testing input requirements"
It "requires php input"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "php" "required"
The output should equal "required"
End
It "has extensions as optional input"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "extensions" "optional"
The output should equal "optional"
End
End
Context "when testing security validations"
It "validates against path traversal in cache directories"
When call validate_input_python "php-composer" "cache-directories" "../../etc/passwd"
The status should be failure
End
It "validates against shell metacharacters in tools"
When call validate_input_python "php-composer" "tools" "composer && rm -rf /"
The status should be failure
End
It "validates against backtick injection in args"
When call validate_input_python "php-composer" "args" "--no-progress \`whoami\`"
The status should be failure
End
It "validates against variable expansion in extensions"
When call validate_input_python "php-composer" "extensions" "mbstring,\${HOME}"
The status should be failure
End
End
Context "when testing PHP-specific validations"
It "validates PHP version boundaries"
When call validate_input_python "php-composer" "php" "10.0"
The status should be failure
End
It "validates Composer version enum restriction"
When call validate_input_python "php-composer" "composer-version" "0"
The status should be failure
End
It "validates stability enum values"
When call validate_input_python "php-composer" "stability" "experimental"
The status should be failure
End
End
End

View File

@@ -1,280 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for php-laravel-phpunit action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "php-laravel-phpunit action"
ACTION_DIR="php-laravel-phpunit"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating php-version input"
It "accepts latest"
When call validate_input_python "php-laravel-phpunit" "php-version" "latest"
The status should be success
End
It "accepts valid PHP version"
When call validate_input_python "php-laravel-phpunit" "php-version" "8.4"
The status should be success
End
It "accepts PHP version with patch"
When call validate_input_python "php-laravel-phpunit" "php-version" "8.4.1"
The status should be success
End
It "accepts PHP 7.4"
When call validate_input_python "php-laravel-phpunit" "php-version" "7.4"
The status should be success
End
It "accepts PHP 8.0"
When call validate_input_python "php-laravel-phpunit" "php-version" "8.0"
The status should be success
End
It "rejects invalid version format"
When call validate_input_python "php-laravel-phpunit" "php-version" "php8.4"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "php-laravel-phpunit" "php-version" "8.4; rm -rf /"
The status should be failure
End
It "accepts empty version (uses default)"
When call validate_input_python "php-laravel-phpunit" "php-version" ""
The status should be success
End
End
Context "when validating php-version-file input"
It "accepts valid PHP version file"
When call validate_input_python "php-laravel-phpunit" "php-version-file" ".php-version"
The status should be success
End
It "accepts custom version file"
When call validate_input_python "php-laravel-phpunit" "php-version-file" "custom-php-version"
The status should be success
End
It "accepts version file with path"
When call validate_input_python "php-laravel-phpunit" "php-version-file" "config/.php-version"
The status should be success
End
It "rejects path traversal in version file"
When call validate_input_python "php-laravel-phpunit" "php-version-file" "../../../etc/passwd"
The status should be failure
End
It "rejects absolute path in version file"
When call validate_input_python "php-laravel-phpunit" "php-version-file" "/etc/passwd"
The status should be failure
End
It "rejects version file with command injection"
When call validate_input_python "php-laravel-phpunit" "php-version-file" ".php-version; rm -rf /"
The status should be failure
End
It "accepts empty version file"
When call validate_input_python "php-laravel-phpunit" "php-version-file" ""
The status should be success
End
End
Context "when validating extensions input"
It "accepts valid PHP extensions"
When call validate_input_python "php-laravel-phpunit" "extensions" "mbstring, intl, json"
The status should be success
End
It "accepts single extension"
When call validate_input_python "php-laravel-phpunit" "extensions" "mbstring"
The status should be success
End
It "accepts extensions without spaces"
When call validate_input_python "php-laravel-phpunit" "extensions" "mbstring,intl,json"
The status should be success
End
It "accepts extensions with underscores"
When call validate_input_python "php-laravel-phpunit" "extensions" "pdo_sqlite, pdo_mysql"
The status should be success
End
It "accepts extensions with numbers"
When call validate_input_python "php-laravel-phpunit" "extensions" "sqlite3, gd2"
The status should be success
End
It "rejects extensions with special characters"
When call validate_input_python "php-laravel-phpunit" "extensions" "mbstring@intl"
The status should be failure
End
It "rejects extensions with command injection"
When call validate_input_python "php-laravel-phpunit" "extensions" "mbstring; rm -rf /"
The status should be failure
End
It "accepts empty extensions"
When call validate_input_python "php-laravel-phpunit" "extensions" ""
The status should be success
End
End
Context "when validating coverage input"
It "accepts none coverage"
When call validate_input_python "php-laravel-phpunit" "coverage" "none"
The status should be success
End
It "accepts xdebug coverage"
When call validate_input_python "php-laravel-phpunit" "coverage" "xdebug"
The status should be success
End
It "accepts pcov coverage"
When call validate_input_python "php-laravel-phpunit" "coverage" "pcov"
The status should be success
End
It "accepts xdebug3 coverage"
When call validate_input_python "php-laravel-phpunit" "coverage" "xdebug3"
The status should be success
End
It "rejects invalid coverage driver"
When call validate_input_python "php-laravel-phpunit" "coverage" "invalid"
The status should be failure
End
It "rejects coverage with command injection"
When call validate_input_python "php-laravel-phpunit" "coverage" "none; rm -rf /"
The status should be failure
End
It "accepts empty coverage"
When call validate_input_python "php-laravel-phpunit" "coverage" ""
The status should be success
End
End
Context "when validating token input"
It "accepts GitHub token expression"
When call validate_input_python "php-laravel-phpunit" "token" "\${{ github.token }}"
The status should be success
End
It "accepts GitHub fine-grained token"
When call validate_input_python "php-laravel-phpunit" "token" "ghp_abcdefghijklmnopqrstuvwxyz1234567890"
The status should be success
End
It "accepts GitHub app token"
When call validate_input_python "php-laravel-phpunit" "token" "ghs_abcdefghijklmnopqrstuvwxyz1234567890"
The status should be success
End
It "rejects invalid token format"
When call validate_input_python "php-laravel-phpunit" "token" "invalid-token"
The status should be failure
End
It "accepts empty token"
When call validate_input_python "php-laravel-phpunit" "token" ""
The status should be success
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Laravel Setup and Composer test"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "php-version"
The output should include "php-version-file"
The output should include "extensions"
The output should include "coverage"
The output should include "token"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "php-version"
The output should include "php-version-file"
The output should include "extensions"
The output should include "coverage"
End
End
Context "when testing input requirements"
It "has all inputs as optional"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "" "all_optional"
The output should equal "none"
End
It "has correct default php-version"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "php-version" "default"
The output should equal "latest"
End
It "has correct default php-version-file"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "php-version-file" "default"
The output should equal ".php-version"
End
End
Context "when testing security validations"
It "validates against path traversal in php-version-file"
When call validate_input_python "php-laravel-phpunit" "php-version-file" "../../etc/passwd"
The status should be failure
End
It "validates against shell metacharacters in extensions"
When call validate_input_python "php-laravel-phpunit" "extensions" "mbstring && rm -rf /"
The status should be failure
End
It "validates against backtick injection in coverage"
When call validate_input_python "php-laravel-phpunit" "coverage" "none\`whoami\`"
The status should be failure
End
It "validates against variable expansion in php-version"
When call validate_input_python "php-laravel-phpunit" "php-version" "8.4\${HOME}"
The status should be failure
End
End
Context "when testing Laravel-specific validations"
It "validates coverage driver enum values"
When call validate_input_python "php-laravel-phpunit" "coverage" "invalid-driver"
The status should be failure
End
It "validates php-version-file path safety"
When call validate_input_python "php-laravel-phpunit" "php-version-file" "/etc/shadow"
The status should be failure
End
It "validates extensions format for Laravel requirements"
When call validate_input_python "php-laravel-phpunit" "extensions" "mbstring, intl, json, pdo_sqlite, sqlite3"
The status should be success
End
End
End

View File

@@ -174,10 +174,10 @@ End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "test_status"
The output should include "tests_run"
The output should include "tests_passed"
The output should include "coverage_path"
The output should include "test-status"
The output should include "tests-run"
The output should include "tests-passed"
The output should include "framework"
End
End
@@ -245,5 +245,214 @@ It "validates default email is secure"
When call validate_input_python "php-tests" "email" "github-actions@github.com"
The status should be success
End
# Helper function that replicates the PHPUnit output parsing logic from action.yml
parse_phpunit_output() {
local phpunit_output="$1"
local phpunit_exit_code="$2"
local tests_run="0"
local tests_passed="0"
# Pattern 1: "OK (N test(s), M assertions)" - success case (handles both singular and plural)
if echo "$phpunit_output" | grep -qE 'OK \([0-9]+ tests?,'; then
tests_run=$(echo "$phpunit_output" | grep -oE 'OK \([0-9]+ tests?,' | grep -oE '[0-9]+' | head -1)
tests_passed="$tests_run"
# Pattern 2: "Tests: N" line - failure/error/skipped case
elif echo "$phpunit_output" | grep -qE '^Tests:'; then
tests_run=$(echo "$phpunit_output" | grep -E '^Tests:' | grep -oE '[0-9]+' | head -1)
# Calculate passed from failures and errors
failures=$(echo "$phpunit_output" | grep -oE 'Failures: [0-9]+' | grep -oE '[0-9]+' | head -1 || echo "0")
errors=$(echo "$phpunit_output" | grep -oE 'Errors: [0-9]+' | grep -oE '[0-9]+' | head -1 || echo "0")
tests_passed=$((tests_run - failures - errors))
# Ensure non-negative
if [ "$tests_passed" -lt 0 ]; then
tests_passed="0"
fi
fi
# Determine status
local status
if [ "$phpunit_exit_code" -eq 0 ]; then
status="success"
else
status="failure"
fi
# Output as KEY=VALUE format
echo "tests_run=$tests_run"
echo "tests_passed=$tests_passed"
echo "status=$status"
}
Context "when parsing PHPUnit output"
# Success cases
It "parses single successful test"
output="OK (1 test, 2 assertions)"
When call parse_phpunit_output "$output" 0
The line 1 of output should equal "tests_run=1"
The line 2 of output should equal "tests_passed=1"
The line 3 of output should equal "status=success"
End
It "parses multiple successful tests"
output="OK (5 tests, 10 assertions)"
When call parse_phpunit_output "$output" 0
The line 1 of output should equal "tests_run=5"
The line 2 of output should equal "tests_passed=5"
The line 3 of output should equal "status=success"
End
It "parses successful tests with plural form"
output="OK (25 tests, 50 assertions)"
When call parse_phpunit_output "$output" 0
The line 1 of output should equal "tests_run=25"
The line 2 of output should equal "tests_passed=25"
The line 3 of output should equal "status=success"
End
# Failure cases
It "parses test failures"
output="FAILURES!
Tests: 5, Assertions: 10, Failures: 2."
When call parse_phpunit_output "$output" 1
The line 1 of output should equal "tests_run=5"
The line 2 of output should equal "tests_passed=3"
The line 3 of output should equal "status=failure"
End
It "parses test errors"
output="ERRORS!
Tests: 5, Assertions: 10, Errors: 1."
When call parse_phpunit_output "$output" 2
The line 1 of output should equal "tests_run=5"
The line 2 of output should equal "tests_passed=4"
The line 3 of output should equal "status=failure"
End
It "parses mixed failures and errors"
output="FAILURES!
Tests: 10, Assertions: 20, Failures: 2, Errors: 1."
When call parse_phpunit_output "$output" 1
The line 1 of output should equal "tests_run=10"
The line 2 of output should equal "tests_passed=7"
The line 3 of output should equal "status=failure"
End
It "handles all tests failing"
output="FAILURES!
Tests: 5, Assertions: 10, Failures: 5."
When call parse_phpunit_output "$output" 1
The line 1 of output should equal "tests_run=5"
The line 2 of output should equal "tests_passed=0"
The line 3 of output should equal "status=failure"
End
It "prevents negative passed count"
output="ERRORS!
Tests: 2, Assertions: 4, Failures: 1, Errors: 2."
When call parse_phpunit_output "$output" 2
The line 1 of output should equal "tests_run=2"
The line 2 of output should equal "tests_passed=0"
The line 3 of output should equal "status=failure"
End
# Skipped tests
It "parses skipped tests with success"
output="OK, but some tests were skipped!
Tests: 5, Assertions: 8, Skipped: 2."
When call parse_phpunit_output "$output" 0
The line 1 of output should equal "tests_run=5"
The line 2 of output should equal "tests_passed=5"
The line 3 of output should equal "status=success"
End
# Edge cases
It "handles no parseable output (fallback)"
output="Some random output without test info"
When call parse_phpunit_output "$output" 1
The line 1 of output should equal "tests_run=0"
The line 2 of output should equal "tests_passed=0"
The line 3 of output should equal "status=failure"
End
It "handles empty output"
output=""
When call parse_phpunit_output "$output" 0
The line 1 of output should equal "tests_run=0"
The line 2 of output should equal "tests_passed=0"
The line 3 of output should equal "status=success"
End
It "handles PHPUnit 10+ format with singular test"
output="OK (1 test, 3 assertions)"
When call parse_phpunit_output "$output" 0
The line 1 of output should equal "tests_run=1"
The line 2 of output should equal "tests_passed=1"
The line 3 of output should equal "status=success"
End
It "handles verbose output with noise"
output="PHPUnit 10.5.0 by Sebastian Bergmann and contributors.
Runtime: PHP 8.3.0
..... 5 / 5 (100%)
Time: 00:00.123, Memory: 10.00 MB
OK (5 tests, 10 assertions)"
When call parse_phpunit_output "$output" 0
The line 1 of output should equal "tests_run=5"
The line 2 of output should equal "tests_passed=5"
The line 3 of output should equal "status=success"
End
It "handles failure output with full details"
output="PHPUnit 10.5.0 by Sebastian Bergmann and contributors.
..F.. 5 / 5 (100%)
Time: 00:00.234, Memory: 12.00 MB
FAILURES!
Tests: 5, Assertions: 10, Failures: 1."
When call parse_phpunit_output "$output" 1
The line 1 of output should equal "tests_run=5"
The line 2 of output should equal "tests_passed=4"
The line 3 of output should equal "status=failure"
End
# Status determination tests
It "marks as success when exit code is 0"
output="OK (3 tests, 6 assertions)"
When call parse_phpunit_output "$output" 0
The line 3 of output should equal "status=success"
End
It "marks as failure when exit code is non-zero"
output="OK (3 tests, 6 assertions)"
When call parse_phpunit_output "$output" 1
The line 3 of output should equal "status=failure"
End
It "handles skipped tests without OK prefix"
output="Tests: 5, Assertions: 8, Skipped: 2."
When call parse_phpunit_output "$output" 0
The line 1 of output should equal "tests_run=5"
The line 2 of output should equal "tests_passed=5"
The line 3 of output should equal "status=success"
End
It "handles risky tests output"
output="FAILURES!
Tests: 8, Assertions: 15, Failures: 1, Risky: 2."
When call parse_phpunit_output "$output" 1
The line 1 of output should equal "tests_run=8"
The line 2 of output should equal "tests_passed=7"
The line 3 of output should equal "status=failure"
End
End
End
End

View File

@@ -1,170 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for php-version-detect action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "php-version-detect action"
ACTION_DIR="php-version-detect"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating default-version input"
It "accepts valid PHP version"
When call validate_input_python "php-version-detect" "default-version" "8.2"
The status should be success
End
It "accepts PHP version with patch"
When call validate_input_python "php-version-detect" "default-version" "8.3.1"
The status should be success
End
It "accepts PHP 7.4"
When call validate_input_python "php-version-detect" "default-version" "7.4"
The status should be success
End
It "accepts PHP 8.0"
When call validate_input_python "php-version-detect" "default-version" "8.0"
The status should be success
End
It "accepts PHP 8.1"
When call validate_input_python "php-version-detect" "default-version" "8.1"
The status should be success
End
It "accepts PHP 8.4"
When call validate_input_python "php-version-detect" "default-version" "8.4"
The status should be success
End
It "rejects PHP version too old"
When call validate_input_python "php-version-detect" "default-version" "5.6"
The status should be failure
End
It "rejects PHP version too new"
When call validate_input_python "php-version-detect" "default-version" "10.0"
The status should be failure
End
It "rejects invalid version format"
When call validate_input_python "php-version-detect" "default-version" "php8.2"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "php-version-detect" "default-version" "8.2; rm -rf /"
The status should be failure
End
It "rejects version without minor"
When call validate_input_python "php-version-detect" "default-version" "8"
The status should be failure
End
It "rejects empty version"
When call validate_input_python "php-version-detect" "default-version" ""
The status should be failure
End
It "rejects version with v prefix"
When call validate_input_python "php-version-detect" "default-version" "v8.2"
The status should be failure
End
It "accepts PHP 8.5 for future compatibility"
When call validate_input_python "php-version-detect" "default-version" "8.5"
The status should be success
End
It "rejects unreasonably high minor version"
When call validate_input_python "php-version-detect" "default-version" "8.100"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "PHP Version Detect"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "default-version"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "php-version"
End
End
Context "when testing input requirements"
It "has default-version as optional input"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "optional"
The output should equal "optional"
End
It "has correct default version"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "default"
The output should equal "8.2"
End
End
Context "when testing security validations"
It "validates against path traversal in version"
When call validate_input_python "php-version-detect" "default-version" "../8.2"
The status should be failure
End
It "validates against shell metacharacters in version"
When call validate_input_python "php-version-detect" "default-version" "8.2|echo"
The status should be failure
End
It "validates against backtick injection"
When call validate_input_python "php-version-detect" "default-version" "8.2\`whoami\`"
The status should be failure
End
It "validates against variable expansion"
When call validate_input_python "php-version-detect" "default-version" "8.2\${HOME}"
The status should be failure
End
End
Context "when testing PHP version range validation"
It "validates PHP 7 minor version boundaries"
When call validate_input_python "php-version-detect" "default-version" "7.0"
The status should be success
End
It "validates PHP 7.4 specifically"
When call validate_input_python "php-version-detect" "default-version" "7.4"
The status should be success
End
It "validates PHP 8 minor version boundaries"
When call validate_input_python "php-version-detect" "default-version" "8.0"
The status should be success
End
It "validates PHP 8.4 boundary"
When call validate_input_python "php-version-detect" "default-version" "8.4"
The status should be success
End
It "validates PHP 9 future version"
When call validate_input_python "php-version-detect" "default-version" "9.0"
The status should be success
End
End
End

View File

@@ -1,332 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for prettier-check action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "prettier-check action"
ACTION_DIR="prettier-check"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating working-directory input"
It "accepts current directory"
When call validate_input_python "prettier-check" "working-directory" "."
The status should be success
End
It "accepts relative directory"
When call validate_input_python "prettier-check" "working-directory" "src"
The status should be success
End
It "accepts nested directory"
When call validate_input_python "prettier-check" "working-directory" "src/components"
The status should be success
End
It "rejects path traversal"
When call validate_input_python "prettier-check" "working-directory" "../malicious"
The status should be failure
End
It "rejects absolute paths"
When call validate_input_python "prettier-check" "working-directory" "/etc/passwd"
The status should be failure
End
It "rejects directory with command injection"
When call validate_input_python "prettier-check" "working-directory" "src; rm -rf /"
The status should be failure
End
End
Context "when validating prettier-version input"
It "accepts latest version"
When call validate_input_python "prettier-check" "prettier-version" "latest"
The status should be success
End
It "accepts semantic version"
When call validate_input_python "prettier-check" "prettier-version" "3.0.0"
The status should be success
End
It "accepts prerelease version"
When call validate_input_python "prettier-check" "prettier-version" "3.0.0-alpha"
The status should be success
End
It "rejects invalid version format"
When call validate_input_python "prettier-check" "prettier-version" "v3.0.0"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "prettier-check" "prettier-version" "3.0.0; rm -rf /"
The status should be failure
End
End
Context "when validating config-file input"
It "accepts valid config file"
When call validate_input_python "prettier-check" "config-file" ".prettierrc"
The status should be success
End
It "accepts config file with extension"
When call validate_input_python "prettier-check" "config-file" ".prettierrc.json"
The status should be success
End
It "accepts config file in subdirectory"
When call validate_input_python "prettier-check" "config-file" "config/.prettierrc"
The status should be success
End
It "rejects path traversal in config file"
When call validate_input_python "prettier-check" "config-file" "../../../etc/passwd"
The status should be failure
End
It "rejects absolute path in config file"
When call validate_input_python "prettier-check" "config-file" "/etc/passwd"
The status should be failure
End
End
Context "when validating ignore-file input"
It "accepts valid ignore file"
When call validate_input_python "prettier-check" "ignore-file" ".prettierignore"
The status should be success
End
It "accepts ignore file in subdirectory"
When call validate_input_python "prettier-check" "ignore-file" "config/.prettierignore"
The status should be success
End
It "rejects path traversal in ignore file"
When call validate_input_python "prettier-check" "ignore-file" "../../../etc/passwd"
The status should be failure
End
It "rejects absolute path in ignore file"
When call validate_input_python "prettier-check" "ignore-file" "/etc/passwd"
The status should be failure
End
End
Context "when validating file-pattern input"
It "accepts valid glob pattern"
When call validate_input_python "prettier-check" "file-pattern" "**/*.{js,ts}"
The status should be success
End
It "accepts simple file pattern"
When call validate_input_python "prettier-check" "file-pattern" "*.js"
The status should be success
End
It "accepts multiple extensions"
When call validate_input_python "prettier-check" "file-pattern" "**/*.{js,jsx,ts,tsx,css}"
The status should be success
End
It "rejects pattern with path traversal"
When call validate_input_python "prettier-check" "file-pattern" "../**/*.js"
The status should be failure
End
It "rejects pattern with absolute path"
When call validate_input_python "prettier-check" "file-pattern" "/etc/**/*.conf"
The status should be failure
End
End
Context "when validating boolean inputs"
It "accepts true for cache"
When call validate_input_python "prettier-check" "cache" "true"
The status should be success
End
It "accepts false for cache"
When call validate_input_python "prettier-check" "cache" "false"
The status should be success
End
It "rejects invalid cache value"
When call validate_input_python "prettier-check" "cache" "yes"
The status should be failure
End
It "accepts true for fail-on-error"
When call validate_input_python "prettier-check" "fail-on-error" "true"
The status should be success
End
It "accepts false for fail-on-error"
When call validate_input_python "prettier-check" "fail-on-error" "false"
The status should be success
End
It "accepts true for check-only"
When call validate_input_python "prettier-check" "check-only" "true"
The status should be success
End
It "accepts false for check-only"
When call validate_input_python "prettier-check" "check-only" "false"
The status should be success
End
End
Context "when validating report-format input"
It "accepts json format"
When call validate_input_python "prettier-check" "report-format" "json"
The status should be success
End
It "accepts sarif format"
When call validate_input_python "prettier-check" "report-format" "sarif"
The status should be success
End
It "rejects invalid format"
When call validate_input_python "prettier-check" "report-format" "xml"
The status should be failure
End
It "rejects empty format"
When call validate_input_python "prettier-check" "report-format" ""
The status should be failure
End
End
Context "when validating max-retries input"
It "accepts valid retry count"
When call validate_input_python "prettier-check" "max-retries" "3"
The status should be success
End
It "accepts minimum retries"
When call validate_input_python "prettier-check" "max-retries" "1"
The status should be success
End
It "accepts maximum retries"
When call validate_input_python "prettier-check" "max-retries" "10"
The status should be success
End
It "rejects zero retries"
When call validate_input_python "prettier-check" "max-retries" "0"
The status should be failure
End
It "rejects too many retries"
When call validate_input_python "prettier-check" "max-retries" "11"
The status should be failure
End
It "rejects non-numeric retries"
When call validate_input_python "prettier-check" "max-retries" "many"
The status should be failure
End
End
Context "when validating plugins input"
It "accepts empty plugins"
When call validate_input_python "prettier-check" "plugins" ""
The status should be success
End
It "accepts valid plugin name"
When call validate_input_python "prettier-check" "plugins" "prettier-plugin-java"
The status should be success
End
It "accepts scoped plugin"
When call validate_input_python "prettier-check" "plugins" "@prettier/plugin-xml"
The status should be success
End
It "accepts multiple plugins"
When call validate_input_python "prettier-check" "plugins" "plugin1,@scope/plugin2"
The status should be success
End
It "rejects plugins with command injection"
When call validate_input_python "prettier-check" "plugins" "plugin1; rm -rf /"
The status should be failure
End
It "rejects plugins with shell operators"
When call validate_input_python "prettier-check" "plugins" "plugin1 && malicious"
The status should be failure
End
It "rejects plugins with pipe"
When call validate_input_python "prettier-check" "plugins" "plugin1 | cat /etc/passwd"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Prettier Check"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "working-directory"
The output should include "prettier-version"
The output should include "config-file"
The output should include "ignore-file"
The output should include "file-pattern"
The output should include "cache"
The output should include "fail-on-error"
The output should include "report-format"
The output should include "max-retries"
The output should include "plugins"
The output should include "check-only"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "files-checked"
The output should include "unformatted-files"
The output should include "sarif-file"
The output should include "cache-hit"
End
End
Context "when testing input requirements"
It "has all inputs as optional"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "any" "all_optional"
The output should equal "none"
End
End
Context "when testing security validations"
It "validates against path traversal in multiple inputs"
When call validate_input_python "prettier-check" "working-directory" "../../malicious"
The status should be failure
End
It "validates against command injection in plugins"
When call validate_input_python "prettier-check" "plugins" "plugin\`whoami\`"
The status should be failure
End
It "validates against shell expansion in file patterns"
When call validate_input_python "prettier-check" "file-pattern" "**/*.js\${HOME}"
The status should be failure
End
End
End

View File

@@ -1,285 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for prettier-fix action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "prettier-fix action"
ACTION_DIR="prettier-fix"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating token input"
It "accepts GitHub token expression"
When call validate_input_python "prettier-fix" "token" "\${{ github.token }}"
The status should be success
End
It "accepts GitHub fine-grained token"
When call validate_input_python "prettier-fix" "token" "ghp_abcdefghijklmnopqrstuvwxyz1234567890"
The status should be success
End
It "accepts GitHub app token"
When call validate_input_python "prettier-fix" "token" "ghs_abcdefghijklmnopqrstuvwxyz1234567890"
The status should be success
End
It "accepts GitHub enterprise token"
When call validate_input_python "prettier-fix" "token" "ghe_abcdefghijklmnopqrstuvwxyz1234567890"
The status should be success
End
It "rejects invalid token format"
When call validate_input_python "prettier-fix" "token" "invalid-token"
The status should be failure
End
It "rejects token with command injection"
When call validate_input_python "prettier-fix" "token" "ghp_token; rm -rf /"
The status should be failure
End
It "accepts empty token (uses default)"
When call validate_input_python "prettier-fix" "token" ""
The status should be success
End
End
Context "when validating username input"
It "accepts valid GitHub username"
When call validate_input_python "prettier-fix" "username" "github-actions"
The status should be success
End
It "accepts username with hyphens"
When call validate_input_python "prettier-fix" "username" "user-name"
The status should be success
End
It "accepts username with numbers"
When call validate_input_python "prettier-fix" "username" "user123"
The status should be success
End
It "accepts single character username"
When call validate_input_python "prettier-fix" "username" "a"
The status should be success
End
It "accepts maximum length username"
When call validate_input_python "prettier-fix" "username" "abcdefghijklmnopqrstuvwxyz0123456789abc"
The status should be success
End
It "rejects username too long"
When call validate_input_python "prettier-fix" "username" "abcdefghijklmnopqrstuvwxyz0123456789abcd"
The status should be failure
End
It "rejects username with command injection"
When call validate_input_python "prettier-fix" "username" "user; rm -rf /"
The status should be failure
End
It "rejects username with shell operators"
When call validate_input_python "prettier-fix" "username" "user && rm -rf /"
The status should be failure
End
It "rejects username with pipe"
When call validate_input_python "prettier-fix" "username" "user | rm -rf /"
The status should be failure
End
It "accepts empty username (uses default)"
When call validate_input_python "prettier-fix" "username" ""
The status should be success
End
End
Context "when validating email input"
It "accepts valid email"
When call validate_input_python "prettier-fix" "email" "user@example.com"
The status should be success
End
It "accepts email with subdomain"
When call validate_input_python "prettier-fix" "email" "user@mail.example.com"
The status should be success
End
It "accepts email with plus sign"
When call validate_input_python "prettier-fix" "email" "user+tag@example.com"
The status should be success
End
It "accepts email with numbers"
When call validate_input_python "prettier-fix" "email" "user123@example123.com"
The status should be success
End
It "accepts email with hyphens"
When call validate_input_python "prettier-fix" "email" "user-name@example-domain.com"
The status should be success
End
It "rejects email without at symbol"
When call validate_input_python "prettier-fix" "email" "userexample.com"
The status should be failure
End
It "rejects email without domain"
When call validate_input_python "prettier-fix" "email" "user@"
The status should be failure
End
It "rejects email without username"
When call validate_input_python "prettier-fix" "email" "@example.com"
The status should be failure
End
It "rejects email without dot in domain"
When call validate_input_python "prettier-fix" "email" "user@example"
The status should be failure
End
It "rejects email with spaces"
When call validate_input_python "prettier-fix" "email" "user @example.com"
The status should be failure
End
It "rejects empty email"
When call validate_input_python "prettier-fix" "email" ""
The status should be failure
End
End
Context "when validating max-retries input"
It "accepts valid retry count"
When call validate_input_python "prettier-fix" "max-retries" "3"
The status should be success
End
It "accepts minimum retries"
When call validate_input_python "prettier-fix" "max-retries" "1"
The status should be success
End
It "accepts maximum retries"
When call validate_input_python "prettier-fix" "max-retries" "10"
The status should be success
End
It "rejects zero retries"
When call validate_input_python "prettier-fix" "max-retries" "0"
The status should be failure
End
It "rejects too many retries"
When call validate_input_python "prettier-fix" "max-retries" "11"
The status should be failure
End
It "rejects non-numeric retries"
When call validate_input_python "prettier-fix" "max-retries" "many"
The status should be failure
End
It "rejects negative retries"
When call validate_input_python "prettier-fix" "max-retries" "-1"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Prettier Fix"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "token"
The output should include "username"
The output should include "email"
The output should include "max-retries"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "files_changed"
The output should include "format_status"
End
End
Context "when testing input requirements"
It "has all inputs as optional"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "" "all_optional"
The output should equal "none"
End
It "has correct default token"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "token" "default"
The output should equal "\${{ github.token }}"
End
It "has correct default username"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "username" "default"
The output should equal "github-actions"
End
It "has correct default email"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "email" "default"
The output should equal "github-actions@github.com"
End
End
Context "when testing security validations"
It "validates against command injection in username"
When call validate_input_python "prettier-fix" "username" "user\`whoami\`"
The status should be failure
End
It "validates against shell metacharacters in email"
When call validate_input_python "prettier-fix" "email" "user@example.com; rm -rf /"
The status should be failure
End
It "validates against variable expansion in token"
When call validate_input_python "prettier-fix" "token" "\${MALICIOUS_VAR}"
The status should be failure
End
It "validates against backtick injection in email"
When call validate_input_python "prettier-fix" "email" "user@example.com\`echo test\`"
The status should be failure
End
End
Context "when testing Prettier-specific validations"
It "validates username length boundaries for Git"
When call validate_input_python "prettier-fix" "username" "$(awk 'BEGIN{for(i=1;i<=40;i++)printf "a"}')"
The status should be failure
End
It "validates email format for Git commits"
When call validate_input_python "prettier-fix" "email" "noreply@github.com"
The status should be success
End
It "validates retry count boundaries"
When call validate_input_python "prettier-fix" "max-retries" "0"
The status should be failure
End
It "validates default values are secure"
When call validate_input_python "prettier-fix" "username" "github-actions"
The status should be success
End
End
End

View File

@@ -0,0 +1,515 @@
#!/usr/bin/env shellspec
# Unit tests for prettier-lint action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "prettier-lint action"
ACTION_DIR="prettier-lint"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating mode input"
It "accepts check mode"
When call validate_input_python "prettier-lint" "mode" "check"
The status should be success
End
It "accepts fix mode"
When call validate_input_python "prettier-lint" "mode" "fix"
The status should be success
End
It "accepts empty mode (uses default)"
When call validate_input_python "prettier-lint" "mode" ""
The status should be success
End
It "rejects invalid mode"
When call validate_input_python "prettier-lint" "mode" "invalid"
The status should be failure
End
It "rejects mode with command injection"
When call validate_input_python "prettier-lint" "mode" "check; rm -rf /"
The status should be failure
End
End
Context "when validating working-directory input"
It "accepts default directory"
When call validate_input_python "prettier-lint" "working-directory" "."
The status should be success
End
It "accepts valid subdirectory"
When call validate_input_python "prettier-lint" "working-directory" "src"
The status should be success
End
It "accepts empty working-directory (uses default)"
When call validate_input_python "prettier-lint" "working-directory" ""
The status should be success
End
It "rejects path traversal"
When call validate_input_python "prettier-lint" "working-directory" "../../../etc"
The status should be failure
End
It "rejects directory with command injection"
When call validate_input_python "prettier-lint" "working-directory" "src; rm -rf /"
The status should be failure
End
End
Context "when validating prettier-version input"
It "accepts latest version"
When call validate_input_python "prettier-lint" "prettier-version" "latest"
The status should be success
End
It "accepts semantic version"
When call validate_input_python "prettier-lint" "prettier-version" "3.2.5"
The status should be success
End
It "accepts major.minor version"
When call validate_input_python "prettier-lint" "prettier-version" "3.2"
The status should be success
End
It "accepts major version"
When call validate_input_python "prettier-lint" "prettier-version" "3"
The status should be success
End
It "accepts version with pre-release"
When call validate_input_python "prettier-lint" "prettier-version" "3.0.0-alpha.1"
The status should be success
End
It "accepts empty version (uses default)"
When call validate_input_python "prettier-lint" "prettier-version" ""
The status should be success
End
It "rejects invalid version format"
When call validate_input_python "prettier-lint" "prettier-version" "invalid"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "prettier-lint" "prettier-version" "3.2.5; echo"
The status should be failure
End
End
Context "when validating config-file input"
It "accepts default config file"
When call validate_input_python "prettier-lint" "config-file" ".prettierrc"
The status should be success
End
It "accepts custom config file"
When call validate_input_python "prettier-lint" "config-file" ".prettierrc.js"
The status should be success
End
It "accepts config file in subdirectory"
When call validate_input_python "prettier-lint" "config-file" "config/prettier.config.js"
The status should be success
End
It "accepts empty config-file (uses default)"
When call validate_input_python "prettier-lint" "config-file" ""
The status should be success
End
It "rejects config file with path traversal"
When call validate_input_python "prettier-lint" "config-file" "../../../.prettierrc"
The status should be failure
End
It "rejects config file with command injection"
When call validate_input_python "prettier-lint" "config-file" ".prettierrc; rm -rf /"
The status should be failure
End
End
Context "when validating ignore-file input"
It "accepts default ignore file"
When call validate_input_python "prettier-lint" "ignore-file" ".prettierignore"
The status should be success
End
It "accepts custom ignore file"
When call validate_input_python "prettier-lint" "ignore-file" ".prettierignore.custom"
The status should be success
End
It "accepts empty ignore-file (uses default)"
When call validate_input_python "prettier-lint" "ignore-file" ""
The status should be success
End
It "rejects ignore file with path traversal"
When call validate_input_python "prettier-lint" "ignore-file" "../../../.prettierignore"
The status should be failure
End
It "rejects ignore file with command injection"
When call validate_input_python "prettier-lint" "ignore-file" ".prettierignore; echo"
The status should be failure
End
End
Context "when validating file-pattern input"
It "accepts default pattern"
When call validate_input_python "prettier-lint" "file-pattern" "**/*.{js,jsx,ts,tsx,css,scss,json,md,yaml,yml}"
The status should be success
End
It "accepts simple pattern"
When call validate_input_python "prettier-lint" "file-pattern" "**/*.js"
The status should be success
End
It "accepts multiple patterns"
When call validate_input_python "prettier-lint" "file-pattern" "**/*.{js,ts}"
The status should be success
End
It "accepts specific directory pattern"
When call validate_input_python "prettier-lint" "file-pattern" "src/**/*.js"
The status should be success
End
It "accepts empty file-pattern (uses default)"
When call validate_input_python "prettier-lint" "file-pattern" ""
The status should be success
End
It "rejects pattern with command injection"
When call validate_input_python "prettier-lint" "file-pattern" "**/*.js; rm -rf /"
The status should be failure
End
End
Context "when validating cache input"
It "accepts true"
When call validate_input_python "prettier-lint" "cache" "true"
The status should be success
End
It "accepts false"
When call validate_input_python "prettier-lint" "cache" "false"
The status should be success
End
It "accepts empty cache (uses default)"
When call validate_input_python "prettier-lint" "cache" ""
The status should be success
End
It "rejects invalid boolean value"
When call validate_input_python "prettier-lint" "cache" "maybe"
The status should be failure
End
End
Context "when validating fail-on-error input"
It "accepts true"
When call validate_input_python "prettier-lint" "fail-on-error" "true"
The status should be success
End
It "accepts false"
When call validate_input_python "prettier-lint" "fail-on-error" "false"
The status should be success
End
It "accepts empty fail-on-error (uses default)"
When call validate_input_python "prettier-lint" "fail-on-error" ""
The status should be success
End
It "rejects invalid boolean value"
When call validate_input_python "prettier-lint" "fail-on-error" "yes"
The status should be failure
End
End
Context "when validating report-format input"
It "accepts json format"
When call validate_input_python "prettier-lint" "report-format" "json"
The status should be success
End
It "accepts sarif format"
When call validate_input_python "prettier-lint" "report-format" "sarif"
The status should be success
End
It "accepts empty report-format (uses default)"
When call validate_input_python "prettier-lint" "report-format" ""
The status should be success
End
It "rejects invalid format"
When call validate_input_python "prettier-lint" "report-format" "invalid"
The status should be failure
End
It "rejects format with command injection"
When call validate_input_python "prettier-lint" "report-format" "json; rm -rf /"
The status should be failure
End
End
Context "when validating max-retries input"
It "accepts default value 3"
When call validate_input_python "prettier-lint" "max-retries" "3"
The status should be success
End
It "accepts retry count of 1"
When call validate_input_python "prettier-lint" "max-retries" "1"
The status should be success
End
It "accepts retry count of 10"
When call validate_input_python "prettier-lint" "max-retries" "10"
The status should be success
End
It "accepts empty max-retries (uses default)"
When call validate_input_python "prettier-lint" "max-retries" ""
The status should be success
End
It "rejects zero retries"
When call validate_input_python "prettier-lint" "max-retries" "0"
The status should be failure
End
It "rejects negative retry count"
When call validate_input_python "prettier-lint" "max-retries" "-1"
The status should be failure
End
It "rejects retry count above 10"
When call validate_input_python "prettier-lint" "max-retries" "11"
The status should be failure
End
It "rejects non-numeric retry count"
When call validate_input_python "prettier-lint" "max-retries" "abc"
The status should be failure
End
It "rejects retry count with command injection"
When call validate_input_python "prettier-lint" "max-retries" "3; echo"
The status should be failure
End
End
Context "when validating plugins input"
It "accepts empty plugins (optional)"
When call validate_input_python "prettier-lint" "plugins" ""
The status should be success
End
It "accepts single plugin"
When call validate_input_python "prettier-lint" "plugins" "prettier-plugin-tailwindcss"
The status should be success
End
It "accepts multiple plugins"
When call validate_input_python "prettier-lint" "plugins" "prettier-plugin-tailwindcss,prettier-plugin-organize-imports"
The status should be success
End
It "accepts scoped plugin"
When call validate_input_python "prettier-lint" "plugins" "@trivago/prettier-plugin-sort-imports"
The status should be success
End
It "rejects plugins with command injection"
When call validate_input_python "prettier-lint" "plugins" "prettier-plugin-tailwindcss; rm -rf /"
The status should be failure
End
End
Context "when validating token input"
It "accepts valid GitHub token (classic)"
When call validate_input_python "prettier-lint" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "accepts valid GitHub fine-grained token"
When call validate_input_python "prettier-lint" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
The status should be success
End
It "accepts empty token (optional)"
When call validate_input_python "prettier-lint" "token" ""
The status should be success
End
It "rejects invalid token format"
When call validate_input_python "prettier-lint" "token" "invalid-token"
The status should be failure
End
It "rejects token with command injection"
When call validate_input_python "prettier-lint" "token" "ghp_123456789012345678901234567890123456; rm -rf /"
The status should be failure
End
End
Context "when validating username input"
It "accepts valid username"
When call validate_input_python "prettier-lint" "username" "github-actions"
The status should be success
End
It "accepts username with hyphens"
When call validate_input_python "prettier-lint" "username" "my-bot-user"
The status should be success
End
It "accepts alphanumeric username"
When call validate_input_python "prettier-lint" "username" "user123"
The status should be success
End
It "accepts empty username (uses default)"
When call validate_input_python "prettier-lint" "username" ""
The status should be success
End
It "rejects username with command injection"
When call validate_input_python "prettier-lint" "username" "user; rm -rf /"
The status should be failure
End
It "rejects username with special characters"
When call validate_input_python "prettier-lint" "username" "user@bot"
The status should be failure
End
End
Context "when validating email input"
It "accepts valid email"
When call validate_input_python "prettier-lint" "email" "github-actions@github.com"
The status should be success
End
It "accepts email with plus sign"
When call validate_input_python "prettier-lint" "email" "user+bot@example.com"
The status should be success
End
It "accepts email with subdomain"
When call validate_input_python "prettier-lint" "email" "bot@ci.example.com"
The status should be success
End
It "accepts empty email (uses default)"
When call validate_input_python "prettier-lint" "email" ""
The status should be success
End
It "rejects invalid email format"
When call validate_input_python "prettier-lint" "email" "not-an-email"
The status should be failure
End
It "rejects email with command injection"
When call validate_input_python "prettier-lint" "email" "user@example.com; rm -rf /"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Prettier Lint"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "mode"
The output should include "working-directory"
The output should include "prettier-version"
The output should include "config-file"
The output should include "ignore-file"
The output should include "file-pattern"
The output should include "cache"
The output should include "fail-on-error"
The output should include "report-format"
The output should include "max-retries"
The output should include "plugins"
The output should include "token"
The output should include "username"
The output should include "email"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "status"
The output should include "files-checked"
The output should include "unformatted-files"
The output should include "sarif-file"
The output should include "files-changed"
End
End
Context "when testing input requirements"
It "has all inputs as optional (with defaults)"
When call is_input_required "$ACTION_FILE" "mode"
The status should be failure
End
End
Context "when testing security validations"
It "validates against path traversal in working-directory"
When call validate_input_python "prettier-lint" "working-directory" "../../../etc"
The status should be failure
End
It "validates against shell metacharacters in mode"
When call validate_input_python "prettier-lint" "mode" "check|echo"
The status should be failure
End
It "validates against command substitution in config-file"
When call validate_input_python "prettier-lint" "config-file" "\$(whoami)"
The status should be failure
End
It "validates against path traversal in token"
When call validate_input_python "prettier-lint" "token" "../../../etc/passwd"
The status should be failure
End
It "validates against shell metacharacters in username"
When call validate_input_python "prettier-lint" "username" "user&whoami"
The status should be failure
End
It "validates against command injection in email"
When call validate_input_python "prettier-lint" "email" "user@example.com\`whoami\`"
The status should be failure
End
It "validates against command injection in plugins"
When call validate_input_python "prettier-lint" "plugins" "plugin1,plugin2; rm -rf /"
The status should be failure
End
End
End

View File

@@ -1,98 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for python-version-detect-v2 action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "python-version-detect-v2 action"
ACTION_DIR="python-version-detect-v2"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating default-version input"
It "accepts valid Python version"
When call validate_input_python "python-version-detect-v2" "default-version" "3.11"
The status should be success
End
It "accepts Python version with patch"
When call validate_input_python "python-version-detect-v2" "default-version" "3.11.5"
The status should be success
End
It "accepts Python 3.8"
When call validate_input_python "python-version-detect-v2" "default-version" "3.8"
The status should be success
End
It "accepts Python 3.12"
When call validate_input_python "python-version-detect-v2" "default-version" "3.12"
The status should be success
End
It "rejects Python version too old"
When call validate_input_python "python-version-detect-v2" "default-version" "2.7"
The status should be failure
End
It "rejects invalid version format"
When call validate_input_python "python-version-detect-v2" "default-version" "python3.11"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "python-version-detect-v2" "default-version" "3.11; rm -rf /"
The status should be failure
End
It "rejects empty version"
When call validate_input_python "python-version-detect-v2" "default-version" ""
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Python Version Detect v2"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "default-version"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "python-version"
End
End
Context "when testing input requirements"
It "has default-version as optional input"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "optional"
The output should equal "optional"
End
End
Context "when testing security validations"
It "validates against path traversal in version"
When call validate_input_python "python-version-detect-v2" "default-version" "../3.11"
The status should be failure
End
It "validates against shell metacharacters in version"
When call validate_input_python "python-version-detect-v2" "default-version" "3.11|echo"
The status should be failure
End
It "validates against backtick injection"
When call validate_input_python "python-version-detect-v2" "default-version" "3.11\`whoami\`"
The status should be failure
End
End
End

View File

@@ -1,108 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for python-version-detect action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "python-version-detect action"
ACTION_DIR="python-version-detect"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating default-version input"
It "accepts valid Python version"
When call validate_input_python "python-version-detect" "default-version" "3.11"
The status should be success
End
It "accepts Python version with patch"
When call validate_input_python "python-version-detect" "default-version" "3.11.5"
The status should be success
End
It "accepts Python 3.8"
When call validate_input_python "python-version-detect" "default-version" "3.8"
The status should be success
End
It "accepts Python 3.12"
When call validate_input_python "python-version-detect" "default-version" "3.12"
The status should be success
End
It "rejects Python version too old"
When call validate_input_python "python-version-detect" "default-version" "2.7"
The status should be failure
End
It "rejects Python version too new"
When call validate_input_python "python-version-detect" "default-version" "4.0"
The status should be failure
End
It "rejects invalid version format"
When call validate_input_python "python-version-detect" "default-version" "python3.11"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "python-version-detect" "default-version" "3.11; rm -rf /"
The status should be failure
End
It "rejects version without minor"
When call validate_input_python "python-version-detect" "default-version" "3"
The status should be failure
End
It "rejects empty version"
When call validate_input_python "python-version-detect" "default-version" ""
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Python Version Detect"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "default-version"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "python-version"
End
End
Context "when testing input requirements"
It "has default-version as optional input"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "optional"
The output should equal "optional"
End
End
Context "when testing security validations"
It "validates against path traversal in version"
When call validate_input_python "python-version-detect" "default-version" "../3.11"
The status should be failure
End
It "validates against shell metacharacters in version"
When call validate_input_python "python-version-detect" "default-version" "3.11|echo"
The status should be failure
End
It "validates against backtick injection"
When call validate_input_python "python-version-detect" "default-version" "3.11\`whoami\`"
The status should be failure
End
End
End

View File

@@ -0,0 +1,116 @@
#!/usr/bin/env shellspec
# Unit tests for security-scan action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "security-scan action"
ACTION_DIR="security-scan"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating token input"
It "accepts valid GitHub token"
When call validate_input_python "security-scan" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "rejects injection in token"
When call validate_input_python "security-scan" "token" "token; rm -rf /"
The status should be failure
End
It "accepts empty token (optional)"
When call validate_input_python "security-scan" "token" ""
The status should be success
End
End
Context "when validating actionlint-enabled input"
It "accepts true value"
When call validate_input_python "security-scan" "actionlint-enabled" "true"
The status should be success
End
It "accepts false value"
When call validate_input_python "security-scan" "actionlint-enabled" "false"
The status should be success
End
It "rejects non-boolean value"
When call validate_input_python "security-scan" "actionlint-enabled" "maybe"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Security Scan"
End
It "defines all expected inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "gitleaks-license"
The output should include "gitleaks-config"
The output should include "trivy-severity"
The output should include "trivy-scanners"
The output should include "trivy-timeout"
The output should include "actionlint-enabled"
The output should include "token"
End
It "defines all expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "has_trivy_results"
The output should include "has_gitleaks_results"
The output should include "total_issues"
The output should include "critical_issues"
End
It "uses composite run type"
run_type=$(get_action_runs_using "$ACTION_FILE")
When call echo "$run_type"
The output should equal "composite"
End
End
Context "when validating inputs per conventions"
It "validates token against github_token convention"
When call validate_input_python "security-scan" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "validates actionlint-enabled as boolean"
When call validate_input_python "security-scan" "actionlint-enabled" "true"
The status should be success
End
It "rejects invalid boolean for actionlint-enabled"
When call validate_input_python "security-scan" "actionlint-enabled" "1"
The status should be failure
End
End
Context "when testing optional inputs"
It "accepts empty gitleaks-license"
When call validate_input_python "security-scan" "gitleaks-license" ""
The status should be success
End
It "accepts empty token"
When call validate_input_python "security-scan" "token" ""
The status should be success
End
It "accepts valid gitleaks-license value"
When call validate_input_python "security-scan" "gitleaks-license" "license-key-123"
The status should be success
End
End
End

View File

@@ -1,69 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for set-git-config action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "set-git-config action"
ACTION_DIR="set-git-config"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating inputs (no validation logic in action)"
# NOTE: This action has no validation logic - all inputs are accepted
# The action simply passes through values and conditionally sets outputs
It "accepts valid token value"
When call validate_input_python "set-git-config" "token" "ghp_123456789012345678901234567890123456"
The status should be success
End
It "accepts any username value"
When call validate_input_python "set-git-config" "username" "any-username"
The status should be success
End
It "accepts valid email value"
When call validate_input_python "set-git-config" "email" "test@example.com"
The status should be success
End
It "accepts any is_fiximus value"
When call validate_input_python "set-git-config" "is_fiximus" "any-value"
The status should be success
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should equal "Set Git Config"
End
It "defines required inputs"
inputs=$(get_action_inputs "$ACTION_FILE")
When call echo "$inputs"
The output should include "token"
The output should include "username"
The output should include "email"
The output should include "is_fiximus"
End
It "defines expected outputs"
outputs=$(get_action_outputs "$ACTION_FILE")
When call echo "$outputs"
The output should include "token"
The output should include "username"
The output should include "email"
The output should include "is_fiximus"
End
End
Context "when testing outputs"
It "produces all expected outputs"
When call test_action_outputs "$ACTION_DIR" "token" "ghp_test" "username" "test" "email" "test@example.com" "is_fiximus" "false"
The status should be success
The stderr should include "Testing action outputs for: set-git-config"
The stderr should include "Output test passed for: set-git-config"
End
End
End

View File

@@ -92,10 +92,6 @@ setup_default_inputs() {
"go-build" | "go-lint")
[[ "$input_name" != "go-version" ]] && export INPUT_GO_VERSION="1.21"
;;
"common-cache")
[[ "$input_name" != "type" ]] && export INPUT_TYPE="npm"
[[ "$input_name" != "paths" ]] && export INPUT_PATHS="node_modules"
;;
"common-retry")
[[ "$input_name" != "command" ]] && export INPUT_COMMAND="echo test"
;;
@@ -114,11 +110,6 @@ setup_default_inputs() {
"validate-inputs")
[[ "$input_name" != "action-type" && "$input_name" != "action" && "$input_name" != "rules-file" && "$input_name" != "fail-on-error" ]] && export INPUT_ACTION_TYPE="test-action"
;;
"version-file-parser")
[[ "$input_name" != "language" ]] && export INPUT_LANGUAGE="node"
[[ "$input_name" != "tool-versions-key" ]] && export INPUT_TOOL_VERSIONS_KEY="nodejs"
[[ "$input_name" != "dockerfile-image" ]] && export INPUT_DOCKERFILE_IMAGE="node"
;;
"codeql-analysis")
[[ "$input_name" != "language" ]] && export INPUT_LANGUAGE="javascript"
[[ "$input_name" != "token" ]] && export INPUT_TOKEN="ghp_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
@@ -163,10 +154,6 @@ cleanup_default_inputs() {
"go-build" | "go-lint")
[[ "$input_name" != "go-version" ]] && unset INPUT_GO_VERSION
;;
"common-cache")
[[ "$input_name" != "type" ]] && unset INPUT_TYPE
[[ "$input_name" != "paths" ]] && unset INPUT_PATHS
;;
"common-retry")
[[ "$input_name" != "command" ]] && unset INPUT_COMMAND
;;
@@ -185,11 +172,6 @@ cleanup_default_inputs() {
"validate-inputs")
[[ "$input_name" != "action-type" && "$input_name" != "action" && "$input_name" != "rules-file" && "$input_name" != "fail-on-error" ]] && unset INPUT_ACTION_TYPE
;;
"version-file-parser")
[[ "$input_name" != "language" ]] && unset INPUT_LANGUAGE
[[ "$input_name" != "tool-versions-key" ]] && unset INPUT_TOOL_VERSIONS_KEY
[[ "$input_name" != "dockerfile-image" ]] && unset INPUT_DOCKERFILE_IMAGE
;;
"codeql-analysis")
[[ "$input_name" != "language" ]] && unset INPUT_LANGUAGE
[[ "$input_name" != "token" ]] && unset INPUT_TOKEN
@@ -244,10 +226,6 @@ shellspec_mock_action_run() {
action_name=$(basename "$action_dir")
case "$action_name" in
"version-file-parser")
echo "detected-version=1.0.0" >>"$GITHUB_OUTPUT"
echo "package-manager=npm" >>"$GITHUB_OUTPUT"
;;
"node-setup")
echo "node-version=18.0.0" >>"$GITHUB_OUTPUT"
echo "package-manager=npm" >>"$GITHUB_OUTPUT"
@@ -258,11 +236,6 @@ shellspec_mock_action_run() {
echo "build-time=45" >>"$GITHUB_OUTPUT"
echo "platforms=linux/amd64" >>"$GITHUB_OUTPUT"
;;
"common-cache")
echo "cache-hit=true" >>"$GITHUB_OUTPUT"
echo "cache-key=Linux-npm-abc123" >>"$GITHUB_OUTPUT"
echo "cache-paths=node_modules" >>"$GITHUB_OUTPUT"
;;
"common-file-check")
echo "found=true" >>"$GITHUB_OUTPUT"
;;

View File

@@ -1,125 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for version-file-parser action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "version-file-parser action"
ACTION_DIR="version-file-parser"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating language input"
It "accepts valid language input"
When call validate_input_python "version-file-parser" "language" "node"
The status should be success
End
It "accepts php language"
When call validate_input_python "version-file-parser" "language" "php"
The status should be success
End
It "accepts python language"
When call validate_input_python "version-file-parser" "language" "python"
The status should be success
End
It "accepts go language"
When call validate_input_python "version-file-parser" "language" "go"
The status should be success
End
It "rejects invalid language with special characters"
When call validate_input_python "version-file-parser" "language" "node; rm -rf /"
The status should be failure
End
It "rejects empty required inputs"
When call validate_input_python "version-file-parser" "language" ""
The status should be failure
End
End
Context "when validating dockerfile-image input"
It "accepts valid dockerfile image"
When call validate_input_python "version-file-parser" "dockerfile-image" "node"
The status should be success
End
It "accepts php dockerfile image"
When call validate_input_python "version-file-parser" "dockerfile-image" "php"
The status should be success
End
It "accepts python dockerfile image"
When call validate_input_python "version-file-parser" "dockerfile-image" "python"
The status should be success
End
It "rejects injection in dockerfile image"
When call validate_input_python "version-file-parser" "dockerfile-image" "node;malicious"
The status should be failure
End
End
Context "when validating optional inputs"
It "accepts valid validation regex"
When call validate_input_python "version-file-parser" "validation-regex" "^[0-9]+\.[0-9]+(\.[0-9]+)?$"
The status should be success
End
It "accepts valid default version"
When call validate_input_python "version-file-parser" "default-version" "18.0.0"
The status should be success
End
It "accepts tool versions key"
When call validate_input_python "version-file-parser" "tool-versions-key" "nodejs"
The status should be success
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "contains required metadata"
When call get_action_name "$ACTION_FILE"
The output should equal "Version File Parser"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "language"
The output should include "tool-versions-key"
The output should include "dockerfile-image"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "detected-version"
The output should include "package-manager"
End
End
Context "when validating security"
It "rejects injection in language parameter"
When call validate_input_python "version-file-parser" "language" "node&&malicious"
The status should be failure
End
It "rejects pipe injection in tool versions key"
When call validate_input_python "version-file-parser" "tool-versions-key" "nodejs|dangerous"
The status should be failure
End
It "validates regex patterns safely"
When call validate_input_python "version-file-parser" "validation-regex" "^[0-9]+\.[0-9]+$"
The status should be success
End
It "rejects malicious regex patterns"
When call validate_input_python "version-file-parser" "validation-regex" ".*; rm -rf /"
The status should be failure
End
End
Context "when testing outputs"
It "produces all expected outputs consistently"
When call test_action_outputs "$ACTION_DIR" "language" "node" "dockerfile-image" "node"
The status should be success
The stderr should include "Testing action outputs for: version-file-parser"
The stderr should include "Output test passed for: version-file-parser"
End
End
End

View File

@@ -1,233 +0,0 @@
#!/usr/bin/env shellspec
# Unit tests for version-validator action validation and logic
# Framework is automatically loaded via spec_helper.sh
Describe "version-validator action"
ACTION_DIR="version-validator"
ACTION_FILE="$ACTION_DIR/action.yml"
Context "when validating version input"
It "accepts valid semantic version"
When call validate_input_python "version-validator" "version" "1.2.3"
The status should be success
End
It "accepts semantic version with v prefix"
When call validate_input_python "version-validator" "version" "v1.2.3"
The status should be success
End
It "accepts prerelease version"
When call validate_input_python "version-validator" "version" "1.2.3-alpha"
The status should be success
End
It "accepts prerelease with number"
When call validate_input_python "version-validator" "version" "1.2.3-alpha.1"
The status should be success
End
It "accepts build metadata"
When call validate_input_python "version-validator" "version" "1.2.3+build.1"
The status should be success
End
It "accepts prerelease with build metadata"
When call validate_input_python "version-validator" "version" "1.2.3-alpha.1+build.1"
The status should be success
End
It "accepts CalVer format"
When call validate_input_python "version-validator" "version" "2024.3.1"
The status should be success
End
It "rejects invalid version format"
When call validate_input_python "version-validator" "version" "invalid.version"
The status should be failure
End
It "rejects version with command injection"
When call validate_input_python "version-validator" "version" "1.2.3; rm -rf /"
The status should be failure
End
It "rejects version with shell expansion"
When call validate_input_python "version-validator" "version" "1.2.3\$(whoami)"
The status should be failure
End
It "rejects empty version"
When call validate_input_python "version-validator" "version" ""
The status should be failure
End
End
Context "when validating validation-regex input"
It "accepts valid regex pattern"
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+\.[0-9]+\.[0-9]+$"
The status should be success
End
It "accepts semantic version regex"
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$"
The status should be success
End
It "accepts empty validation-regex (uses default)"
When call validate_input_python "version-validator" "validation-regex" ""
The status should be success
End
It "accepts valid regex patterns with quantifiers"
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+\\.[0-9]+$"
The status should be success
End
It "rejects regex with command injection"
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+$; rm -rf /"
The status should be failure
End
End
Context "when validating ReDoS patterns"
It "rejects nested quantifiers (a+)+"
When call validate_input_python "version-validator" "validation-regex" "(a+)+"
The status should be failure
End
It "rejects nested quantifiers (a*)+"
When call validate_input_python "version-validator" "validation-regex" "(a*)+"
The status should be failure
End
It "rejects nested quantifiers (a+)*"
When call validate_input_python "version-validator" "validation-regex" "(a+)*"
The status should be failure
End
It "rejects nested quantifiers (a*)*"
When call validate_input_python "version-validator" "validation-regex" "(a*)*"
The status should be failure
End
It "rejects quantified groups (a+){2,5}"
When call validate_input_python "version-validator" "validation-regex" "(a+){2,5}"
The status should be failure
End
It "rejects consecutive quantifiers .*.* (ReDoS)"
When call validate_input_python "version-validator" "validation-regex" ".*.*"
The status should be failure
End
It "rejects consecutive quantifiers .*+ (ReDoS)"
When call validate_input_python "version-validator" "validation-regex" ".*+"
The status should be failure
End
It "rejects duplicate alternatives (a|a)+"
When call validate_input_python "version-validator" "validation-regex" "(a|a)+"
The status should be failure
End
It "rejects overlapping alternatives (a|ab)+"
When call validate_input_python "version-validator" "validation-regex" "(a|ab)+"
The status should be failure
End
It "accepts safe pattern with single quantifier"
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+$"
The status should be success
End
It "accepts safe pattern with character class"
When call validate_input_python "version-validator" "validation-regex" "^[a-zA-Z0-9]+$"
The status should be success
End
It "accepts safe pattern with optional group"
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+(\\.[0-9]+)?$"
The status should be success
End
It "accepts safe alternation without repetition"
When call validate_input_python "version-validator" "validation-regex" "^(alpha|beta|gamma)$"
The status should be success
End
End
Context "when validating language input"
It "accepts valid language name"
When call validate_input_python "version-validator" "language" "nodejs"
The status should be success
End
It "accepts version as language"
When call validate_input_python "version-validator" "language" "version"
The status should be success
End
It "accepts empty language (uses default)"
When call validate_input_python "version-validator" "language" ""
The status should be success
End
It "rejects language with command injection"
When call validate_input_python "version-validator" "language" "version; rm -rf /"
The status should be failure
End
End
Context "when checking action.yml structure"
It "has valid YAML syntax"
When call validate_action_yml_quiet "$ACTION_FILE"
The status should be success
End
It "has correct action name"
name=$(get_action_name "$ACTION_FILE")
When call echo "$name"
The output should match pattern "*Version*"
End
It "defines expected inputs"
When call get_action_inputs "$ACTION_FILE"
The output should include "version"
The output should include "validation-regex"
The output should include "language"
End
It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE"
The output should include "is-valid"
The output should include "validated-version"
The output should include "error-message"
End
End
Context "when testing input requirements"
It "requires version input"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "version" "required"
The status should be success
The output should equal "required"
End
It "has validation-regex as optional input"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "validation-regex" "optional"
The status should be success
The output should equal "optional"
End
It "has language as optional input"
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "language" "optional"
The status should be success
The output should equal "optional"
End
End
Context "when testing security validations"
It "validates against path traversal in version"
When call validate_input_python "version-validator" "version" "../1.2.3"
The status should be failure
End
It "validates against shell metacharacters in version"
When call validate_input_python "version-validator" "version" "1.2.3|echo"
The status should be failure
End
It "validates against backtick injection in language"
When call validate_input_python "version-validator" "language" "version\`whoami\`"
The status should be failure
End
It "validates against variable expansion in version"
When call validate_input_python "version-validator" "version" "1.2.3\${HOME}"
The status should be failure
End
End
Context "when testing version validation functionality"
It "validates semantic version format restrictions"
When call validate_input_python "version-validator" "version" "1.2"
The status should be success
End
It "validates regex pattern safety"
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+$"
The status should be success
End
It "validates language parameter format"
When call validate_input_python "version-validator" "language" "NODEJS"
The status should be success
End
It "validates complex version formats"
When call validate_input_python "version-validator" "version" "1.0.0-beta.1+exp.sha.5114f85"
The status should be success
End
End
End

94
_tools/bump-major-version.sh Executable file
View File

@@ -0,0 +1,94 @@
#!/bin/sh
# Bump from one major version to another (annual version bump)
set -eu
OLD_VERSION="${1:-}"
NEW_VERSION="${2:-}"
# Source shared utilities
# shellcheck source=_tools/shared.sh
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# shellcheck disable=SC1091
. "$SCRIPT_DIR/shared.sh"
# Check git availability
require_git
if [ -z "$OLD_VERSION" ] || [ -z "$NEW_VERSION" ]; then
printf '%b' "${RED}Error: OLD_VERSION and NEW_VERSION arguments required${NC}\n"
printf 'Usage: %s v2025 v2026\n' "$0"
exit 1
fi
# Validate major version format
if ! validate_major_version "$OLD_VERSION"; then
printf '%b' "${RED}Error: Invalid old version format: $OLD_VERSION${NC}\n"
printf 'Expected: vYYYY (e.g., v2025)\n'
exit 1
fi
if ! validate_major_version "$NEW_VERSION"; then
printf '%b' "${RED}Error: Invalid new version format: $NEW_VERSION${NC}\n"
printf 'Expected: vYYYY (e.g., v2026)\n'
exit 1
fi
printf '%b' "${BLUE}Bumping major version from $OLD_VERSION to $NEW_VERSION${NC}\n"
printf '\n'
# Get SHA for new version tag
if ! git rev-parse "$NEW_VERSION" >/dev/null 2>&1; then
printf '%b' "${YELLOW}Warning: Tag $NEW_VERSION not found${NC}\n"
printf 'Creating tag %s pointing to current HEAD...\n' "$NEW_VERSION"
if ! current_sha=$(git rev-parse HEAD 2>&1); then
printf '%b' "${RED}Error: Failed to get current HEAD SHA${NC}\n" >&2
printf 'Git command failed: git rev-parse HEAD\n' >&2
exit 1
fi
git tag -a "$NEW_VERSION" -m "Major version $NEW_VERSION"
printf '%b' "${GREEN}✓ Created tag $NEW_VERSION pointing to $current_sha${NC}\n"
printf '\n'
fi
if ! new_sha=$(git rev-list -n 1 "$NEW_VERSION" 2>&1); then
printf '%b' "${RED}Error: Failed to get SHA for tag $NEW_VERSION${NC}\n" >&2
printf 'Git command failed: git rev-list -n 1 "%s"\n' "$NEW_VERSION" >&2
exit 1
fi
if [ -z "$new_sha" ]; then
printf '%b' "${RED}Error: Empty SHA returned for tag $NEW_VERSION${NC}\n" >&2
exit 1
fi
printf '%b' "Target SHA for $NEW_VERSION: ${GREEN}$new_sha${NC}\n"
printf '\n'
# Update all action references
printf '%b' "${BLUE}Updating action references...${NC}\n"
"$SCRIPT_DIR/update-action-refs.sh" "$NEW_VERSION" "tag"
# Commit the changes
if ! git diff --quiet; then
git add -- */action.yml
git commit -m "chore: bump major version from $OLD_VERSION to $NEW_VERSION
This commit updates all internal action references from $OLD_VERSION
to $NEW_VERSION.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>"
printf '%b' "${GREEN}✅ Committed version bump${NC}\n"
else
printf '%b' "${BLUE}No changes to commit${NC}\n"
fi
printf '\n'
printf '%b' "${GREEN}✅ Major version bumped successfully${NC}\n"
printf '\n'
printf '%b' "${YELLOW}Remember to update READMEs:${NC}\n"
printf ' make docs\n'

120
_tools/check-version-refs.sh Executable file
View File

@@ -0,0 +1,120 @@
#!/bin/sh
# Check and display all current SHA-pinned action references
set -eu
# Source shared utilities
# shellcheck source=_tools/shared.sh
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# shellcheck disable=SC1091
. "$SCRIPT_DIR/shared.sh"
# Warn once if git is not available
if ! has_git; then
printf '%b' "${YELLOW}Warning: git is not installed or not in PATH${NC}\n" >&2
printf 'Git tag information will not be available.\n' >&2
fi
# Check for required coreutils
for tool in find grep sed printf sort cut tr wc; do
if ! command -v "$tool" >/dev/null 2>&1; then
printf '%b' "${RED}Error: Required tool '%s' is not installed or not in PATH${NC}\n" "$tool" >&2
printf 'Please install coreutils to use this script.\n' >&2
exit 1
fi
done
printf '%b' "${BLUE}Current SHA-pinned action references:${NC}\n"
printf '\n'
# Create temp files for processing
temp_file=$(safe_mktemp)
trap 'rm -f "$temp_file"' EXIT
temp_input=$(safe_mktemp)
trap 'rm -f "$temp_file" "$temp_input"' EXIT
# Find all action references and collect SHA|action pairs
# Use input redirection to avoid subshell issues with pipeline
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_input"
while IFS= read -r line; do
# Extract action name and SHA using sed
action=$(echo "$line" | sed -n 's|.*ivuorinen/actions/\([a-z-]*\)@.*|\1|p')
sha=$(echo "$line" | sed -n 's|.*@\([a-f0-9]\{40\}\).*|\1|p')
if [ -n "$action" ] && [ -n "$sha" ]; then
printf '%s\n' "$sha|$action" >> "$temp_file"
fi
done < "$temp_input"
# Check if we found any references
if [ ! -s "$temp_file" ]; then
printf '%b' "${YELLOW}No SHA-pinned references found${NC}\n"
exit 0
fi
# Sort by SHA and group
sort "$temp_file" | uniq > "${temp_file}.sorted"
mv "${temp_file}.sorted" "$temp_file"
# Count unique SHAs
sha_count=$(cut -d'|' -f1 "$temp_file" | sort -u | wc -l | tr -d ' ')
if [ "$sha_count" -eq 1 ]; then
printf '%b' "${GREEN}✓ All references use the same SHA (consistent)${NC}\n"
printf '\n'
fi
# Process and display grouped by SHA
current_sha=""
actions_list=""
while IFS='|' read -r sha action; do
if [ "$sha" != "$current_sha" ]; then
# Print previous SHA group if exists
if [ -n "$current_sha" ]; then
# Try to find tags pointing to this SHA
if has_git; then
tags=$(git tag --points-at "$current_sha" 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
else
tags=""
fi
printf '%b' "${GREEN}SHA: $current_sha${NC}\n"
if [ -n "$tags" ]; then
printf '%b' " Tags: ${BLUE}$tags${NC}\n"
fi
printf ' Actions: %s\n' "$actions_list"
printf '\n'
fi
# Start new SHA group
current_sha="$sha"
actions_list="$action"
else
# Add to current SHA group
actions_list="$actions_list, $action"
fi
done < "$temp_file"
# Print last SHA group
if [ -n "$current_sha" ]; then
if has_git; then
tags=$(git tag --points-at "$current_sha" 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
else
tags=""
fi
printf '%b' "${GREEN}SHA: $current_sha${NC}\n"
if [ -n "$tags" ]; then
printf '%b' " Tags: ${BLUE}$tags${NC}\n"
fi
printf ' Actions: %s\n' "$actions_list"
printf '\n'
fi
printf '%b' "${BLUE}Summary:${NC}\n"
printf ' Unique SHAs: %s\n' "$sha_count"
if [ "$sha_count" -gt 1 ]; then
printf '%b' " ${YELLOW}⚠ Warning: Multiple SHAs in use (consider updating)${NC}\n"
fi

View File

@@ -1,15 +1,15 @@
#!/usr/bin/env bash
#!/bin/sh
# Build script for GitHub Actions Testing Docker Image
set -euo pipefail
set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
IMAGE_NAME="ghcr.io/ivuorinen/actions"
IMAGE_TAG="${1:-testing-tools}"
FULL_IMAGE_NAME="${IMAGE_NAME}:${IMAGE_TAG}"
echo "Building GitHub Actions Testing Docker Image..."
echo "Image: $FULL_IMAGE_NAME"
printf 'Building GitHub Actions Testing Docker Image...\n'
printf 'Image: %s\n' "$FULL_IMAGE_NAME"
# Enable BuildKit for better caching and performance
export DOCKER_BUILDKIT=1
@@ -17,7 +17,7 @@ export DOCKER_BUILDKIT=1
# Build the multi-stage image
# Check for buildx support up front, then run the appropriate build command
if docker buildx version >/dev/null 2>&1; then
echo "Using buildx (multi-arch capable)"
printf 'Using buildx (multi-arch capable)\n'
docker buildx build \
--pull \
--tag "$FULL_IMAGE_NAME" \
@@ -26,7 +26,7 @@ if docker buildx version >/dev/null 2>&1; then
--load \
"$SCRIPT_DIR"
else
echo "⚠️ buildx not available, using standard docker build"
printf '⚠️ buildx not available, using standard docker build\n'
docker build \
--pull \
--tag "$FULL_IMAGE_NAME" \
@@ -35,22 +35,22 @@ else
"$SCRIPT_DIR"
fi
echo "Build completed successfully!"
echo ""
echo "Testing the image..."
printf 'Build completed successfully!\n'
printf '\n'
printf 'Testing the image...\n'
# Test basic functionality
docker run --rm "$FULL_IMAGE_NAME" whoami
docker run --rm "$FULL_IMAGE_NAME" shellspec --version
docker run --rm "$FULL_IMAGE_NAME" act --version
echo "Image tests passed!"
echo ""
echo "To test the image locally:"
echo " docker run --rm -it $FULL_IMAGE_NAME"
echo ""
echo "To push to registry:"
echo " docker push $FULL_IMAGE_NAME"
echo ""
echo "To use in GitHub Actions:"
echo " container: $FULL_IMAGE_NAME"
printf 'Image tests passed!\n'
printf '\n'
printf 'To test the image locally:\n'
printf ' docker run --rm -it %s\n' "$FULL_IMAGE_NAME"
printf '\n'
printf 'To push to registry:\n'
printf ' docker push %s\n' "$FULL_IMAGE_NAME"
printf '\n'
printf 'To use in GitHub Actions:\n'
printf ' container: %s\n' "$FULL_IMAGE_NAME"

41
_tools/get-action-sha.sh Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/sh
# Get the SHA for a specific version tag
set -eu
VERSION="${1:-}"
# Source shared utilities
# shellcheck source=_tools/shared.sh
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# shellcheck disable=SC1091
. "$SCRIPT_DIR/shared.sh"
# Check git availability
require_git
if [ -z "$VERSION" ]; then
printf '%b' "${RED}Error: VERSION argument required${NC}\n" >&2
printf 'Usage: %s v2025\n' "$0" >&2
exit 1
fi
# Check if tag exists
if ! git rev-parse "$VERSION" >/dev/null 2>&1; then
printf '%b' "${RED}Error: Tag $VERSION not found${NC}\n" >&2
printf '\n' >&2
printf '%b' "${BLUE}Available tags:${NC}\n" >&2
git tag -l 'v*' | head -20 >&2
exit 1
fi
# Get SHA for the tag
sha=$(git rev-list -n 1 "$VERSION")
# Check if output is for terminal or pipe
if [ -t 1 ]; then
# Terminal output - show with colors
printf '%b' "${GREEN}$sha${NC}\n"
else
# Piped output - just the SHA
printf '%s\n' "$sha"
fi

152
_tools/release-undo.sh Executable file
View File

@@ -0,0 +1,152 @@
#!/bin/sh
# Undo the most recent release by deleting tags and optionally resetting HEAD
set -eu
# Source shared utilities
# shellcheck source=_tools/shared.sh
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# shellcheck disable=SC1091
. "$SCRIPT_DIR/shared.sh"
# Check git availability
require_git
msg_info "Finding most recent release tags..."
# Portable version sort function
# Sorts CalVer tags vYYYY.MM.DD numerically
version_sort_tags() {
# Try GNU sort first (Linux and some macOS with GNU coreutils)
if sort --version 2>/dev/null | grep -q GNU; then
sort -V
return
fi
# Try gsort (macOS with GNU coreutils via Homebrew)
if command -v gsort >/dev/null 2>&1; then
gsort -V
return
fi
# Fallback: awk-based numeric version sort with validation
awk -F. '{
# Validate CalVer format: vYYYY.MM.DD or YYYY.MM.DD
if ($0 !~ /^v?[0-9]+\.[0-9]+\.[0-9]+$/) {
printf "Warning: Skipping malformed tag: %s\n", $0 > "/dev/stderr"
next
}
# Check we have exactly 3 fields after splitting on dots
if (NF != 3) {
printf "Warning: Skipping invalid tag (wrong field count): %s\n", $0 > "/dev/stderr"
next
}
# Save original input before modification
original = $0
# Remove leading v and split into year, month, day
gsub(/^v/, "", $0)
# Verify each field is numeric after field recalculation
if ($1 !~ /^[0-9]+$/ || $2 !~ /^[0-9]+$/ || $3 !~ /^[0-9]+$/) {
printf "Warning: Skipping tag with non-numeric components: %s\n", original > "/dev/stderr"
next
}
printf "%04d.%02d.%02d %s\n", $1, $2, $3, original
}' | sort -n | cut -d' ' -f2
}
# Find all release tags matching vYYYY.MM.DD pattern
all_tags=$(git tag -l 'v[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9]' | version_sort_tags)
if [ -z "$all_tags" ]; then
msg_warn "No release tags found"
exit 0
fi
# Get most recent tag
latest_tag=$(echo "$all_tags" | tail -n 1)
# Extract version components
version_no_v="${latest_tag#v}"
year=$(echo "$version_no_v" | cut -d'.' -f1)
month=$(echo "$version_no_v" | cut -d'.' -f2)
day=$(echo "$version_no_v" | cut -d'.' -f3)
major="v$year"
minor="v$year.$month"
patch="v$year.$month.$day"
printf '\n'
msg_info "Most recent release:"
printf ' Patch: %s\n' "$patch"
printf ' Minor: %s\n' "$minor"
printf ' Major: %s\n' "$major"
printf '\n'
# Show which tags exist
msg_info "Tags that will be deleted:"
for tag in "$patch" "$minor" "$major"; do
if check_tag_exists "$tag"; then
tag_sha=$(git rev-list -n 1 "$tag")
tag_sha_short=$(echo "$tag_sha" | cut -c1-7)
printf ' %s (points to %s)\n' "$tag" "$tag_sha_short"
fi
done
printf '\n'
# Check if HEAD commit is a release commit
head_message=$(git log -1 --pretty=%s)
if echo "$head_message" | grep -q "^chore: update action references for release"; then
msg_warn "Last commit appears to be a release preparation commit:"
printf ' %s\n' "$head_message"
printf '\n'
reset_head=true
else
reset_head=false
fi
# Confirm deletion
msg_warn "This will:"
printf ' 1. Delete tags: %s, %s, %s\n' "$patch" "$minor" "$major"
if [ "$reset_head" = "true" ]; then
printf ' 2. Reset HEAD to previous commit (undo release prep)\n'
fi
printf '\n'
if ! prompt_confirmation "Proceed with rollback?"; then
msg_warn "Rollback cancelled"
exit 0
fi
printf '\n'
# Delete tags
msg_info "Deleting tags..."
for tag in "$patch" "$minor" "$major"; do
if check_tag_exists "$tag"; then
git tag -d "$tag"
msg_item "Deleted tag: $tag"
else
msg_notice "Tag not found: $tag (skipping)"
fi
done
# Reset HEAD if needed
if [ "$reset_head" = "true" ]; then
printf '\n'
msg_info "Resetting HEAD to previous commit..."
git reset --hard HEAD~1
msg_item "Reset complete"
new_head=$(git rev-parse HEAD)
new_head_short=$(echo "$new_head" | cut -c1-7)
printf 'New HEAD: %s%s%s\n' "$GREEN" "$new_head_short" "$NC"
fi
printf '\n'
msg_done "Rollback complete"
printf '\n'
msg_warn "Note:"
printf ' Tags were deleted locally only\n'
printf ' If you had pushed the tags, delete them from remote:\n'
printf ' git push origin --delete %s %s %s\n' "$patch" "$minor" "$major"

289
_tools/release.sh Executable file
View File

@@ -0,0 +1,289 @@
#!/bin/sh
# Release script for creating versioned tags and updating action references
set -eu
# Parse arguments
VERSION=""
DRY_RUN=false
SKIP_CONFIRM=false
PREP_ONLY=false
TAG_ONLY=false
while [ $# -gt 0 ]; do
case "$1" in
--dry-run)
DRY_RUN=true
shift
;;
--yes|--no-confirm)
SKIP_CONFIRM=true
shift
;;
--prep-only)
PREP_ONLY=true
shift
;;
--tag-only)
TAG_ONLY=true
shift
;;
--help|-h)
printf 'Usage: %s [OPTIONS] VERSION\n' "$0"
printf '\n'
printf 'Options:\n'
printf ' --dry-run Show what would happen without making changes\n'
printf ' --yes Skip confirmation prompt\n'
printf ' --no-confirm Alias for --yes\n'
printf ' --prep-only Only update refs and commit (no tags)\n'
printf ' --tag-only Only create tags (assumes prep done)\n'
printf ' --help, -h Show this help message\n'
printf '\n'
printf 'Examples:\n'
printf ' %s v2025.11.01\n' "$0"
printf ' %s --dry-run v2025.11.01\n' "$0"
printf ' %s --yes v2025.11.01\n' "$0"
exit 0
;;
-*)
printf 'Unknown option: %s\n' "$1" >&2
printf 'Use --help for usage information\n' >&2
exit 1
;;
*)
VERSION="$1"
shift
;;
esac
done
# Source shared utilities
# shellcheck source=_tools/shared.sh
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# shellcheck disable=SC1091
. "$SCRIPT_DIR/shared.sh"
if [ -z "$VERSION" ]; then
msg_error "VERSION argument required"
printf 'Usage: %s [OPTIONS] VERSION\n' "$0"
printf 'Use --help for more information\n'
exit 1
fi
# Validate version format
if ! validate_version "$VERSION"; then
msg_error "Invalid version format: $VERSION"
printf 'Expected: vYYYY.MM.DD with zero-padded month/day (e.g., v2025.10.18, v2025.01.05)\n'
printf 'Invalid: v2025.1.5 (must be zero-padded)\n'
exit 1
fi
# Extract version components
# Remove leading 'v'
version_no_v="${VERSION#v}"
# Extract year, month, day
year=$(echo "$version_no_v" | cut -d'.' -f1)
month=$(echo "$version_no_v" | cut -d'.' -f2)
day=$(echo "$version_no_v" | cut -d'.' -f3)
major="v$year"
minor="v$year.$month"
patch="v$year.$month.$day"
# Show dry-run banner if applicable
if [ "$DRY_RUN" = "true" ]; then
msg_plain "$YELLOW" "=== DRY RUN MODE ==="
printf 'No changes will be made to git repository\n'
printf '\n'
fi
msg_info "Creating release $VERSION"
printf ' Major: %s\n' "$major"
printf ' Minor: %s\n' "$minor"
printf ' Patch: %s\n' "$patch"
printf '\n'
# Check if git is available (required for all modes)
if ! require_git 2>/dev/null; then
msg_error "git not available"
exit 1
fi
# Pre-flight checks (skip for --tag-only since prep should be done)
if [ "$TAG_ONLY" = "false" ]; then
msg_info "Running pre-flight checks..."
msg_item "git is available"
# Check if on main branch
if ! check_on_branch "main"; then
current_branch=$(git rev-parse --abbrev-ref HEAD)
msg_error "Not on main branch (currently on: $current_branch)"
if [ "$DRY_RUN" = "false" ]; then
exit 1
fi
else
msg_item "On main branch"
fi
# Check if working directory is clean
if ! check_git_clean; then
msg_error "Working directory has uncommitted changes"
if [ "$DRY_RUN" = "false" ]; then
printf 'Please commit or stash changes before creating a release\n'
exit 1
fi
else
msg_item "Working directory is clean"
fi
# Check if patch tag already exists
if check_tag_exists "$patch"; then
msg_error "Tag $patch already exists"
if [ "$DRY_RUN" = "false" ]; then
printf 'Use a different version or delete the existing tag first\n'
exit 1
fi
else
msg_item "Tag $patch does not exist"
fi
printf '\n'
fi
# Get current commit SHA
current_sha=$(git rev-parse HEAD)
printf 'Current HEAD: %s%s%s\n' "$GREEN" "$current_sha" "$NC"
printf '\n'
# Confirmation prompt (skip if --yes or --dry-run)
if [ "$DRY_RUN" = "false" ] && [ "$SKIP_CONFIRM" = "false" ]; then
if ! prompt_confirmation "Proceed with release $VERSION?"; then
msg_warn "Release cancelled by user"
exit 0
fi
printf '\n'
fi
# Skip prep if --tag-only
if [ "$TAG_ONLY" = "true" ]; then
msg_info "Skipping preparation (--tag-only mode)"
printf '\n'
else
# Update all action references to current SHA
msg_info "Updating action references to $current_sha..."
if [ "$DRY_RUN" = "true" ]; then
msg_warn "[DRY RUN] Would run: update-action-refs.sh $current_sha direct"
else
"$SCRIPT_DIR/update-action-refs.sh" "$current_sha" "direct"
fi
fi
# Commit the changes (skip if --tag-only)
if [ "$TAG_ONLY" = "false" ]; then
if ! git diff --quiet; then
if [ "$DRY_RUN" = "true" ]; then
msg_warn "[DRY RUN] Would add: */action.yml"
msg_warn "[DRY RUN] Would commit: update action references for release $VERSION"
else
git add -- */action.yml
git commit -m "chore: update action references for release $VERSION
This commit updates all internal action references to point to the current
commit SHA in preparation for release $VERSION."
# Update SHA since we just created a new commit
current_sha=$(git rev-parse HEAD)
msg_done "Committed updated action references"
printf 'New HEAD: %s%s%s\n' "$GREEN" "$current_sha" "$NC"
fi
else
msg_info "No changes to commit"
fi
fi
# Exit early if --prep-only
if [ "$PREP_ONLY" = "true" ]; then
printf '\n'
msg_done "Preparation complete (--prep-only mode)"
msg_warn "Run with --tag-only to create tags"
exit 0
fi
# Create/update tags
printf '\n'
msg_info "Creating tags..."
# Create patch tag
if [ "$DRY_RUN" = "true" ]; then
msg_warn "[DRY RUN] Would create tag: $patch"
else
git tag -a "$patch" -m "Release $patch"
msg_item "Created tag: $patch"
fi
# Move/create minor tag
if git rev-parse "$minor" >/dev/null 2>&1; then
if [ "$DRY_RUN" = "true" ]; then
msg_warn "[DRY RUN] Would force-update tag: $minor"
else
git tag -f -a "$minor" -m "Latest $minor release: $patch"
msg_item "Updated tag: $minor (force)"
fi
else
if [ "$DRY_RUN" = "true" ]; then
msg_warn "[DRY RUN] Would create tag: $minor"
else
git tag -a "$minor" -m "Latest $minor release: $patch"
msg_item "Created tag: $minor"
fi
fi
# Move/create major tag
if git rev-parse "$major" >/dev/null 2>&1; then
if [ "$DRY_RUN" = "true" ]; then
msg_warn "[DRY RUN] Would force-update tag: $major"
else
git tag -f -a "$major" -m "Latest $major release: $patch"
msg_item "Updated tag: $major (force)"
fi
else
if [ "$DRY_RUN" = "true" ]; then
msg_warn "[DRY RUN] Would create tag: $major"
else
git tag -a "$major" -m "Latest $major release: $patch"
msg_item "Created tag: $major"
fi
fi
printf '\n'
if [ "$DRY_RUN" = "true" ]; then
msg_done "Dry run complete - no changes made"
printf '\n'
msg_info "Would have created release $VERSION"
else
msg_done "Release $VERSION created successfully"
fi
printf '\n'
msg_plain "$YELLOW" "All tags point to: $current_sha"
printf '\n'
msg_info "Tags created:"
printf ' %s\n' "$patch"
printf ' %s\n' "$minor"
printf ' %s\n' "$major"
printf '\n'
# Enhanced next steps
if [ "$DRY_RUN" = "false" ]; then
msg_warn "Next steps:"
printf ' 1. Review changes: git show HEAD\n'
printf ' 2. Verify CI status: gh run list --limit 5\n'
printf ' 3. Push tags: git push origin main --tags --force-with-lease\n'
printf ' 4. Update workflow refs: make update-version-refs MAJOR=%s\n' "$major"
printf ' 5. Update README examples if needed\n'
printf ' 6. Create GitHub release: gh release create %s --generate-notes\n' "$VERSION"
printf '\n'
msg_info "If something went wrong:"
printf ' Rollback: make release-undo\n'
else
msg_warn "To execute this release:"
printf ' Run without --dry-run flag\n'
fi

257
_tools/shared.sh Executable file
View File

@@ -0,0 +1,257 @@
#!/bin/sh
# Shared functions and utilities for _tools/ scripts
# This file is sourced by other scripts, not executed directly
# Colors (exported for use by sourcing scripts)
# shellcheck disable=SC2034
RED='\033[0;31m'
# shellcheck disable=SC2034
GREEN='\033[0;32m'
# shellcheck disable=SC2034
BLUE='\033[0;34m'
# shellcheck disable=SC2034
YELLOW='\033[1;33m'
# shellcheck disable=SC2034
NC='\033[0m' # No Color
# Validate CalVer version format: vYYYY.MM.DD (zero-padded)
validate_version() {
version="$1"
# Check format: vYYYY.MM.DD (require zero-padding) using grep
if ! echo "$version" | grep -qE '^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}$'; then
return 1
fi
# Extract components
version_no_v="${version#v}"
year=$(echo "$version_no_v" | cut -d'.' -f1)
month=$(echo "$version_no_v" | cut -d'.' -f2)
day=$(echo "$version_no_v" | cut -d'.' -f3)
# Validate year (2020-2099)
if [ "$year" -lt 2020 ] || [ "$year" -gt 2099 ]; then
return 1
fi
# Validate month (01-12)
if [ "$month" -lt 1 ] || [ "$month" -gt 12 ]; then
return 1
fi
# Validate day (01-31)
if [ "$day" -lt 1 ] || [ "$day" -gt 31 ]; then
return 1
fi
return 0
}
# Validate major version format: vYYYY
validate_major_version() {
version="$1"
# Check format: vYYYY using grep
if ! echo "$version" | grep -qE '^v[0-9]{4}$'; then
return 1
fi
# Extract year
year="${version#v}"
# Validate year (2020-2099)
if [ "$year" -lt 2020 ] || [ "$year" -gt 2099 ]; then
return 1
fi
return 0
}
# Validate minor version format: vYYYY.MM (zero-padded)
validate_minor_version() {
version="$1"
# Check format: vYYYY.MM (require zero-padding) using grep
if ! echo "$version" | grep -qE '^v[0-9]{4}\.[0-9]{2}$'; then
return 1
fi
# Extract components
version_no_v="${version#v}"
year=$(echo "$version_no_v" | cut -d'.' -f1)
month=$(echo "$version_no_v" | cut -d'.' -f2)
# Validate year (2020-2099)
if [ "$year" -lt 2020 ] || [ "$year" -gt 2099 ]; then
return 1
fi
# Validate month (01-12)
if [ "$month" -lt 1 ] || [ "$month" -gt 12 ]; then
return 1
fi
return 0
}
# Check if working directory is clean (no uncommitted changes)
check_git_clean() {
if ! has_git; then
return 1
fi
if ! git diff --quiet || ! git diff --cached --quiet; then
return 1
fi
return 0
}
# Check if currently on specified branch (default: main)
check_on_branch() {
target_branch="${1:-main}"
if ! has_git; then
return 1
fi
current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
if [ "$current_branch" != "$target_branch" ]; then
return 1
fi
return 0
}
# Check if a git tag exists
check_tag_exists() {
tag="$1"
if ! has_git; then
return 1
fi
if git rev-parse "$tag" >/dev/null 2>&1; then
return 0
fi
return 1
}
# Prompt user for yes/no confirmation
# Usage: if prompt_confirmation "Continue?"; then ...; fi
prompt_confirmation() {
prompt_text="${1:-Continue?}"
timeout_seconds="${2:-30}"
# Check if stdin is a TTY (interactive terminal)
if [ ! -t 0 ]; then
msg_error "Non-interactive session detected - cannot prompt for confirmation"
return 1
fi
# Check if timeout command is available for optional timeout support
if command -v timeout >/dev/null 2>&1; then
printf '%s [y/N] (timeout in %ss) ' "$prompt_text" "$timeout_seconds"
# Create a temporary file to store the response
_temp_response=$(mktemp) || return 1
# Use timeout with --foreground to allow reading from TTY
# Write response to temp file instead of trying to capture in command substitution
if timeout --foreground "$timeout_seconds" sh -c "read -r r && printf '%s' \"\$r\" > '$_temp_response'" 2>/dev/null; then
response=$(cat "$_temp_response")
rm -f "$_temp_response"
else
rm -f "$_temp_response"
printf '\n'
msg_warn "Confirmation timeout - defaulting to No"
return 1
fi
else
# No timeout available - plain read
printf '%s [y/N] ' "$prompt_text"
read -r response || return 1
fi
case "$response" in
[yY]|[yY][eE][sS])
return 0
;;
*)
return 1
;;
esac
}
# Message output functions for consistent, colored output
# These functions provide a clean API for printing status messages
# msg_error "message" - Print error message in red with ✗ symbol to stderr
msg_error() {
printf '%s✗ %s%s\n' "$RED" "$1" "$NC" >&2
}
# msg_success "message" - Print success message in green with ✓ symbol
msg_success() {
printf '%s✓ %s%s\n' "$GREEN" "$1" "$NC"
}
# msg_done "message" - Print completion message in green with ✅ symbol
msg_done() {
printf '%s✅ %s%s\n' "$GREEN" "$1" "$NC"
}
# msg_info "message" - Print info/status message in blue (no symbol)
msg_info() {
printf '%s%s%s\n' "$BLUE" "$1" "$NC"
}
# msg_warn "message" - Print warning message in yellow (no symbol)
msg_warn() {
printf '%s%s%s\n' "$YELLOW" "$1" "$NC"
}
# msg_item "message" - Print indented item with ✓ in green
msg_item() {
printf ' %s✓%s %s\n' "$GREEN" "$NC" "$1"
}
# msg_notice "message" - Print indented notice with in blue
msg_notice() {
printf ' %s%s %s\n' "$BLUE" "$NC" "$1"
}
# msg_plain "color" "message" - Print plain colored message (no symbol)
# Usage: msg_plain "$YELLOW" "=== BANNER ==="
msg_plain() {
color="$1"
message="$2"
printf '%s%s%s\n' "$color" "$message" "$NC"
}
# Get the directory where the calling script is located
get_script_dir() {
cd "$(dirname -- "$1")" && pwd
}
# Check if git is available
has_git() {
command -v git >/dev/null 2>&1
}
# Require git to be available, exit with error if not
require_git() {
if ! has_git; then
msg_error "git is not installed or not in PATH"
printf 'Please install git to use this script.\n' >&2
exit 1
fi
}
# Create temp file with error checking
safe_mktemp() {
_temp_file=""
if ! _temp_file=$(mktemp); then
msg_error "Failed to create temp file"
exit 1
fi
printf '%s' "$_temp_file"
}

71
_tools/update-action-refs.sh Executable file
View File

@@ -0,0 +1,71 @@
#!/bin/sh
# Update all action references to a specific version tag or SHA
set -eu
TARGET="${1:-}"
MODE="${2:-tag}" # 'tag' or 'direct'
# Source shared utilities
# shellcheck source=_tools/shared.sh
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
# shellcheck disable=SC1091
. "$SCRIPT_DIR/shared.sh"
# Check git availability
require_git
if [ -z "$TARGET" ]; then
printf '%b' "${RED}Error: TARGET argument required${NC}\n"
printf 'Usage: %s v2025 [mode]\n' "$0"
printf ' mode: '\''tag'\'' (default) or '\''direct'\''\n'
exit 1
fi
# Get SHA based on mode
if [ "$MODE" = "direct" ]; then
# Direct SHA provided
target_sha="$TARGET"
printf '%b' "${BLUE}Using direct SHA: $target_sha${NC}\n"
elif [ "$MODE" = "tag" ]; then
# Resolve tag to SHA
if ! git rev-parse "$TARGET" >/dev/null 2>&1; then
printf '%b' "${RED}Error: Tag $TARGET not found${NC}\n"
exit 1
fi
target_sha=$(git rev-list -n 1 "$TARGET")
printf '%b' "${BLUE}Resolved $TARGET to SHA: $target_sha${NC}\n"
else
printf '%b' "${RED}Error: Invalid mode: $MODE${NC}\n"
printf 'Mode must be '\''tag'\'' or '\''direct'\''\n'
exit 1
fi
# Validate SHA format
if ! echo "$target_sha" | grep -qE '^[a-f0-9]{40}$'; then
printf '%b' "${RED}Error: Invalid SHA format: $target_sha${NC}\n"
exit 1
fi
printf '%b' "${BLUE}Updating action references...${NC}\n"
# Update all action.yml files (excluding tests and .github workflows)
# Create temp file to store results
temp_file=$(safe_mktemp)
trap 'rm -f "$temp_file"' EXIT
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" | while IFS= read -r file; do
# Use .bak extension for cross-platform sed compatibility
if sed -i.bak "s|ivuorinen/actions/\([a-z-]*\)@[a-f0-9]\{40\}|ivuorinen/actions/\1@$target_sha|g" "$file"; then
rm -f "${file}.bak"
printf '%b' " ${GREEN}${NC} Updated: $file\n"
echo "$file" >> "$temp_file"
fi
done
printf '\n'
if [ -s "$temp_file" ]; then
updated_count=$(wc -l < "$temp_file" | tr -d ' ')
printf '%b' "${GREEN}✅ Updated $updated_count action files${NC}\n"
else
printf '%b' "${BLUE}No files needed updating${NC}\n"
fi

View File

@@ -0,0 +1,44 @@
# ivuorinen/actions/action-versioning
## Action Versioning
### Description
Automatically update SHA-pinned action references to match latest version tags
### Inputs
| name | description | required | default |
|-----------------|------------------------------------------------|----------|---------|
| `major-version` | <p>Major version tag to sync (e.g., v2025)</p> | `true` | `""` |
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
### Outputs
| name | description |
|---------------------|------------------------------------------------------------|
| `updated` | <p>Whether action references were updated (true/false)</p> |
| `commit-sha` | <p>SHA of the commit that was created (if any)</p> |
| `needs-annual-bump` | <p>Whether annual version bump is needed (true/false)</p> |
### Runs
This action is a `composite` action.
### Usage
```yaml
- uses: ivuorinen/actions/action-versioning@main
with:
major-version:
# Major version tag to sync (e.g., v2025)
#
# Required: true
# Default: ""
token:
# GitHub token for authentication
#
# Required: false
# Default: ""
```

View File

@@ -0,0 +1,165 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
# permissions:
# - contents: write # Required for creating commits
---
name: Action Versioning
description: 'Automatically update SHA-pinned action references to match latest version tags'
author: 'Ismo Vuorinen'
branding:
icon: git-commit
color: blue
inputs:
major-version:
description: 'Major version tag to sync (e.g., v2025)'
required: true
token:
description: 'GitHub token for authentication'
required: false
default: ''
outputs:
updated:
description: 'Whether action references were updated (true/false)'
value: ${{ steps.check-update.outputs.updated }}
commit-sha:
description: 'SHA of the commit that was created (if any)'
value: ${{ steps.commit.outputs.sha }}
needs-annual-bump:
description: 'Whether annual version bump is needed (true/false)'
value: ${{ steps.check-year.outputs.needs-bump }}
runs:
using: composite
steps:
- name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
with:
token: ${{ inputs.token || github.token }}
fetch-depth: 0
- name: Check Current Year
id: check-year
shell: sh
env:
MAJOR_VERSION: ${{ inputs.major-version }}
run: |
set -eu
current_year=$(date +%Y)
version_year="${MAJOR_VERSION#v}"
if [ "$version_year" != "$current_year" ]; then
echo "::warning::Annual version bump needed: $MAJOR_VERSION -> v$current_year"
printf '%s\n' "needs-bump=true" >> "$GITHUB_OUTPUT"
else
printf '%s\n' "needs-bump=false" >> "$GITHUB_OUTPUT"
fi
- name: Fetch Version Tag SHA
id: fetch-sha
shell: sh
env:
MAJOR_VERSION: ${{ inputs.major-version }}
run: |
set -eu
# Fetch all tags
git fetch --tags --force
# Get SHA for the major version tag
if ! tag_sha=$(git rev-list -n 1 "$MAJOR_VERSION" 2>/dev/null); then
echo "::error::Tag $MAJOR_VERSION not found"
exit 1
fi
printf '%s\n' "tag-sha=$tag_sha" >> "$GITHUB_OUTPUT"
echo "Tag $MAJOR_VERSION points to: $tag_sha"
- name: Check if Update Needed
id: check-update
shell: sh
env:
TAG_SHA: ${{ steps.fetch-sha.outputs.tag-sha }}
run: |
set -eu
# Find all action references and check if any don't match the tag SHA
needs_update=false
# Create temp file for action references
temp_file=$(mktemp)
trap 'rm -f "$temp_file"' EXIT
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_file"
while IFS= read -r line; do
current_sha=$(echo "$line" | grep -oE '@[a-f0-9]{40}' | sed 's/@//')
if [ "$current_sha" != "$TAG_SHA" ]; then
echo "Found outdated reference: $current_sha (should be $TAG_SHA)"
needs_update=true
fi
done < "$temp_file"
if [ "$needs_update" = "true" ]; then
printf '%s\n' "updated=true" >> "$GITHUB_OUTPUT"
echo "Update needed - references are outdated"
else
printf '%s\n' "updated=false" >> "$GITHUB_OUTPUT"
echo "No update needed - all references are current"
fi
- name: Update Action References
if: steps.check-update.outputs.updated == 'true'
shell: sh
env:
TAG_SHA: ${{ steps.fetch-sha.outputs.tag-sha }}
run: |
set -eu
echo "Updating all action references to SHA: $TAG_SHA"
# Update all action.yml files (excluding tests and .github)
# Use .bak extension for cross-platform sed compatibility
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec sed -i.bak \
"s|ivuorinen/actions/\([a-z-]*\)@[a-f0-9]\{40\}|ivuorinen/actions/\1@$TAG_SHA|g" {} \;
# Remove backup files
find . -maxdepth 2 -name "action.yml.bak" -path "*/action.yml.bak" ! -path "./_*" ! -path "./.github/*" -delete
echo "Action references updated successfully"
- name: Commit Changes
if: steps.check-update.outputs.updated == 'true'
id: commit
shell: sh
env:
MAJOR_VERSION: ${{ inputs.major-version }}
TAG_SHA: ${{ steps.fetch-sha.outputs.tag-sha }}
run: |
set -eu
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -- */action.yml
if git diff --staged --quiet; then
echo "No changes to commit"
printf '%s\n' "sha=" >> "$GITHUB_OUTPUT"
else
git commit -m "chore: update action references to $MAJOR_VERSION ($TAG_SHA)" \
-m "" \
-m "This commit updates all internal action references to point to the latest" \
-m "$MAJOR_VERSION tag SHA." \
-m "" \
-m "🤖 Generated with [Claude Code](https://claude.com/claude-code)" \
-m "" \
-m "Co-Authored-By: Claude <noreply@anthropic.com>"
commit_sha=$(git rev-parse HEAD)
printf '%s\n' "sha=$commit_sha" >> "$GITHUB_OUTPUT"
echo "Created commit: $commit_sha"
fi

View File

@@ -1,25 +1,25 @@
---
# Validation rules for github-release action
# Validation rules for action-versioning action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 100% (2/2 inputs)
#
# This file defines validation rules for the github-release GitHub Action.
# This file defines validation rules for the action-versioning GitHub Action.
# Rules are automatically applied by validate-inputs action when this
# action is used.
#
schema_version: '1.0'
action: github-release
description: Creates a GitHub release with a version and changelog.
action: action-versioning
description: Automatically update SHA-pinned action references to match latest version tags
generator_version: 1.0.0
required_inputs:
- version
- major-version
optional_inputs:
- changelog
- token
conventions:
changelog: security_patterns
version: flexible_version
major-version: semantic_version
token: github_token
overrides: {}
statistics:
total_inputs: 2
@@ -31,7 +31,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

View File

@@ -10,7 +10,7 @@ Lints and fixes Ansible playbooks, commits changes, and uploads SARIF report.
| name | description | required | default |
|---------------|--------------------------------------------------------------------|----------|-----------------------------|
| `token` | <p>GitHub token for authentication</p> | `false` | `${{ github.token }}` |
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
| `username` | <p>GitHub username for commits</p> | `false` | `github-actions` |
| `email` | <p>GitHub email for commits</p> | `false` | `github-actions@github.com` |
| `max-retries` | <p>Maximum number of retry attempts for pip install operations</p> | `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

View File

@@ -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
@@ -45,55 +45,19 @@ runs:
steps:
- name: Validate Inputs
id: validate
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
EMAIL: ${{ inputs.email }}
USERNAME: ${{ inputs.username }}
MAX_RETRIES: ${{ inputs.max-retries }}
run: |
set -euo pipefail
# Validate GitHub token format (basic validation)
if [[ -n "$GITHUB_TOKEN" ]]; then
# Skip validation for GitHub expressions (they'll be resolved at runtime)
if ! [[ "$GITHUB_TOKEN" =~ ^gh[efpousr]_[a-zA-Z0-9]{36}$ ]] && ! [[ "$GITHUB_TOKEN" =~ ^\$\{\{ ]]; then
echo "::warning::GitHub token format may be invalid. Expected format: gh*_36characters"
fi
fi
# Validate email format (basic check)
if [[ "$EMAIL" != *"@"* ]] || [[ "$EMAIL" != *"."* ]]; then
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
exit 1
fi
# Validate username format (prevent command injection)
if [[ "$USERNAME" == *";"* ]] || [[ "$USERNAME" == *"&&"* ]] || [[ "$USERNAME" == *"|"* ]]; then
echo "::error::Invalid username: '$USERNAME'. Command injection patterns not allowed"
exit 1
fi
# Validate username length
username="$USERNAME"
if [ ${#username} -gt 39 ]; then
echo "::error::Username too long: ${#username} characters. GitHub usernames are max 39 characters"
exit 1
fi
# Validate max retries (positive integer with reasonable upper limit)
if ! [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
exit 1
fi
echo "Input validation completed successfully"
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
with:
action-type: 'ansible-lint-fix'
token: ${{ inputs.token }}
email: ${{ inputs.email }}
username: ${{ inputs.username }}
max-retries: ${{ inputs.max-retries }}
- name: Check for Ansible Files
id: check-files
shell: bash
shell: sh
run: |
set -euo pipefail
set -eu
# Check for both .yml and .yaml files
if find . \( -name "*.yml" -o -name "*.yaml" \) -type f | grep -q .; then
@@ -104,30 +68,33 @@ runs:
echo "No Ansible files found. Skipping lint and fix."
fi
- name: Cache Python Dependencies
if: steps.check-files.outputs.files_found == 'true'
id: cache-pip
uses: ./common-cache
- name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
with:
type: 'pip'
paths: '~/.cache/pip'
key-files: 'requirements*.txt,pyproject.toml,setup.py,setup.cfg'
key-prefix: 'ansible-lint-fix'
token: ${{ inputs.token || github.token }}
- name: Setup Python
if: steps.check-files.outputs.files_found == 'true'
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: '3.11'
cache: 'pip'
- name: Install ansible-lint
id: install-ansible-lint
if: steps.check-files.outputs.files_found == 'true'
uses: ./common-retry
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3.0.4
with:
timeout_minutes: 5
max_attempts: ${{ inputs.max-retries }}
command: 'pip install ansible-lint==6.22.1'
max-retries: ${{ inputs.max-retries }}
description: 'Installing Python dependencies (ansible-lint)'
- name: Run ansible-lint
if: steps.check-files.outputs.files_found == 'true'
id: lint
shell: bash
shell: sh
run: |
set -euo pipefail
set -eu
# Run ansible-lint and capture exit code
if ansible-lint --write --parseable-severity --format sarif > ansible-lint.sarif; then
@@ -153,30 +120,16 @@ runs:
# Exit with the original ansible-lint exit code
exit "$lint_exit_code"
- name: Set Git Config for Fixes
if: steps.check-files.outputs.files_found == 'true'
uses: ./set-git-config
with:
token: ${{ inputs.token }}
username: ${{ inputs.username }}
email: ${{ inputs.email }}
- name: Commit Fixes
if: steps.check-files.outputs.files_found == 'true'
shell: bash
run: |
set -euo pipefail
if git diff --quiet; then
echo "No changes to commit."
else
git add .
git commit -m "fix: applied ansible lint fixes"
git push
fi
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
with:
commit_message: 'style: apply ansible lint fixes'
commit_user_name: ${{ inputs.username }}
commit_user_email: ${{ inputs.email }}
- 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@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
with:
sarif_file: ansible-lint.sarif

View File

@@ -1,58 +0,0 @@
# ivuorinen/actions/biome-check
## Biome Check
### Description
Run Biome check on the repository
### Inputs
| name | description | required | default |
|---------------|--------------------------------------------------------------------|----------|-----------------------------|
| `token` | <p>GitHub token for authentication</p> | `false` | `${{ github.token }}` |
| `username` | <p>GitHub username for commits</p> | `false` | `github-actions` |
| `email` | <p>GitHub email for commits</p> | `false` | `github-actions@github.com` |
| `max-retries` | <p>Maximum number of retry attempts for npm install operations</p> | `false` | `3` |
### Outputs
| name | description |
|------------------|---------------------------------------|
| `check_status` | <p>Check status (success/failure)</p> |
| `errors_count` | <p>Number of errors found</p> |
| `warnings_count` | <p>Number of warnings found</p> |
### Runs
This action is a `composite` action.
### Usage
```yaml
- uses: ivuorinen/actions/biome-check@main
with:
token:
# GitHub token for authentication
#
# Required: false
# Default: ${{ github.token }}
username:
# GitHub username for commits
#
# Required: false
# Default: github-actions
email:
# GitHub email for commits
#
# Required: false
# Default: github-actions@github.com
max-retries:
# Maximum number of retry attempts for npm install operations
#
# Required: false
# Default: 3
```

View File

@@ -1,238 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
# permissions:
# - contents: read # Required for checking out repository
# - security-events: write # Required for uploading SARIF results
---
name: Biome Check
description: Run Biome check on the repository
author: Ismo Vuorinen
branding:
icon: check-circle
color: green
inputs:
token:
description: 'GitHub token for authentication'
required: false
default: ${{ github.token }}
username:
description: 'GitHub username for commits'
required: false
default: 'github-actions'
email:
description: 'GitHub email for commits'
required: false
default: 'github-actions@github.com'
max-retries:
description: 'Maximum number of retry attempts for npm install operations'
required: false
default: '3'
outputs:
check_status:
description: 'Check status (success/failure)'
value: ${{ steps.check.outputs.status }}
errors_count:
description: 'Number of errors found'
value: ${{ steps.check.outputs.errors }}
warnings_count:
description: 'Number of warnings found'
value: ${{ steps.check.outputs.warnings }}
runs:
using: composite
steps:
- name: Validate Inputs (Centralized)
uses: ./validate-inputs
with:
action: biome-check
- name: Validate Inputs (Additional)
id: validate
shell: bash
env:
GITHUB_TOKEN: ${{ inputs.token }}
EMAIL: ${{ inputs.email }}
USERNAME: ${{ inputs.username }}
MAX_RETRIES: ${{ inputs.max-retries }}
run: |
set -euo pipefail
# Validate GitHub token presence (no format validation to avoid false warnings)
if [[ -n "$GITHUB_TOKEN" ]] && ! [[ "$GITHUB_TOKEN" =~ ^\$\{\{ ]]; then
# Token is present and not a GitHub expression, assume it's valid
echo "Using provided GitHub token"
fi
# Validate email format (basic check)
if [[ "$EMAIL" != *"@"* ]] || [[ "$EMAIL" != *"."* ]]; then
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
exit 1
fi
# Validate username format (GitHub canonical rules)
username="$USERNAME"
# Check length (GitHub limit)
if [ ${#username} -gt 39 ]; then
echo "::error::Username too long: ${#username} characters. GitHub usernames are max 39 characters"
exit 1
fi
# Check allowed characters (letters, digits, hyphens only)
if ! [[ "$username" =~ ^[a-zA-Z0-9-]+$ ]]; then
echo "::error::Invalid username characters in '$username'. Only letters, digits, and hyphens allowed"
exit 1
fi
# Check doesn't start or end with hyphen
if [[ "$username" == -* ]] || [[ "$username" == *- ]]; then
echo "::error::Invalid username '$username'. Cannot start or end with hyphen"
exit 1
fi
# Check no consecutive hyphens
if [[ "$username" == *--* ]]; then
echo "::error::Invalid username '$username'. Consecutive hyphens not allowed"
exit 1
fi
# Validate max retries (positive integer with reasonable upper limit)
if ! [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
exit 1
fi
echo "Input validation completed successfully"
- name: Checkout Repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
token: ${{ inputs.token }}
- name: Set Git Config
uses: ./set-git-config
with:
token: ${{ inputs.token }}
username: ${{ inputs.username }}
email: ${{ inputs.email }}
- name: Node Setup
id: node-setup
uses: ./node-setup
- name: Cache Node Dependencies
id: cache
uses: ./common-cache
with:
type: 'npm'
paths: 'node_modules'
key-files: 'package-lock.json,yarn.lock,pnpm-lock.yaml,bun.lockb'
key-prefix: 'biome-check-${{ steps.node-setup.outputs.package-manager }}'
- name: Install Biome
shell: bash
env:
PACKAGE_MANAGER: ${{ steps.node-setup.outputs.package-manager }}
MAX_RETRIES: ${{ inputs.max-retries }}
run: |
set -euo pipefail
# Check if biome is already installed
if command -v biome >/dev/null 2>&1; then
echo "✅ Biome already installed: $(biome --version)"
exit 0
fi
echo "Installing Biome using $PACKAGE_MANAGER..."
for attempt in $(seq 1 "$MAX_RETRIES"); do
echo "Attempt $attempt of $MAX_RETRIES"
case "$PACKAGE_MANAGER" in
"pnpm")
if pnpm add -g @biomejs/biome; then
echo "✅ Biome installed successfully with pnpm"
exit 0
fi
;;
"yarn")
if yarn global add @biomejs/biome; then
echo "✅ Biome installed successfully with yarn"
exit 0
fi
;;
"bun")
if bun add -g @biomejs/biome; then
echo "✅ Biome installed successfully with bun"
exit 0
fi
;;
"npm"|*)
if npm install -g @biomejs/biome; then
echo "✅ Biome installed successfully with npm"
exit 0
fi
;;
esac
if [ $attempt -lt "$MAX_RETRIES" ]; then
echo "❌ Installation failed, retrying in 5 seconds..."
sleep 5
fi
done
echo "::error::Failed to install Biome after $MAX_RETRIES attempts"
exit 1
- name: Run Biome Check
id: check
shell: bash
run: |
set -euo pipefail
echo "Running Biome check..."
# Run Biome check with SARIF reporter
biome_exit_code=0
biome check . --reporter=sarif > biome-report.sarif || biome_exit_code=$?
# Handle failures gracefully
if [ $biome_exit_code -ne 0 ] && [ ! -s biome-report.sarif ]; then
echo "::warning::SARIF report generation failed with exit code $biome_exit_code"
# Create empty SARIF file to avoid upload errors
echo '{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[]}' > biome-report.sarif
fi
# Parse SARIF output for error counts
if [ -f biome-report.sarif ]; then
errors=$(jq '[.runs[]?.results[]? | select(.level == "error" or .level == "warning")] | length' biome-report.sarif 2>/dev/null || echo "0")
warnings="0" # Biome doesn't separate warnings in SARIF output
else
errors="0"
warnings="0"
fi
if [ $biome_exit_code -eq 0 ]; then
echo "status=success" >> "$GITHUB_OUTPUT"
echo "errors=0" >> "$GITHUB_OUTPUT"
echo "warnings=0" >> "$GITHUB_OUTPUT"
else
echo "status=failure" >> "$GITHUB_OUTPUT"
echo "errors=$errors" >> "$GITHUB_OUTPUT"
echo "warnings=$warnings" >> "$GITHUB_OUTPUT"
echo "::error::Biome check found $errors issues"
fi
echo "✅ Biome check completed"
# Exit with biome's exit code to fail the job on errors
exit $biome_exit_code
- name: Upload Biome Results
if: always()
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
with:
sarif_file: biome-report.sarif

View File

@@ -1,41 +0,0 @@
---
# Validation rules for biome-check action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 100% (4/4 inputs)
#
# This file defines validation rules for the biome-check GitHub Action.
# Rules are automatically applied by validate-inputs action when this
# action is used.
#
schema_version: '1.0'
action: biome-check
description: Run Biome check on the repository
generator_version: 1.0.0
required_inputs: []
optional_inputs:
- email
- max-retries
- token
- username
conventions:
email: email
max-retries: numeric_range_1_10
token: github_token
username: username
overrides: {}
statistics:
total_inputs: 4
validated_inputs: 4
skipped_inputs: 0
coverage_percentage: 100
validation_coverage: 100
auto_detected: true
manual_review_required: false
quality_indicators:
has_required_inputs: false
has_token_validation: true
has_version_validation: false
has_file_validation: false
has_security_validation: true

Some files were not shown because too many files have changed in this diff Show More