Compare commits

..

1 Commits

Author SHA1 Message Date
0fa9a68f07 chore: update action references for release v2025.10.26
This commit updates all internal action references to point to the current
commit SHA in preparation for release v2025.10.26.
2025-10-26 23:56:09 +02:00
207 changed files with 17578 additions and 7570 deletions

View File

@@ -17,7 +17,7 @@ runs:
using: composite using: composite
steps: steps:
- name: Install uv - name: Install uv
uses: astral-sh/setup-uv@1e862dfacbd1d6d858c55d9b792c756523627244 # v7.1.4 uses: astral-sh/setup-uv@2ddd2b9cb38ad8efd50337e8ab201519a34c9f24 # v7.1.1
with: with:
enable-cache: true enable-cache: true
@@ -33,7 +33,7 @@ runs:
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with: with:
node-version: '24' node-version: '22'
cache: npm cache: npm
- name: Install Node dependencies - name: Install Node dependencies

View File

@@ -35,13 +35,13 @@ jobs:
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Check Required Configurations - name: Check Required Configurations
id: check-configs id: check-configs
shell: sh shell: bash
run: | run: |
# Initialize all flags as false # Initialize all flags as false
{ {
@@ -87,7 +87,7 @@ jobs:
- name: Verify SARIF files - name: Verify SARIF files
id: verify-sarif id: verify-sarif
shell: sh shell: bash
run: | run: |
# Initialize outputs # Initialize outputs
{ {
@@ -117,21 +117,21 @@ jobs:
- name: Upload Trivy results - name: Upload Trivy results
if: steps.verify-sarif.outputs.has_trivy == 'true' if: steps.verify-sarif.outputs.has_trivy == 'true'
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
with: with:
sarif_file: 'trivy-results.sarif' sarif_file: 'trivy-results.sarif'
category: 'trivy' category: 'trivy'
- name: Upload Gitleaks results - name: Upload Gitleaks results
if: steps.verify-sarif.outputs.has_gitleaks == 'true' if: steps.verify-sarif.outputs.has_gitleaks == 'true'
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
with: with:
sarif_file: 'gitleaks-report.sarif' sarif_file: 'gitleaks-report.sarif'
category: 'gitleaks' category: 'gitleaks'
- name: Archive security reports - name: Archive security reports
if: always() if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: security-reports-${{ github.run_id }} name: security-reports-${{ github.run_id }}
path: | path: |

View File

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

View File

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

View File

@@ -34,18 +34,18 @@ jobs:
steps: # Add languages used in your actions steps: # Add languages used in your actions
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 uses: github/codeql-action/init@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
queries: security-and-quality queries: security-and-quality
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 uses: github/codeql-action/autobuild@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 uses: github/codeql-action/analyze@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
with: with:
category: '/language:${{matrix.language}}' category: '/language:${{matrix.language}}'

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ jobs:
version: ${{ steps.daily-version.outputs.version }} version: ${{ steps.daily-version.outputs.version }}
steps: steps:
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: Create tag if necessary - name: Create tag if necessary
uses: fregante/daily-version-action@fb1a60b7c4daf1410cd755e360ebec3901e58588 # v2.1.3 uses: fregante/daily-version-action@fb1a60b7c4daf1410cd755e360ebec3901e58588 # v2.1.3

View File

@@ -47,7 +47,6 @@ concurrency:
permissions: permissions:
contents: read contents: read
packages: read # Required for private dependencies
jobs: jobs:
megalinter: megalinter:
@@ -57,17 +56,15 @@ jobs:
permissions: permissions:
actions: write actions: write
checks: write # Create and update check runs
contents: write contents: write
issues: write issues: write
packages: read # Access private packages
pull-requests: write pull-requests: write
security-events: write security-events: write
statuses: write statuses: write
steps: steps:
- name: Checkout Code - name: Checkout Code
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }} token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
fetch-depth: 0 fetch-depth: 0
@@ -79,7 +76,7 @@ jobs:
- name: Check MegaLinter Results - name: Check MegaLinter Results
id: check-results id: check-results
if: always() if: always()
shell: sh shell: bash
run: | run: |
printf '%s\n' "status=success" >> "$GITHUB_OUTPUT" printf '%s\n' "status=success" >> "$GITHUB_OUTPUT"
@@ -94,7 +91,7 @@ jobs:
- name: Upload Reports - name: Upload Reports
if: always() if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: MegaLinter reports name: MegaLinter reports
path: | path: |
@@ -104,14 +101,14 @@ jobs:
- name: Upload SARIF Report - name: Upload SARIF Report
if: always() && hashFiles('megalinter-reports/sarif/*.sarif') if: always() && hashFiles('megalinter-reports/sarif/*.sarif')
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
with: with:
sarif_file: megalinter-reports/sarif sarif_file: megalinter-reports/sarif
category: megalinter category: megalinter
- name: Prepare Git for Fixes - name: Prepare Git for Fixes
if: steps.ml.outputs.has_updated_sources == 1 if: steps.ml.outputs.has_updated_sources == 1
shell: sh shell: bash
run: | run: |
sudo chown -Rc $UID .git/ sudo chown -Rc $UID .git/
git config --global user.name "fiximus" git config --global user.name "fiximus"
@@ -124,7 +121,7 @@ jobs:
env.APPLY_FIXES_MODE == 'pull_request' && env.APPLY_FIXES_MODE == 'pull_request' &&
(github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) && (github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository) &&
!contains(github.event.head_commit.message, 'skip fix') !contains(github.event.head_commit.message, 'skip fix')
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
id: cpr id: cpr
with: with:
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }} token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
@@ -196,7 +193,7 @@ jobs:
- name: Cleanup - name: Cleanup
if: always() if: always()
shell: sh shell: bash
run: |- run: |-
# Remove temporary files but keep reports # Remove temporary files but keep reports
find . -type f -name "megalinter.*" ! -name "megalinter-reports" -delete || true find . -type f -name "megalinter.*" ! -name "megalinter-reports" -delete || true

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,7 +24,7 @@ jobs:
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
fetch-depth: 0 fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
@@ -49,7 +49,7 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
if: steps.action-versioning.outputs.updated == 'true' if: steps.action-versioning.outputs.updated == 'true'
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}' commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}'
@@ -78,7 +78,7 @@ jobs:
- name: Check for Annual Bump - name: Check for Annual Bump
if: steps.action-versioning.outputs.needs-annual-bump == 'true' if: steps.action-versioning.outputs.needs-annual-bump == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with: with:
script: | script: |
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();

2
.gitignore vendored
View File

@@ -13,7 +13,6 @@
.cache .cache
.cache/ .cache/
.coverage .coverage
.worktrees/
.coverage.* .coverage.*
.docusaurus .docusaurus
.dynamodb/ .dynamodb/
@@ -84,4 +83,3 @@ tests/reports/**/*.json
!uv.lock !uv.lock
code-scanning-report-* code-scanning-report-*
*.sarif *.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 TYPESCRIPT_ES_CONFIG_FILE: .eslintrc.json
FILTER_REGEX_EXCLUDE: > FILTER_REGEX_EXCLUDE: >
(node_modules|\.automation/test|docs/json-schemas|\.worktrees) (node_modules|\.automation/test|docs/json-schemas)

2
.nvmrc
View File

@@ -1 +1 @@
24 v22

View File

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

View File

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

View File

@@ -45,32 +45,6 @@
- macOS/Windows runners may lack Linux tools (jq, bc, specific GNU utils) - macOS/Windows runners may lack Linux tools (jq, bc, specific GNU utils)
- Always provide fallbacks or explicit installation steps - 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) ## EditorConfig Rules (.editorconfig)
**CRITICAL**: EditorConfig violations are blocking errors and must be fixed always. **CRITICAL**: EditorConfig violations are blocking errors and must be fixed always.

View File

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

View File

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

View File

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

View File

@@ -39,7 +39,7 @@
**Core Memories** (read first for project understanding): **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 - `validator_system` Validation architecture, components, usage patterns
- `development_standards` Quality rules, workflows, security, completion checklist - `development_standards` Quality rules, workflows, security, completion checklist
@@ -71,11 +71,11 @@
Flat structure. Each action self-contained with `action.yml`. Flat structure. Each action self-contained with `action.yml`.
**24 Actions**: Setup (language-version-detect), Utilities (action-versioning, version-file-parser), **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-lint, csharp-lint-check, eslint-lint, go-lint, pr-lint, pre-commit, prettier-lint, python-lint-fix, terraform-lint-fix), 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), Build (csharp-build, go-build, docker-build), Testing (php-tests, php-laravel-phpunit, php-composer), Build (csharp-build, go-build, docker-build),
Publishing (npm-publish, docker-publish, csharp-publish), Publishing (npm-publish, docker-publish, docker-publish-gh, docker-publish-hub, csharp-publish),
Repository (release-monthly, sync-labels, stale, compress-images, codeql-analysis), Repository (github-release, release-monthly, sync-labels, stale, compress-images, common-cache, common-file-check, common-retry, codeql-analysis),
Validation (validate-inputs) Validation (validate-inputs)
## Commands ## Commands

View File

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

323
README.md
View File

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

View File

@@ -57,21 +57,6 @@ get_action_name() {
uv run "$script_dir/../shared/validation_core.py" --name "$action_file" uv run "$script_dir/../shared/validation_core.py" --name "$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 using Python validation module
test_input_validation() { test_input_validation() {
local action_dir="$1" local action_dir="$1"
@@ -363,5 +348,5 @@ run_action_tests() {
} }
# Export all functions # Export all functions
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name 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 export -f test_input_validation test_action_outputs test_external_usage measure_action_time run_action_tests

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: workflow_dispatch:
push: push:
paths: paths:
- 'eslint-lint/**' - 'eslint-check/**'
- 'prettier-lint/**' - 'eslint-fix/**'
- 'prettier-check/**'
- 'prettier-fix/**'
- 'node-setup/**' - 'node-setup/**'
- 'common-cache/**'
- '_tests/integration/workflows/lint-fix-chain-test.yml' - '_tests/integration/workflows/lint-fix-chain-test.yml'
jobs: jobs:
@@ -61,15 +64,14 @@ jobs:
node-version: '18' node-version: '18'
working-directory: './test-project' working-directory: './test-project'
- name: Test eslint-lint check mode (should find errors) - name: Test eslint-check (should find errors)
id: eslint-check id: eslint-check
uses: ./eslint-lint uses: ./eslint-check
with: with:
mode: 'check'
working-directory: './test-project' working-directory: './test-project'
continue-on-error: true continue-on-error: true
- name: Validate eslint-lint check found issues - name: Validate eslint-check found issues
run: | run: |
echo "ESLint check outcome: ${{ steps.eslint-check.outcome }}" echo "ESLint check outcome: ${{ steps.eslint-check.outcome }}"
echo "Error count: ${{ steps.eslint-check.outputs.error-count }}" echo "Error count: ${{ steps.eslint-check.outputs.error-count }}"
@@ -84,24 +86,23 @@ jobs:
echo "✅ ESLint check validated" echo "✅ ESLint check validated"
- name: Test eslint-lint fix mode (should fix issues) - name: Test eslint-fix (should fix issues)
id: eslint-fix id: eslint-fix
uses: ./eslint-lint uses: ./eslint-fix
with: with:
mode: 'fix'
working-directory: './test-project' working-directory: './test-project'
token: ${{ github.token }} token: ${{ github.token }}
email: 'test@example.com' email: 'test@example.com'
username: 'test-user' username: 'test-user'
- name: Validate eslint-lint fix ran - name: Validate eslint-fix ran
run: | run: |
echo "Errors fixed: ${{ steps.eslint-fix.outputs.errors-fixed }}" echo "Fixed count: ${{ steps.eslint-fix.outputs.fixed-count }}"
echo "Files changed: ${{ steps.eslint-fix.outputs.files-changed }}" echo "Files fixed: ${{ steps.eslint-fix.outputs.files-fixed }}"
# Check that fixes were attempted # Check that fixes were attempted
if [[ -n "${{ steps.eslint-fix.outputs.errors-fixed }}" ]]; then if [[ -n "${{ steps.eslint-fix.outputs.fixed-count }}" ]]; then
echo "✅ ESLint fixed ${{ steps.eslint-fix.outputs.errors-fixed }} issues" echo "✅ ESLint fixed ${{ steps.eslint-fix.outputs.fixed-count }} issues"
else else
echo "⚠️ No fix count reported (may be expected if no fixable issues)" echo "⚠️ No fix count reported (may be expected if no fixable issues)"
fi fi
@@ -155,11 +156,10 @@ jobs:
node-version: '18' node-version: '18'
working-directory: './test-prettier' working-directory: './test-prettier'
- name: Test prettier-lint check mode (should find issues) - name: Test prettier-check (should find issues)
id: prettier-check id: prettier-check
uses: ./prettier-lint uses: ./prettier-check
with: with:
mode: 'check'
working-directory: './test-prettier' working-directory: './test-prettier'
continue-on-error: true continue-on-error: true
@@ -174,17 +174,16 @@ jobs:
echo "⚠️ WARNING: Expected Prettier to find formatting issues" echo "⚠️ WARNING: Expected Prettier to find formatting issues"
fi fi
- name: Test prettier-lint fix mode (should fix issues) - name: Test prettier-fix (should fix issues)
id: prettier-fix id: prettier-fix
uses: ./prettier-lint uses: ./prettier-fix
with: with:
mode: 'fix'
working-directory: './test-prettier' working-directory: './test-prettier'
token: ${{ github.token }} token: ${{ github.token }}
email: 'test@example.com' email: 'test@example.com'
username: 'test-user' username: 'test-user'
- name: Validate prettier-lint fix ran - name: Validate prettier-fix ran
run: | run: |
echo "Prettier fix completed" echo "Prettier fix completed"
@@ -262,25 +261,22 @@ jobs:
- name: Run ESLint check - name: Run ESLint check
id: lint-check id: lint-check
uses: ./eslint-lint uses: ./eslint-check
with: with:
mode: 'check'
working-directory: './test-chain' working-directory: './test-chain'
continue-on-error: true continue-on-error: true
- name: Run Prettier check - name: Run Prettier check
id: format-check id: format-check
uses: ./prettier-lint uses: ./prettier-check
with: with:
mode: 'check'
working-directory: './test-chain' working-directory: './test-chain'
continue-on-error: true continue-on-error: true
- name: Run ESLint fix - name: Run ESLint fix
id: lint-fix id: lint-fix
uses: ./eslint-lint uses: ./eslint-fix
with: with:
mode: 'fix'
working-directory: './test-chain' working-directory: './test-chain'
token: ${{ github.token }} token: ${{ github.token }}
email: 'test@example.com' email: 'test@example.com'
@@ -288,9 +284,8 @@ jobs:
- name: Run Prettier fix - name: Run Prettier fix
id: format-fix id: format-fix
uses: ./prettier-lint uses: ./prettier-fix
with: with:
mode: 'fix'
working-directory: './test-chain' working-directory: './test-chain'
token: ${{ github.token }} token: ${{ github.token }}
email: 'test@example.com' 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: push:
paths: paths:
- 'pre-commit/**' - 'pre-commit/**'
- 'set-git-config/**'
- 'validate-inputs/**' - 'validate-inputs/**'
- '_tests/integration/workflows/pre-commit-test.yml' - '_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 if [[ $action_name == *"$ACTION_FILTER"* ]]; then
actions+=("$action_name") actions+=("$action_name")
fi 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 else
# All actions # All actions
while IFS= read -r action_dir; do while IFS= read -r action_dir; do
local action_name local action_name
action_name=$(basename "$action_dir") action_name=$(basename "$action_dir")
actions+=("$action_name") 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 fi
log_info "Discovered ${#actions[@]} actions to test: ${actions[*]}" log_info "Discovered ${#actions[@]} actions to test: ${actions[*]}"
@@ -203,7 +203,7 @@ install_shellspec() {
# Pinned SHA256 checksum for ShellSpec 0.28.1 # Pinned SHA256 checksum for ShellSpec 0.28.1
# Source: https://github.com/shellspec/shellspec/archive/refs/tags/0.28.1.tar.gz # Source: https://github.com/shellspec/shellspec/archive/refs/tags/0.28.1.tar.gz
local checksum="400d835466429a5fe6c77a62775a9173729d61dd43e05dfa893e8cf6cb511783" local checksum="351e7a63b8df47c07b022c19d21a167b85693f5eb549fa96e64f64844b680024"
# Ensure cleanup of the downloaded file # Ensure cleanup of the downloaded file
# Use ${tarball:-} to handle unbound variable when trap fires after function returns # Use ${tarball:-} to handle unbound variable when trap fires after function returns

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" It "defines expected outputs"
When call get_action_outputs "$ACTION_FILE" When call get_action_outputs "$ACTION_FILE"
The output should include "test-status" The output should include "test_status"
The output should include "tests-run" The output should include "tests_run"
The output should include "tests-passed" The output should include "tests_passed"
The output should include "framework" The output should include "coverage_path"
End End
End End
@@ -245,214 +245,5 @@ It "validates default email is secure"
When call validate_input_python "php-tests" "email" "github-actions@github.com" When call validate_input_python "php-tests" "email" "github-actions@github.com"
The status should be success The status should be success
End 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
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

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

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

View File

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

View File

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

View File

@@ -34,7 +34,7 @@ runs:
using: composite using: composite
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
token: ${{ inputs.token || github.token }} token: ${{ inputs.token || github.token }}
fetch-depth: 0 fetch-depth: 0

View File

@@ -45,19 +45,55 @@ runs:
steps: steps:
- name: Validate Inputs - name: Validate Inputs
id: validate id: validate
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8 shell: bash
with: env:
action-type: 'ansible-lint-fix' GITHUB_TOKEN: ${{ inputs.token }}
token: ${{ inputs.token }} EMAIL: ${{ inputs.email }}
email: ${{ inputs.email }} USERNAME: ${{ inputs.username }}
username: ${{ inputs.username }} MAX_RETRIES: ${{ inputs.max-retries }}
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 - name: Check for Ansible Files
id: check-files id: check-files
shell: sh shell: bash
run: | run: |
set -eu set -euo pipefail
# Check for both .yml and .yaml files # Check for both .yml and .yaml files
if find . \( -name "*.yml" -o -name "*.yaml" \) -type f | grep -q .; then if find . \( -name "*.yml" -o -name "*.yaml" \) -type f | grep -q .; then
@@ -69,32 +105,35 @@ runs:
fi fi
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
token: ${{ inputs.token || github.token }} token: ${{ inputs.token || github.token }}
- name: Setup Python - name: Cache Python Dependencies
if: steps.check-files.outputs.files_found == 'true' if: steps.check-files.outputs.files_found == 'true'
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 id: cache-pip
uses: ivuorinen/actions/common-cache@e2222afff180ee77f330ef4325f60d6e85477c01
with: with:
python-version: '3.11' type: 'pip'
cache: 'pip' paths: '~/.cache/pip'
key-files: 'requirements*.txt,pyproject.toml,setup.py,setup.cfg'
key-prefix: 'ansible-lint-fix'
- name: Install ansible-lint - name: Install ansible-lint
id: install-ansible-lint id: install-ansible-lint
if: steps.check-files.outputs.files_found == 'true' if: steps.check-files.outputs.files_found == 'true'
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3 uses: ivuorinen/actions/common-retry@e2222afff180ee77f330ef4325f60d6e85477c01
with: with:
timeout_minutes: 5
max_attempts: ${{ inputs.max-retries }}
command: 'pip install ansible-lint==6.22.1' command: 'pip install ansible-lint==6.22.1'
max-retries: ${{ inputs.max-retries }}
description: 'Installing Python dependencies (ansible-lint)'
- name: Run ansible-lint - name: Run ansible-lint
if: steps.check-files.outputs.files_found == 'true' if: steps.check-files.outputs.files_found == 'true'
id: lint id: lint
shell: sh shell: bash
run: | run: |
set -eu set -euo pipefail
# Run ansible-lint and capture exit code # Run ansible-lint and capture exit code
if ansible-lint --write --parseable-severity --format sarif > ansible-lint.sarif; then 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 with the original ansible-lint exit code
exit "$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@e2222afff180ee77f330ef4325f60d6e85477c01
with:
token: ${{ inputs.token }}
username: ${{ inputs.username }}
email: ${{ inputs.email }}
- name: Commit Fixes - name: Commit Fixes
if: steps.check-files.outputs.files_found == 'true' if: steps.check-files.outputs.files_found == 'true'
uses: stefanzweifel/git-auto-commit-action@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0 shell: bash
with: run: |
commit_message: 'style: apply ansible lint fixes' set -euo pipefail
commit_user_name: ${{ inputs.username }}
commit_user_email: ${{ inputs.email }} 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 - name: Upload SARIF Report
if: steps.check-files.outputs.files_found == 'true' if: steps.check-files.outputs.files_found == 'true'
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
with: with:
sarif_file: ansible-lint.sarif 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@e2222afff180ee77f330ef4325f60d6e85477c01
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@e2222afff180ee77f330ef4325f60d6e85477c01
with:
token: ${{ inputs.token }}
username: ${{ inputs.username }}
email: ${{ inputs.email }}
- name: Node Setup
id: node-setup
uses: ivuorinen/actions/node-setup@e2222afff180ee77f330ef4325f60d6e85477c01
- name: Cache Node Dependencies
id: cache
uses: ivuorinen/actions/common-cache@e2222afff180ee77f330ef4325f60d6e85477c01
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@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
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@e2222afff180ee77f330ef4325f60d6e85477c01
with:
token: ${{ inputs.token }}
username: ${{ inputs.username }}
email: ${{ inputs.email }}
- name: Node Setup
id: node-setup
uses: ivuorinen/actions/node-setup@e2222afff180ee77f330ef4325f60d6e85477c01
- name: Cache Node Dependencies
id: cache
uses: ivuorinen/actions/common-cache@e2222afff180ee77f330ef4325f60d6e85477c01
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@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: '22'
- 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@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
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@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4
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@28e16e81777b558cc906c8750092100bbb34c5e3 # v7.0.0
with:
commit_message: 'style: autofix Biome violations'
commit_user_name: ${{ inputs.username }}
commit_user_email: ${{ inputs.email }}
add_options: '-u'

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` | `""` | | `threads` | <p>Number of threads that can be used by CodeQL</p> | `false` | `""` |
| `output` | <p>Path to save SARIF results</p> | `false` | `../results` | | `output` | <p>Path to save SARIF results</p> | `false` | `../results` |
| `skip-queries` | <p>Build database but skip running queries</p> | `false` | `false` | | `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 ### Outputs
@@ -139,4 +140,10 @@ This action is a `composite` action.
# #
# Required: false # Required: false
# Default: false # Default: false
add-snippets:
# Add code snippets to SARIF output
#
# Required: false
# Default: false
``` ```

View File

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

View File

@@ -2,7 +2,7 @@
# Validation rules for codeql-analysis action # Validation rules for codeql-analysis action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0 # 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. # This file defines validation rules for the codeql-analysis GitHub Action.
# Rules are automatically applied by validate-inputs action when this # Rules are automatically applied by validate-inputs action when this
@@ -16,6 +16,7 @@ generator_version: 1.0.0
required_inputs: required_inputs:
- language - language
optional_inputs: optional_inputs:
- add-snippets
- build-mode - build-mode
- category - category
- checkout-ref - checkout-ref
@@ -32,6 +33,7 @@ optional_inputs:
- upload-results - upload-results
- working-directory - working-directory
conventions: conventions:
add-snippets: boolean
build-mode: codeql_build_mode build-mode: codeql_build_mode
category: category_format category: category_format
checkout-ref: branch_name checkout-ref: branch_name
@@ -60,8 +62,8 @@ overrides:
threads: numeric_range_1_128 threads: numeric_range_1_128
token: github_token token: github_token
statistics: statistics:
total_inputs: 16 total_inputs: 17
validated_inputs: 15 validated_inputs: 16
skipped_inputs: 0 skipped_inputs: 0
coverage_percentage: 94 coverage_percentage: 94
validation_coverage: 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

@@ -57,7 +57,7 @@ runs:
steps: steps:
- name: Validate Inputs - name: Validate Inputs
id: validate id: validate
shell: sh shell: bash
env: env:
WORKING_DIRECTORY: ${{ inputs.working-directory }} WORKING_DIRECTORY: ${{ inputs.working-directory }}
IMAGE_QUALITY: ${{ inputs.image-quality }} IMAGE_QUALITY: ${{ inputs.image-quality }}
@@ -67,7 +67,7 @@ runs:
USERNAME: ${{ inputs.username }} USERNAME: ${{ inputs.username }}
GITHUB_TOKEN: ${{ inputs.token }} GITHUB_TOKEN: ${{ inputs.token }}
run: | run: |
set -eu set -euo pipefail
# Validate working directory # Validate working directory
if [ ! -d "$WORKING_DIRECTORY" ]; then if [ ! -d "$WORKING_DIRECTORY" ]; then
@@ -76,77 +76,81 @@ runs:
fi fi
# Validate path security (prevent absolute paths and path traversal) # Validate path security (prevent absolute paths and path traversal)
case "$WORKING_DIRECTORY" in if [[ "$WORKING_DIRECTORY" == "/"* ]] || [[ "$WORKING_DIRECTORY" == "~"* ]] || [[ "$WORKING_DIRECTORY" =~ ^[A-Za-z]:[/\\] ]]; then
/*|~*|[A-Za-z]:*|*..*) echo "::error::Invalid working-directory: '$WORKING_DIRECTORY'. Absolute paths not allowed"
echo "::error::Invalid working-directory: '$WORKING_DIRECTORY'. Absolute paths and path traversal not allowed" exit 1
exit 1 fi
;;
esac
# Validate image quality (0-100) - must be numeric if [[ "$WORKING_DIRECTORY" == *".."* ]]; then
case "$IMAGE_QUALITY" in echo "::error::Invalid working-directory: '$WORKING_DIRECTORY'. Path traversal not allowed"
''|*[!0-9]*) exit 1
echo "::error::Invalid image-quality: '$IMAGE_QUALITY'. Must be a number between 0 and 100" fi
exit 1
;; # Validate image quality (0-100)
esac if ! [[ "$IMAGE_QUALITY" =~ ^[0-9]+$ ]]; then
echo "::error::Invalid image-quality: '$IMAGE_QUALITY'. Must be a number between 0 and 100"
exit 1
fi
if [ "$IMAGE_QUALITY" -lt 0 ] || [ "$IMAGE_QUALITY" -gt 100 ]; then if [ "$IMAGE_QUALITY" -lt 0 ] || [ "$IMAGE_QUALITY" -gt 100 ]; then
echo "::error::Invalid image-quality: '$IMAGE_QUALITY'. Must be between 0 and 100" echo "::error::Invalid image-quality: '$IMAGE_QUALITY'. Must be between 0 and 100"
exit 1 exit 1
fi fi
# Validate PNG quality (0-100) - must be numeric # Validate PNG quality (0-100)
case "$PNG_QUALITY" in if ! [[ "$PNG_QUALITY" =~ ^[0-9]+$ ]]; then
''|*[!0-9]*) echo "::error::Invalid png-quality: '$PNG_QUALITY'. Must be a number between 0 and 100"
echo "::error::Invalid png-quality: '$PNG_QUALITY'. Must be a number between 0 and 100" exit 1
exit 1 fi
;;
esac
if [ "$PNG_QUALITY" -lt 0 ] || [ "$PNG_QUALITY" -gt 100 ]; then if [ "$PNG_QUALITY" -lt 0 ] || [ "$PNG_QUALITY" -gt 100 ]; then
echo "::error::Invalid png-quality: '$PNG_QUALITY'. Must be between 0 and 100" echo "::error::Invalid png-quality: '$PNG_QUALITY'. Must be between 0 and 100"
exit 1 exit 1
fi fi
# Validate ignore paths format (prevent command injection and path traversal) # Validate ignore paths format (prevent command injection)
case "$IGNORE_PATHS" in if [[ "$IGNORE_PATHS" == *";"* ]] || [[ "$IGNORE_PATHS" == *"&&"* ]] || \
*\;*|*\&\&*|*\|*|*\`*|*\$\(*|*\$\{*|*\<*|*\>*|*..\*) [[ "$IGNORE_PATHS" == *"|"* ]] || [[ "$IGNORE_PATHS" == *'`'* ]] || \
echo "::error::Invalid ignore-paths: '$IGNORE_PATHS'. Command injection patterns and path traversal not allowed" [[ "$IGNORE_PATHS" == *'$('* ]] || [[ "$IGNORE_PATHS" == *'${'* ]] || \
exit 1 [[ "$IGNORE_PATHS" == *"<"* ]] || [[ "$IGNORE_PATHS" == *">"* ]]; then
;; echo "::error::Invalid ignore-paths: '$IGNORE_PATHS'. Command injection patterns not allowed"
esac exit 1
# 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)
case "$USERNAME" in
*\;*|*\&\&*|*\|*)
echo "::error::Invalid username: '$USERNAME'. Command injection patterns not allowed"
exit 1
;;
esac
# Validate token format if provided (basic GitHub token pattern)
if [ -n "$GITHUB_TOKEN" ]; then
case "$GITHUB_TOKEN" in
gh[efpousr]_?????????????????????????????????????)
;;
*)
echo "::warning::GitHub token format may be invalid. Expected format: gh*_36characters"
;;
esac
fi fi
# Validate ignore paths for path traversal
if [[ "$IGNORE_PATHS" == *".."* ]]; then
echo "::error::Invalid ignore-paths: '$IGNORE_PATHS'. Path traversal not allowed"
exit 1
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 token format if provided (basic GitHub token pattern)
if [[ -n "$GITHUB_TOKEN" ]]; then
if ! [[ "$GITHUB_TOKEN" =~ ^gh[efpousr]_[a-zA-Z0-9]{36}$ ]]; then
echo "::warning::GitHub token format may be invalid. Expected format: gh*_36characters"
fi
fi
- name: Set Git Config
id: set-git-config
uses: ivuorinen/actions/set-git-config@e2222afff180ee77f330ef4325f60d6e85477c01
with:
token: ${{ inputs.token }}
username: ${{ inputs.username }}
email: ${{ inputs.email }}
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
token: ${{ inputs.token }} token: ${{ inputs.token }}
@@ -163,10 +167,9 @@ runs:
- name: Create New Pull Request If Needed - name: Create New Pull Request If Needed
if: steps.calibre.outputs.markdown != '' if: steps.calibre.outputs.markdown != ''
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412 # v7.0.9 uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
with: with:
token: ${{ inputs.token }} title: Compressed Images Nightly
title: 'chore: compress images'
branch-suffix: timestamp branch-suffix: timestamp
commit-message: 'chore: compress images' commit-message: Compressed Images
body: ${{ steps.calibre.outputs.markdown }} body: ${{ steps.calibre.outputs.markdown }}

View File

@@ -32,7 +32,7 @@ outputs:
value: ${{ steps.test.outputs.status }} value: ${{ steps.test.outputs.status }}
dotnet_version: dotnet_version:
description: 'Version of .NET SDK used' description: 'Version of .NET SDK used'
value: ${{ steps.detect-dotnet-version.outputs.detected-version }} value: ${{ steps.detect-dotnet-version.outputs.dotnet-version }}
artifacts_path: artifacts_path:
description: 'Path to build artifacts' description: 'Path to build artifacts'
value: '**/bin/Release/**/*' value: '**/bin/Release/**/*'
@@ -44,130 +44,51 @@ runs:
using: composite using: composite
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
token: ${{ inputs.token || github.token }} token: ${{ inputs.token || github.token }}
- name: Detect .NET SDK Version - name: Detect .NET SDK Version
id: detect-dotnet-version id: detect-dotnet-version
shell: sh uses: ivuorinen/actions/dotnet-version-detect@e2222afff180ee77f330ef4325f60d6e85477c01
env: with:
DEFAULT_VERSION: "${{ inputs.dotnet-version || '7.0' }}" default-version: "${{ inputs.dotnet-version || '7.0' }}"
run: |
set -eu
# Function to validate version format
validate_version() {
version=$1
case "$version" in
[0-9]* | [0-9]*\.[0-9]* | [0-9]*\.[0-9]*\.[0-9]*)
return 0
;;
*)
return 1
;;
esac
}
# Function to clean version string
clean_version() {
printf '%s' "$1" | sed 's/^[vV]//' | tr -d ' \n\r'
}
detected_version=""
# Parse .tool-versions file
if [ -f .tool-versions ]; then
echo "Checking .tool-versions for dotnet..." >&2
version=$(awk '/^dotnet[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in .tool-versions: $version" >&2
detected_version="$version"
fi
fi
fi
# Parse Dockerfile
if [ -z "$detected_version" ] && [ -f Dockerfile ]; then
echo "Checking Dockerfile for dotnet..." >&2
version=$(grep -iF "FROM" Dockerfile | grep -F "dotnet:" | head -1 | \
sed -n -E "s/.*dotnet:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in Dockerfile: $version" >&2
detected_version="$version"
fi
fi
fi
# Parse devcontainer.json
if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then
echo "Checking devcontainer.json for dotnet..." >&2
if command -v jq >/dev/null 2>&1; then
version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*dotnet:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in devcontainer: $version" >&2
detected_version="$version"
fi
fi
else
echo "jq not found; skipping devcontainer.json parsing" >&2
fi
fi
# Parse global.json
if [ -z "$detected_version" ] && [ -f global.json ]; then
echo "Checking global.json..." >&2
if command -v jq >/dev/null 2>&1; then
version=$(jq -r '.sdk.version // empty' global.json 2>/dev/null || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in global.json: $version" >&2
detected_version="$version"
fi
fi
else
echo "jq not found; skipping global.json parsing" >&2
fi
fi
# Use default version if nothing detected
if [ -z "$detected_version" ]; then
detected_version="$DEFAULT_VERSION"
echo "Using default .NET version: $detected_version" >&2
fi
# Set output
printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT"
echo "Final detected .NET version: $detected_version" >&2
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
with: with:
dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }} dotnet-version: ${{ steps.detect-dotnet-version.outputs.dotnet-version }}
cache: true
cache-dependency-path: '**/packages.lock.json' - name: Cache NuGet packages
id: cache-nuget
uses: ivuorinen/actions/common-cache@e2222afff180ee77f330ef4325f60d6e85477c01
with:
type: 'nuget'
paths: '~/.nuget/packages'
key-files: '**/*.csproj,**/*.props,**/*.targets'
key-prefix: 'csharp-build'
- name: Restore Dependencies - name: Restore Dependencies
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3 if: steps.cache-nuget.outputs.cache-hit != 'true'
uses: ivuorinen/actions/common-retry@e2222afff180ee77f330ef4325f60d6e85477c01
with: with:
timeout_minutes: 10
max_attempts: ${{ inputs.max-retries }}
command: | command: |
echo "Restoring .NET dependencies..." echo "Restoring .NET dependencies..."
dotnet restore --verbosity normal dotnet restore --verbosity normal
max-retries: ${{ inputs.max-retries }}
description: 'Restoring .NET dependencies'
- name: Skip Restore (Cache Hit)
if: steps.cache-nuget.outputs.cache-hit == 'true'
shell: bash
run: |
echo "Cache hit - skipping dotnet restore"
- name: Build Solution - name: Build Solution
id: build id: build
shell: sh shell: bash
run: | run: |
set -eu set -euo pipefail
echo "Building .NET solution..." echo "Building .NET solution..."
if dotnet build --configuration Release --no-restore --verbosity normal; then if dotnet build --configuration Release --no-restore --verbosity normal; then
echo "status=success" >> "$GITHUB_OUTPUT" echo "status=success" >> "$GITHUB_OUTPUT"
@@ -180,9 +101,9 @@ runs:
- name: Run Tests - name: Run Tests
id: test id: test
shell: sh shell: bash
run: | run: |
set -eu set -euo pipefail
echo "Running .NET tests..." echo "Running .NET tests..."
if find . -name "*.csproj" | xargs grep -lE "(Microsoft\.NET\.Test\.Sdk|xunit|nunit)" | head -1 | grep -q .; then if find . -name "*.csproj" | xargs grep -lE "(Microsoft\.NET\.Test\.Sdk|xunit|nunit)" | head -1 | grep -q .; then
if dotnet test --configuration Release --no-build \ if dotnet test --configuration Release --no-build \
@@ -203,7 +124,7 @@ runs:
- name: Upload Test Results - name: Upload Test Results
if: always() if: always()
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: csharp-test-results name: csharp-test-results
path: | path: |

View File

@@ -36,15 +36,15 @@ runs:
steps: steps:
- name: Validate Inputs - name: Validate Inputs
id: validate id: validate
shell: sh shell: bash
env: env:
DOTNET_VERSION: ${{ inputs.dotnet-version }} DOTNET_VERSION: ${{ inputs.dotnet-version }}
run: | run: |
set -eu set -euo pipefail
# Validate .NET version format if provided # Validate .NET version format if provided
if [ -n "$DOTNET_VERSION" ]; then if [[ -n "$DOTNET_VERSION" ]]; then
if ! printf '%s' "$DOTNET_VERSION" | grep -qE '^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$'; then if ! [[ "$DOTNET_VERSION" =~ ^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$ ]]; then
echo "::error::Invalid dotnet-version format: '$DOTNET_VERSION'. Expected format: X.Y or X.Y.Z (e.g., 7.0, 8.0.100)" echo "::error::Invalid dotnet-version format: '$DOTNET_VERSION'. Expected format: X.Y or X.Y.Z (e.g., 7.0, 8.0.100)"
exit 1 exit 1
fi fi
@@ -60,128 +60,33 @@ runs:
echo "Input validation completed successfully" echo "Input validation completed successfully"
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
token: ${{ inputs.token || github.token }} token: ${{ inputs.token || github.token }}
- name: Detect .NET SDK Version - name: Detect .NET SDK Version
id: detect-dotnet-version id: detect-dotnet-version
shell: sh uses: ivuorinen/actions/dotnet-version-detect@e2222afff180ee77f330ef4325f60d6e85477c01
env: with:
DEFAULT_VERSION: "${{ inputs.dotnet-version || '7.0' }}" default-version: ${{ inputs.dotnet-version || '7.0' }}
run: |
set -eu
# Function to validate version format
validate_version() {
version=$1
case "$version" in
[0-9]* | [0-9]*\.[0-9]* | [0-9]*\.[0-9]*\.[0-9]*)
return 0
;;
*)
return 1
;;
esac
}
# Function to clean version string
clean_version() {
printf '%s' "$1" | sed 's/^[vV]//' | tr -d ' \n\r'
}
detected_version=""
# Parse .tool-versions file
if [ -f .tool-versions ]; then
echo "Checking .tool-versions for dotnet..." >&2
version=$(awk '/^dotnet[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in .tool-versions: $version" >&2
detected_version="$version"
fi
fi
fi
# Parse Dockerfile
if [ -z "$detected_version" ] && [ -f Dockerfile ]; then
echo "Checking Dockerfile for dotnet..." >&2
version=$(grep -iF "FROM" Dockerfile | grep -F "dotnet:" | head -1 | \
sed -n -E "s/.*dotnet:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in Dockerfile: $version" >&2
detected_version="$version"
fi
fi
fi
# Parse devcontainer.json
if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then
echo "Checking devcontainer.json for dotnet..." >&2
if command -v jq >/dev/null 2>&1; then
version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*dotnet:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in devcontainer: $version" >&2
detected_version="$version"
fi
fi
else
echo "jq not found; skipping devcontainer.json parsing" >&2
fi
fi
# Parse global.json
if [ -z "$detected_version" ] && [ -f global.json ]; then
echo "Checking global.json..." >&2
if command -v jq >/dev/null 2>&1; then
version=$(jq -r '.sdk.version // empty' global.json 2>/dev/null || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in global.json: $version" >&2
detected_version="$version"
fi
fi
else
echo "jq not found; skipping global.json parsing" >&2
fi
fi
# Use default version if nothing detected
if [ -z "$detected_version" ]; then
detected_version="$DEFAULT_VERSION"
echo "Using default .NET version: $detected_version" >&2
fi
# Set output
printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT"
echo "Final detected .NET version: $detected_version" >&2
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
with: with:
dotnet-version: ${{ steps.detect-dotnet-version.outputs.detected-version }} dotnet-version: ${{ steps.detect-dotnet-version.outputs.dotnet-version }}
cache: true
cache-dependency-path: '**/packages.lock.json'
- name: Install dotnet-format - name: Install dotnet-format
shell: sh shell: bash
run: | run: |
set -eu set -euo pipefail
dotnet tool install --global dotnet-format --version 7.0.1 dotnet tool install --global dotnet-format --version 7.0.1
- name: Run dotnet-format - name: Run dotnet-format
id: dotnet-format id: dotnet-format
shell: sh shell: bash
run: | run: |
set -eu set -euo pipefail
# Initialize counters # Initialize counters
errors_count=0 errors_count=0
@@ -206,6 +111,6 @@ runs:
fi fi
- name: Upload SARIF Report - name: Upload SARIF Report
uses: github/codeql-action/upload-sarif@e12f0178983d466f2f6028f5cc7a6d786fd97f4b # v4.31.4 uses: github/codeql-action/upload-sarif@16140ae1a102900babc80a33c44059580f687047 # v4.30.9
with: with:
sarif_file: dotnet-format.sarif sarif_file: dotnet-format.sarif

View File

@@ -8,12 +8,11 @@ Publishes a C# project to GitHub Packages.
### Inputs ### Inputs
| name | description | required | default | | name | description | required | default |
|------------------|--------------------------------------------------------------------|----------|-------------| |------------------|----------------------------------------------------|----------|-------------|
| `dotnet-version` | <p>Version of .NET SDK to use.</p> | `false` | `""` | | `dotnet-version` | <p>Version of .NET SDK to use.</p> | `false` | `""` |
| `namespace` | <p>GitHub namespace for the package.</p> | `true` | `ivuorinen` | | `namespace` | <p>GitHub namespace for the package.</p> | `true` | `ivuorinen` |
| `token` | <p>GitHub token with package write permissions</p> | `false` | `""` | | `token` | <p>GitHub token with package write permissions</p> | `false` | `""` |
| `max-retries` | <p>Maximum number of retry attempts for dependency restoration</p> | `false` | `3` |
### Outputs ### Outputs
@@ -49,10 +48,4 @@ This action is a `composite` action.
# #
# Required: false # Required: false
# Default: "" # Default: ""
max-retries:
# Maximum number of retry attempts for dependency restoration
#
# Required: false
# Default: 3
``` ```

View File

@@ -22,10 +22,6 @@ inputs:
token: token:
description: 'GitHub token with package write permissions' description: 'GitHub token with package write permissions'
required: false required: false
max-retries:
description: 'Maximum number of retry attempts for dependency restoration'
required: false
default: '3'
outputs: outputs:
publish_status: publish_status:
@@ -42,20 +38,20 @@ runs:
using: composite using: composite
steps: steps:
- name: Mask Secrets - name: Mask Secrets
shell: sh shell: bash
env: env:
API_KEY: ${{ inputs.token || github.token }} API_KEY: ${{ inputs.token || github.token }}
run: | run: |
echo "::add-mask::$API_KEY" echo "::add-mask::$API_KEY"
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with: with:
token: ${{ inputs.token || github.token }} token: ${{ inputs.token || github.token }}
- name: Validate Inputs - name: Validate Inputs
id: validate id: validate
uses: ivuorinen/actions/validate-inputs@8fb52522ab00fe73cf181ef299e56066f0b2c8d8 uses: ivuorinen/actions/validate-inputs@e2222afff180ee77f330ef4325f60d6e85477c01
with: with:
action-type: 'csharp-publish' action-type: 'csharp-publish'
token: ${{ inputs.token }} token: ${{ inputs.token }}
@@ -64,138 +60,56 @@ runs:
- name: Detect .NET SDK Version - name: Detect .NET SDK Version
id: detect-dotnet-version id: detect-dotnet-version
shell: sh uses: ivuorinen/actions/dotnet-version-detect@e2222afff180ee77f330ef4325f60d6e85477c01
env: with:
DEFAULT_VERSION: '7.0' default-version: '7.0'
run: |
set -eu
# Function to validate version format
validate_version() {
version=$1
case "$version" in
[0-9]* | [0-9]*\.[0-9]* | [0-9]*\.[0-9]*\.[0-9]*)
return 0
;;
*)
return 1
;;
esac
}
# Function to clean version string
clean_version() {
printf '%s' "$1" | sed 's/^[vV]//' | tr -d ' \n\r'
}
detected_version=""
# Parse .tool-versions file
if [ -f .tool-versions ]; then
echo "Checking .tool-versions for dotnet..." >&2
version=$(awk '/^dotnet[[:space:]]/ {gsub(/#.*/, ""); print $2; exit}' .tool-versions 2>/dev/null || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in .tool-versions: $version" >&2
detected_version="$version"
fi
fi
fi
# Parse Dockerfile
if [ -z "$detected_version" ] && [ -f Dockerfile ]; then
echo "Checking Dockerfile for dotnet..." >&2
version=$(grep -iF "FROM" Dockerfile | grep -F "dotnet:" | head -1 | \
sed -n -E "s/.*dotnet:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in Dockerfile: $version" >&2
detected_version="$version"
fi
fi
fi
# Parse devcontainer.json
if [ -z "$detected_version" ] && [ -f .devcontainer/devcontainer.json ]; then
echo "Checking devcontainer.json for dotnet..." >&2
if command -v jq >/dev/null 2>&1; then
version=$(jq -r '.image // empty' .devcontainer/devcontainer.json 2>/dev/null | sed -n -E "s/.*dotnet:([0-9]+(\.[0-9]+)*)(-[^:]*)?.*/\1/p" || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in devcontainer: $version" >&2
detected_version="$version"
fi
fi
else
echo "jq not found; skipping devcontainer.json parsing" >&2
fi
fi
# Parse global.json
if [ -z "$detected_version" ] && [ -f global.json ]; then
echo "Checking global.json..." >&2
if command -v jq >/dev/null 2>&1; then
version=$(jq -r '.sdk.version // empty' global.json 2>/dev/null || echo "")
if [ -n "$version" ]; then
version=$(clean_version "$version")
if validate_version "$version"; then
echo "Found .NET version in global.json: $version" >&2
detected_version="$version"
fi
fi
else
echo "jq not found; skipping global.json parsing" >&2
fi
fi
# Use default version if nothing detected
if [ -z "$detected_version" ]; then
detected_version="$DEFAULT_VERSION"
echo "Using default .NET version: $detected_version" >&2
fi
# Set output
printf 'detected-version=%s\n' "$detected_version" >> "$GITHUB_OUTPUT"
echo "Final detected .NET version: $detected_version" >&2
- name: Setup .NET SDK - name: Setup .NET SDK
uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0 uses: actions/setup-dotnet@d4c94342e560b34958eacfc5d055d21461ed1c5d # v5.0.0
with: with:
dotnet-version: ${{ inputs.dotnet-version || steps.detect-dotnet-version.outputs.detected-version }} dotnet-version: ${{ inputs.dotnet-version || steps.detect-dotnet-version.outputs.dotnet-version }}
cache: true
cache-dependency-path: '**/packages.lock.json' - name: Cache NuGet packages
id: cache-nuget
uses: ivuorinen/actions/common-cache@e2222afff180ee77f330ef4325f60d6e85477c01
with:
type: 'nuget'
paths: '~/.nuget/packages'
key-files: '**/*.csproj,**/*.props,**/*.targets'
key-prefix: 'csharp-publish'
- name: Restore Dependencies - name: Restore Dependencies
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3 shell: bash
with: env:
timeout_minutes: 10 CACHE_HIT: ${{ steps.cache-nuget.outputs.cache-hit }}
max_attempts: ${{ inputs.max-retries }} run: |
command: | set -euo pipefail
echo "Restoring .NET dependencies..."
dotnet restore --verbosity normal # Always run dotnet restore to ensure project.assets.json is present
if [[ "$CACHE_HIT" == 'true' ]]; then
echo "Cache hit - running fast dotnet restore"
fi
dotnet restore
- name: Build Solution - name: Build Solution
shell: sh shell: bash
run: | run: |
set -eu set -euo pipefail
dotnet build --configuration Release --no-restore dotnet build --configuration Release --no-restore
- name: Pack Solution - name: Pack Solution
shell: sh shell: bash
run: | run: |
set -eu set -euo pipefail
dotnet pack --configuration Release --no-build --no-restore --output ./artifacts dotnet pack --configuration Release --no-build --no-restore --output ./artifacts
- name: Extract Package Version - name: Extract Package Version
id: extract-version id: extract-version
shell: sh shell: bash
run: | run: |
set -eu set -euo pipefail
# Find the newest .nupkg file by modification time and extract version # Find the newest .nupkg file by modification time and extract version
PACKAGE_FILE=$(find ./artifacts -name "*.nupkg" -type f -printf '%T@ %p\n' | sort -rn | head -n 1 | cut -d' ' -f2-) PACKAGE_FILE=$(find ./artifacts -name "*.nupkg" -type f -printf '%T@ %p\n' | sort -rn | head -n 1 | cut -d' ' -f2-)
@@ -211,12 +125,12 @@ runs:
- name: Publish Package - name: Publish Package
id: publish-package id: publish-package
shell: sh shell: bash
env: env:
API_KEY: ${{ inputs.token || github.token }} API_KEY: ${{ inputs.token || github.token }}
NAMESPACE: ${{ inputs.namespace }} NAMESPACE: ${{ inputs.namespace }}
run: | run: |
set -eu set -euo pipefail
PACKAGE_URL="https://github.com/$NAMESPACE/packages/nuget" PACKAGE_URL="https://github.com/$NAMESPACE/packages/nuget"
printf '%s\n' "package_url=$PACKAGE_URL" >> "$GITHUB_OUTPUT" printf '%s\n' "package_url=$PACKAGE_URL" >> "$GITHUB_OUTPUT"
@@ -241,7 +155,7 @@ runs:
- name: Set publish status output - name: Set publish status output
if: always() if: always()
id: set-status id: set-status
shell: sh shell: bash
env: env:
PUBLISH_STATUS: ${{ steps.publish-package.outcome == 'success' && 'success' || 'failure' }} PUBLISH_STATUS: ${{ steps.publish-package.outcome == 'success' && 'success' || 'failure' }}
run: |- run: |-

View File

@@ -2,7 +2,7 @@
# Validation rules for csharp-publish action # Validation rules for csharp-publish action
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY # Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
# Schema version: 1.0 # Schema version: 1.0
# Coverage: 100% (4/4 inputs) # Coverage: 100% (3/3 inputs)
# #
# This file defines validation rules for the csharp-publish GitHub Action. # This file defines validation rules for the csharp-publish GitHub Action.
# Rules are automatically applied by validate-inputs action when this # Rules are automatically applied by validate-inputs action when this
@@ -17,17 +17,15 @@ required_inputs:
- namespace - namespace
optional_inputs: optional_inputs:
- dotnet-version - dotnet-version
- max-retries
- token - token
conventions: conventions:
dotnet-version: dotnet_version dotnet-version: dotnet_version
max-retries: numeric_range_1_10
namespace: namespace_with_lookahead namespace: namespace_with_lookahead
token: github_token token: github_token
overrides: {} overrides: {}
statistics: statistics:
total_inputs: 4 total_inputs: 3
validated_inputs: 4 validated_inputs: 3
skipped_inputs: 0 skipped_inputs: 0
coverage_percentage: 100 coverage_percentage: 100
validation_coverage: 100 validation_coverage: 100

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