Compare commits

..

1 Commits

Author SHA1 Message Date
ff0ca4bc92 chore: update action references for release v2025.11.02
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:44:00 +02:00
234 changed files with 18872 additions and 10164 deletions

View File

@@ -17,12 +17,12 @@ runs:
using: composite
steps:
- name: Install uv
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
with:
enable-cache: true
- name: Set up Python
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0
with:
python-version-file: pyproject.toml
@@ -31,7 +31,7 @@ runs:
run: uv sync --frozen
- name: Setup Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: '24'
cache: npm

View File

@@ -15,19 +15,3 @@ paths-ignore:
# Use security and quality query suite
queries:
- uses: security-and-quality
# Suppress specific false positives
# These findings have been manually reviewed and determined to be false positives
# with appropriate security controls in place
query-filters:
# docker-publish: Code injection in validated context
# False positive: User input is validated and sanitized before use
# - Only relative paths and trusted git URLs are allowed
# - Absolute paths and arbitrary URLs are rejected
# - Path traversal attempts are blocked
# - Custom contexts require explicit opt-in via use-custom-context: true
# - Wraps docker/build-push-action (trusted Docker-maintained action)
# - Action is designed for trusted workflows only (documented in action.yml)
- exclude:
id: js/actions/code-injection
kind: problem

View File

@@ -1,7 +1,6 @@
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,34 +35,216 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
- name: Run Security Scan
id: security-scan
uses: ./security-scan
- 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"
printf '%s\n' "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
with:
gitleaks-license: ${{ secrets.GITLEAKS_LICENSE }}
token: ${{ secrets.GITHUB_TOKEN }}
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
printf '%s\n' "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
printf '%s\n' "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@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
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@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
sarif_file: 'gitleaks-report.sarif'
category: 'gitleaks'
- name: Archive security reports
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
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}`);
}
- name: Notify on Critical Issues
if: failure() && steps.security-scan.outputs.critical_issues != '0'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
if: failure()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |-
const { repo, owner } = context.repo;
const critical = '${{ steps.security-scan.outputs.critical_issues }}';
const total = '${{ steps.security-scan.outputs.total_issues }}';
const critical = core.getInput('critical_issues');
const body = `🚨 Critical security issues found in GitHub Actions
${critical} critical security issues (out of ${total} total) were found during the security scan.
${critical} critical security issues were found during the security scan.
### Scan Results
- 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' }}
- Trivy: ${{ steps.verify-sarif.outputs.has_trivy == 'true' && 'Completed' || 'Skipped/Failed' }}
- Gitleaks: ${{ steps.check-configs.outputs.run_gitleaks == 'true' && 'Completed' || 'Skipped' }}
[View detailed scan results](https://github.com/${owner}/${repo}/actions/runs/${context.runId})

View File

@@ -35,10 +35,10 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Log in to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
@@ -49,7 +49,7 @@ jobs:
- name: Extract metadata
id: meta
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
with:
images: ghcr.io/${{ github.repository_owner }}/actions
tags: |

View File

@@ -35,12 +35,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Run CodeQL Analysis
uses: ./codeql-analysis
with:
language: ${{ matrix.language }}
queries: security-and-quality
config-file: .github/codeql/codeql-config.yml
token: ${{ github.token }}

51
.github/workflows/codeql.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
---
# 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@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
languages: ${{ matrix.language }}
queries: security-and-quality
- name: Autobuild
uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
category: '/language:${{matrix.language}}'

View File

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

View File

@@ -17,7 +17,7 @@ jobs:
pull-requests: read
steps:
- name: Get dates for last month
shell: sh
shell: bash
run: |
# Calculate the first day of the previous month
first_day=$(date -d "last month" +%Y-%m-01)
@@ -30,7 +30,7 @@ jobs:
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@67526e7bd8100b870f10b1c120780a8375777b43 # v3.25.5
uses: github/issue-metrics@637a24e71b78bc10881e61972b19ea9ff736e14a # v3.25.2
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:ivuorinen/actions is:issue created:${{ env.last_month }} -reason:"not planned"'

View File

@@ -20,30 +20,27 @@ jobs:
version: ${{ steps.daily-version.outputs.version }}
steps:
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Create daily release
- name: Create tag if necessary
uses: fregante/daily-version-action@fb1a60b7c4daf1410cd755e360ebec3901e58588 # v2.1.3
id: daily-version
run: |
set -eu
VERSION="v$(date '+%Y.%m.%d')"
printf '%s\n' "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Create changelog text
if: steps.daily-version.outputs.created
id: changelog
uses: loopwerk/tag-changelog@941366edb8920e2071eae0449031830984b9f26e # v1.3.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
config_file: .github/tag-changelog-config.js
# Check if release already exists
if gh release view "$VERSION" >/dev/null 2>&1; then
printf '%s\n' "created=false" >> "$GITHUB_OUTPUT"
printf '%s\n' "Release $VERSION already exists - skipping"
exit 0
fi
# Create release with auto-generated changelog (also creates tag)
gh release create "$VERSION" \
--title "Release $VERSION" \
--generate-notes \
--target main
printf '%s\n' "created=true" >> "$GITHUB_OUTPUT"
printf '%s\n' "Created release $VERSION"
- name: Create release
if: steps.daily-version.outputs.created
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag: ${{ steps.daily-version.outputs.version }}
name: Release ${{ steps.daily-version.outputs.version }}
body: ${{ steps.changelog.outputs.changes }}
allowUpdates: true

View File

@@ -24,9 +24,17 @@ on:
merge_group:
env:
# MegaLinter configuration - these override the action's defaults
# Apply linter fixes configuration
APPLY_FIXES: all
APPLY_FIXES_EVENT: pull_request
APPLY_FIXES_MODE: commit
# Disable linters that do not work or conflict
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
@@ -39,7 +47,6 @@ concurrency:
permissions:
contents: read
packages: read # Required for private dependencies
jobs:
megalinter:
@@ -49,42 +56,124 @@ 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@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
fetch-depth: 0
- name: Run MegaLinter
id: pr-lint
uses: ./pr-lint
- 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: |
printf '%s\n' "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"
printf '%s\n' "status=failure" >> "$GITHUB_OUTPUT"
fi
else
echo "::warning::MegaLinter log file not found"
fi
- name: Upload Reports
if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
username: fiximus
email: github-bot@ivuorinen.net
name: MegaLinter reports
path: |
megalinter-reports
mega-linter.log
retention-days: 30
- name: Upload SARIF Report
if: always() && hashFiles('megalinter-reports/sarif/*.sarif')
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
sarif_file: megalinter-reports/sarif
category: megalinter
- name: Check Results
- 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
if: always()
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const status = '${{ steps.pr-lint.outputs.validation_status }}';
const status = '${{ steps.check-results.outputs.status }}';
const conclusion = status === 'success' ? 'success' : 'failure';
const summary = `## MegaLinter Results
@@ -104,7 +193,7 @@ jobs:
- name: Cleanup
if: always()
shell: sh
shell: bash
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@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
- uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2.5.0
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
with:
generate_release_notes: true

View File

@@ -35,7 +35,7 @@ jobs:
steps:
- name: Checkout PR
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
repository: ${{ github.event.pull_request.head.repo.full_name }}
@@ -43,7 +43,7 @@ jobs:
- name: Fetch PR Base
run: |
set -eu
set -euo pipefail
# 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,9 +97,6 @@ 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: [],
@@ -233,40 +230,11 @@ jobs:
if (findings.permissions.length > 0) {
const permSection = ['## 🔐 GitHub Actions Permissions Changes'];
findings.permissions.forEach(change => {
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>`);
}
}
permSection.push(`**${change.file}**:`);
permSection.push('```diff');
permSection.push(`- ${change.old}`);
permSection.push(`+ ${change.new}`);
permSection.push('```');
});
sections.push(permSection.join('\n'));
}
@@ -346,15 +314,15 @@ jobs:
// Export critical count as output
core.setOutput('critical_issues', criticalCount.toString());
// Generate final comment with unique marker
let comment = `${SECURITY_COMMENT_MARKER}\n## ✅ Security Analysis\n\n`;
// Generate final comment
let comment = '## ✅ 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 using unique marker
// Find existing security comment
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -362,7 +330,8 @@ jobs:
});
const existingComment = comments.find(comment =>
comment.body && comment.body.includes(SECURITY_COMMENT_MARKER)
comment.body.includes('Security Analysis') ||
comment.body.includes('🔐 GitHub Actions Permissions')
);
if (existingComment) {

View File

@@ -25,7 +25,7 @@ jobs:
steps:
- name: 🚀 Run stale
uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30

View File

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

View File

@@ -49,16 +49,16 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup test environment
uses: ./.github/actions/setup-test-environment
- name: Run unit tests
shell: sh
shell: bash
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: sh
shell: bash
run: ./_tests/run-tests.sh --type unit --format sarif
if: always()
- name: Upload SARIF file
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
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@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup test environment
uses: ./.github/actions/setup-test-environment
@@ -107,10 +107,10 @@ jobs:
install-act: 'true'
- name: Run integration tests
shell: sh
shell: bash
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: sh
shell: bash
run: |
if [ -d "_tests/reports/integration" ] && [ -n "$(find _tests/reports/integration -type f 2>/dev/null)" ]; then
printf '%s\n' "reports-found=true" >> "$GITHUB_OUTPUT"
printf '%s\n' "reports-found=true" >> $GITHUB_OUTPUT
echo "Integration test reports found"
else
printf '%s\n' "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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
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@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
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: sh
shell: bash
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@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Setup test environment
uses: ./.github/actions/setup-test-environment
@@ -240,7 +240,7 @@ jobs:
extra_args: --debug --only-verified
- name: Scan shell scripts
shell: sh
shell: bash
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@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.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: sh
shell: bash
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: sh
shell: bash
run: |-
echo "❌ One or more test jobs failed"
exit 1

View File

@@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
@@ -49,7 +49,7 @@ jobs:
- name: Create Pull Request
if: steps.action-versioning.outputs.updated == 'true'
uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}'
@@ -68,6 +68,8 @@ jobs:
```bash
make check-version-refs
```
🤖 Auto-generated by version-maintenance workflow
branch: automated/version-update-${{ steps.version.outputs.major }}
delete-branch: true
labels: |
@@ -76,7 +78,7 @@ jobs:
- name: Check for Annual Bump
if: steps.action-versioning.outputs.needs-annual-bump == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const currentYear = new Date().getFullYear();
@@ -118,6 +120,8 @@ jobs:
\`\`\`bash
make check-version-refs
\`\`\`
🤖 Auto-generated by version-maintenance workflow
`,
labels: ['maintenance', 'high-priority']
});

2
.gitignore vendored
View File

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

View File

@@ -1,2 +0,0 @@
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|\.worktrees)
(node_modules|\.automation/test|docs/json-schemas)

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.18
rev: 0.9.5
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.20.0
rev: v0.18.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.10
rev: v0.14.2
hooks:
# Run the linter with auto-fix
- id: ruff-check
@@ -78,19 +78,24 @@ repos:
exclude: '^_tests/.*\.sh$'
- repo: https://github.com/rhysd/actionlint
rev: v1.7.9
rev: v1.7.8
hooks:
- id: actionlint
args: ['-shellcheck=']
- repo: https://github.com/renovatebot/pre-commit-hooks
rev: 41.159.4
hooks:
- id: renovate-config-validator
- repo: https://github.com/bridgecrewio/checkov.git
rev: '3.2.495'
rev: '3.2.489'
hooks:
- id: checkov
args:
- '--quiet'
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.0
rev: v8.28.0
hooks:
- id: gitleaks

View File

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

View File

@@ -1 +1 @@
3.14.2
3.14.0

View File

@@ -45,32 +45,6 @@
- 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.

View File

@@ -5,14 +5,13 @@
- **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)
- **Total Actions**: 43 self-contained actions
## Structure
```text
/
├── <action-dirs>/ # 44 self-contained actions
├── <action-dirs>/ # 43 self-contained actions
│ ├── action.yml # Action definition
│ ├── README.md # Auto-generated
│ └── CustomValidator.py # Optional validator
@@ -26,14 +25,12 @@
└── Makefile # Build automation
```
## Action Categories (44 total)
## Action Categories (43 total)
**Setup (7)**: node-setup, set-git-config, php-version-detect, python-version-detect, python-version-detect-v2, go-version-detect, dotnet-version-detect
**Linting (13)**: ansible-lint-fix, biome-check/fix, csharp-lint-check, eslint-check/fix, go-lint, pr-lint, pre-commit, prettier-check/fix, python-lint-fix, terraform-lint-fix
**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
@@ -88,28 +85,3 @@ make test # All tests (pytest + ShellSpec)
- ✅ 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,2 +1 @@
.venv
.worktrees/

View File

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

View File

@@ -39,7 +39,7 @@
**Core Memories** (read first for project understanding):
- `repository_overview` 30 actions, categories, structure, status
- `repository_overview` 43 actions, categories, structure, status
- `validator_system` Validation architecture, components, usage patterns
- `development_standards` Quality rules, workflows, security, completion checklist
@@ -71,11 +71,11 @@
Flat structure. Each action self-contained with `action.yml`.
**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),
**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),
Validation (validate-inputs)
## Commands

View File

@@ -217,7 +217,7 @@ check-version-refs: ## List all current SHA-pinned action references
# Formatting targets
format-markdown: ## Format markdown files
@echo "$(BLUE)📝 Formatting markdown...$(RESET)"
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" "#.worktrees" 2>/dev/null; then \
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" 2>/dev/null; then \
echo "$(GREEN)✅ Markdown formatted$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Markdown formatting issues found$(RESET)" | tee -a $(LOG_FILE); \
@@ -269,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" "#.worktrees"; then \
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules"; then \
echo "$(GREEN)✅ Markdown linting passed$(RESET)"; \
else \
echo "$(YELLOW)⚠️ Markdown linting issues found$(RESET)" | tee -a $(LOG_FILE); \
@@ -291,7 +291,7 @@ lint-shell: ## Lint shell scripts
echo " or: apt-get install shellcheck"; \
exit 1; \
fi
@if find . -name "*.sh" -not -path "./_tests/*" -not -path "./.worktrees/*" -exec shellcheck -x {} +; then \
@if find . -name "*.sh" -not -path "./_tests/*" -exec shellcheck -x {} +; then \
echo "$(GREEN)✅ Shell linting passed$(RESET)"; \
else \
echo "$(RED)❌ Shell linting issues found$(RESET)"; \
@@ -340,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" -not -path "./_tests/*" -not -path "./.worktrees/*" -print0 | while IFS= read -r -d '' file; do \
find . -name "*.sh" -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; \
@@ -721,8 +721,7 @@ 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" \) \
-not -path "./_tests/*" -not -path "./.worktrees/*" -not -path "./node_modules/*" | \
find . -name "*.yml" -o -name "*.yaml" -o -name "*.md" -o -name "*.sh" | \
entr -c $(MAKE) format; \
else \
echo "$(RED)❌ Error: entr not found. Install with: brew install entr$(RESET)"; \

328
README.md
View File

@@ -22,105 +22,136 @@ Each action is fully self-contained and can be used independently in any GitHub
## 📚 Action Catalog
This repository contains **26 reusable GitHub Actions** for CI/CD automation.
This repository contains **44 reusable GitHub Actions** for CI/CD automation.
### Quick Reference (26 Actions)
### Quick Reference (44 Actions)
| 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 |
| 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... | 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] | Repository | 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, Token auth, Outputs |
| 📝 | [`csharp-lint-check`][csharp-lint-check] | Linting | Runs linters like StyleCop or dotnet-format for C# code styl... | Auto-detection, Token auth, 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, Token auth, 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, Token auth, Outputs |
| | [`eslint-check`][eslint-check] | Linting | Run ESLint check on the repository with advanced configurati... | Caching, Token auth, 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, Token auth, Outputs |
| 📝 | [`go-lint`][go-lint] | Linting | Run golangci-lint with advanced configuration, caching, and ... | Caching, Token auth, Outputs |
| 📝 | [`go-version-detect`][go-version-detect] | Setup | Detects the Go version from the project's go.mod file or def... | Auto-detection, Token auth, 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 ... | Token auth, 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, 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-check`][prettier-check] | Linting | Run Prettier check on the repository with advanced configura... | Caching, Token auth, 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, Token auth, Outputs |
| 📝 | [`python-version-detect-v2`][python-version-detect-v2] | Setup | Detects Python version from project configuration files usin... | Auto-detection, Token auth, 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] | Validation | Centralized Python-based input validation for GitHub Actions... | Token auth, Outputs |
| 📦 | [`version-file-parser`][version-file-parser] | Utilities | Universal parser for common version detection files (.tool-v... | Auto-detection, Outputs |
| ✅ | [`version-validator`][version-validator] | Utilities | Validates and normalizes version strings using customizable ... | Auto-detection, Outputs |
### Actions by Category
#### 🔧 Setup (1 action)
#### 🔧 Setup (7 actions)
| 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 |
| Action | Description | Languages | Features |
|:----------------------------------------------------------|:------------------------------------------------------|:--------------------------------|:---------------------------------------------|
| 📝 [`dotnet-version-detect`][dotnet-version-detect] | Detects .NET SDK version from global.json or defau... | C#, .NET | Auto-detection, Token auth, Outputs |
| 📝 [`go-version-detect`][go-version-detect] | Detects the Go version from the project's go.mod f... | Go | Auto-detection, Token auth, 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, Token auth, Outputs |
| 📝 [`python-version-detect`][python-version-detect] | Detects Python version from project configuration ... | Python | Auto-detection, Token auth, Outputs |
| 📝 [`python-version-detect-v2`][python-version-detect-v2] | Detects Python version from project configuration ... | Python | Auto-detection, Token auth, Outputs |
| 🔀 [`set-git-config`][set-git-config] | Sets Git configuration for actions. | - | Token auth, Outputs |
#### 🛠️ Utilities (1 action)
#### 🛠️ Utilities (3 actions)
| Action | Description | Languages | Features |
|:--------------------------------------------|:------------------------------------------------------|:---------------|:--------------------|
| 🔀 [`action-versioning`][action-versioning] | Automatically update SHA-pinned action references ... | GitHub Actions | Token auth, Outputs |
| Action | Description | Languages | Features |
|:------------------------------------------------|:------------------------------------------------------|:----------------------------|:------------------------|
| 🔀 [`action-versioning`][action-versioning] | Automatically update SHA-pinned action references ... | - | Token auth, Outputs |
| 📦 [`version-file-parser`][version-file-parser] | Universal parser for common version detection file... | Multiple Languages | Auto-detection, Outputs |
| ✅ [`version-validator`][version-validator] | Validates and normalizes version strings using cus... | Semantic Versioning, CalVer | Auto-detection, Outputs |
#### 📝 Linting (10 actions)
#### 📝 Linting (13 actions)
| Action | Description | Languages | Features |
|:-----------------------------------------------|:------------------------------------------------------|:---------------------------------------------|:---------------------------------------------|
| 📦 [`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 |
| 📦 [`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, Token auth, Outputs |
| ✅ [`eslint-check`][eslint-check] | Run ESLint check on the repository with advanced c... | JavaScript, TypeScript | Caching, Token auth, 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, 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 |
| ✅ [`prettier-check`][prettier-check] | Run Prettier check on the repository with advanced... | JavaScript, TypeScript, Markdown, YAML, JSON | Caching, Token auth, Outputs |
| 📝 [`prettier-fix`][prettier-fix] | Run Prettier to fix code style violations | JavaScript, TypeScript, Markdown, YAML, JSON | 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 (1 action)
#### 🧪 Testing (3 actions)
| Action | Description | Languages | Features |
|:---------------------------|:------------------------------------------------------|:-------------|:---------------------------------------------|
| [`php-tests`][php-tests] | Run PHPUnit tests with optional Laravel setup and ... | PHP, Laravel | Caching, Auto-detection, Token auth, Outputs |
| 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 |
#### 🏗️ Build (3 actions)
| Action | Description | Languages | Features |
|:----------------------------------|:------------------------------------------------------|:----------|:---------------------------------------------|
| 📝 [`csharp-build`][csharp-build] | Builds and tests C# projects. | C#, .NET | Caching, Auto-detection, Token auth, Outputs |
| 📝 [`csharp-build`][csharp-build] | Builds and tests C# projects. | C#, .NET | 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, Token auth, Outputs |
#### 🚀 Publishing (3 actions)
#### 🚀 Publishing (5 actions)
| 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 |
| 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, Token auth, 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 | Token auth, Outputs |
#### 📦 Repository (5 actions)
#### 📦 Repository (9 actions)
| 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 |
| 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 |
| 💾 [`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... | YAML, GitHub | Token auth, Outputs |
#### ✅ Validation (1 action)
@@ -130,66 +161,83 @@ This repository contains **26 reusable GitHub Actions** for CI/CD automation.
### Feature Matrix
| 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] | - | - | ✅ | ✅ |
| Action | Caching | Auto-detection | Token auth | Outputs |
|:-------------------------------------------------------|:-------:|:--------------:|:----------:|:-------:|
| [`action-versioning`][action-versioning] | - | - | ✅ | ✅ |
| [`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] | - | ✅ | - | ✅ |
### Language Support
| 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] |
| 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# | [`codeql-analysis`][codeql-analysis], [`csharp-build`][csharp-build], [`csharp-lint-check`][csharp-lint-check], [`csharp-publish`][csharp-publish], [`dotnet-version-detect`][dotnet-version-detect] |
| C++ | [`codeql-analysis`][codeql-analysis] |
| CalVer | [`version-validator`][version-validator] |
| Conventional Commits | [`pr-lint`][pr-lint] |
| Docker | [`docker-build`][docker-build], [`docker-publish`][docker-publish], [`docker-publish-gh`][docker-publish-gh], [`docker-publish-hub`][docker-publish-hub] |
| GitHub | [`sync-labels`][sync-labels] |
| GitHub Actions | [`validate-inputs`][validate-inputs] |
| Go | [`codeql-analysis`][codeql-analysis], [`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] |
| Java | [`codeql-analysis`][codeql-analysis] |
| JavaScript | [`biome-check`][biome-check], [`biome-fix`][biome-fix], [`codeql-analysis`][codeql-analysis], [`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] |
| Multiple Languages | [`pre-commit`][pre-commit], [`version-file-parser`][version-file-parser] |
| 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 | [`codeql-analysis`][codeql-analysis], [`pre-commit`][pre-commit], [`python-lint-fix`][python-lint-fix], [`python-version-detect`][python-version-detect], [`python-version-detect-v2`][python-version-detect-v2] |
| Ruby | [`codeql-analysis`][codeql-analysis] |
| Semantic Versioning | [`version-validator`][version-validator] |
| Terraform | [`terraform-lint-fix`][terraform-lint-fix] |
| TypeScript | [`biome-check`][biome-check], [`biome-fix`][biome-fix], [`codeql-analysis`][codeql-analysis], [`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], [`sync-labels`][sync-labels], [`validate-inputs`][validate-inputs] |
| npm | [`npm-publish`][npm-publish] |
### Action Usage
@@ -213,30 +261,48 @@ All actions can be used independently in your workflows:
[action-versioning]: action-versioning/README.md
[ansible-lint-fix]: ansible-lint-fix/README.md
[biome-lint]: biome-lint/README.md
[biome-check]: biome-check/README.md
[biome-fix]: biome-fix/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
[eslint-lint]: eslint-lint/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
[go-build]: go-build/README.md
[go-lint]: go-lint/README.md
[language-version-detect]: language-version-detect/README.md
[go-version-detect]: go-version-detect/README.md
[node-setup]: node-setup/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-lint]: prettier-lint/README.md
[prettier-check]: prettier-check/README.md
[prettier-fix]: prettier-fix/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
[security-scan]: security-scan/README.md
[set-git-config]: set-git-config/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

@@ -231,7 +231,7 @@ When security issues are fixed:
- Replaced custom Bun installation with official action
- Replaced custom Trivy installation with official action
- Added secret masking to 7 critical actions (including docker-publish)
- Migrated from custom common-cache to official actions/cache
- Optimized file hashing in common-cache
- Status: ✅ Complete
### Phase 3: Documentation & Policy (2024)

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,28 +57,6 @@ 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"
@@ -370,5 +348,5 @@ run_action_tests() {
}
# Export all functions
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name get_action_runs_using is_input_required
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name
export -f test_input_validation test_action_outputs test_external_usage measure_action_time run_action_tests

View File

@@ -21,9 +21,6 @@ import sys
import yaml # pylint: disable=import-error
# Default value for unknown action names (matches shared.validation_core.DEFAULT_UNKNOWN)
_DEFAULT_UNKNOWN = "Unknown"
class ActionValidator:
"""Handles validation of GitHub Action inputs using Python regex engine."""
@@ -89,7 +86,7 @@ class ActionValidator:
return True, ""
# Check for environment variable reference (e.g., $GITHUB_TOKEN)
if re.match(r"^\$[A-Za-z_]\w*$", token, re.ASCII):
if re.match(r"^\$[A-Za-z_][A-Za-z0-9_]*$", token):
return True, ""
# Check against all known token patterns
@@ -333,16 +330,16 @@ def get_action_name(action_file: str) -> str:
action_file: Path to the action.yml file
Returns:
Action name or _DEFAULT_UNKNOWN if not found
Action name or "Unknown" if not found
"""
try:
with Path(action_file).open(encoding="utf-8") as f:
data = yaml.safe_load(f)
return data.get("name", _DEFAULT_UNKNOWN)
return data.get("name", "Unknown")
except Exception:
return _DEFAULT_UNKNOWN
return "Unknown"
def _show_usage():

View File

@@ -0,0 +1,471 @@
---
name: Integration Test - Common Cache
on:
workflow_dispatch:
push:
paths:
- 'common-cache/**'
- '_tests/integration/workflows/common-cache-test.yml'
jobs:
test-common-cache-key-generation:
name: Test Cache Key Generation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test basic key generation
run: |
RUNNER_OS="Linux"
CACHE_TYPE="npm"
KEY_PREFIX=""
cache_key="$RUNNER_OS"
[ -n "$CACHE_TYPE" ] && cache_key="${cache_key}-${CACHE_TYPE}"
expected="Linux-npm"
if [[ "$cache_key" != "$expected" ]]; then
echo "❌ ERROR: Expected '$expected', got '$cache_key'"
exit 1
fi
echo "✓ Basic cache key generation works"
- name: Test key with prefix
run: |
RUNNER_OS="Linux"
CACHE_TYPE="npm"
KEY_PREFIX="node-20"
cache_key="$RUNNER_OS"
[ -n "$KEY_PREFIX" ] && cache_key="${cache_key}-${KEY_PREFIX}"
[ -n "$CACHE_TYPE" ] && cache_key="${cache_key}-${CACHE_TYPE}"
expected="Linux-node-20-npm"
if [[ "$cache_key" != "$expected" ]]; then
echo "❌ ERROR: Expected '$expected', got '$cache_key'"
exit 1
fi
echo "✓ Cache key with prefix works"
- name: Test OS-specific keys
run: |
for os in "Linux" "macOS" "Windows"; do
CACHE_TYPE="test"
cache_key="$os-$CACHE_TYPE"
if [[ ! "$cache_key" =~ ^(Linux|macOS|Windows)-test$ ]]; then
echo "❌ ERROR: Invalid key for OS $os: $cache_key"
exit 1
fi
echo "✓ OS-specific key for $os: $cache_key"
done
test-common-cache-file-hashing:
name: Test File Hashing
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create test files
run: |
mkdir -p test-cache
cd test-cache
echo "content1" > file1.txt
echo "content2" > file2.txt
echo "content3" > file3.txt
- name: Test single file hash
run: |
cd test-cache
file_hash=$(cat file1.txt | sha256sum | cut -d' ' -f1)
if [[ ! "$file_hash" =~ ^[a-f0-9]{64}$ ]]; then
echo "❌ ERROR: Invalid hash format: $file_hash"
exit 1
fi
echo "✓ Single file hash: $file_hash"
- name: Test multiple file hash
run: |
cd test-cache
multi_hash=$(cat file1.txt file2.txt file3.txt | sha256sum | cut -d' ' -f1)
if [[ ! "$multi_hash" =~ ^[a-f0-9]{64}$ ]]; then
echo "❌ ERROR: Invalid hash format: $multi_hash"
exit 1
fi
echo "✓ Multiple file hash: $multi_hash"
- name: Test hash changes with content
run: |
cd test-cache
# Get initial hash
hash1=$(cat file1.txt | sha256sum | cut -d' ' -f1)
# Modify file
echo "modified" > file1.txt
# Get new hash
hash2=$(cat file1.txt | sha256sum | cut -d' ' -f1)
if [[ "$hash1" == "$hash2" ]]; then
echo "❌ ERROR: Hash should change when content changes"
exit 1
fi
echo "✓ Hash changes with content modification"
- name: Test comma-separated file list processing
run: |
cd test-cache
KEY_FILES="file1.txt,file2.txt,file3.txt"
IFS=',' read -ra FILES <<< "$KEY_FILES"
existing_files=()
for file in "${FILES[@]}"; do
file=$(echo "$file" | xargs)
if [ -f "$file" ]; then
existing_files+=("$file")
fi
done
if [ ${#existing_files[@]} -ne 3 ]; then
echo "❌ ERROR: Should find 3 files, found ${#existing_files[@]}"
exit 1
fi
echo "✓ Comma-separated file list processing works"
- name: Test missing file handling
run: |
cd test-cache
KEY_FILES="file1.txt,missing.txt,file2.txt"
IFS=',' read -ra FILES <<< "$KEY_FILES"
existing_files=()
for file in "${FILES[@]}"; do
file=$(echo "$file" | xargs)
if [ -f "$file" ]; then
existing_files+=("$file")
fi
done
if [ ${#existing_files[@]} -ne 2 ]; then
echo "❌ ERROR: Should find 2 files, found ${#existing_files[@]}"
exit 1
fi
echo "✓ Missing files correctly skipped"
test-common-cache-env-vars:
name: Test Environment Variables
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test single env var inclusion
run: |
export NODE_VERSION="20.9.0"
ENV_VARS="NODE_VERSION"
IFS=',' read -ra VARS <<< "$ENV_VARS"
env_hash=""
for var in "${VARS[@]}"; do
if [ -n "${!var}" ]; then
env_hash="${env_hash}-${var}-${!var}"
fi
done
expected="-NODE_VERSION-20.9.0"
if [[ "$env_hash" != "$expected" ]]; then
echo "❌ ERROR: Expected '$expected', got '$env_hash'"
exit 1
fi
echo "✓ Single env var inclusion works"
- name: Test multiple env vars
run: |
export NODE_VERSION="20.9.0"
export PACKAGE_MANAGER="npm"
ENV_VARS="NODE_VERSION,PACKAGE_MANAGER"
IFS=',' read -ra VARS <<< "$ENV_VARS"
env_hash=""
for var in "${VARS[@]}"; do
if [ -n "${!var}" ]; then
env_hash="${env_hash}-${var}-${!var}"
fi
done
expected="-NODE_VERSION-20.9.0-PACKAGE_MANAGER-npm"
if [[ "$env_hash" != "$expected" ]]; then
echo "❌ ERROR: Expected '$expected', got '$env_hash'"
exit 1
fi
echo "✓ Multiple env vars inclusion works"
- name: Test undefined env var skipping
run: |
export NODE_VERSION="20.9.0"
ENV_VARS="NODE_VERSION,UNDEFINED_VAR"
IFS=',' read -ra VARS <<< "$ENV_VARS"
env_hash=""
for var in "${VARS[@]}"; do
if [ -n "${!var}" ]; then
env_hash="${env_hash}-${var}-${!var}"
fi
done
# Should only include NODE_VERSION
expected="-NODE_VERSION-20.9.0"
if [[ "$env_hash" != "$expected" ]]; then
echo "❌ ERROR: Expected '$expected', got '$env_hash'"
exit 1
fi
echo "✓ Undefined env vars correctly skipped"
test-common-cache-path-processing:
name: Test Path Processing
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test single path
run: |
CACHE_PATHS="~/.npm"
IFS=',' read -ra PATHS <<< "$CACHE_PATHS"
if [ ${#PATHS[@]} -ne 1 ]; then
echo "❌ ERROR: Should have 1 path, got ${#PATHS[@]}"
exit 1
fi
echo "✓ Single path processing works"
- name: Test multiple paths
run: |
CACHE_PATHS="~/.npm,~/.yarn/cache,node_modules"
IFS=',' read -ra PATHS <<< "$CACHE_PATHS"
if [ ${#PATHS[@]} -ne 3 ]; then
echo "❌ ERROR: Should have 3 paths, got ${#PATHS[@]}"
exit 1
fi
echo "✓ Multiple paths processing works"
- name: Test path with spaces (trimming)
run: |
CACHE_PATHS=" ~/.npm , ~/.yarn/cache , node_modules "
IFS=',' read -ra PATHS <<< "$CACHE_PATHS"
trimmed_paths=()
for path in "${PATHS[@]}"; do
trimmed=$(echo "$path" | xargs)
trimmed_paths+=("$trimmed")
done
# Check first path is trimmed
if [[ "${trimmed_paths[0]}" != "~/.npm" ]]; then
echo "❌ ERROR: Path not trimmed: '${trimmed_paths[0]}'"
exit 1
fi
echo "✓ Path trimming works"
test-common-cache-complete-key-generation:
name: Test Complete Key Generation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create test files
run: |
mkdir -p test-complete
cd test-complete
echo "package-lock content" > package-lock.json
- name: Test complete cache key with all components
run: |
cd test-complete
RUNNER_OS="Linux"
CACHE_TYPE="npm"
KEY_PREFIX="node-20"
# Generate file hash
files_hash=$(cat package-lock.json | sha256sum | cut -d' ' -f1)
# Generate env hash
export NODE_VERSION="20.9.0"
env_hash="-NODE_VERSION-20.9.0"
# Generate final key
cache_key="$RUNNER_OS"
[ -n "$KEY_PREFIX" ] && cache_key="${cache_key}-${KEY_PREFIX}"
[ -n "$CACHE_TYPE" ] && cache_key="${cache_key}-${CACHE_TYPE}"
[ -n "$files_hash" ] && cache_key="${cache_key}-${files_hash}"
[ -n "$env_hash" ] && cache_key="${cache_key}${env_hash}"
echo "Generated cache key: $cache_key"
# Verify structure
if [[ ! "$cache_key" =~ ^Linux-node-20-npm-[a-f0-9]{64}-NODE_VERSION-20\.9\.0$ ]]; then
echo "❌ ERROR: Invalid cache key structure: $cache_key"
exit 1
fi
echo "✓ Complete cache key generation works"
test-common-cache-restore-keys:
name: Test Restore Keys
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test single restore key
run: |
RESTORE_KEYS="Linux-npm-"
if [[ -z "$RESTORE_KEYS" ]]; then
echo "❌ ERROR: Restore keys should not be empty"
exit 1
fi
echo "✓ Single restore key: $RESTORE_KEYS"
- name: Test multiple restore keys
run: |
RESTORE_KEYS="Linux-node-20-npm-,Linux-node-npm-,Linux-npm-"
IFS=',' read -ra KEYS <<< "$RESTORE_KEYS"
if [ ${#KEYS[@]} -ne 3 ]; then
echo "❌ ERROR: Should have 3 restore keys, got ${#KEYS[@]}"
exit 1
fi
echo "✓ Multiple restore keys work"
test-common-cache-type-specific-scenarios:
name: Test Type-Specific Scenarios
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test NPM cache key
run: |
TYPE="npm"
FILES="package-lock.json"
PATHS="~/.npm,node_modules"
echo "✓ NPM cache configuration valid"
echo " Type: $TYPE"
echo " Key files: $FILES"
echo " Paths: $PATHS"
- name: Test Composer cache key
run: |
TYPE="composer"
FILES="composer.lock"
PATHS="~/.composer/cache,vendor"
echo "✓ Composer cache configuration valid"
echo " Type: $TYPE"
echo " Key files: $FILES"
echo " Paths: $PATHS"
- name: Test Go cache key
run: |
TYPE="go"
FILES="go.sum"
PATHS="~/go/pkg/mod,~/.cache/go-build"
echo "✓ Go cache configuration valid"
echo " Type: $TYPE"
echo " Key files: $FILES"
echo " Paths: $PATHS"
- name: Test Pip cache key
run: |
TYPE="pip"
FILES="requirements.txt"
PATHS="~/.cache/pip"
echo "✓ Pip cache configuration valid"
echo " Type: $TYPE"
echo " Key files: $FILES"
echo " Paths: $PATHS"
test-common-cache-edge-cases:
name: Test Edge Cases
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test empty prefix
run: |
KEY_PREFIX=""
cache_key="Linux"
[ -n "$KEY_PREFIX" ] && cache_key="${cache_key}-${KEY_PREFIX}"
if [[ "$cache_key" != "Linux" ]]; then
echo "❌ ERROR: Empty prefix should not modify key"
exit 1
fi
echo "✓ Empty prefix handling works"
- name: Test no key files
run: |
KEY_FILES=""
files_hash=""
if [ -n "$KEY_FILES" ]; then
echo "❌ ERROR: Should detect empty key files"
exit 1
fi
echo "✓ No key files handling works"
- name: Test no env vars
run: |
ENV_VARS=""
env_hash=""
if [ -n "$ENV_VARS" ]; then
echo "❌ ERROR: Should detect empty env vars"
exit 1
fi
echo "✓ No env vars handling works"
integration-test-summary:
name: Integration Test Summary
runs-on: ubuntu-latest
needs:
- test-common-cache-key-generation
- test-common-cache-file-hashing
- test-common-cache-env-vars
- test-common-cache-path-processing
- test-common-cache-complete-key-generation
- test-common-cache-restore-keys
- test-common-cache-type-specific-scenarios
- test-common-cache-edge-cases
steps:
- name: Summary
run: |
echo "=========================================="
echo "Common Cache Integration Tests - PASSED"
echo "=========================================="
echo ""
echo "✓ Cache key generation tests"
echo "✓ File hashing tests"
echo "✓ Environment variable tests"
echo "✓ Path processing tests"
echo "✓ Complete key generation tests"
echo "✓ Restore keys tests"
echo "✓ Type-specific scenario tests"
echo "✓ Edge case tests"
echo ""
echo "All common-cache integration tests completed successfully!"

View File

@@ -0,0 +1,440 @@
---
name: Integration Test - GitHub Release
on:
workflow_dispatch:
push:
paths:
- 'github-release/**'
- '_tests/integration/workflows/github-release-test.yml'
jobs:
test-github-release-validation:
name: Test Input Validation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test invalid version format
run: |
VERSION='not.a.version'
if [[ "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ ERROR: Invalid version format should have failed"
exit 1
fi
echo "✓ Invalid version format correctly rejected"
- name: Test version with alphabetic characters
run: |
VERSION='abc.def.ghi'
if [[ "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ ERROR: Alphabetic version should have failed"
exit 1
fi
echo "✓ Alphabetic version correctly rejected"
- name: Test valid version formats
run: |
for version in "1.2.3" "v1.2.3" "1.0.0-alpha" "2.0.0+build"; do
if ! [[ "$version" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ ERROR: Valid version '$version' should have passed"
exit 1
fi
echo "✓ Valid version '$version' accepted"
done
test-github-release-version-formats:
name: Test Version Format Support
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test basic SemVer (dry run)
run: |
echo "Testing basic SemVer format: 1.2.3"
VERSION="1.2.3"
if ! [[ "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ ERROR: Valid version rejected"
exit 1
fi
echo "✓ Basic SemVer format accepted"
- name: Test SemVer with v prefix
run: |
echo "Testing SemVer with v prefix: v1.2.3"
VERSION="v1.2.3"
if ! [[ "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ ERROR: Valid version rejected"
exit 1
fi
echo "✓ SemVer with v prefix accepted"
- name: Test prerelease version
run: |
echo "Testing prerelease version: 1.0.0-alpha.1"
VERSION="1.0.0-alpha.1"
if ! [[ "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ ERROR: Valid prerelease version rejected"
exit 1
fi
echo "✓ Prerelease version accepted"
- name: Test version with build metadata
run: |
echo "Testing version with build metadata: 1.0.0+build.123"
VERSION="1.0.0+build.123"
if ! [[ "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ ERROR: Valid build metadata version rejected"
exit 1
fi
echo "✓ Version with build metadata accepted"
- name: Test complex version
run: |
echo "Testing complex version: v2.1.0-rc.1+build.456"
VERSION="v2.1.0-rc.1+build.456"
if ! [[ "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ ERROR: Valid complex version rejected"
exit 1
fi
echo "✓ Complex version accepted"
test-github-release-tool-availability:
name: Test Tool Availability Checks
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test gh CLI detection logic
run: |
# Test the logic for checking gh availability
# In actual action, this would fail if gh is not available
if command -v gh >/dev/null 2>&1; then
echo "✓ gh CLI is available in this environment"
gh --version
else
echo "⚠️ gh CLI not available in test environment (would fail in actual action)"
echo "✓ Tool detection logic works correctly"
fi
- name: Test jq detection logic
run: |
# Test the logic for checking jq availability
# In actual action, this would fail if jq is not available
if command -v jq >/dev/null 2>&1; then
echo "✓ jq is available in this environment"
jq --version
else
echo "⚠️ jq not available in test environment (would fail in actual action)"
echo "✓ Tool detection logic works correctly"
fi
- name: Test tool requirement validation
run: |
# Verify the action correctly checks for required tools
REQUIRED_TOOLS=("gh" "jq")
echo "Testing tool requirement checks..."
for tool in "${REQUIRED_TOOLS[@]}"; do
if command -v "$tool" >/dev/null 2>&1; then
echo " ✓ $tool: available"
else
echo " ⚠️ $tool: not available (action would fail at this check)"
fi
done
echo "✓ Tool requirement validation logic verified"
- name: Test gh authentication logic
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Test authentication detection logic
if [[ -n "$GITHUB_TOKEN" ]]; then
echo "✓ GITHUB_TOKEN environment variable is set"
else
echo "⚠️ GITHUB_TOKEN not set in test environment"
fi
# Test gh auth status check (without requiring it to pass)
if command -v gh >/dev/null 2>&1; then
if gh auth status >/dev/null 2>&1; then
echo "✓ gh authentication successful"
else
echo "⚠️ gh auth check failed (expected without proper setup)"
fi
else
echo "⚠️ gh not available, skipping auth check"
fi
echo "✓ Authentication detection logic verified"
test-github-release-changelog-validation:
name: Test Changelog Validation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test empty changelog (should trigger autogenerated notes)
run: |
echo "Testing empty changelog handling..."
CHANGELOG=""
if [[ -n "$CHANGELOG" ]]; then
echo "❌ ERROR: Empty string should be empty"
exit 1
fi
echo "✓ Empty changelog correctly detected"
- name: Test normal changelog
run: |
echo "Testing normal changelog..."
CHANGELOG="## Features
- Added new feature
- Improved performance"
if [[ -z "$CHANGELOG" ]]; then
echo "❌ ERROR: Changelog should not be empty"
exit 1
fi
if [[ ${#CHANGELOG} -gt 10000 ]]; then
echo "⚠️ Changelog is very long"
fi
echo "✓ Normal changelog processed correctly"
- name: Test very long changelog warning
run: |
echo "Testing very long changelog..."
# Create a changelog with >10000 characters
CHANGELOG=$(printf 'A%.0s' {1..10001})
if [[ ${#CHANGELOG} -le 10000 ]]; then
echo "❌ ERROR: Test changelog should be >10000 chars"
exit 1
fi
echo "✓ Long changelog warning would trigger (${#CHANGELOG} characters)"
test-github-release-changelog-types:
name: Test Changelog Content Types
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test multiline changelog
run: |
echo "Testing multiline changelog..."
CHANGELOG="## Version 1.2.3
### Features
- Feature A
- Feature B
### Bug Fixes
- Fix #123
- Fix #456"
echo "✓ Multiline changelog supported"
- name: Test changelog with special characters
run: |
echo "Testing changelog with special characters..."
CHANGELOG='## Release Notes
Special chars: $, `, \, ", '\'', !, @, #, %, &, *, (, )'
echo "✓ Special characters in changelog supported"
- name: Test changelog with markdown
run: |
echo "Testing changelog with markdown..."
CHANGELOG="## Changes
**Bold** and *italic* text
- [x] Task completed
- [ ] Task pending
\`\`\`bash
echo 'code block'
\`\`\`
| Table | Header |
|-------|--------|
| Cell | Data |"
echo "✓ Markdown in changelog supported"
test-github-release-output-format:
name: Test Output Format
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Verify output structure (mock test)
run: |
echo "Testing output structure..."
# Check if jq is available for this test
if ! command -v jq >/dev/null 2>&1; then
echo "⚠️ jq not available, skipping JSON parsing test"
echo "✓ Output format validation logic verified (jq would be required in actual action)"
exit 0
fi
# Mock release info JSON (similar to gh release view output)
RELEASE_INFO='{
"url": "https://github.com/owner/repo/releases/tag/v1.0.0",
"id": "RE_12345",
"uploadUrl": "https://uploads.github.com/repos/owner/repo/releases/12345/assets{?name,label}"
}'
# Extract outputs
release_url=$(echo "$RELEASE_INFO" | jq -r '.url')
release_id=$(echo "$RELEASE_INFO" | jq -r '.id')
upload_url=$(echo "$RELEASE_INFO" | jq -r '.uploadUrl')
echo "Release URL: $release_url"
echo "Release ID: $release_id"
echo "Upload URL: $upload_url"
# Verify output format
if [[ ! "$release_url" =~ ^https://github\.com/.*/releases/tag/.* ]]; then
echo "❌ ERROR: Invalid release URL format"
exit 1
fi
if [[ ! "$release_id" =~ ^RE_.* ]]; then
echo "❌ ERROR: Invalid release ID format"
exit 1
fi
if [[ ! "$upload_url" =~ ^https://uploads\.github\.com/.* ]]; then
echo "❌ ERROR: Invalid upload URL format"
exit 1
fi
echo "✓ Output format validation passed"
test-github-release-integration-scenarios:
name: Test Integration Scenarios
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test release workflow without actual creation
run: |
echo "Simulating release workflow..."
# Validate version
VERSION="v1.2.3-test"
if ! [[ "$VERSION" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$ ]]; then
echo "❌ ERROR: Version validation failed"
exit 1
fi
echo "✓ Version validation passed"
# Check tool availability (non-fatal for test environment)
gh_available=false
jq_available=false
if command -v gh >/dev/null 2>&1; then
echo "✓ gh CLI is available"
gh_available=true
else
echo "⚠️ gh not available (would fail in actual action)"
fi
if command -v jq >/dev/null 2>&1; then
echo "✓ jq is available"
jq_available=true
else
echo "⚠️ jq not available (would fail in actual action)"
fi
# Create mock changelog
CHANGELOG="Test release notes"
NOTES_FILE="$(mktemp)"
printf '%s' "$CHANGELOG" > "$NOTES_FILE"
# Verify changelog file
if [[ ! -f "$NOTES_FILE" ]]; then
echo "❌ ERROR: Changelog file not created"
exit 1
fi
CONTENT=$(cat "$NOTES_FILE")
if [[ "$CONTENT" != "$CHANGELOG" ]]; then
echo "❌ ERROR: Changelog content mismatch"
exit 1
fi
rm -f "$NOTES_FILE"
echo "✓ Release workflow simulation passed"
- name: Test autogenerated changelog scenario
run: |
echo "Testing autogenerated changelog scenario..."
VERSION="v2.0.0"
CHANGELOG=""
if [[ -z "$CHANGELOG" ]]; then
echo "✓ Would use --generate-notes flag"
else
echo "✓ Would use custom changelog"
fi
- name: Test custom changelog scenario
run: |
echo "Testing custom changelog scenario..."
VERSION="v2.1.0"
CHANGELOG="## Custom Release Notes
This release includes:
- Feature X
- Bug fix Y"
if [[ -n "$CHANGELOG" ]]; then
echo "✓ Would use --notes-file with custom changelog"
else
echo "✓ Would use --generate-notes"
fi
integration-test-summary:
name: Integration Test Summary
runs-on: ubuntu-latest
needs:
- test-github-release-validation
- test-github-release-version-formats
- test-github-release-tool-availability
- test-github-release-changelog-validation
- test-github-release-changelog-types
- test-github-release-output-format
- test-github-release-integration-scenarios
steps:
- name: Summary
run: |
echo "=========================================="
echo "GitHub Release Integration Tests - PASSED"
echo "=========================================="
echo ""
echo "✓ Input validation tests"
echo "✓ Version format tests"
echo "✓ Tool availability tests"
echo "✓ Changelog validation tests"
echo "✓ Changelog content tests"
echo "✓ Output format tests"
echo "✓ Integration scenario tests"
echo ""
echo "All github-release integration tests completed successfully!"

View File

@@ -4,9 +4,12 @@ on:
workflow_dispatch:
push:
paths:
- 'eslint-lint/**'
- 'prettier-lint/**'
- 'eslint-check/**'
- 'eslint-fix/**'
- 'prettier-check/**'
- 'prettier-fix/**'
- 'node-setup/**'
- 'common-cache/**'
- '_tests/integration/workflows/lint-fix-chain-test.yml'
jobs:
@@ -61,15 +64,14 @@ jobs:
node-version: '18'
working-directory: './test-project'
- name: Test eslint-lint check mode (should find errors)
- name: Test eslint-check (should find errors)
id: eslint-check
uses: ./eslint-lint
uses: ./eslint-check
with:
mode: 'check'
working-directory: './test-project'
continue-on-error: true
- name: Validate eslint-lint check found issues
- name: Validate eslint-check found issues
run: |
echo "ESLint check outcome: ${{ steps.eslint-check.outcome }}"
echo "Error count: ${{ steps.eslint-check.outputs.error-count }}"
@@ -84,24 +86,23 @@ jobs:
echo "✅ ESLint check validated"
- name: Test eslint-lint fix mode (should fix issues)
- name: Test eslint-fix (should fix issues)
id: eslint-fix
uses: ./eslint-lint
uses: ./eslint-fix
with:
mode: 'fix'
working-directory: './test-project'
token: ${{ github.token }}
email: 'test@example.com'
username: 'test-user'
- name: Validate eslint-lint fix ran
- name: Validate eslint-fix ran
run: |
echo "Errors fixed: ${{ steps.eslint-fix.outputs.errors-fixed }}"
echo "Files changed: ${{ steps.eslint-fix.outputs.files-changed }}"
echo "Fixed count: ${{ steps.eslint-fix.outputs.fixed-count }}"
echo "Files fixed: ${{ steps.eslint-fix.outputs.files-fixed }}"
# Check that fixes were attempted
if [[ -n "${{ steps.eslint-fix.outputs.errors-fixed }}" ]]; then
echo "✅ ESLint fixed ${{ steps.eslint-fix.outputs.errors-fixed }} issues"
if [[ -n "${{ steps.eslint-fix.outputs.fixed-count }}" ]]; then
echo "✅ ESLint fixed ${{ steps.eslint-fix.outputs.fixed-count }} issues"
else
echo "⚠️ No fix count reported (may be expected if no fixable issues)"
fi
@@ -155,11 +156,10 @@ jobs:
node-version: '18'
working-directory: './test-prettier'
- name: Test prettier-lint check mode (should find issues)
- name: Test prettier-check (should find issues)
id: prettier-check
uses: ./prettier-lint
uses: ./prettier-check
with:
mode: 'check'
working-directory: './test-prettier'
continue-on-error: true
@@ -174,17 +174,16 @@ jobs:
echo "⚠️ WARNING: Expected Prettier to find formatting issues"
fi
- name: Test prettier-lint fix mode (should fix issues)
- name: Test prettier-fix (should fix issues)
id: prettier-fix
uses: ./prettier-lint
uses: ./prettier-fix
with:
mode: 'fix'
working-directory: './test-prettier'
token: ${{ github.token }}
email: 'test@example.com'
username: 'test-user'
- name: Validate prettier-lint fix ran
- name: Validate prettier-fix ran
run: |
echo "Prettier fix completed"
@@ -262,25 +261,22 @@ jobs:
- name: Run ESLint check
id: lint-check
uses: ./eslint-lint
uses: ./eslint-check
with:
mode: 'check'
working-directory: './test-chain'
continue-on-error: true
- name: Run Prettier check
id: format-check
uses: ./prettier-lint
uses: ./prettier-check
with:
mode: 'check'
working-directory: './test-chain'
continue-on-error: true
- name: Run ESLint fix
id: lint-fix
uses: ./eslint-lint
uses: ./eslint-fix
with:
mode: 'fix'
working-directory: './test-chain'
token: ${{ github.token }}
email: 'test@example.com'
@@ -288,9 +284,8 @@ jobs:
- name: Run Prettier fix
id: format-fix
uses: ./prettier-lint
uses: ./prettier-fix
with:
mode: 'fix'
working-directory: './test-chain'
token: ${{ github.token }}
email: 'test@example.com'

View File

@@ -0,0 +1,513 @@
---
name: Integration Test - Node Setup
on:
workflow_dispatch:
push:
paths:
- 'node-setup/**'
- 'version-file-parser/**'
- 'common-cache/**'
- 'common-retry/**'
- '_tests/integration/workflows/node-setup-test.yml'
jobs:
test-node-setup-version-validation:
name: Test Version Validation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test invalid default version format (alphabetic)
run: |
VERSION="abc"
if [[ "$VERSION" =~ ^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$ ]]; then
echo "❌ ERROR: Should reject alphabetic version"
exit 1
fi
echo "✓ Alphabetic version correctly rejected"
- name: Test invalid default version (too low)
run: |
VERSION="10"
major=$(echo "$VERSION" | cut -d'.' -f1)
if [ "$major" -lt 14 ] || [ "$major" -gt 30 ]; then
echo "✓ Version $VERSION correctly rejected (major < 14)"
else
echo "❌ ERROR: Should reject Node.js $VERSION"
exit 1
fi
- name: Test invalid default version (too high)
run: |
VERSION="50"
major=$(echo "$VERSION" | cut -d'.' -f1)
if [ "$major" -lt 14 ] || [ "$major" -gt 30 ]; then
echo "✓ Version $VERSION correctly rejected (major > 30)"
else
echo "❌ ERROR: Should reject Node.js $VERSION"
exit 1
fi
- name: Test valid version formats
run: |
for version in "20" "20.9" "20.9.0" "18" "22.1.0"; do
if [[ "$version" =~ ^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$ ]]; then
major=$(echo "$version" | cut -d'.' -f1)
if [ "$major" -ge 14 ] && [ "$major" -le 30 ]; then
echo "✓ Version $version accepted"
else
echo "❌ ERROR: Version $version should be accepted"
exit 1
fi
else
echo "❌ ERROR: Version $version format validation failed"
exit 1
fi
done
test-node-setup-package-manager-validation:
name: Test Package Manager Validation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test valid package managers
run: |
for pm in "npm" "yarn" "pnpm" "bun" "auto"; do
case "$pm" in
"npm"|"yarn"|"pnpm"|"bun"|"auto")
echo "✓ Package manager $pm accepted"
;;
*)
echo "❌ ERROR: Valid package manager $pm rejected"
exit 1
;;
esac
done
- name: Test invalid package manager
run: |
PM="invalid-pm"
case "$PM" in
"npm"|"yarn"|"pnpm"|"bun"|"auto")
echo "❌ ERROR: Invalid package manager should be rejected"
exit 1
;;
*)
echo "✓ Invalid package manager correctly rejected"
;;
esac
test-node-setup-url-validation:
name: Test URL Validation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test valid registry URLs
run: |
for url in "https://registry.npmjs.org" "http://localhost:4873" "https://npm.custom.com/"; do
if [[ "$url" == "https://"* ]] || [[ "$url" == "http://"* ]]; then
echo "✓ Registry URL $url accepted"
else
echo "❌ ERROR: Valid URL $url rejected"
exit 1
fi
done
- name: Test invalid registry URLs
run: |
for url in "ftp://registry.com" "not-a-url" "registry.com"; do
if [[ "$url" == "https://"* ]] || [[ "$url" == "http://"* ]]; then
echo "❌ ERROR: Invalid URL $url should be rejected"
exit 1
else
echo "✓ Invalid URL $url correctly rejected"
fi
done
test-node-setup-retries-validation:
name: Test Retries Validation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test valid retry counts
run: |
for retries in "1" "3" "5" "10"; do
if [[ "$retries" =~ ^[0-9]+$ ]] && [ "$retries" -gt 0 ] && [ "$retries" -le 10 ]; then
echo "✓ Max retries $retries accepted"
else
echo "❌ ERROR: Valid retry count $retries rejected"
exit 1
fi
done
- name: Test invalid retry counts
run: |
for retries in "0" "11" "abc" "-1"; do
if [[ "$retries" =~ ^[0-9]+$ ]] && [ "$retries" -gt 0 ] && [ "$retries" -le 10 ]; then
echo "❌ ERROR: Invalid retry count $retries should be rejected"
exit 1
else
echo "✓ Invalid retry count $retries correctly rejected"
fi
done
test-node-setup-boolean-validation:
name: Test Boolean Validation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test valid boolean values
run: |
for value in "true" "false"; do
if [[ "$value" == "true" ]] || [[ "$value" == "false" ]]; then
echo "✓ Boolean value $value accepted"
else
echo "❌ ERROR: Valid boolean $value rejected"
exit 1
fi
done
- name: Test invalid boolean values
run: |
for value in "yes" "no" "1" "0" "True" "FALSE" ""; do
if [[ "$value" != "true" ]] && [[ "$value" != "false" ]]; then
echo "✓ Invalid boolean value '$value' correctly rejected"
else
echo "❌ ERROR: Invalid boolean $value should be rejected"
exit 1
fi
done
test-node-setup-token-validation:
name: Test Auth Token Validation
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test injection pattern detection
run: |
for token in "token;malicious" "token&&command" "token|pipe"; do
if [[ "$token" == *";"* ]] || [[ "$token" == *"&&"* ]] || [[ "$token" == *"|"* ]]; then
echo "✓ Injection pattern in token correctly detected"
else
echo "❌ ERROR: Should detect injection pattern in: $token"
exit 1
fi
done
- name: Test valid tokens
run: |
for token in "npm_AbCdEf1234567890" "github_pat_12345abcdef" "simple-token"; do
if [[ "$token" == *";"* ]] || [[ "$token" == *"&&"* ]] || [[ "$token" == *"|"* ]]; then
echo "❌ ERROR: Valid token should not be rejected: $token"
exit 1
else
echo "✓ Valid token accepted"
fi
done
test-node-setup-package-manager-resolution:
name: Test Package Manager Resolution
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test auto detection with detected PM
run: |
INPUT_PM="auto"
DETECTED_PM="pnpm"
if [ "$INPUT_PM" = "auto" ]; then
if [ -n "$DETECTED_PM" ]; then
FINAL_PM="$DETECTED_PM"
else
FINAL_PM="npm"
fi
else
FINAL_PM="$INPUT_PM"
fi
if [[ "$FINAL_PM" != "pnpm" ]]; then
echo "❌ ERROR: Should use detected PM (pnpm)"
exit 1
fi
echo "✓ Auto-detected package manager correctly resolved"
- name: Test auto detection without detected PM
run: |
INPUT_PM="auto"
DETECTED_PM=""
if [ "$INPUT_PM" = "auto" ]; then
if [ -n "$DETECTED_PM" ]; then
FINAL_PM="$DETECTED_PM"
else
FINAL_PM="npm"
fi
else
FINAL_PM="$INPUT_PM"
fi
if [[ "$FINAL_PM" != "npm" ]]; then
echo "❌ ERROR: Should default to npm"
exit 1
fi
echo "✓ Defaults to npm when no detection"
- name: Test explicit package manager
run: |
INPUT_PM="yarn"
DETECTED_PM="pnpm"
if [ "$INPUT_PM" = "auto" ]; then
if [ -n "$DETECTED_PM" ]; then
FINAL_PM="$DETECTED_PM"
else
FINAL_PM="npm"
fi
else
FINAL_PM="$INPUT_PM"
fi
if [[ "$FINAL_PM" != "yarn" ]]; then
echo "❌ ERROR: Should use explicit PM (yarn)"
exit 1
fi
echo "✓ Explicit package manager correctly used"
test-node-setup-feature-detection:
name: Test Feature Detection
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Create test package.json with ESM
run: |
mkdir -p test-esm
cd test-esm
cat > package.json <<'EOF'
{
"name": "test-esm",
"version": "1.0.0",
"type": "module"
}
EOF
- name: Test ESM detection
run: |
cd test-esm
if command -v jq >/dev/null 2>&1; then
pkg_type=$(jq -r '.type // "commonjs"' package.json 2>/dev/null)
if [[ "$pkg_type" == "module" ]]; then
echo "✓ ESM support correctly detected"
else
echo "❌ ERROR: Should detect ESM support"
exit 1
fi
else
echo "⚠️ jq not available, skipping ESM detection test"
echo "✓ ESM detection logic verified (jq would be required in actual action)"
fi
- name: Create test with TypeScript
run: |
mkdir -p test-ts
cd test-ts
touch tsconfig.json
cat > package.json <<'EOF'
{
"name": "test-ts",
"devDependencies": {
"typescript": "^5.0.0"
}
}
EOF
- name: Test TypeScript detection
run: |
cd test-ts
typescript_support="false"
if [ -f tsconfig.json ]; then
typescript_support="true"
fi
if [[ "$typescript_support" != "true" ]]; then
echo "❌ ERROR: Should detect TypeScript"
exit 1
fi
echo "✓ TypeScript support correctly detected"
- name: Create test with frameworks
run: |
mkdir -p test-frameworks
cd test-frameworks
cat > package.json <<'EOF'
{
"name": "test-frameworks",
"dependencies": {
"react": "^18.0.0",
"next": "^14.0.0"
}
}
EOF
- name: Test framework detection
run: |
cd test-frameworks
if command -v jq >/dev/null 2>&1; then
has_next=$(jq -e '.dependencies.next or .devDependencies.next' package.json >/dev/null 2>&1 && echo "yes" || echo "no")
has_react=$(jq -e '.dependencies.react or .devDependencies.react' package.json >/dev/null 2>&1 && echo "yes" || echo "no")
if [[ "$has_next" == "yes" ]] && [[ "$has_react" == "yes" ]]; then
echo "✓ Frameworks (Next.js, React) correctly detected"
else
echo "❌ ERROR: Should detect Next.js and React"
exit 1
fi
else
echo "⚠️ jq not available, skipping framework detection test"
echo "✓ Framework detection logic verified (jq would be required in actual action)"
fi
test-node-setup-security:
name: Test Security Measures
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Test token sanitization
run: |
TOKEN="test-token
with-newline"
# Should remove newlines
sanitized=$(echo "$TOKEN" | tr -d '\n\r')
if [[ "$sanitized" == *$'\n'* ]] || [[ "$sanitized" == *$'\r'* ]]; then
echo "❌ ERROR: Newlines not removed"
exit 1
fi
echo "✓ Token sanitization works correctly"
- name: Test package manager sanitization
run: |
PM="npm
with-newline"
# Should remove newlines
sanitized=$(echo "$PM" | tr -d '\n\r')
if [[ "$sanitized" == *$'\n'* ]] || [[ "$sanitized" == *$'\r'* ]]; then
echo "❌ ERROR: Newlines not removed from PM"
exit 1
fi
echo "✓ Package manager sanitization works correctly"
test-node-setup-integration-workflow:
name: Test Integration Workflow
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Simulate complete workflow
run: |
echo "=== Simulating Node Setup Workflow ==="
# 1. Validation
echo "Step 1: Validate inputs"
DEFAULT_VERSION="20"
PACKAGE_MANAGER="npm"
REGISTRY_URL="https://registry.npmjs.org"
CACHE="true"
INSTALL="true"
MAX_RETRIES="3"
echo "✓ Inputs validated"
# 2. Version parsing
echo "Step 2: Parse Node.js version"
NODE_VERSION="20.9.0"
echo "✓ Version parsed: $NODE_VERSION"
# 3. Package manager resolution
echo "Step 3: Resolve package manager"
if [ "$PACKAGE_MANAGER" = "auto" ]; then
FINAL_PM="npm"
else
FINAL_PM="$PACKAGE_MANAGER"
fi
echo "✓ Package manager resolved: $FINAL_PM"
# 4. Setup Node.js
echo "Step 4: Setup Node.js $NODE_VERSION"
if command -v node >/dev/null 2>&1; then
echo "✓ Node.js available: $(node --version)"
fi
# 5. Enable Corepack
echo "Step 5: Enable Corepack"
if command -v corepack >/dev/null 2>&1; then
echo "✓ Corepack available"
else
echo "⚠️ Corepack not available in test environment"
fi
# 6. Cache dependencies
if [[ "$CACHE" == "true" ]]; then
echo "Step 6: Cache dependencies"
echo "✓ Would use common-cache action"
fi
# 7. Install dependencies
if [[ "$INSTALL" == "true" ]]; then
echo "Step 7: Install dependencies"
echo "✓ Would run: $FINAL_PM install"
fi
echo "=== Workflow simulation completed ==="
integration-test-summary:
name: Integration Test Summary
runs-on: ubuntu-latest
needs:
- test-node-setup-version-validation
- test-node-setup-package-manager-validation
- test-node-setup-url-validation
- test-node-setup-retries-validation
- test-node-setup-boolean-validation
- test-node-setup-token-validation
- test-node-setup-package-manager-resolution
- test-node-setup-feature-detection
- test-node-setup-security
- test-node-setup-integration-workflow
steps:
- name: Summary
run: |
echo "=========================================="
echo "Node Setup Integration Tests - PASSED"
echo "=========================================="
echo ""
echo "✓ Version validation tests"
echo "✓ Package manager validation tests"
echo "✓ URL validation tests"
echo "✓ Retries validation tests"
echo "✓ Boolean validation tests"
echo "✓ Token validation tests"
echo "✓ Package manager resolution tests"
echo "✓ Feature detection tests"
echo "✓ Security measure tests"
echo "✓ Integration workflow tests"
echo ""
echo "All node-setup integration tests completed successfully!"

View File

@@ -5,6 +5,7 @@ on:
push:
paths:
- 'pre-commit/**'
- 'set-git-config/**'
- 'validate-inputs/**'
- '_tests/integration/workflows/pre-commit-test.yml'

View File

@@ -0,0 +1,241 @@
---
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

@@ -151,14 +151,14 @@ discover_actions() {
if [[ $action_name == *"$ACTION_FILTER"* ]]; then
actions+=("$action_name")
fi
done < <(find "${TEST_ROOT}/.." -mindepth 2 -maxdepth 2 -type f -name "action.yml" -exec dirname {} \; | sort)
done < <(find "${TEST_ROOT}/.." -mindepth 1 -maxdepth 1 -type d -name "*-*" | 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 2 -maxdepth 2 -type f -name "action.yml" -exec dirname {} \; | sort)
done < <(find "${TEST_ROOT}/.." -mindepth 1 -maxdepth 1 -type d -name "*-*" | sort)
fi
log_info "Discovered ${#actions[@]} actions to test: ${actions[*]}"

View File

@@ -25,9 +25,6 @@ from typing import Any
import yaml # pylint: disable=import-error
# Default value for unknown items (used by ActionFileParser)
DEFAULT_UNKNOWN = "Unknown"
class ValidationCore:
"""Core validation functionality with standardized patterns and functions."""
@@ -500,9 +497,9 @@ class ActionFileParser:
"""Get the action name from an action.yml file."""
try:
data = ActionFileParser.load_action_file(action_file)
return data.get("name", DEFAULT_UNKNOWN)
return data.get("name", "Unknown")
except (OSError, ValueError, yaml.YAMLError, AttributeError):
return DEFAULT_UNKNOWN
return "Unknown"
@staticmethod
def get_action_inputs(action_file: str) -> list[str]:
@@ -524,16 +521,6 @@ 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."""
@@ -800,11 +787,6 @@ 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",
@@ -852,12 +834,6 @@ 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:
@@ -877,7 +853,6 @@ 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

@@ -1,142 +0,0 @@
#!/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

@@ -0,0 +1,149 @@
#!/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

@@ -0,0 +1,148 @@
#!/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

@@ -1,230 +0,0 @@
#!/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

@@ -0,0 +1,168 @@
#!/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

@@ -0,0 +1,99 @@
#!/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

@@ -0,0 +1,165 @@
#!/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

@@ -0,0 +1,40 @@
#!/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

@@ -0,0 +1,48 @@
#!/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

@@ -0,0 +1,85 @@
#!/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

@@ -0,0 +1,355 @@
#!/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

@@ -0,0 +1,115 @@
#!/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

@@ -1,527 +0,0 @@
#!/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

@@ -0,0 +1,141 @@
#!/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

@@ -0,0 +1,171 @@
#!/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

@@ -1,297 +0,0 @@
#!/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

@@ -0,0 +1,242 @@
#!/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

@@ -0,0 +1,407 @@
#!/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

@@ -0,0 +1,280 @@
#!/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 "framework"
The output should include "test_status"
The output should include "tests_run"
The output should include "tests_passed"
The output should include "coverage_path"
End
End
@@ -245,214 +245,5 @@ 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

@@ -0,0 +1,170 @@
#!/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

@@ -0,0 +1,332 @@
#!/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

@@ -0,0 +1,285 @@
#!/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

@@ -1,515 +0,0 @@
#!/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

@@ -0,0 +1,98 @@
#!/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

@@ -0,0 +1,108 @@
#!/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

@@ -1,116 +0,0 @@
#!/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

@@ -0,0 +1,69 @@
#!/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,6 +92,10 @@ 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"
;;
@@ -110,6 +114,11 @@ 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"
@@ -154,6 +163,10 @@ 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
;;
@@ -172,6 +185,11 @@ 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
@@ -226,6 +244,10 @@ 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"
@@ -236,6 +258,11 @@ 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

@@ -0,0 +1,125 @@
#!/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

@@ -0,0 +1,233 @@
#!/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

View File

@@ -76,7 +76,11 @@ if ! git diff --quiet; then
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."
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

View File

@@ -34,7 +34,7 @@ runs:
using: composite
steps:
- name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
token: ${{ inputs.token || github.token }}
fetch-depth: 0
@@ -95,7 +95,7 @@ runs:
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=$(printf '%s' "$line" | grep -oE '@[a-f0-9]{40}' | sed 's/@//')
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)"
@@ -153,7 +153,11 @@ runs:
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 "$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"

View File

@@ -45,19 +45,55 @@ runs:
steps:
- name: Validate Inputs
id: validate
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 }}
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"
- name: Check for Ansible Files
id: check-files
shell: sh
shell: bash
run: |
set -eu
set -euo pipefail
# Check for both .yml and .yaml files
if find . \( -name "*.yml" -o -name "*.yaml" \) -type f | grep -q .; then
@@ -69,32 +105,35 @@ runs:
fi
- name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
token: ${{ inputs.token || github.token }}
- name: Setup Python
- name: Cache Python Dependencies
if: steps.check-files.outputs.files_found == 'true'
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
id: cache-pip
uses: ivuorinen/actions/common-cache@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
with:
python-version: '3.14'
cache: 'pip'
type: 'pip'
paths: '~/.cache/pip'
key-files: 'requirements*.txt,pyproject.toml,setup.py,setup.cfg'
key-prefix: 'ansible-lint-fix'
- name: Install ansible-lint
id: install-ansible-lint
if: steps.check-files.outputs.files_found == 'true'
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3.0.4
uses: ivuorinen/actions/common-retry@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
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: sh
shell: bash
run: |
set -eu
set -euo pipefail
# Run ansible-lint and capture exit code
if ansible-lint --write --parseable-severity --format sarif > ansible-lint.sarif; then
@@ -120,16 +159,31 @@ runs:
# Exit with the original ansible-lint exit code
exit "$lint_exit_code"
- name: Set Git Config for Fixes
id: set-git-config
if: steps.check-files.outputs.files_found == 'true'
uses: ivuorinen/actions/set-git-config@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
with:
token: ${{ inputs.token }}
username: ${{ inputs.username }}
email: ${{ inputs.email }}
- name: Commit Fixes
if: steps.check-files.outputs.files_found == 'true'
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style: apply ansible lint fixes'
commit_user_name: ${{ inputs.username }}
commit_user_email: ${{ inputs.email }}
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
- name: Upload SARIF Report
if: steps.check-files.outputs.files_found == 'true'
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
sarif_file: ansible-lint.sarif

58
biome-check/README.md Normal file
View File

@@ -0,0 +1,58 @@
# 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
```

238
biome-check/action.yml Normal file
View File

@@ -0,0 +1,238 @@
# 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: ivuorinen/actions/validate-inputs@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
with:
action-type: 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: ivuorinen/actions/set-git-config@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
with:
token: ${{ inputs.token }}
username: ${{ inputs.username }}
email: ${{ inputs.email }}
- name: Node Setup
id: node-setup
uses: ivuorinen/actions/node-setup@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
- name: Cache Node Dependencies
id: cache
uses: ivuorinen/actions/common-cache@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
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@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
sarif_file: biome-report.sarif

41
biome-check/rules.yml Normal file
View File

@@ -0,0 +1,41 @@
---
# 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

57
biome-fix/README.md Normal file
View File

@@ -0,0 +1,57 @@
# ivuorinen/actions/biome-fix
## Biome Fix
### Description
Run Biome fix 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 |
|-----------------|----------------------------------------------|
| `files_changed` | <p>Number of files changed by formatting</p> |
| `fix_status` | <p>Fix status (success/failure)</p> |
### Runs
This action is a `composite` action.
### Usage
```yaml
- uses: ivuorinen/actions/biome-fix@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
```

204
biome-fix/action.yml Normal file
View File

@@ -0,0 +1,204 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
# permissions:
# - contents: write # Required for pushing fixes back to repository
---
name: Biome Fix
description: Run Biome fix 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:
files_changed:
description: 'Number of files changed by formatting'
value: ${{ steps.fix.outputs.files_changed }}
fix_status:
description: 'Fix status (success/failure)'
value: ${{ steps.fix.outputs.status }}
runs:
using: composite
steps:
- name: Validate Inputs
id: validate
shell: sh
env:
GITHUB_TOKEN: ${{ inputs.token }}
EMAIL: ${{ inputs.email }}
USERNAME: ${{ inputs.username }}
MAX_RETRIES: ${{ inputs.max-retries }}
run: |
set -eu
# Validate GitHub token format (basic validation)
if [ -n "$GITHUB_TOKEN" ]; then
# Skip validation for GitHub expressions (they'll be resolved at runtime)
if ! echo "$GITHUB_TOKEN" | grep -Eq '^gh[efpousr]_[a-zA-Z0-9]{36}$' && ! echo "$GITHUB_TOKEN" | grep -q '^\${{'; then
echo "::warning::GitHub token format may be invalid. Expected format: gh*_36characters"
fi
fi
# Validate email format (basic check)
case "$EMAIL" in
*@*.*) ;;
*)
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
exit 1
;;
esac
# Validate username format (prevent command injection)
if echo "$USERNAME" | grep -Eq '[;&|]'; then
echo "::error::Invalid username: '$USERNAME'. Command injection patterns not allowed"
exit 1
fi
# Validate username length
username="$USERNAME"
username_len=$(echo -n "$username" | wc -c | tr -d ' ')
if [ "$username_len" -gt 39 ]; then
echo "::error::Username too long: ${username_len} characters. GitHub usernames are max 39 characters"
exit 1
fi
# Validate max retries (positive integer with reasonable upper limit)
if ! echo "$MAX_RETRIES" | grep -Eq '^[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: ivuorinen/actions/set-git-config@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
with:
token: ${{ inputs.token }}
username: ${{ inputs.username }}
email: ${{ inputs.email }}
- name: Node Setup
id: node-setup
uses: ivuorinen/actions/node-setup@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
- name: Cache Node Dependencies
id: cache
uses: ivuorinen/actions/common-cache@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
with:
type: 'npm'
paths: 'node_modules'
key-files: 'package-lock.json,yarn.lock,pnpm-lock.yaml,bun.lockb'
key-prefix: 'biome-fix-${{ steps.node-setup.outputs.package-manager }}'
- name: Install Biome
shell: sh
env:
PACKAGE_MANAGER: ${{ steps.node-setup.outputs.package-manager }}
MAX_RETRIES: ${{ inputs.max-retries }}
run: |
set -eu
# 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 Fix
id: fix
shell: sh
run: |
set -eu
echo "Running Biome fix..."
# Run Biome fix and capture exit code
biome_exit_code=0
biome check --write . || biome_exit_code=$?
# Count changed files using git diff (strip whitespace from wc output)
files_changed=$(git diff --name-only | wc -l | tr -d ' ')
# Set status based on biome check result and changes
if [ $biome_exit_code -eq 0 ] && [ "$files_changed" -eq 0 ]; then
status="success"
else
status="failure"
fi
echo "files_changed=$files_changed" >> "$GITHUB_OUTPUT"
echo "status=$status" >> "$GITHUB_OUTPUT"
echo "✅ Biome fix completed. Files changed: $files_changed, Status: $status"
- name: Push Fixes
if: success()
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
with:
commit_message: 'style: autofix Biome violations'
add_options: '-u'

41
biome-fix/rules.yml Normal file
View File

@@ -0,0 +1,41 @@
---
# Validation rules for biome-fix 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-fix GitHub Action.
# Rules are automatically applied by validate-inputs action when this
# action is used.
#
schema_version: '1.0'
action: biome-fix
description: Run Biome fix 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

View File

@@ -1,73 +0,0 @@
# ivuorinen/actions/biome-lint
## Biome Lint
### Description
Run Biome linter in check or fix mode
### Inputs
| name | description | required | default |
|-----------------|---------------------------------------------------------------------------------|----------|-----------------------------|
| `mode` | <p>Mode to run (check or fix)</p> | `false` | `check` |
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
| `username` | <p>GitHub username for commits (fix mode only)</p> | `false` | `github-actions` |
| `email` | <p>GitHub email for commits (fix mode only)</p> | `false` | `github-actions@github.com` |
| `max-retries` | <p>Maximum number of retry attempts for npm install operations</p> | `false` | `3` |
| `fail-on-error` | <p>Whether to fail the action if linting errors are found (check mode only)</p> | `false` | `true` |
### Outputs
| name | description |
|------------------|---------------------------------------------------|
| `status` | <p>Overall status (success/failure)</p> |
| `errors_count` | <p>Number of errors found (check mode only)</p> |
| `warnings_count` | <p>Number of warnings found (check mode only)</p> |
| `files_changed` | <p>Number of files changed (fix mode only)</p> |
### Runs
This action is a `composite` action.
### Usage
```yaml
- uses: ivuorinen/actions/biome-lint@main
with:
mode:
# Mode to run (check or fix)
#
# Required: false
# Default: check
token:
# GitHub token for authentication
#
# Required: false
# Default: ""
username:
# GitHub username for commits (fix mode only)
#
# Required: false
# Default: github-actions
email:
# GitHub email for commits (fix mode only)
#
# Required: false
# Default: github-actions@github.com
max-retries:
# Maximum number of retry attempts for npm install operations
#
# Required: false
# Default: 3
fail-on-error:
# Whether to fail the action if linting errors are found (check mode only)
#
# Required: false
# Default: true
```

View File

@@ -1,373 +0,0 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
# permissions:
# - contents: write # Required for fix mode to push changes
# - security-events: write # Required for check mode to upload SARIF
---
name: Biome Lint
description: Run Biome linter in check or fix mode
author: Ismo Vuorinen
branding:
icon: check-circle
color: green
inputs:
mode:
description: 'Mode to run (check or fix)'
required: false
default: 'check'
token:
description: 'GitHub token for authentication'
required: false
default: ''
username:
description: 'GitHub username for commits (fix mode only)'
required: false
default: 'github-actions'
email:
description: 'GitHub email for commits (fix mode only)'
required: false
default: 'github-actions@github.com'
max-retries:
description: 'Maximum number of retry attempts for npm install operations'
required: false
default: '3'
fail-on-error:
description: 'Whether to fail the action if linting errors are found (check mode only)'
required: false
default: 'true'
outputs:
status:
description: 'Overall status (success/failure)'
value: ${{ steps.check.outputs.status || steps.fix.outputs.status }}
errors_count:
description: 'Number of errors found (check mode only)'
value: ${{ steps.check.outputs.errors }}
warnings_count:
description: 'Number of warnings found (check mode only)'
value: ${{ steps.check.outputs.warnings }}
files_changed:
description: 'Number of files changed (fix mode only)'
value: ${{ steps.fix.outputs.files_changed }}
runs:
using: composite
steps:
- name: Validate Inputs
id: validate
shell: sh
env:
MODE: ${{ inputs.mode }}
GITHUB_TOKEN: ${{ inputs.token }}
EMAIL: ${{ inputs.email }}
USERNAME: ${{ inputs.username }}
MAX_RETRIES: ${{ inputs.max-retries }}
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
run: |
set -eu
# Validate mode
case "$MODE" in
"check"|"fix")
echo "Mode: $MODE"
;;
*)
echo "::error::Invalid mode: '$MODE'. Must be 'check' or 'fix'"
exit 1
;;
esac
# Validate GitHub token presence if provided
if [ -n "$GITHUB_TOKEN" ]; then
case "$GITHUB_TOKEN" in
\$\{\{*)
# Token is a GitHub Actions expression, skip validation
;;
*)
echo "Using provided GitHub token"
;;
esac
fi
# Validate email format (basic check) - required for fix mode
if [ "$MODE" = "fix" ]; then
case "$EMAIL" in
*@*.*) ;;
*)
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
exit 1
;;
esac
# 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)
case "$username" in
*[!a-zA-Z0-9-]*)
echo "::error::Invalid username characters in '$username'. Only letters, digits, and hyphens allowed"
exit 1
;;
esac
# Check doesn't start or end with hyphen
case "$username" in
-*|*-)
echo "::error::Invalid username '$username'. Cannot start or end with hyphen"
exit 1
;;
esac
# Check no consecutive hyphens
case "$username" in
*--*)
echo "::error::Invalid username '$username'. Consecutive hyphens not allowed"
exit 1
;;
esac
fi
# Validate max retries (positive integer with reasonable upper limit)
case "$MAX_RETRIES" in
''|*[!0-9]*)
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
exit 1
;;
esac
if [ "$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
# Validate fail-on-error (boolean)
if [ "$FAIL_ON_ERROR" != "true" ] && [ "$FAIL_ON_ERROR" != "false" ]; then
echo "::error::Invalid fail-on-error value: '$FAIL_ON_ERROR'. Must be 'true' or 'false'"
exit 1
fi
echo "Input validation completed successfully"
- name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
with:
token: ${{ inputs.token || github.token }}
- name: Detect Package Manager
id: detect-pm
shell: sh
run: |
set -eu
# Detect package manager from lockfiles
if [ -f bun.lockb ]; then
package_manager="bun"
elif [ -f pnpm-lock.yaml ]; then
package_manager="pnpm"
elif [ -f yarn.lock ]; then
package_manager="yarn"
else
package_manager="npm"
fi
printf 'package-manager=%s\n' "$package_manager" >> "$GITHUB_OUTPUT"
echo "Detected package manager: $package_manager"
- name: Setup Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
with:
node-version: '24'
- name: Enable Corepack
shell: sh
run: |
set -eu
corepack enable
- name: Install Package Manager
shell: sh
env:
PACKAGE_MANAGER: ${{ steps.detect-pm.outputs.package-manager }}
run: |
set -eu
case "$PACKAGE_MANAGER" in
pnpm)
corepack prepare pnpm@latest --activate
;;
yarn)
corepack prepare yarn@stable --activate
;;
bun|npm)
# Bun installed separately, npm built-in
;;
esac
- name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
with:
bun-version: latest
- name: Cache Node Dependencies
id: cache
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
with:
path: node_modules
key: ${{ runner.os }}-biome-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
restore-keys: |
${{ runner.os }}-biome-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-
${{ runner.os }}-biome-lint-${{ inputs.mode }}-
- name: Install Biome
shell: sh
env:
PACKAGE_MANAGER: ${{ steps.detect-pm.outputs.package-manager }}
MAX_RETRIES: ${{ inputs.max-retries }}
run: |
set -eu
# 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
if: inputs.mode == 'check'
id: check
shell: sh
env:
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
run: |
set -eu
echo "Running Biome check mode..."
# 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"
echo "✅ Biome check completed successfully"
else
echo "status=failure" >> "$GITHUB_OUTPUT"
echo "errors=$errors" >> "$GITHUB_OUTPUT"
echo "warnings=$warnings" >> "$GITHUB_OUTPUT"
echo "::error::Biome check found $errors issues"
fi
# Exit with biome's exit code if fail-on-error is true
if [ "$FAIL_ON_ERROR" = "true" ]; then
exit $biome_exit_code
fi
- name: Upload SARIF Report
if: inputs.mode == 'check' && always()
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
with:
sarif_file: biome-report.sarif
- name: Run Biome Fix
if: inputs.mode == 'fix'
id: fix
shell: sh
run: |
set -eu
echo "Running Biome fix mode..."
# Run Biome fix and capture exit code
biome_exit_code=0
biome check --write . || biome_exit_code=$?
# Count changed files using git diff
files_changed=$(git diff --name-only | wc -l | tr -d ' ')
# Set status based on biome check result and changes
if [ $biome_exit_code -eq 0 ] && [ "$files_changed" -eq 0 ]; then
status="success"
echo "✅ No changes needed"
else
status="failure"
echo "⚠️ Fixed $files_changed file(s)"
fi
printf '%s\n' "files_changed=$files_changed" >> "$GITHUB_OUTPUT"
printf '%s\n' "status=$status" >> "$GITHUB_OUTPUT"
- name: Commit and Push Fixes
if: inputs.mode == 'fix' && success()
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style: autofix Biome violations'
commit_user_name: ${{ inputs.username }}
commit_user_email: ${{ inputs.email }}
add_options: '-u'

View File

@@ -81,13 +81,21 @@ class CustomValidator(BaseValidator):
# Validate threads
if inputs.get("threads"):
valid &= self.validate_with(
self.codeql_validator, "validate_threads", inputs["threads"]
)
result = self.codeql_validator.validate_threads(inputs["threads"])
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
valid &= result
# Validate RAM
if inputs.get("ram"):
valid &= self.validate_with(self.codeql_validator, "validate_ram", inputs["ram"])
result = self.codeql_validator.validate_ram(inputs["ram"])
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
valid &= result
# Validate debug mode
if inputs.get("debug"):
@@ -218,10 +226,19 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Check for empty queries first
if not queries or not queries.strip():
self.add_error("CodeQL queries cannot be empty")
return False
return self.validate_with(self.codeql_validator, "validate_codeql_queries", queries)
# Use the CodeQL validator
result = self.codeql_validator.validate_codeql_queries(queries)
# Copy any errors from codeql validator
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
return result
def validate_categories(self, categories: str) -> bool:
"""Validate CodeQL categories.
@@ -232,7 +249,14 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
return self.validate_with(self.codeql_validator, "validate_category_format", categories)
# Use the CodeQL validator
result = self.codeql_validator.validate_category_format(categories)
# Copy any errors from codeql validator
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
return result
def validate_category(self, category: str) -> bool:
"""Validate CodeQL category (singular).
@@ -243,7 +267,14 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
return self.validate_with(self.codeql_validator, "validate_category_format", category)
# Use the CodeQL validator
result = self.codeql_validator.validate_category_format(category)
# Copy any errors from codeql validator
for error in self.codeql_validator.errors:
if error not in self.errors:
self.add_error(error)
self.codeql_validator.clear_errors()
return result
def validate_config_file(self, config_file: str) -> bool:
"""Validate CodeQL configuration file path.
@@ -256,11 +287,21 @@ class CustomValidator(BaseValidator):
"""
if not config_file or not config_file.strip():
return True
# Allow GitHub Actions expressions
if self.is_github_expression(config_file):
return True
return self.validate_with(
self.file_validator, "validate_yaml_file", config_file, "config-file"
)
# Use FileValidator for yaml file validation
result = self.file_validator.validate_yaml_file(config_file, "config-file")
# Copy any errors from file validator
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
return result
def validate_database(self, database: str) -> bool:
"""Validate CodeQL database path.
@@ -271,13 +312,25 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(database):
return True
result = self.validate_with(self.file_validator, "validate_file_path", database, "database")
# Use FileValidator for path validation
result = self.file_validator.validate_file_path(database, "database")
# Copy any errors from file validator
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
# Database paths often contain the language
# e.g., "codeql-database/javascript" or "/tmp/codeql_databases/python"
# Just validate it's a reasonable path after basic validation
if result and database.startswith("/tmp/"): # noqa: S108
return True
return result
def validate_debug(self, debug: str) -> bool:
@@ -289,9 +342,20 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(debug):
return True
return self.validate_with(self.boolean_validator, "validate_boolean", debug, "debug")
# Use BooleanValidator
result = self.boolean_validator.validate_boolean(debug, "debug")
# Copy any errors from boolean validator
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
return result
def validate_upload_database(self, upload: str) -> bool:
"""Validate upload-database setting.
@@ -302,11 +366,20 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(upload):
return True
return self.validate_with(
self.boolean_validator, "validate_boolean", upload, "upload-database"
)
# Use BooleanValidator
result = self.boolean_validator.validate_boolean(upload, "upload-database")
# Copy any errors from boolean validator
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
return result
def validate_upload_sarif(self, upload: str) -> bool:
"""Validate upload-sarif setting.
@@ -317,11 +390,20 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(upload):
return True
return self.validate_with(
self.boolean_validator, "validate_boolean", upload, "upload-sarif"
)
# Use BooleanValidator
result = self.boolean_validator.validate_boolean(upload, "upload-sarif")
# Copy any errors from boolean validator
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
return result
def validate_packs(self, packs: str) -> bool:
"""Validate CodeQL packs.
@@ -405,9 +487,16 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
return self.validate_with(
self.token_validator, "validate_github_token", token, required=False
)
# Use the TokenValidator for proper validation
result = self.token_validator.validate_github_token(token, required=False)
# Copy any errors from token validator
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
return result
def validate_token(self, token: str) -> bool:
"""Validate GitHub token.
@@ -418,12 +507,21 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Check for empty token
if not token or not token.strip():
self.add_error("Input 'token' is missing or empty")
return False
return self.validate_with(
self.token_validator, "validate_github_token", token, required=True
)
# Use the TokenValidator for proper validation
result = self.token_validator.validate_github_token(token, required=True)
# Copy any errors from token validator
for error in self.token_validator.errors:
if error not in self.errors:
self.add_error(error)
self.token_validator.clear_errors()
return result
def validate_working_directory(self, directory: str) -> bool:
"""Validate working directory path.
@@ -434,11 +532,20 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(directory):
return True
return self.validate_with(
self.file_validator, "validate_file_path", directory, "working-directory"
)
# Use FileValidator for path validation
result = self.file_validator.validate_file_path(directory, "working-directory")
# Copy any errors from file validator
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
return result
def validate_upload_results(self, value: str) -> bool:
"""Validate upload-results boolean value.
@@ -449,14 +556,27 @@ class CustomValidator(BaseValidator):
Returns:
True if valid, False otherwise
"""
# Check for empty
if not value or not value.strip():
self.add_error("upload-results cannot be empty")
return False
# Allow GitHub Actions expressions
if self.is_github_expression(value):
return True
# Check for uppercase TRUE/FALSE first
if value in ["TRUE", "FALSE"]:
self.add_error("Must be lowercase 'true' or 'false'")
return False
return self.validate_with(
self.boolean_validator, "validate_boolean", value, "upload-results"
)
# Use BooleanValidator for normal validation
result = self.boolean_validator.validate_boolean(value, "upload-results")
# Copy any errors from boolean validator
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
return result

View File

@@ -26,6 +26,7 @@ Run CodeQL security analysis for a single language with configurable query suite
| `threads` | <p>Number of threads that can be used by CodeQL</p> | `false` | `""` |
| `output` | <p>Path to save SARIF results</p> | `false` | `../results` |
| `skip-queries` | <p>Build database but skip running queries</p> | `false` | `false` |
| `add-snippets` | <p>Add code snippets to SARIF output</p> | `false` | `false` |
### Outputs
@@ -139,4 +140,10 @@ This action is a `composite` action.
#
# Required: false
# Default: false
add-snippets:
# Add code snippets to SARIF output
#
# Required: false
# Default: false
```

View File

@@ -90,6 +90,11 @@ inputs:
required: false
default: 'false'
add-snippets:
description: 'Add code snippets to SARIF output'
required: false
default: 'false'
outputs:
language-analyzed:
description: 'Language that was analyzed'
@@ -107,7 +112,7 @@ runs:
using: composite
steps:
- name: Validate inputs
uses: ivuorinen/actions/validate-inputs@5cc7373a22402ee8985376bc713f00e09b5b2edb
uses: ivuorinen/actions/validate-inputs@2f1c73dd8b23ffec17bb93ebd0f32e7a7c7546fc
with:
action-type: codeql-analysis
language: ${{ inputs.language }}
@@ -126,52 +131,50 @@ runs:
threads: ${{ inputs.threads }}
output: ${{ inputs.output }}
skip-queries: ${{ inputs.skip-queries }}
add-snippets: ${{ inputs.add-snippets }}
- name: Validate checkout safety
shell: sh
shell: bash
env:
CHECKOUT_REF: ${{ inputs.checkout-ref }}
EVENT_NAME: ${{ github.event_name }}
run: |
set -eu
# Security check: Warn if checking out custom ref on pull_request_target
if [ "$EVENT_NAME" = "pull_request_target" ] && [ -n "$CHECKOUT_REF" ]; then
if [[ "$EVENT_NAME" == "pull_request_target" ]] && [[ -n "$CHECKOUT_REF" ]]; then
echo "::warning::Using custom checkout-ref on pull_request_target is potentially unsafe"
echo "::warning::Ensure the ref is validated before running untrusted code"
fi
- name: Checkout repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ inputs.checkout-ref || github.sha }}
token: ${{ inputs.token }}
- name: Set analysis category
id: set-category
shell: sh
shell: bash
env:
CATEGORY: ${{ inputs.category }}
LANGUAGE: ${{ inputs.language }}
run: |
set -eu
if [ -n "$CATEGORY" ]; then
if [[ -n "$CATEGORY" ]]; then
category="$CATEGORY"
else
category="/language:$LANGUAGE"
fi
echo "category=$category" >> "$GITHUB_OUTPUT"
echo "category=$category" >> $GITHUB_OUTPUT
echo "Using analysis category: $category"
- name: Set build mode
id: set-build-mode
shell: sh
shell: bash
env:
BUILD_MODE: ${{ inputs.build-mode }}
LANGUAGE: ${{ inputs.language }}
run: |
set -eu
build_mode="$BUILD_MODE"
if [ -z "$build_mode" ]; then
if [[ -z "$build_mode" ]]; then
# Auto-detect build mode based on language
case "$LANGUAGE" in
javascript|python|ruby|actions)
@@ -182,11 +185,11 @@ runs:
;;
esac
fi
echo "build-mode=$build_mode" >> "$GITHUB_OUTPUT"
echo "build-mode=$build_mode" >> $GITHUB_OUTPUT
echo "Using build mode: $build_mode"
- name: Initialize CodeQL
uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
languages: ${{ inputs.language }}
queries: ${{ inputs.queries }}
@@ -199,22 +202,23 @@ runs:
threads: ${{ inputs.threads }}
- name: Autobuild
uses: github/codeql-action/autobuild@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
if: ${{ steps.set-build-mode.outputs.build-mode == 'autobuild' }}
- name: Perform CodeQL Analysis
id: analysis
uses: github/codeql-action/analyze@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
with:
category: ${{ steps.set-category.outputs.category }}
upload: ${{ inputs.upload-results }}
output: ${{ inputs.output }}
ram: ${{ inputs.ram }}
threads: ${{ inputs.threads }}
add-snippets: ${{ inputs.add-snippets }}
skip-queries: ${{ inputs.skip-queries }}
- name: Summary
shell: sh
shell: bash
env:
LANGUAGE: ${{ inputs.language }}
CATEGORY: ${{ steps.set-category.outputs.category }}
@@ -224,15 +228,14 @@ runs:
UPLOAD_RESULTS: ${{ inputs.upload-results }}
OUTPUT: ${{ inputs.output }}
run: |
set -eu
echo "✅ CodeQL analysis completed for language: $LANGUAGE"
echo "📊 Category: $CATEGORY"
echo "🏗️ Build mode: $BUILD_MODE"
echo "🔍 Queries: ${QUERIES:-default}"
echo "📦 Packs: ${PACKS:-none}"
if [ "$UPLOAD_RESULTS" = "true" ]; then
if [[ "$UPLOAD_RESULTS" == "true" ]]; then
echo "📤 Results uploaded to GitHub Security tab"
fi
if [ -n "$OUTPUT" ]; then
if [[ -n "$OUTPUT" ]]; then
echo "💾 SARIF saved to: $OUTPUT"
fi

View File

@@ -2,7 +2,7 @@
# Validation rules for codeql-analysis action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 94% (15/16 inputs)
# Coverage: 94% (16/17 inputs)
#
# This file defines validation rules for the codeql-analysis GitHub Action.
# Rules are automatically applied by validate-inputs action when this
@@ -16,6 +16,7 @@ generator_version: 1.0.0
required_inputs:
- language
optional_inputs:
- add-snippets
- build-mode
- category
- checkout-ref
@@ -32,6 +33,7 @@ optional_inputs:
- upload-results
- working-directory
conventions:
add-snippets: boolean
build-mode: codeql_build_mode
category: category_format
checkout-ref: branch_name
@@ -42,7 +44,7 @@ conventions:
packs: codeql_packs
queries: codeql_queries
ram: numeric_range_256_32768
skip-queries: boolean
skip-queries: codeql_queries
source-root: file_path
threads: numeric_range_1_128
token: github_token
@@ -51,7 +53,6 @@ overrides:
build-mode: codeql_build_mode
category: category_format
config: codeql_config
language: codeql_language
output: file_path
packs: codeql_packs
queries: codeql_queries
@@ -61,8 +62,8 @@ overrides:
threads: numeric_range_1_128
token: github_token
statistics:
total_inputs: 16
validated_inputs: 15
total_inputs: 17
validated_inputs: 16
skipped_inputs: 0
coverage_percentage: 94
validation_coverage: 94

244
common-cache/CustomValidator.py Executable file
View File

@@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""Custom validator for common-cache action.
This validator handles caching-specific validation including:
- Cache types (npm, composer, go, pip, maven, gradle)
- Cache paths (comma-separated list)
- Cache keys and restore keys
- Path validation with special handling for multiple paths
"""
from __future__ import annotations
from pathlib import Path
import sys
# Add validate-inputs directory to path to import validators
validate_inputs_path = Path(__file__).parent.parent / "validate-inputs"
sys.path.insert(0, str(validate_inputs_path))
from validators.base import BaseValidator
from validators.file import FileValidator
class CustomValidator(BaseValidator):
"""Custom validator for common-cache action.
Provides validation for cache configuration.
"""
def __init__(self, action_type: str = "common-cache") -> None:
"""Initialize the common-cache validator."""
super().__init__(action_type)
self.file_validator = FileValidator(action_type)
def validate_inputs(self, inputs: dict[str, str]) -> bool:
"""Validate common-cache specific inputs.
Args:
inputs: Dictionary of input names to values
Returns:
True if all validations pass, False otherwise
"""
valid = True
# Validate type (required)
if "type" in inputs:
valid &= self.validate_cache_type(inputs["type"])
else:
# Type is required
self.add_error("Cache type is required")
valid = False
# Validate paths (required)
if "paths" in inputs:
valid &= self.validate_cache_paths(inputs["paths"])
else:
# Paths is required
self.add_error("Cache paths are required")
valid = False
# Validate key-prefix (optional)
if inputs.get("key-prefix"):
valid &= self.validate_key_prefix(inputs["key-prefix"])
# Validate key-files (optional)
if inputs.get("key-files"):
valid &= self.validate_key_files(inputs["key-files"])
# Validate restore-keys (optional)
if inputs.get("restore-keys"):
valid &= self.validate_restore_keys(inputs["restore-keys"])
# Validate env-vars (optional)
if inputs.get("env-vars"):
valid &= self.validate_env_vars(inputs["env-vars"])
return valid
def get_required_inputs(self) -> list[str]:
"""Get list of required inputs for common-cache.
Returns:
List of required input names
"""
return ["type", "paths"]
def get_validation_rules(self) -> dict:
"""Get validation rules for common-cache.
Returns:
Dictionary of validation rules
"""
return {
"type": "Cache type (npm, composer, go, pip, maven, gradle)",
"paths": "Comma-separated list of paths to cache",
"key-prefix": "Optional prefix for cache key",
"key-files": "Files to include in cache key hash",
"restore-keys": "Fallback cache keys to try",
}
def validate_cache_type(self, cache_type: str) -> bool:
"""Validate cache type.
Args:
cache_type: Type of cache
Returns:
True if valid, False otherwise
"""
# Check for empty
if not cache_type or not cache_type.strip():
self.add_error("Cache type cannot be empty")
return False
# Allow GitHub Actions expressions
if self.is_github_expression(cache_type):
return True
# Note: The test says "accepts invalid cache type (no validation in action)"
# This suggests we should accept any value, not just the supported ones
# So we'll just validate for security issues, not restrict to specific types
# Check for command injection using base validator
return self.validate_security_patterns(cache_type, "cache type")
def validate_cache_paths(self, paths: str) -> bool:
"""Validate cache paths (comma-separated).
Args:
paths: Comma-separated paths
Returns:
True if valid, False otherwise
"""
# Check for empty
if not paths or not paths.strip():
self.add_error("Cache paths cannot be empty")
return False
# Allow GitHub Actions expressions
if self.is_github_expression(paths):
return True
# Split paths and validate each
path_list = [p.strip() for p in paths.split(",")]
for path in path_list:
if not path:
continue
# Use FileValidator for path validation
result = self.file_validator.validate_file_path(path, "paths")
# Propagate errors from file validator
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
return False
return True
def validate_key_prefix(self, key_prefix: str) -> bool:
"""Validate cache key prefix.
Args:
key_prefix: Key prefix
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(key_prefix):
return True
# Check for command injection using base validator
return self.validate_security_patterns(key_prefix, "key-prefix")
def validate_key_files(self, key_files: str) -> bool:
"""Validate key files (comma-separated).
Args:
key_files: Comma-separated file paths
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(key_files):
return True
# Split files and validate each
file_list = [f.strip() for f in key_files.split(",")]
for file_path in file_list:
if not file_path:
continue
# Use FileValidator for path validation
result = self.file_validator.validate_file_path(file_path, "key-files")
# Propagate errors from file validator
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
return False
return True
def validate_restore_keys(self, restore_keys: str) -> bool:
"""Validate restore keys.
Args:
restore_keys: Restore keys specification
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(restore_keys):
return True
# Check for command injection using base validator
return self.validate_security_patterns(restore_keys, "restore-keys")
def validate_env_vars(self, env_vars: str) -> bool:
"""Validate environment variables.
Args:
env_vars: Environment variables specification
Returns:
True if valid, False otherwise
"""
# Allow GitHub Actions expressions
if self.is_github_expression(env_vars):
return True
# Check for command injection using base validator
return self.validate_security_patterns(env_vars, "env-vars")

72
common-cache/README.md Normal file
View File

@@ -0,0 +1,72 @@
# ivuorinen/actions/common-cache
## Common Cache
### Description
Standardized caching strategy for all actions
### Inputs
| name | description | required | default |
|----------------|------------------------------------------------------|----------|---------|
| `type` | <p>Type of cache (npm, composer, go, pip, etc.)</p> | `true` | `""` |
| `paths` | <p>Paths to cache (comma-separated)</p> | `true` | `""` |
| `key-prefix` | <p>Custom prefix for cache key</p> | `false` | `""` |
| `key-files` | <p>Files to hash for cache key (comma-separated)</p> | `false` | `""` |
| `restore-keys` | <p>Fallback keys for cache restoration</p> | `false` | `""` |
| `env-vars` | <p>Environment variables to include in cache key</p> | `false` | `""` |
### Outputs
| name | description |
|---------------|-----------------------------|
| `cache-hit` | <p>Cache hit indicator</p> |
| `cache-key` | <p>Generated cache key</p> |
| `cache-paths` | <p>Resolved cache paths</p> |
### Runs
This action is a `composite` action.
### Usage
```yaml
- uses: ivuorinen/actions/common-cache@main
with:
type:
# Type of cache (npm, composer, go, pip, etc.)
#
# Required: true
# Default: ""
paths:
# Paths to cache (comma-separated)
#
# Required: true
# Default: ""
key-prefix:
# Custom prefix for cache key
#
# Required: false
# Default: ""
key-files:
# Files to hash for cache key (comma-separated)
#
# Required: false
# Default: ""
restore-keys:
# Fallback keys for cache restoration
#
# Required: false
# Default: ""
env-vars:
# Environment variables to include in cache key
#
# Required: false
# Default: ""
```

122
common-cache/action.yml Normal file
View File

@@ -0,0 +1,122 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
# permissions:
# - contents: read # Required for reading cache contents
---
name: Common Cache
description: 'Standardized caching strategy for all actions'
author: 'Ismo Vuorinen'
branding:
icon: database
color: gray-dark
inputs:
type:
description: 'Type of cache (npm, composer, go, pip, etc.)'
required: true
paths:
description: 'Paths to cache (comma-separated)'
required: true
key-prefix:
description: 'Custom prefix for cache key'
required: false
default: ''
key-files:
description: 'Files to hash for cache key (comma-separated)'
required: false
default: ''
restore-keys:
description: 'Fallback keys for cache restoration'
required: false
default: ''
env-vars:
description: 'Environment variables to include in cache key'
required: false
default: ''
outputs:
cache-hit:
description: 'Cache hit indicator'
value: ${{ steps.cache.outputs.cache-hit }}
cache-key:
description: 'Generated cache key'
value: ${{ steps.prepare.outputs.cache-key }}
cache-paths:
description: 'Resolved cache paths'
value: ${{ steps.prepare.outputs.cache-paths }}
runs:
using: composite
steps:
- id: prepare
shell: bash
env:
RUNNER_OS: ${{ runner.os }}
CACHE_TYPE: ${{ inputs.type }}
KEY_PREFIX: ${{ inputs.key-prefix }}
KEY_FILES: ${{ inputs.key-files }}
ENV_VARS: ${{ inputs.env-vars }}
CACHE_PATHS: ${{ inputs.paths }}
run: |
set -euo pipefail
# Generate standardized cache key components
os_key="$RUNNER_OS"
type_key="$CACHE_TYPE"
prefix_key="$KEY_PREFIX"
# Process file hashes
# Note: For simple glob patterns, hashFiles() function could be used directly
# in the cache key. This manual approach is used to support comma-separated
# file lists with complex cache key construction.
files_hash=""
if [ -n "$KEY_FILES" ]; then
IFS=',' read -ra FILES <<< "$KEY_FILES"
existing_files=()
for file in "${FILES[@]}"; do
# Trim whitespace
file=$(echo "$file" | xargs)
if [ -f "$file" ]; then
existing_files+=("$file")
fi
done
# Hash all files together for better performance
if [ ${#existing_files[@]} -gt 0 ]; then
files_hash=$(cat "${existing_files[@]}" | sha256sum | cut -d' ' -f1)
fi
fi
# Process environment variables
env_hash=""
if [ -n "$ENV_VARS" ]; then
IFS=',' read -ra VARS <<< "$ENV_VARS"
for var in "${VARS[@]}"; do
if [ -n "${!var}" ]; then
env_hash="${env_hash}-${var}-${!var}"
fi
done
fi
# Generate final cache key
cache_key="${os_key}"
[ -n "$prefix_key" ] && cache_key="${cache_key}-${prefix_key}"
[ -n "$type_key" ] && cache_key="${cache_key}-${type_key}"
[ -n "$files_hash" ] && cache_key="${cache_key}-${files_hash}"
[ -n "$env_hash" ] && cache_key="${cache_key}-${env_hash}"
echo "cache-key=${cache_key}" >> $GITHUB_OUTPUT
# Process cache paths
IFS=',' read -ra PATHS <<< "$CACHE_PATHS"
cache_paths=""
for path in "${PATHS[@]}"; do
cache_paths="${cache_paths}${path}\n"
done
echo "cache-paths=${cache_paths}" >> $GITHUB_OUTPUT
- id: cache
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: ${{ steps.prepare.outputs.cache-paths }}
key: ${{ steps.prepare.outputs.cache-key }}
restore-keys: ${{ inputs.restore-keys }}

42
common-cache/rules.yml Normal file
View File

@@ -0,0 +1,42 @@
---
# Validation rules for common-cache action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 50% (3/6 inputs)
#
# This file defines validation rules for the common-cache GitHub Action.
# Rules are automatically applied by validate-inputs action when this
# action is used.
#
schema_version: '1.0'
action: common-cache
description: Standardized caching strategy for all actions
generator_version: 1.0.0
required_inputs:
- paths
- type
optional_inputs:
- env-vars
- key-files
- key-prefix
- restore-keys
conventions:
key-files: file_path
key-prefix: prefix
paths: file_path
overrides: {}
statistics:
total_inputs: 6
validated_inputs: 3
skipped_inputs: 0
coverage_percentage: 50
validation_coverage: 50
auto_detected: true
manual_review_required: true
quality_indicators:
has_required_inputs: true
has_token_validation: false
has_version_validation: false
has_file_validation: true
has_security_validation: false

View File

@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""Custom validator for common-file-check action.
This validator handles file checking validation including:
- File patterns with glob support (*, ?, **, {}, [])
- Path security validation
- Injection detection
"""
from __future__ import annotations
from pathlib import Path
import sys
# Add validate-inputs directory to path to import validators
validate_inputs_path = Path(__file__).parent.parent / "validate-inputs"
sys.path.insert(0, str(validate_inputs_path))
from validators.base import BaseValidator
from validators.boolean import BooleanValidator
from validators.file import FileValidator
class CustomValidator(BaseValidator):
"""Custom validator for common-file-check action.
Provides validation for file pattern checking.
"""
def __init__(self, action_type: str = "common-file-check") -> None:
"""Initialize the common-file-check validator."""
super().__init__(action_type)
self.file_validator = FileValidator(action_type)
self.boolean_validator = BooleanValidator(action_type)
def validate_inputs(self, inputs: dict[str, str]) -> bool:
"""Validate common-file-check specific inputs.
Args:
inputs: Dictionary of input names to values
Returns:
True if all validations pass, False otherwise
"""
valid = True
# Validate file-pattern (required)
if "file-pattern" in inputs:
valid &= self.validate_file_pattern(inputs["file-pattern"])
elif "file_pattern" in inputs:
valid &= self.validate_file_pattern(inputs["file_pattern"])
else:
# File pattern is required
self.add_error("File pattern is required")
valid = False
# Validate fail-on-missing (optional)
if inputs.get("fail-on-missing") or inputs.get("fail_on_missing"):
fail_on_missing = inputs.get("fail-on-missing", inputs.get("fail_on_missing"))
# Use BooleanValidator for boolean validation
result = self.boolean_validator.validate_optional_boolean(
fail_on_missing, "fail-on-missing"
)
# Propagate errors
for error in self.boolean_validator.errors:
if error not in self.errors:
self.add_error(error)
self.boolean_validator.clear_errors()
valid &= result
return valid
def get_required_inputs(self) -> list[str]:
"""Get list of required inputs for common-file-check.
Returns:
List of required input names
"""
return ["file-pattern"]
def get_validation_rules(self) -> dict:
"""Get validation rules for common-file-check.
Returns:
Dictionary of validation rules
"""
return {
"file-pattern": "File glob pattern to check",
"fail-on-missing": "Whether to fail if file is missing (true/false)",
}
def validate_file_pattern(self, pattern: str) -> bool:
"""Validate file pattern (glob pattern).
Args:
pattern: File pattern with glob support
Returns:
True if valid, False otherwise
"""
# Check for empty
if not pattern or not pattern.strip():
self.add_error("File pattern cannot be empty")
return False
# Allow GitHub Actions expressions
if self.is_github_expression(pattern):
return True
# Use base validator's path security check
if not self.validate_path_security(pattern, "file-pattern"):
return False
# Also check for command injection patterns using base validator
return self.validate_security_patterns(pattern, "file-pattern")

View File

@@ -0,0 +1,36 @@
# ivuorinen/actions/common-file-check
## Common File Check
### Description
A reusable action to check if a specific file or type of files exists in the repository.
Emits an output "found" which is true or false.
### Inputs
| name | description | required | default |
|----------------|-----------------------------------------|----------|---------|
| `file-pattern` | <p>Glob pattern for files to check.</p> | `true` | `""` |
### Outputs
| name | description |
|---------|----------------------------------------------------------------|
| `found` | <p>Indicates if the files matching the pattern were found.</p> |
### Runs
This action is a `composite` action.
### Usage
```yaml
- uses: ivuorinen/actions/common-file-check@main
with:
file-pattern:
# Glob pattern for files to check.
#
# Required: true
# Default: ""
```

View File

@@ -0,0 +1,87 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
# permissions:
# - contents: read # Required for checking files in repository
---
name: Common File Check
description: |
A reusable action to check if a specific file or type of files exists in the repository.
Emits an output "found" which is true or false.
author: 'Ismo Vuorinen'
branding:
icon: search
color: gray-dark
inputs:
file-pattern:
description: 'Glob pattern for files to check.'
required: true
outputs:
found:
description: 'Indicates if the files matching the pattern were found.'
value: ${{ steps.check-files.outputs.found }}
runs:
using: composite
steps:
- name: Validate Inputs
id: validate
shell: bash
env:
FILE_PATTERN: ${{ inputs.file-pattern }}
run: |
set -euo pipefail
# Validate file pattern is not empty
if [[ -z "$FILE_PATTERN" ]]; then
echo "::error::file-pattern input is required and cannot be empty"
exit 1
fi
# Validate file pattern format (basic glob pattern validation)
pattern="$FILE_PATTERN"
# Check for path traversal attempts
if [[ "$pattern" == *".."* ]]; then
echo "::error::Invalid file pattern: '$pattern'. Path traversal (..) not allowed"
exit 1
fi
# Check for absolute paths (should be relative patterns)
if [[ "$pattern" == /* ]]; then
echo "::error::Invalid file pattern: '$pattern'. Absolute paths not allowed, use relative patterns"
exit 1
fi
# Basic validation for dangerous patterns
if [[ "$pattern" == *";"* ]] || [[ "$pattern" == *"|"* ]] || [[ "$pattern" == *"&"* ]] || [[ "$pattern" == *"\$"* ]]; then
echo "::error::Invalid file pattern: '$pattern'. Command injection characters not allowed"
exit 1
fi
# Check for reasonable pattern length (prevent extremely long patterns)
if [ ${#pattern} -gt 255 ]; then
echo "::error::File pattern too long: ${#pattern} characters. Maximum allowed is 255 characters"
exit 1
fi
# Validate common glob pattern characters are safe
if ! [[ "$pattern" =~ ^[a-zA-Z0-9*?./_{}\[\]-]+$ ]]; then
echo "::warning::File pattern contains special characters: '$pattern'. Ensure this is intentional and safe"
fi
echo "Validated file pattern: '$pattern'"
- name: Check for Files
id: check-files
shell: bash
env:
FILE_PATTERN: ${{ inputs.file-pattern }}
run: |-
set -euo pipefail
if find . -name "$FILE_PATTERN" | grep -q .; then
echo "found=true" >> $GITHUB_OUTPUT
else
echo "found=false" >> $GITHUB_OUTPUT
fi

View File

@@ -0,0 +1,39 @@
---
# Validation rules for common-file-check action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 100% (1/1 inputs)
#
# This file defines validation rules for the common-file-check GitHub Action.
# Rules are automatically applied by validate-inputs action when this
# action is used.
#
schema_version: '1.0'
action: common-file-check
description: 'A reusable action to check if a specific file or type of files exists in the repository.
Emits an output "found" which is true or false.
'
generator_version: 1.0.0
required_inputs:
- file-pattern
optional_inputs: []
conventions:
file-pattern: file_path
overrides: {}
statistics:
total_inputs: 1
validated_inputs: 1
skipped_inputs: 0
coverage_percentage: 100
validation_coverage: 100
auto_detected: true
manual_review_required: false
quality_indicators:
has_required_inputs: true
has_token_validation: false
has_version_validation: false
has_file_validation: true
has_security_validation: false

185
common-retry/CustomValidator.py Executable file
View File

@@ -0,0 +1,185 @@
#!/usr/bin/env python3
"""Custom validator for common-retry action."""
from __future__ import annotations
from pathlib import Path
import sys
from typing import Any
# Add validate-inputs directory to path to import validators
validate_inputs_path = Path(__file__).parent.parent / "validate-inputs"
sys.path.insert(0, str(validate_inputs_path))
from validators.base import BaseValidator
from validators.file import FileValidator
from validators.numeric import NumericValidator
from validators.security import SecurityValidator
class CustomValidator(BaseValidator):
"""Custom validator for common-retry action."""
def __init__(self, action_type: str = "common-retry") -> None:
"""Initialize common-retry validator."""
super().__init__(action_type)
self.file_validator = FileValidator()
self.numeric_validator = NumericValidator()
self.security_validator = SecurityValidator()
def validate_inputs(self, inputs: dict[str, str]) -> bool:
"""Validate common-retry action inputs."""
valid = True
# Validate required inputs
if "command" not in inputs or not inputs["command"]:
self.add_error("Input 'command' is required")
valid = False
elif inputs["command"]:
# Validate command for security issues
result = self.security_validator.validate_no_injection(inputs["command"])
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
# Validate optional inputs
return self._validate_optionals(inputs=inputs, prev_valid=valid)
def _validate_optionals(self, inputs: dict[str, Any], *, prev_valid: bool) -> bool:
"""Validate optional inputs for common-retry action.
Args:
inputs: Dictionary of input names and values
prev_valid: Previous validity state
Returns:
True if all optional validations pass, False otherwise
"""
valid = prev_valid
# Backoff strategy - fixed is the correct value, not constant
backoff_strategy = inputs.get("backoff-strategy")
backoff_strategies = ["exponential", "linear", "fixed"]
if backoff_strategy and backoff_strategy not in backoff_strategies:
self.add_error(f"Invalid backoff strategy: {inputs['backoff-strategy']}")
valid = False
# Max retries
max_retries = inputs.get("max-retries")
if max_retries:
result = self.numeric_validator.validate_numeric_range(
max_retries, min_val=1, max_val=10
)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
if not result:
valid = False
# Retry delay
retry_delay = inputs.get("retry-delay")
if retry_delay:
result = self.numeric_validator.validate_numeric_range(
retry_delay, min_val=1, max_val=300
)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
if not result:
valid = False
# Shell type - only bash and sh are allowed
shell = inputs.get("shell")
valid_shells = ["bash", "sh"]
if shell and shell not in valid_shells:
self.add_error(f"Invalid shell type: {inputs['shell']}")
valid = False
# Timeout
timeout = inputs.get("timeout")
if timeout:
result = self.numeric_validator.validate_numeric_range(timeout, min_val=1, max_val=3600)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
if not result:
valid = False
# Working directory
working_directory = inputs.get("working-directory")
if working_directory:
result = self.file_validator.validate_file_path(working_directory)
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
valid = False
# Description
description = inputs.get("description")
if description:
# Validate description for security patterns
result = self.security_validator.validate_no_injection(description)
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
# Success codes - validate for injection
success_codes = inputs.get("success-codes")
if success_codes:
result = self.security_validator.validate_no_injection(success_codes)
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
# Retry codes - validate for injection
retry_codes = inputs.get("retry-codes")
if retry_codes:
result = self.security_validator.validate_no_injection(retry_codes)
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
return valid
def get_required_inputs(self) -> list[str]:
"""Get list of required inputs."""
return ["command"]
def get_validation_rules(self) -> dict:
"""Get validation rules."""
return {
"command": {
"type": "string",
"required": True,
"description": "Command to retry",
},
"backoff-strategy": {
"type": "string",
"required": False,
"description": "Backoff strategy",
},
"max-retries": {
"type": "numeric",
"required": False,
"description": "Maximum number of retries",
},
"retry-delay": {
"type": "numeric",
"required": False,
"description": "Delay between retries",
},
"shell": {
"type": "string",
"required": False,
"description": "Shell to use",
},
"timeout": {
"type": "numeric",
"required": False,
"description": "Command timeout",
},
}

101
common-retry/README.md Normal file
View File

@@ -0,0 +1,101 @@
# ivuorinen/actions/common-retry
## Common Retry
### Description
Standardized retry utility for network operations and flaky commands
### Inputs
| name | description | required | default |
|---------------------|---------------------------------------------------------------------|----------|---------------------|
| `command` | <p>Command to execute with retry logic</p> | `true` | `""` |
| `max-retries` | <p>Maximum number of retry attempts</p> | `false` | `3` |
| `retry-delay` | <p>Initial delay between retries in seconds</p> | `false` | `5` |
| `backoff-strategy` | <p>Backoff strategy (linear, exponential, fixed)</p> | `false` | `exponential` |
| `timeout` | <p>Timeout for each attempt in seconds</p> | `false` | `300` |
| `working-directory` | <p>Working directory to execute command in</p> | `false` | `.` |
| `shell` | <p>Shell to use for command execution</p> | `false` | `bash` |
| `success-codes` | <p>Comma-separated list of success exit codes</p> | `false` | `0` |
| `retry-codes` | <p>Comma-separated list of exit codes that should trigger retry</p> | `false` | `1,2,124,126,127` |
| `description` | <p>Human-readable description of the operation for logging</p> | `false` | `Command execution` |
### Outputs
| name | description |
|-------------|---------------------------------------------------|
| `success` | <p>Whether the command succeeded (true/false)</p> |
| `attempts` | <p>Number of attempts made</p> |
| `exit-code` | <p>Final exit code of the command</p> |
| `duration` | <p>Total execution duration in seconds</p> |
### Runs
This action is a `composite` action.
### Usage
```yaml
- uses: ivuorinen/actions/common-retry@main
with:
command:
# Command to execute with retry logic
#
# Required: true
# Default: ""
max-retries:
# Maximum number of retry attempts
#
# Required: false
# Default: 3
retry-delay:
# Initial delay between retries in seconds
#
# Required: false
# Default: 5
backoff-strategy:
# Backoff strategy (linear, exponential, fixed)
#
# Required: false
# Default: exponential
timeout:
# Timeout for each attempt in seconds
#
# Required: false
# Default: 300
working-directory:
# Working directory to execute command in
#
# Required: false
# Default: .
shell:
# Shell to use for command execution
#
# Required: false
# Default: bash
success-codes:
# Comma-separated list of success exit codes
#
# Required: false
# Default: 0
retry-codes:
# Comma-separated list of exit codes that should trigger retry
#
# Required: false
# Default: 1,2,124,126,127
description:
# Human-readable description of the operation for logging
#
# Required: false
# Default: Command execution
```

246
common-retry/action.yml Normal file
View File

@@ -0,0 +1,246 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
# permissions:
# - (none required) # Permissions depend on the command being executed
---
name: Common Retry
description: 'Standardized retry utility for network operations and flaky commands'
author: 'Ismo Vuorinen'
branding:
icon: refresh-cw
color: orange
inputs:
command:
description: 'Command to execute with retry logic'
required: true
max-retries:
description: 'Maximum number of retry attempts'
required: false
default: '3'
retry-delay:
description: 'Initial delay between retries in seconds'
required: false
default: '5'
backoff-strategy:
description: 'Backoff strategy (linear, exponential, fixed)'
required: false
default: 'exponential'
timeout:
description: 'Timeout for each attempt in seconds'
required: false
default: '300'
working-directory:
description: 'Working directory to execute command in'
required: false
default: '.'
shell:
description: 'Shell to use for command execution'
required: false
default: 'bash'
success-codes:
description: 'Comma-separated list of success exit codes'
required: false
default: '0'
retry-codes:
description: 'Comma-separated list of exit codes that should trigger retry'
required: false
default: '1,2,124,126,127'
description:
description: 'Human-readable description of the operation for logging'
required: false
default: 'Command execution'
outputs:
success:
description: 'Whether the command succeeded (true/false)'
value: ${{ steps.execute.outputs.success }}
attempts:
description: 'Number of attempts made'
value: ${{ steps.execute.outputs.attempts }}
exit-code:
description: 'Final exit code of the command'
value: ${{ steps.execute.outputs.exit-code }}
duration:
description: 'Total execution duration in seconds'
value: ${{ steps.execute.outputs.duration }}
runs:
using: composite
steps:
- name: Validate Inputs
id: validate
shell: bash
env:
MAX_RETRIES: ${{ inputs.max-retries }}
RETRY_DELAY: ${{ inputs.retry-delay }}
BACKOFF_STRATEGY: ${{ inputs.backoff-strategy }}
TIMEOUT: ${{ inputs.timeout }}
SHELL: ${{ inputs.shell }}
WORKING_DIRECTORY: ${{ inputs.working-directory }}
run: |
set -euo pipefail
# Validate max-retries (1-10)
if ! [[ "$MAX_RETRIES" =~ ^[1-9]$|^10$ ]]; then
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be 1-10"
exit 1
fi
# Validate retry-delay (1-300)
if ! [[ "$RETRY_DELAY" =~ ^[1-9][0-9]?$|^[12][0-9][0-9]$|^300$ ]]; then
echo "::error::Invalid retry-delay: '$RETRY_DELAY'. Must be 1-300 seconds"
exit 1
fi
# Validate backoff-strategy
if ! [[ "$BACKOFF_STRATEGY" =~ ^(linear|exponential|fixed)$ ]]; then
echo "::error::Invalid backoff-strategy: '$BACKOFF_STRATEGY'. Must be linear, exponential, or fixed"
exit 1
fi
# Validate timeout (1-3600)
if ! [[ "$TIMEOUT" =~ ^[1-9][0-9]?$|^[1-9][0-9][0-9]$|^[12][0-9][0-9][0-9]$|^3[0-5][0-9][0-9]$|^3600$ ]]; then
echo "::error::Invalid timeout: '$TIMEOUT'. Must be 1-3600 seconds"
exit 1
fi
# Validate shell (only bash supported due to script features)
if ! [[ "$SHELL" =~ ^bash$ ]]; then
echo "::error::Invalid shell: '$SHELL'. Must be bash (sh not supported due to pipefail requirement)"
exit 1
fi
# Validate working directory doesn't contain path traversal
if [[ "$WORKING_DIRECTORY" == *".."* ]]; then
echo "::error::Invalid working-directory: '$WORKING_DIRECTORY'. Path traversal (..) not allowed"
exit 1
fi
echo "Input validation completed successfully"
- name: Execute with Retry Logic
id: execute
shell: ${{ inputs.shell }}
working-directory: ${{ inputs.working-directory }}
env:
SUCCESS_CODES_INPUT: ${{ inputs.success-codes }}
RETRY_CODES_INPUT: ${{ inputs.retry-codes }}
MAX_RETRIES: ${{ inputs.max-retries }}
RETRY_DELAY: ${{ inputs.retry-delay }}
TIMEOUT: ${{ inputs.timeout }}
BACKOFF_STRATEGY: ${{ inputs.backoff-strategy }}
OPERATION_DESCRIPTION: ${{ inputs.description }}
COMMAND: ${{ inputs.command }}
run: |-
set -euo pipefail
# Parse success and retry codes
IFS=',' read -ra SUCCESS_CODES <<< "$SUCCESS_CODES_INPUT"
IFS=',' read -ra RETRY_CODES <<< "$RETRY_CODES_INPUT"
# Initialize variables
attempt=1
max_attempts="$MAX_RETRIES"
base_delay="$RETRY_DELAY"
timeout_seconds="$TIMEOUT"
backoff_strategy="$BACKOFF_STRATEGY"
operation_description="$OPERATION_DESCRIPTION"
start_time=$(date +%s)
echo "Starting retry execution: $operation_description"
echo "Command: $COMMAND"
echo "Max attempts: $max_attempts"
echo "Base delay: ${base_delay}s"
echo "Backoff strategy: $backoff_strategy"
echo "Timeout per attempt: ${timeout_seconds}s"
# Function to check if exit code is in array
contains_code() {
local code=$1
shift
local codes=("$@")
for c in "${codes[@]}"; do
if [[ "$c" == "$code" ]]; then
return 0
fi
done
return 1
}
# Function to calculate delay based on backoff strategy
calculate_delay() {
local attempt_num=$1
case "$backoff_strategy" in
"linear")
echo $((base_delay * attempt_num))
;;
"exponential")
echo $((base_delay * (2 ** (attempt_num - 1))))
;;
"fixed")
echo $base_delay
;;
esac
}
# Main retry loop
while [ $attempt -le $max_attempts ]; do
echo "Attempt $attempt of $max_attempts: $operation_description"
# Execute command with timeout
exit_code=0
if timeout "$timeout_seconds" bash -c "$COMMAND"; then
exit_code=0
else
exit_code=$?
fi
# Check if exit code indicates success
if contains_code "$exit_code" "${SUCCESS_CODES[@]}"; then
end_time=$(date +%s)
duration=$((end_time - start_time))
echo "success=true" >> $GITHUB_OUTPUT
echo "attempts=$attempt" >> $GITHUB_OUTPUT
echo "exit-code=$exit_code" >> $GITHUB_OUTPUT
echo "duration=$duration" >> $GITHUB_OUTPUT
echo "✅ Operation succeeded on attempt $attempt (exit code: $exit_code, duration: ${duration}s)"
exit 0
fi
# Check if we should retry this exit code
if ! contains_code "$exit_code" "${RETRY_CODES[@]}"; then
end_time=$(date +%s)
duration=$((end_time - start_time))
echo "success=false" >> $GITHUB_OUTPUT
echo "attempts=$attempt" >> $GITHUB_OUTPUT
echo "exit-code=$exit_code" >> $GITHUB_OUTPUT
echo "duration=$duration" >> $GITHUB_OUTPUT
echo "::error::Operation failed with non-retryable exit code: $exit_code"
exit $exit_code
fi
# Calculate delay for next attempt
if [ $attempt -lt $max_attempts ]; then
delay=$(calculate_delay $attempt)
max_delay=300 # Cap delay at 5 minutes
if [ $delay -gt $max_delay ]; then
delay=$max_delay
fi
echo "❌ Attempt $attempt failed (exit code: $exit_code). Waiting ${delay}s before retry..."
sleep $delay
fi
attempt=$((attempt + 1))
done
# All attempts exhausted
end_time=$(date +%s)
duration=$((end_time - start_time))
echo "success=false" >> $GITHUB_OUTPUT
echo "attempts=$max_attempts" >> $GITHUB_OUTPUT
echo "exit-code=$exit_code" >> $GITHUB_OUTPUT
echo "duration=$duration" >> $GITHUB_OUTPUT
echo "::error::Operation failed after $max_attempts attempts (final exit code: $exit_code, total duration: ${duration}s)"
exit $exit_code

50
common-retry/rules.yml Normal file
View File

@@ -0,0 +1,50 @@
---
# Validation rules for common-retry action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0
# Coverage: 70% (7/10 inputs)
#
# This file defines validation rules for the common-retry GitHub Action.
# Rules are automatically applied by validate-inputs action when this
# action is used.
#
schema_version: '1.0'
action: common-retry
description: Standardized retry utility for network operations and flaky commands
generator_version: 1.0.0
required_inputs:
- command
optional_inputs:
- backoff-strategy
- description
- max-retries
- retry-codes
- retry-delay
- shell
- success-codes
- timeout
- working-directory
conventions:
backoff-strategy: backoff_strategy
description: security_patterns
max-retries: numeric_range_1_10
retry-delay: numeric_range_1_300
shell: shell_type
timeout: numeric_range_1_3600
working-directory: file_path
overrides: {}
statistics:
total_inputs: 10
validated_inputs: 7
skipped_inputs: 0
coverage_percentage: 70
validation_coverage: 70
auto_detected: true
manual_review_required: true
quality_indicators:
has_required_inputs: true
has_token_validation: false
has_version_validation: false
has_file_validation: true
has_security_validation: true

View File

@@ -36,35 +36,47 @@ class CustomValidator(BaseValidator):
# Validate optional inputs
if inputs.get("image-quality"):
valid &= self.validate_with(
self.numeric_validator,
"validate_numeric_range",
inputs["image-quality"],
min_val=0,
max_val=100,
result = self.numeric_validator.validate_numeric_range(
inputs["image-quality"], min_val=0, max_val=100
)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
if not result:
valid = False
if inputs.get("png-quality"):
valid &= self.validate_with(
self.numeric_validator,
"validate_numeric_range",
inputs["png-quality"],
min_val=0,
max_val=100,
result = self.numeric_validator.validate_numeric_range(
inputs["png-quality"], min_val=0, max_val=100
)
for error in self.numeric_validator.errors:
if error not in self.errors:
self.add_error(error)
self.numeric_validator.clear_errors()
if not result:
valid = False
if inputs.get("directory"):
valid &= self.validate_with(
self.file_validator, "validate_file_path", inputs["directory"], "directory"
)
result = self.file_validator.validate_file_path(inputs["directory"], "directory")
for error in self.file_validator.errors:
if error not in self.errors:
self.add_error(error)
self.file_validator.clear_errors()
if not result:
valid = False
if inputs.get("ignore-paths"):
valid &= self.validate_with(
self.security_validator,
"validate_no_injection",
inputs["ignore-paths"],
"ignore-paths",
# Validate for injection
result = self.security_validator.validate_no_injection(
inputs["ignore-paths"], "ignore-paths"
)
for error in self.security_validator.errors:
if error not in self.errors:
self.add_error(error)
self.security_validator.clear_errors()
if not result:
valid = False
return valid

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