mirror of
https://github.com/ivuorinen/actions.git
synced 2026-01-26 11:34:00 +00:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ab371bdebf | |||
|
|
842e6c1878 | ||
|
|
cea720416b | ||
|
|
2b1c797263 | ||
| 681e0f828a | |||
|
|
4e3e2a559e | ||
|
|
80f0e018cd | ||
|
|
d0687ee76e | ||
|
|
fd3c871d7d | ||
|
|
7de94a65a6 | ||
|
|
8112d86ab7 | ||
|
|
22ca79df3c | ||
|
|
953659172d | ||
| 5c5f1c3d54 | |||
|
|
8599e8913f | ||
|
|
a261fcd118 | ||
| a1c0435c22 | |||
| 2f1c73dd8b | |||
| fd49ff6968 | |||
|
|
82edd1dc12 | ||
| 63a18808a0 | |||
|
|
8527166fbb | ||
| fb5a978260 | |||
|
|
ca7fc1a5ff | ||
| 42a40cfaf1 | |||
| b06748cbef | |||
| cbbb0c8b8c | |||
|
|
1a8997715c | ||
|
|
f50ab425b8 | ||
|
|
41b1778849 | ||
|
|
bbb05559e6 | ||
|
|
7c18e12b06 | ||
|
|
88053f4197 | ||
|
|
ee9a4877e8 | ||
|
|
c32f2813f0 | ||
|
|
e416c272b5 | ||
| 74968d942f | |||
| e2222afff1 | |||
|
|
81f54fda92 | ||
| a09e59aa7c | |||
| 2d8ff47548 | |||
|
|
a3fb0bd8db | ||
|
|
42312cdbe4 | ||
|
|
222a2fa571 | ||
| 6ebc5a21d5 | |||
|
|
020a8fd26c | ||
| 7061aafd35 | |||
|
|
d3c2de1bd1 | ||
|
|
f48f914224 |
@@ -17,7 +17,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d # v7.1.0
|
||||
uses: astral-sh/setup-uv@5a7eac68fb9809dea845d802897dc5c723910fa3 # v7.1.3
|
||||
with:
|
||||
enable-cache: true
|
||||
|
||||
@@ -33,7 +33,7 @@ runs:
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
with:
|
||||
node-version: '22'
|
||||
node-version: '24'
|
||||
cache: npm
|
||||
|
||||
- name: Install Node dependencies
|
||||
|
||||
18
.github/workflows/action-security.yml
vendored
18
.github/workflows/action-security.yml
vendored
@@ -35,13 +35,13 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check Required Configurations
|
||||
id: check-configs
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
# Initialize all flags as false
|
||||
{
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
# Check Gitleaks configuration and license
|
||||
if [ -f ".gitleaks.toml" ] && [ -n "${{ secrets.GITLEAKS_LICENSE }}" ]; then
|
||||
echo "Gitleaks config and license found"
|
||||
echo "run_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "run_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::Gitleaks config or license missing - skipping Gitleaks scan"
|
||||
fi
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
|
||||
- name: Verify SARIF files
|
||||
id: verify-sarif
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
# Initialize outputs
|
||||
{
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
# Check Trivy results
|
||||
if [ -f "trivy-results.sarif" ]; then
|
||||
if jq -e . </dev/null 2>&1 <"trivy-results.sarif"; then
|
||||
echo "has_trivy=true" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "has_trivy=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::Trivy SARIF file exists but is not valid JSON"
|
||||
fi
|
||||
@@ -108,7 +108,7 @@ jobs:
|
||||
if [ "${{ steps.check-configs.outputs.run_gitleaks }}" = "true" ]; then
|
||||
if [ -f "gitleaks-report.sarif" ]; then
|
||||
if jq -e . </dev/null 2>&1 <"gitleaks-report.sarif"; then
|
||||
echo "has_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "has_gitleaks=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "::warning::Gitleaks SARIF file exists but is not valid JSON"
|
||||
fi
|
||||
@@ -117,21 +117,21 @@ jobs:
|
||||
|
||||
- name: Upload Trivy results
|
||||
if: steps.verify-sarif.outputs.has_trivy == 'true'
|
||||
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
sarif_file: 'trivy-results.sarif'
|
||||
category: 'trivy'
|
||||
|
||||
- name: Upload Gitleaks results
|
||||
if: steps.verify-sarif.outputs.has_gitleaks == 'true'
|
||||
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
sarif_file: 'gitleaks-report.sarif'
|
||||
category: 'gitleaks'
|
||||
|
||||
- name: Archive security reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: security-reports-${{ github.run_id }}
|
||||
path: |
|
||||
|
||||
4
.github/workflows/build-testing-image.yml
vendored
4
.github/workflows/build-testing-image.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
uses: docker/metadata-action@318604b99e75e41977312d83839a89be02ca4893 # v5.9.0
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/actions
|
||||
tags: |
|
||||
|
||||
2
.github/workflows/codeql-new.yml
vendored
2
.github/workflows/codeql-new.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
|
||||
- name: Run CodeQL Analysis
|
||||
uses: ./codeql-analysis
|
||||
|
||||
8
.github/workflows/codeql.yml
vendored
8
.github/workflows/codeql.yml
vendored
@@ -34,18 +34,18 @@ jobs:
|
||||
|
||||
steps: # Add languages used in your actions
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
queries: security-and-quality
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/autobuild@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
category: '/language:${{matrix.language}}'
|
||||
|
||||
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@@ -12,6 +12,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@40c09b7dc99638e5ddb0bfd91c1673effc064d8a # v4.8.1
|
||||
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||
|
||||
5
.github/workflows/issue-stats.yml
vendored
5
.github/workflows/issue-stats.yml
vendored
@@ -1,3 +1,4 @@
|
||||
---
|
||||
name: Monthly issue metrics
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@@ -16,7 +17,7 @@ jobs:
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: Get dates for last month
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
# Calculate the first day of the previous month
|
||||
first_day=$(date -d "last month" +%Y-%m-01)
|
||||
@@ -29,7 +30,7 @@ jobs:
|
||||
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run issue-metrics tool
|
||||
uses: github/issue-metrics@c640329f02bd24b12b91d51cd385f0b1c25cefb9 # v3.25.1
|
||||
uses: github/issue-metrics@637a24e71b78bc10881e61972b19ea9ff736e14a # v3.25.2
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SEARCH_QUERY: 'repo:ivuorinen/actions is:issue created:${{ env.last_month }} -reason:"not planned"'
|
||||
|
||||
2
.github/workflows/new-release.yml
vendored
2
.github/workflows/new-release.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
version: ${{ steps.daily-version.outputs.version }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
|
||||
- name: Create tag if necessary
|
||||
uses: fregante/daily-version-action@fb1a60b7c4daf1410cd755e360ebec3901e58588 # v2.1.3
|
||||
|
||||
20
.github/workflows/pr-lint.yml
vendored
20
.github/workflows/pr-lint.yml
vendored
@@ -47,6 +47,7 @@ concurrency:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: read # Required for private dependencies
|
||||
|
||||
jobs:
|
||||
megalinter:
|
||||
@@ -56,14 +57,17 @@ jobs:
|
||||
|
||||
permissions:
|
||||
actions: write
|
||||
checks: write # Create and update check runs
|
||||
contents: write
|
||||
issues: write
|
||||
packages: read # Access private packages
|
||||
pull-requests: write
|
||||
security-events: write
|
||||
statuses: write
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
with:
|
||||
token: ${{ secrets.FIXIMUS_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
fetch-depth: 0
|
||||
@@ -75,14 +79,14 @@ jobs:
|
||||
- name: Check MegaLinter Results
|
||||
id: check-results
|
||||
if: always()
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
echo "status=success" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "status=success" >> "$GITHUB_OUTPUT"
|
||||
|
||||
if [ -f "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log" ]; then
|
||||
if grep -q "ERROR\|CRITICAL" "${{ env.REPORT_OUTPUT_FOLDER }}/megalinter.log"; then
|
||||
echo "Linting errors found"
|
||||
echo "status=failure" >> "$GITHUB_OUTPUT"
|
||||
printf '%s\n' "status=failure" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
else
|
||||
echo "::warning::MegaLinter log file not found"
|
||||
@@ -90,7 +94,7 @@ jobs:
|
||||
|
||||
- name: Upload Reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: MegaLinter reports
|
||||
path: |
|
||||
@@ -100,14 +104,14 @@ jobs:
|
||||
|
||||
- name: Upload SARIF Report
|
||||
if: always() && hashFiles('megalinter-reports/sarif/*.sarif')
|
||||
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
sarif_file: megalinter-reports/sarif
|
||||
category: megalinter
|
||||
|
||||
- name: Prepare Git for Fixes
|
||||
if: steps.ml.outputs.has_updated_sources == 1
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
sudo chown -Rc $UID .git/
|
||||
git config --global user.name "fiximus"
|
||||
@@ -192,7 +196,7 @@ jobs:
|
||||
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |-
|
||||
# Remove temporary files but keep reports
|
||||
find . -type f -name "megalinter.*" ! -name "megalinter-reports" -delete || true
|
||||
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -16,7 +16,7 @@ jobs:
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
- uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
- uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
- uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2
|
||||
with:
|
||||
generate_release_notes: true
|
||||
|
||||
55
.github/workflows/security-suite.yml
vendored
55
.github/workflows/security-suite.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout PR
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
with:
|
||||
fetch-depth: 0
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
|
||||
- name: Fetch PR Base
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
# Fetch the base ref from base repository with authentication (works for private repos and forked PRs)
|
||||
# Using ref instead of SHA because git fetch requires ref names, not raw commit IDs
|
||||
# Use authenticated URL to avoid 403/404 on private repositories
|
||||
@@ -97,6 +97,9 @@ jobs:
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Unique marker to identify our bot comment
|
||||
const SECURITY_COMMENT_MARKER = '<!-- security-analysis-bot-comment -->';
|
||||
|
||||
const findings = {
|
||||
permissions: [],
|
||||
actions: [],
|
||||
@@ -230,11 +233,40 @@ jobs:
|
||||
if (findings.permissions.length > 0) {
|
||||
const permSection = ['## 🔐 GitHub Actions Permissions Changes'];
|
||||
findings.permissions.forEach(change => {
|
||||
permSection.push(`**${change.file}**:`);
|
||||
permSection.push('```diff');
|
||||
permSection.push(`- ${change.old}`);
|
||||
permSection.push(`+ ${change.new}`);
|
||||
permSection.push('```');
|
||||
permSection.push(`\n**${change.file}**:`);
|
||||
|
||||
// Parse permissions into lines
|
||||
const oldLines = (change.old === 'None' ? [] : change.old.split('\n').map(l => l.trim()).filter(Boolean));
|
||||
const newLines = (change.new === 'None' ? [] : change.new.split('\n').map(l => l.trim()).filter(Boolean));
|
||||
|
||||
// Create sets for comparison
|
||||
const oldSet = new Set(oldLines);
|
||||
const newSet = new Set(newLines);
|
||||
|
||||
// Find added, removed, and unchanged
|
||||
const removed = oldLines.filter(l => !newSet.has(l));
|
||||
const added = newLines.filter(l => !oldSet.has(l));
|
||||
const unchanged = oldLines.filter(l => newSet.has(l));
|
||||
|
||||
// Only show diff if there are actual changes
|
||||
if (removed.length > 0 || added.length > 0) {
|
||||
permSection.push('```diff');
|
||||
|
||||
// Show removed permissions
|
||||
removed.forEach(line => permSection.push(`- ${line}`));
|
||||
|
||||
// Show added permissions
|
||||
added.forEach(line => permSection.push(`+ ${line}`));
|
||||
|
||||
permSection.push('```');
|
||||
|
||||
// Summary for context
|
||||
if (unchanged.length > 0 && unchanged.length <= 3) {
|
||||
permSection.push(`<details><summary>Unchanged (${unchanged.length})</summary>\n\n${unchanged.map(l => `- ${l}`).join('\n')}\n</details>`);
|
||||
} else if (unchanged.length > 3) {
|
||||
permSection.push(`<sub>*${unchanged.length} permissions unchanged*</sub>`);
|
||||
}
|
||||
}
|
||||
});
|
||||
sections.push(permSection.join('\n'));
|
||||
}
|
||||
@@ -314,15 +346,15 @@ jobs:
|
||||
// Export critical count as output
|
||||
core.setOutput('critical_issues', criticalCount.toString());
|
||||
|
||||
// Generate final comment
|
||||
let comment = '## ✅ Security Analysis\n\n';
|
||||
// Generate final comment with unique marker
|
||||
let comment = `${SECURITY_COMMENT_MARKER}\n## ✅ Security Analysis\n\n`;
|
||||
if (sections.length === 0) {
|
||||
comment += 'No security issues detected in this PR.';
|
||||
} else {
|
||||
comment += sections.join('\n\n');
|
||||
}
|
||||
|
||||
// Find existing security comment
|
||||
// Find existing security comment using unique marker
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
@@ -330,8 +362,7 @@ jobs:
|
||||
});
|
||||
|
||||
const existingComment = comments.find(comment =>
|
||||
comment.body.includes('Security Analysis') ||
|
||||
comment.body.includes('🔐 GitHub Actions Permissions')
|
||||
comment.body && comment.body.includes(SECURITY_COMMENT_MARKER)
|
||||
);
|
||||
|
||||
if (existingComment) {
|
||||
|
||||
2
.github/workflows/sync-labels.yml
vendored
2
.github/workflows/sync-labels.yml
vendored
@@ -35,6 +35,6 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: ⤵️ Checkout Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
- name: ⤵️ Sync Latest Labels Definitions
|
||||
uses: ./sync-labels
|
||||
|
||||
58
.github/workflows/test-actions.yml
vendored
58
.github/workflows/test-actions.yml
vendored
@@ -49,16 +49,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
|
||||
- name: Setup test environment
|
||||
uses: ./.github/actions/setup-test-environment
|
||||
|
||||
- name: Run unit tests
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
if [[ "${{ github.event.inputs.test-type }}" == "unit" || "${{ github.event.inputs.test-type }}" == "all" || -z "${{ github.event.inputs.test-type }}" ]]; then
|
||||
if [[ -n "${{ github.event.inputs.action-filter }}" ]]; then
|
||||
if [ "${{ github.event.inputs.test-type }}" = "unit" ] || [ "${{ github.event.inputs.test-type }}" = "all" ] || [ -z "${{ github.event.inputs.test-type }}" ]; then
|
||||
if [ -n "${{ github.event.inputs.action-filter }}" ]; then
|
||||
make test-action ACTION="${{ github.event.inputs.action-filter }}"
|
||||
else
|
||||
make test-unit
|
||||
@@ -68,19 +68,19 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Generate SARIF report
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: ./_tests/run-tests.sh --type unit --format sarif
|
||||
if: always()
|
||||
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
if: always() && hashFiles('_tests/reports/test-results.sarif') != ''
|
||||
with:
|
||||
sarif_file: _tests/reports/test-results.sarif
|
||||
category: github-actions-tests
|
||||
|
||||
- name: Upload unit test results
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
if: always()
|
||||
with:
|
||||
name: unit-test-results
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
|
||||
- name: Setup test environment
|
||||
uses: ./.github/actions/setup-test-environment
|
||||
@@ -107,10 +107,10 @@ jobs:
|
||||
install-act: 'true'
|
||||
|
||||
- name: Run integration tests
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
if [[ "${{ github.event.inputs.test-type }}" == "integration" || "${{ github.event.inputs.test-type }}" == "all" || -z "${{ github.event.inputs.test-type }}" ]]; then
|
||||
if [[ -n "${{ github.event.inputs.action-filter }}" ]]; then
|
||||
if [ "${{ github.event.inputs.test-type }}" = "integration" ] || [ "${{ github.event.inputs.test-type }}" = "all" ] || [ -z "${{ github.event.inputs.test-type }}" ]; then
|
||||
if [ -n "${{ github.event.inputs.action-filter }}" ]; then
|
||||
./_tests/run-tests.sh --type integration --action "${{ github.event.inputs.action-filter }}"
|
||||
else
|
||||
make test-integration
|
||||
@@ -122,18 +122,18 @@ jobs:
|
||||
- name: Check for integration test reports
|
||||
id: check-integration-reports
|
||||
if: always()
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
if [ -d "_tests/reports/integration" ] && [ -n "$(find _tests/reports/integration -type f 2>/dev/null)" ]; then
|
||||
echo "reports-found=true" >> $GITHUB_OUTPUT
|
||||
printf '%s\n' "reports-found=true" >> $GITHUB_OUTPUT
|
||||
echo "Integration test reports found"
|
||||
else
|
||||
echo "reports-found=false" >> $GITHUB_OUTPUT
|
||||
printf '%s\n' "reports-found=false" >> $GITHUB_OUTPUT
|
||||
echo "No integration test reports found"
|
||||
fi
|
||||
|
||||
- name: Upload integration test results
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
if: always() && steps.check-integration-reports.outputs.reports-found == 'true'
|
||||
with:
|
||||
name: integration-test-results
|
||||
@@ -156,7 +156,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
|
||||
- name: Setup test environment
|
||||
uses: ./.github/actions/setup-test-environment
|
||||
@@ -167,7 +167,7 @@ jobs:
|
||||
run: make test-coverage
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
with:
|
||||
name: coverage-report
|
||||
path: _tests/coverage/
|
||||
@@ -176,9 +176,9 @@ jobs:
|
||||
|
||||
- name: Comment coverage summary
|
||||
if: github.event_name == 'pull_request'
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
if [[ -f _tests/coverage/summary.json ]]; then
|
||||
if [ -f _tests/coverage/summary.json ]; then
|
||||
coverage=$(jq -r '.coverage_percent' _tests/coverage/summary.json)
|
||||
tested_actions=$(jq -r '.tested_actions' _tests/coverage/summary.json)
|
||||
total_actions=$(jq -r '.total_actions' _tests/coverage/summary.json)
|
||||
@@ -224,7 +224,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
|
||||
- name: Setup test environment
|
||||
uses: ./.github/actions/setup-test-environment
|
||||
@@ -235,12 +235,12 @@ jobs:
|
||||
uses: trufflesecurity/trufflehog@0f58ae7c5036094a1e3e750d18772af92821b503
|
||||
with:
|
||||
path: ./
|
||||
base: ${{ github.event.repository.default_branch }}
|
||||
head: HEAD
|
||||
base: ${{ github.event_name == 'pull_request' && github.event.repository.default_branch || '' }}
|
||||
head: ${{ github.event_name == 'pull_request' && 'HEAD' || '' }}
|
||||
extra_args: --debug --only-verified
|
||||
|
||||
- name: Scan shell scripts
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
# Scan all shell scripts in _tests/
|
||||
find _tests/ -name "*.sh" -exec shellcheck -x {} \; || {
|
||||
@@ -263,14 +263,14 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download test results
|
||||
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
|
||||
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||
with:
|
||||
pattern: '*-test-results'
|
||||
merge-multiple: true
|
||||
path: test-results/
|
||||
|
||||
- name: Generate test summary
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
{
|
||||
echo "## 🧪 Test Results Summary"
|
||||
@@ -278,20 +278,20 @@ jobs:
|
||||
|
||||
# Unit tests
|
||||
unit_count=$(find test-results -type f -path "*/unit/*.txt" | wc -l || true)
|
||||
if [[ "${unit_count:-0}" -gt 0 ]]; then
|
||||
if [ "${unit_count:-0}" -gt 0 ]; then
|
||||
echo "- **Unit Tests**: $unit_count action(s) tested"
|
||||
fi
|
||||
|
||||
# Integration tests
|
||||
integration_count=$(find test-results -type f -path "*/integration/*.txt" | wc -l || true)
|
||||
if [[ "${integration_count:-0}" -gt 0 ]]; then
|
||||
if [ "${integration_count:-0}" -gt 0 ]; then
|
||||
echo "- **Integration Tests**: $integration_count action(s) tested"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
unit_success="${{ needs.unit-tests.result == 'success' }}"
|
||||
integration_ok="${{ needs.integration-tests.result == 'success' || needs.integration-tests.result == 'skipped' }}"
|
||||
if [[ "$unit_success" == "true" && "$integration_ok" == "true" ]]; then
|
||||
if [ "$unit_success" = "true" ] && [ "$integration_ok" = "true" ]; then
|
||||
status="✅ All tests passed"
|
||||
else
|
||||
status="❌ Some tests failed"
|
||||
@@ -307,7 +307,7 @@ jobs:
|
||||
|
||||
- name: Fail if tests failed
|
||||
if: needs.unit-tests.result == 'failure' || needs.integration-tests.result == 'failure'
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |-
|
||||
echo "❌ One or more test jobs failed"
|
||||
exit 1
|
||||
|
||||
127
.github/workflows/version-maintenance.yml
vendored
Normal file
127
.github/workflows/version-maintenance.yml
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
---
|
||||
name: Version Maintenance
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Run weekly on Monday at 9 AM UTC
|
||||
- cron: '0 9 * * 1'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
major-version:
|
||||
description: 'Major version to check (e.g., v2025)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
check-and-update:
|
||||
name: Check Version References
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Determine Major Version
|
||||
id: version
|
||||
shell: sh
|
||||
run: |
|
||||
if [ -n "${{ inputs.major-version }}" ]; then
|
||||
printf '%s\n' "major=${{ inputs.major-version }}" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
current_year=$(date +%Y)
|
||||
printf '%s\n' "major=v$current_year" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Run Action Versioning
|
||||
id: action-versioning
|
||||
uses: ./action-versioning
|
||||
with:
|
||||
major-version: ${{ steps.version.outputs.major }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create Pull Request
|
||||
if: steps.action-versioning.outputs.updated == 'true'
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: 'chore: update action references to ${{ steps.version.outputs.major }}'
|
||||
title: 'chore: Update action references to ${{ steps.version.outputs.major }}'
|
||||
body: |
|
||||
## Version Maintenance
|
||||
|
||||
This PR updates all internal action references to match the latest ${{ steps.version.outputs.major }} tag.
|
||||
|
||||
**Updated SHA**: `${{ steps.action-versioning.outputs.commit-sha }}`
|
||||
|
||||
### Changes
|
||||
- Updated all `*/action.yml` files to reference the current ${{ steps.version.outputs.major }} SHA
|
||||
|
||||
### Verification
|
||||
```bash
|
||||
make check-version-refs
|
||||
```
|
||||
|
||||
🤖 Auto-generated by version-maintenance workflow
|
||||
branch: automated/version-update-${{ steps.version.outputs.major }}
|
||||
delete-branch: true
|
||||
labels: |
|
||||
automated
|
||||
dependencies
|
||||
|
||||
- name: Check for Annual Bump
|
||||
if: steps.action-versioning.outputs.needs-annual-bump == 'true'
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const currentYear = new Date().getFullYear();
|
||||
const majorVersion = '${{ steps.version.outputs.major }}';
|
||||
|
||||
await github.rest.issues.create({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
title: `🔄 Annual Version Bump Needed: ${majorVersion} → v${currentYear}`,
|
||||
body: `## Annual Version Bump Required
|
||||
|
||||
It's time to bump the major version from ${majorVersion} to v${currentYear}.
|
||||
|
||||
### Steps
|
||||
|
||||
1. **Create the new major version tag:**
|
||||
\`\`\`bash
|
||||
git tag -a v${currentYear} -m "Major version v${currentYear}"
|
||||
git push origin v${currentYear}
|
||||
\`\`\`
|
||||
|
||||
2. **Bump all action references:**
|
||||
\`\`\`bash
|
||||
make bump-major-version OLD=${majorVersion} NEW=v${currentYear}
|
||||
\`\`\`
|
||||
|
||||
3. **Update documentation:**
|
||||
\`\`\`bash
|
||||
make docs
|
||||
\`\`\`
|
||||
|
||||
4. **Commit and push:**
|
||||
\`\`\`bash
|
||||
git push origin main
|
||||
\`\`\`
|
||||
|
||||
### Verification
|
||||
|
||||
\`\`\`bash
|
||||
make check-version-refs
|
||||
\`\`\`
|
||||
|
||||
🤖 Auto-generated by version-maintenance workflow
|
||||
`,
|
||||
labels: ['maintenance', 'high-priority']
|
||||
});
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,6 +13,7 @@
|
||||
.cache
|
||||
.cache/
|
||||
.coverage
|
||||
.worktrees/
|
||||
.coverage.*
|
||||
.docusaurus
|
||||
.dynamodb/
|
||||
@@ -83,3 +84,4 @@ tests/reports/**/*.json
|
||||
!uv.lock
|
||||
code-scanning-report-*
|
||||
*.sarif
|
||||
TODO.md
|
||||
|
||||
2
.markdownlintignore
Normal file
2
.markdownlintignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
.worktrees/
|
||||
@@ -32,4 +32,4 @@ JAVASCRIPT_ES_CONFIG_FILE: .eslintrc.json
|
||||
TYPESCRIPT_ES_CONFIG_FILE: .eslintrc.json
|
||||
|
||||
FILTER_REGEX_EXCLUDE: >
|
||||
(node_modules|\.automation/test|docs/json-schemas)
|
||||
(node_modules|\.automation/test|docs/json-schemas|\.worktrees)
|
||||
|
||||
@@ -14,7 +14,7 @@ repos:
|
||||
types: [markdown, python, yaml]
|
||||
files: ^(docs/.*|README\.md|CONTRIBUTING\.md|CHANGELOG\.md|.*\.py|.*\.ya?ml)$
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
rev: 0.9.2
|
||||
rev: 0.9.8
|
||||
hooks:
|
||||
- id: uv-lock
|
||||
- id: uv-sync
|
||||
@@ -44,7 +44,7 @@ repos:
|
||||
args: [--autofix, --no-sort-keys]
|
||||
|
||||
- repo: https://github.com/DavidAnson/markdownlint-cli2
|
||||
rev: v0.18.1
|
||||
rev: v0.19.0
|
||||
hooks:
|
||||
- id: markdownlint-cli2
|
||||
args: [--fix]
|
||||
@@ -55,7 +55,7 @@ repos:
|
||||
- id: yamllint
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.14.0
|
||||
rev: v0.14.5
|
||||
hooks:
|
||||
# Run the linter with auto-fix
|
||||
- id: ruff-check
|
||||
@@ -74,7 +74,7 @@ repos:
|
||||
rev: v0.11.0.1
|
||||
hooks:
|
||||
- id: shellcheck
|
||||
args: ['--severity=warning', '-x']
|
||||
args: ['-x']
|
||||
exclude: '^_tests/.*\.sh$'
|
||||
|
||||
- repo: https://github.com/rhysd/actionlint
|
||||
@@ -84,18 +84,18 @@ repos:
|
||||
args: ['-shellcheck=']
|
||||
|
||||
- repo: https://github.com/renovatebot/pre-commit-hooks
|
||||
rev: 41.148.2
|
||||
rev: 42.6.2
|
||||
hooks:
|
||||
- id: renovate-config-validator
|
||||
|
||||
- repo: https://github.com/bridgecrewio/checkov.git
|
||||
rev: '3.2.483'
|
||||
rev: '3.2.489'
|
||||
hooks:
|
||||
- id: checkov
|
||||
args:
|
||||
- '--quiet'
|
||||
|
||||
- repo: https://github.com/gitleaks/gitleaks
|
||||
rev: v8.28.0
|
||||
rev: v8.29.0
|
||||
hooks:
|
||||
- id: gitleaks
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
.github/renovate.json
|
||||
.venv
|
||||
.worktrees/
|
||||
|
||||
@@ -1 +1 @@
|
||||
3.13.3
|
||||
3.14.0
|
||||
|
||||
@@ -22,17 +22,19 @@
|
||||
- Unquoted variables cause word splitting and globbing
|
||||
- Example: `"$variable"` not `$variable`, `basename -- "$path"` not `basename $path`
|
||||
|
||||
6. **ALWAYS** use local paths (`./action-name`) for intra-repo actions
|
||||
- Avoids external dependencies and version drift
|
||||
- Pattern: `uses: ./common-cache` not `uses: ivuorinen/actions/common-cache@main`
|
||||
6. **ALWAYS** use SHA-pinned references for internal actions in action.yml
|
||||
- Security: immutable, auditable, portable when used externally
|
||||
- Pattern: `uses: ivuorinen/actions/common-cache@7061aafd35a2f21b57653e34f2b634b2a19334a9`
|
||||
- Test workflows use local: `uses: ./common-cache` (within repo only)
|
||||
|
||||
7. **ALWAYS** test regex patterns against edge cases
|
||||
- Include prerelease tags (`1.0.0-rc.1`), build metadata (`1.0.0+build.123`)
|
||||
- Version validation should support full semver/calver formats
|
||||
|
||||
8. **ALWAYS** use `set -euo pipefail` at script start
|
||||
- `-e`: Exit on error, `-u`: Exit on undefined variable, `-o pipefail`: Exit on pipe failures
|
||||
- Critical for fail-fast behavior in composite actions
|
||||
8. **ALWAYS** use POSIX shell (`set -eu`) for all scripts
|
||||
- Maximum portability: works on Alpine, busybox, all shells
|
||||
- Use `#!/bin/sh` not `#!/usr/bin/env bash`
|
||||
- Use `set -eu` not `set -euo pipefail` (pipefail not POSIX)
|
||||
|
||||
9. **Avoid** nesting `${{ }}` expressions inside quoted strings in specific contexts
|
||||
- In `hashFiles()`: `"${{ inputs.value }}"` breaks cache key generation - use unquoted or extract to variable
|
||||
@@ -43,6 +45,32 @@
|
||||
- macOS/Windows runners may lack Linux tools (jq, bc, specific GNU utils)
|
||||
- Always provide fallbacks or explicit installation steps
|
||||
|
||||
11. **NEVER** use `set-git-config` action - use direct git config or action parameters instead
|
||||
- Git-related actions (`peter-evans/create-pull-request`, `stefanzweifel/git-auto-commit-action`) handle their own auth
|
||||
- For direct git commands, configure git manually when needed: `git config user.name/user.email`
|
||||
- Pattern for actions with git-auto-commit:
|
||||
|
||||
```yaml
|
||||
- uses: stefanzweifel/git-auto-commit-action@SHA
|
||||
with:
|
||||
commit_user_name: ${{ inputs.username }}
|
||||
commit_user_email: ${{ inputs.email }}
|
||||
```
|
||||
|
||||
- Pattern for actions with direct git commands:
|
||||
|
||||
```yaml
|
||||
- shell: bash
|
||||
run: |
|
||||
git config user.name "${{ inputs.username }}"
|
||||
git config user.email "${{ inputs.email }}"
|
||||
git add .
|
||||
git commit -m "message"
|
||||
git push
|
||||
```
|
||||
|
||||
- Rationale: Avoids complexity, matches proven workflow pattern, no credential conflicts
|
||||
|
||||
## EditorConfig Rules (.editorconfig)
|
||||
|
||||
**CRITICAL**: EditorConfig violations are blocking errors and must be fixed always.
|
||||
@@ -92,42 +120,71 @@ Comprehensive linting with 30+ rule categories including:
|
||||
|
||||
**Example**: `# ruff: noqa: T201, S603` for action step scripts only
|
||||
|
||||
## Shell Script Standards
|
||||
## Shell Script Standards (POSIX)
|
||||
|
||||
### Required Hardening Checklist
|
||||
**ALL scripts use POSIX shell** (`#!/bin/sh`) for maximum portability.
|
||||
|
||||
- ✅ **Shebang**: `#!/usr/bin/env bash` (POSIX-compliant)
|
||||
- ✅ **Error Handling**: `set -euo pipefail` at script start
|
||||
- ✅ **Safe IFS**: `IFS=$' \t\n'` (space, tab, newline only)
|
||||
- ✅ **Exit Trap**: `trap cleanup EXIT` for cleanup operations
|
||||
- ✅ **Error Trap**: `trap 'echo "Error at line $LINENO" >&2' ERR` for debugging
|
||||
### Required POSIX Compliance Checklist
|
||||
|
||||
- ✅ **Shebang**: `#!/bin/sh` (POSIX-compliant, not bash)
|
||||
- ✅ **Error Handling**: `set -eu` at script start (no pipefail - not POSIX)
|
||||
- ✅ **Defensive Expansion**: Use `${var:-default}` or `${var:?message}` patterns
|
||||
- ✅ **Quote Everything**: Always quote expansions: `"$var"`, `basename -- "$path"`
|
||||
- ✅ **Tool Availability**: `command -v tool >/dev/null 2>&1 || { echo "Missing tool"; exit 1; }`
|
||||
- ✅ **Portable Output**: Use `printf` instead of `echo -e`
|
||||
- ✅ **Portable Sourcing**: Use `. file` instead of `source file`
|
||||
- ✅ **POSIX Tests**: Use `[ ]` instead of `[[ ]]`
|
||||
- ✅ **Parsing**: Use `cut`, `grep`, pipes instead of here-strings `<<<`
|
||||
- ✅ **No Associative Arrays**: Use temp files or line-based processing
|
||||
|
||||
### Key POSIX Differences from Bash
|
||||
|
||||
| Bash Feature | POSIX Replacement |
|
||||
| --------------------- | --------------------------------- |
|
||||
| `#!/usr/bin/env bash` | `#!/bin/sh` |
|
||||
| `set -euo pipefail` | `set -eu` |
|
||||
| `[[ condition ]]` | `[ condition ]` |
|
||||
| `[[ $var =~ regex ]]` | `echo "$var" \| grep -qE 'regex'` |
|
||||
| `<<<` here-strings | `echo \| cut` or pipes |
|
||||
| `source file` | `. file` |
|
||||
| `$BASH_SOURCE` | `$0` |
|
||||
| `((var++))` | `var=$((var + 1))` |
|
||||
| `((var < 10))` | `[ "$var" -lt 10 ]` |
|
||||
| `echo -e` | `printf '%b'` |
|
||||
| `declare -A map` | temp files + sort/uniq |
|
||||
| Process substitution | pipes or temp files |
|
||||
|
||||
### Examples
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
IFS=$' \t\n'
|
||||
|
||||
# Cleanup trap
|
||||
cleanup() { rm -f /tmp/tempfile; }
|
||||
trap cleanup EXIT
|
||||
|
||||
# Error trap with line number
|
||||
trap 'echo "Error at line $LINENO" >&2' ERR
|
||||
```sh
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Defensive parameter expansion
|
||||
config_file="${CONFIG_FILE:-config.yml}" # Use default if unset
|
||||
required_param="${REQUIRED_PARAM:?Missing value}" # Error if unset
|
||||
required_param="${REQUIRED_PARAM:?Missing value}" # Error if unset
|
||||
|
||||
# Always quote expansions
|
||||
echo "Processing: $config_file"
|
||||
printf 'Processing: %s\n' "$config_file"
|
||||
result=$(basename -- "$file_path")
|
||||
|
||||
# POSIX test conditions
|
||||
if [ -f "$config_file" ]; then
|
||||
printf 'Found config\n'
|
||||
fi
|
||||
|
||||
# Portable output
|
||||
printf '%b' "Color: ${GREEN}text${NC}\n"
|
||||
```
|
||||
|
||||
### Why POSIX Shell
|
||||
|
||||
- **Portability**: Works on Alpine Linux, busybox, minimal containers, all POSIX shells
|
||||
- **Performance**: POSIX shells are lighter and faster than bash
|
||||
- **CI-Friendly**: Minimal dependencies, works everywhere
|
||||
- **Standards**: Follows POSIX best practices
|
||||
- **Compatibility**: Works with sh, dash, ash, bash, zsh
|
||||
|
||||
### Additional Requirements
|
||||
|
||||
- **Security**: All external actions SHA-pinned
|
||||
@@ -189,48 +246,49 @@ if: github.event_name == 'push'
|
||||
- Don't quote in `with:`, `env:`, `if:` - GitHub evaluates these
|
||||
- Never nest expressions: `"${{ inputs.value }}"` inside hashFiles breaks caching
|
||||
|
||||
### **Local Action References**
|
||||
### Internal Action References (SHA-Pinned)
|
||||
|
||||
**CRITICAL**: When referencing actions within the same repository:
|
||||
**CRITICAL**: Action files (`*/action.yml`) use SHA-pinned references for security:
|
||||
|
||||
- ✅ **CORRECT**: `uses: ./action-name` (relative to workspace root)
|
||||
- ❌ **INCORRECT**: `uses: ../action-name` (relative paths that assume directory structure)
|
||||
- ❌ **INCORRECT**: `uses: owner/repo/action-name@main` (floating branch reference)
|
||||
- ✅ **CORRECT**: `uses: ivuorinen/actions/action-name@7061aafd35a2f21b57653e34f2b634b2a19334a9`
|
||||
- ❌ **INCORRECT**: `uses: ./action-name` (security risk, not portable when used externally)
|
||||
- ❌ **INCORRECT**: `uses: ivuorinen/actions/action-name@main` (floating reference)
|
||||
|
||||
**Rationale**:
|
||||
|
||||
- Uses GitHub workspace root (`$GITHUB_WORKSPACE`) as reference point
|
||||
- Clear and unambiguous regardless of where action is called from
|
||||
- Follows GitHub's recommended pattern for same-repository references
|
||||
- Avoids issues if action checks out repository to different location
|
||||
- Eliminates external dependencies and supply chain risks
|
||||
- **Security**: Immutable, auditable references
|
||||
- **Reproducibility**: Exact version control
|
||||
- **Portability**: Works when actions used externally (e.g., `ivuorinen/f2b` using `ivuorinen/actions/pr-lint`)
|
||||
- **Prevention**: No accidental version drift
|
||||
|
||||
**Examples**:
|
||||
**Test Workflows Exception**:
|
||||
|
||||
Test workflows in `_tests/` use local references since they run within the repo:
|
||||
|
||||
```yaml
|
||||
# ✅ Correct - relative to workspace root
|
||||
- uses: ./validate-inputs
|
||||
- uses: ./common-cache
|
||||
- uses: ./node-setup
|
||||
|
||||
# ❌ Incorrect - relative directory navigation
|
||||
- uses: ../validate-inputs
|
||||
- uses: ../common-cache
|
||||
- uses: ../node-setup
|
||||
|
||||
# ❌ Incorrect - external reference to same repo
|
||||
- uses: ivuorinen/actions/validate-inputs@main
|
||||
- uses: ivuorinen/actions/common-cache@v1
|
||||
# ✅ Test workflows only
|
||||
uses: ./validate-inputs
|
||||
```
|
||||
|
||||
### **Step Output References**
|
||||
### External Action References (SHA-Pinned)
|
||||
|
||||
```yaml
|
||||
# ✅ Correct - SHA-pinned
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
|
||||
# ❌ Incorrect - floating reference
|
||||
uses: actions/checkout@main
|
||||
uses: actions/checkout@v4
|
||||
```
|
||||
|
||||
### Step Output References
|
||||
|
||||
**CRITICAL**: Steps must have `id:` to reference their outputs:
|
||||
|
||||
```yaml
|
||||
# ❌ INCORRECT - missing id
|
||||
- name: Detect Version
|
||||
uses: ./version-detect
|
||||
uses: ivuorinen/actions/version-detect@<SHA>
|
||||
|
||||
- name: Setup
|
||||
with:
|
||||
@@ -239,7 +297,7 @@ if: github.event_name == 'push'
|
||||
# ✅ CORRECT - id present
|
||||
- name: Detect Version
|
||||
id: detect-version # Required for output reference
|
||||
uses: ./version-detect
|
||||
uses: ivuorinen/actions/version-detect@<SHA>
|
||||
|
||||
- name: Setup
|
||||
with:
|
||||
@@ -250,7 +308,7 @@ if: github.event_name == 'push'
|
||||
|
||||
- **No Secrets**: Never commit secrets or keys to repository
|
||||
- **No Logging**: Never expose or log secrets/keys in code
|
||||
- **SHA Pinning**: All external actions use SHA commits, not tags
|
||||
- **SHA Pinning**: All action references (internal + external) use SHA commits, not tags
|
||||
- **Input Validation**: All actions import from shared validation library (`validate-inputs/`) - stateless validation functions, no inter-action dependencies
|
||||
- **Output Sanitization**: Use `printf` or heredoc for `$GITHUB_OUTPUT` writes
|
||||
- **Injection Prevention**: Validate inputs for command injection patterns (`;`, `&&`, `|`, backticks)
|
||||
@@ -276,6 +334,7 @@ if: github.event_name == 'push'
|
||||
- **Convention-Based**: Automatic rule generation based on input naming patterns
|
||||
- **Error Handling**: Comprehensive error messages and proper exit codes
|
||||
- **Defensive Programming**: Check tool availability, validate inputs, handle edge cases
|
||||
- **POSIX Compliance**: All scripts portable across POSIX shells
|
||||
|
||||
## Pre-commit and Security Configuration
|
||||
|
||||
|
||||
201
.serena/memories/development_standards.md
Normal file
201
.serena/memories/development_standards.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# Development Standards & Workflows
|
||||
|
||||
## Quality Standards (ZERO TOLERANCE)
|
||||
|
||||
### Production Ready Criteria
|
||||
|
||||
- ALL tests pass (100% success rate)
|
||||
- ALL linting passes (zero issues)
|
||||
- ALL validation checks pass
|
||||
- NO warnings or errors
|
||||
|
||||
### Communication
|
||||
|
||||
- Direct, factual only
|
||||
- Never claim "production ready" until literally everything passes
|
||||
- No hype, buzzwords, or excessive enthusiasm
|
||||
|
||||
## Required Commands
|
||||
|
||||
### Development Cycle
|
||||
|
||||
```bash
|
||||
make all # Complete: docs, format, lint, test
|
||||
make dev # Format + lint (development)
|
||||
make lint # All linters (MUST pass 100%)
|
||||
make test # All tests (MUST pass 100%)
|
||||
make format # Auto-fix formatting
|
||||
```
|
||||
|
||||
### Task Completion Checklist
|
||||
|
||||
After ANY coding task:
|
||||
|
||||
- [ ] `make lint` - Fix all issues (blocking)
|
||||
- [ ] `make test` - Ensure 100% pass
|
||||
- [ ] EditorConfig compliance verified
|
||||
|
||||
### Validation System
|
||||
|
||||
```bash
|
||||
make update-validators # Generate validation rules
|
||||
make update-validators-dry # Preview changes
|
||||
make generate-tests # Create missing tests
|
||||
make generate-tests-dry # Preview test generation
|
||||
```
|
||||
|
||||
### Version Management
|
||||
|
||||
```bash
|
||||
make release [VERSION=vYYYY.MM.DD] # Create new release (auto-generates version from date if omitted)
|
||||
make update-version-refs MAJOR=vYYYY # Update refs to version
|
||||
make bump-major-version OLD=vYYYY NEW=vYYYY # Annual bump
|
||||
make check-version-refs # Verify current refs
|
||||
```
|
||||
|
||||
See `versioning_system` memory for complete details.
|
||||
|
||||
## Code Style
|
||||
|
||||
### EditorConfig (BLOCKING ERRORS)
|
||||
|
||||
- **Indent**: 2 spaces (4 for Python, tabs for Makefile)
|
||||
- **Charset**: UTF-8
|
||||
- **Line Endings**: LF
|
||||
- **Max Line**: 200 chars (120 for Markdown)
|
||||
- **Final Newline**: Required
|
||||
- **Trailing Whitespace**: Trimmed
|
||||
|
||||
### Shell Scripts (POSIX REQUIRED)
|
||||
|
||||
**ALL scripts use POSIX shell** (`#!/bin/sh`) for maximum portability:
|
||||
|
||||
```bash
|
||||
#!/bin/sh
|
||||
set -eu # MANDATORY (no pipefail - not POSIX)
|
||||
# Quote everything: "$variable", basename -- "$path"
|
||||
# Check tools: command -v jq >/dev/null 2>&1
|
||||
# Use printf instead of echo -e for portability
|
||||
```
|
||||
|
||||
**Why POSIX:**
|
||||
|
||||
- Works on Alpine Linux, busybox, minimal containers
|
||||
- Faster than bash
|
||||
- Maximum compatibility (sh, dash, ash, bash, zsh)
|
||||
- CI-friendly, minimal dependencies
|
||||
|
||||
**Key Differences from Bash:**
|
||||
|
||||
- Use `#!/bin/sh` not `#!/usr/bin/env bash`
|
||||
- Use `set -eu` not `set -euo pipefail` (pipefail not POSIX)
|
||||
- Use `[ ]` not `[[ ]]`
|
||||
- Use `printf` not `echo -e`
|
||||
- Use `. file` not `source file`
|
||||
- Use `cut`/`grep` for parsing, not here-strings `<<<`
|
||||
- Use temp files instead of associative arrays
|
||||
- Use `$0` not `$BASH_SOURCE`
|
||||
|
||||
### Python (Ruff)
|
||||
|
||||
- **Line Length**: 100 chars
|
||||
- **Indent**: 4 spaces
|
||||
- **Quotes**: Double
|
||||
- **Docstrings**: Google style
|
||||
- **Type Hints**: Required
|
||||
|
||||
### YAML/Actions
|
||||
|
||||
- **Indent**: 2 spaces
|
||||
- **Internal Actions (action.yml)**: `ivuorinen/actions/action-name@<SHA>` (SHA-pinned, security)
|
||||
- **Test Workflows**: `./action-name` (local reference, runs within repo)
|
||||
- **Internal Workflows**: `./action-name` (local reference for sync-labels.yml etc)
|
||||
- **External Actions**: SHA-pinned (not `@main`/`@v1`)
|
||||
- **Step IDs**: Required when outputs referenced
|
||||
- **Permissions**: Minimal scope (contents: read default)
|
||||
- **Output Sanitization**: Use `printf`, never `echo` for `$GITHUB_OUTPUT`
|
||||
|
||||
## Versioning System
|
||||
|
||||
### Internal References (SHA-Pinned)
|
||||
|
||||
All `*/action.yml` files use SHA-pinned references for security and reproducibility:
|
||||
|
||||
```yaml
|
||||
uses: ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9
|
||||
```
|
||||
|
||||
**Why SHA-pinned internally:**
|
||||
|
||||
- Security: immutable, auditable references
|
||||
- Reproducibility: exact version control
|
||||
- Portability: works when actions used externally
|
||||
- Prevention: no accidental version drift
|
||||
|
||||
### Test Workflows (Local References)
|
||||
|
||||
Test workflows in `_tests/` use local references:
|
||||
|
||||
```yaml
|
||||
uses: ./validate-inputs
|
||||
```
|
||||
|
||||
**Why local in tests:** Tests run within the repo, faster, simpler
|
||||
|
||||
### External User References
|
||||
|
||||
Users reference with version tags:
|
||||
|
||||
```yaml
|
||||
uses: ivuorinen/actions/validate-inputs@v2025
|
||||
```
|
||||
|
||||
### Version Format (CalVer)
|
||||
|
||||
- Major: `v2025` (year)
|
||||
- Minor: `v2025.10` (year.month)
|
||||
- Patch: `v2025.10.18` (year.month.day)
|
||||
|
||||
All three tags point to the same commit SHA.
|
||||
|
||||
### Creating Releases
|
||||
|
||||
```bash
|
||||
make release # Auto-generates vYYYY.MM.DD from today's date
|
||||
make release VERSION=v2025.10.18 # Specific version
|
||||
git push origin main --tags --force-with-lease
|
||||
```
|
||||
|
||||
## Security Requirements
|
||||
|
||||
1. **SHA Pinning**: All action references use commit SHAs (not moving tags)
|
||||
2. **Token Safety**: `${{ github.token }}`, never hardcoded
|
||||
3. **Input Validation**: All inputs validated via centralized system
|
||||
4. **Output Sanitization**: `printf '%s\n' "$value" >> $GITHUB_OUTPUT`
|
||||
5. **Injection Prevention**: Validate for `;`, `&&`, `|`, backticks
|
||||
6. **Tool Availability**: `command -v tool` checks before use
|
||||
7. **Variable Quoting**: Always `"$var"` in shell
|
||||
8. **No Secrets**: Never commit credentials/keys
|
||||
|
||||
## Never Do
|
||||
|
||||
- Never `git commit` (manual commits not allowed)
|
||||
- Never use `--no-verify` flags
|
||||
- Never modify linting config to make tests pass
|
||||
- Never assume linting issues are acceptable
|
||||
- Never skip testing after changes
|
||||
- Never create files unless absolutely necessary
|
||||
- Never nest `${{ }}` in quoted YAML strings (breaks hashFiles)
|
||||
- Never use `@main` for internal action references (use SHA-pinned)
|
||||
- Never use bash-specific features (scripts must be POSIX sh)
|
||||
|
||||
## Preferred Patterns
|
||||
|
||||
- POSIX shell for all scripts (not bash)
|
||||
- SHA-pinned internal action references (security)
|
||||
- Edit existing files over creating new ones
|
||||
- Use centralized validation for all input handling
|
||||
- Follow existing conventions in codebase
|
||||
- Actions use composition, not dependencies
|
||||
- Custom validators in action directories
|
||||
- Convention-based automatic detection
|
||||
101
.serena/memories/documentation_guide.md
Normal file
101
.serena/memories/documentation_guide.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Documentation Guide
|
||||
|
||||
## Documentation Locations
|
||||
|
||||
### Validation System Docs (`validate-inputs/docs/`)
|
||||
|
||||
Read when working with validators or validation logic:
|
||||
|
||||
**API.md** - Complete API reference
|
||||
|
||||
- BaseValidator methods and properties
|
||||
- Core validators (Boolean, Version, Token, Numeric, Docker, File, Network, Security, CodeQL)
|
||||
- Registry system usage
|
||||
- Custom validator patterns
|
||||
- Convention system
|
||||
|
||||
**DEVELOPER_GUIDE.md** - Creating new validators
|
||||
|
||||
- Quick start guide
|
||||
- Creating core validators (in validators/ directory)
|
||||
- Creating custom validators (in action directories)
|
||||
- Adding convention patterns
|
||||
- Writing tests, debugging, common patterns
|
||||
|
||||
**ACTION_MAINTAINER.md** - Using validation in actions
|
||||
|
||||
- How validation works (automatic integration)
|
||||
- Validation flow (input collection, validator selection, execution, error reporting)
|
||||
- Using automatic validation via conventions
|
||||
- Custom validation for complex scenarios
|
||||
- Testing validation, common scenarios, troubleshooting
|
||||
|
||||
**README_ARCHITECTURE.md** - System architecture
|
||||
|
||||
- Feature overview
|
||||
- Quick start examples
|
||||
- Architecture details
|
||||
- Modular validator structure
|
||||
- Convention-based detection
|
||||
- Custom validator support
|
||||
|
||||
### Testing Framework (`_tests/README.md`)
|
||||
|
||||
Read when writing or debugging tests:
|
||||
|
||||
- ShellSpec framework overview
|
||||
- Multi-level testing strategy (unit, integration, external usage)
|
||||
- Directory structure explanation
|
||||
- Test writing patterns
|
||||
- Running tests (`make test`, `make test-unit`, `make test-action ACTION=name`)
|
||||
- Coverage reporting
|
||||
- Mocking and fixtures
|
||||
- CI integration
|
||||
|
||||
### Docker Testing Tools (`_tools/docker-testing-tools/README.md`)
|
||||
|
||||
Read when working with CI or testing infrastructure:
|
||||
|
||||
- Pre-built Docker image with all testing tools
|
||||
- Pre-installed tools (ShellSpec, nektos/act, TruffleHog, actionlint, etc.)
|
||||
- Building locally (build.sh, test.sh)
|
||||
- Performance benefits (saves ~3 minutes per run)
|
||||
- Multi-stage build process
|
||||
- Usage in workflows
|
||||
|
||||
### Top-Level Documentation
|
||||
|
||||
**README.md** - Main project readme (auto-generated)
|
||||
|
||||
- Actions catalog
|
||||
- Usage examples
|
||||
- Quick reference
|
||||
|
||||
**SECURITY.md** - Security policy
|
||||
|
||||
- Reporting vulnerabilities
|
||||
- Security practices
|
||||
|
||||
**LICENSE.md** - MIT license
|
||||
|
||||
**CLAUDE.md** - Project instructions (covered in development_standards memory)
|
||||
|
||||
## When to Read What
|
||||
|
||||
**Starting new validator work**: Read `DEVELOPER_GUIDE.md`, then `API.md` for reference
|
||||
|
||||
**Using validation in action**: Read `ACTION_MAINTAINER.md`
|
||||
|
||||
**Understanding architecture**: Read `README_ARCHITECTURE.md`
|
||||
|
||||
**Writing tests**: Read `_tests/README.md`
|
||||
|
||||
**Setting up CI/testing**: Read `_tools/docker-testing-tools/README.md`
|
||||
|
||||
**API reference lookup**: Read `API.md` (has method tables, validator details)
|
||||
|
||||
## Documentation is Auto-Generated
|
||||
|
||||
- Action READMEs generated via `action-docs` (don't edit manually)
|
||||
- Validation system README auto-generated
|
||||
- Keep CLAUDE.md and docs/ files updated manually
|
||||
@@ -1,75 +0,0 @@
|
||||
# Linting Improvements - September 2025
|
||||
|
||||
## Summary
|
||||
|
||||
Successfully reduced linting issues from 213 to 99 in the modular validator architecture.
|
||||
|
||||
## Issues Fixed
|
||||
|
||||
### Critical Issues Resolved
|
||||
|
||||
1. **Print Statements** - All converted to proper logging with logger
|
||||
2. **F-string Logging** - Converted to lazy % formatting
|
||||
3. **Mutable Class Attributes** - Added `ClassVar` type annotations
|
||||
4. **Import Sorting** - Fixed and organized
|
||||
5. **File Path Operations** - Replaced os.path with Path
|
||||
6. **Exception Handling** - Improved specific exception catching
|
||||
|
||||
## Code Changes Made
|
||||
|
||||
### Logging Improvements
|
||||
|
||||
```python
|
||||
# Before
|
||||
print(f"::error::{error}")
|
||||
|
||||
# After
|
||||
logger.error("::error::%s", error)
|
||||
```
|
||||
|
||||
### Class Attributes
|
||||
|
||||
```python
|
||||
# Before
|
||||
SUPPORTED_LANGUAGES = {...}
|
||||
|
||||
# After
|
||||
SUPPORTED_LANGUAGES: ClassVar[set[str]] = {...}
|
||||
```
|
||||
|
||||
### Path Operations
|
||||
|
||||
```python
|
||||
# Before
|
||||
if os.path.exists(self.temp_output.name):
|
||||
|
||||
# After
|
||||
if Path(self.temp_output.name).exists():
|
||||
```
|
||||
|
||||
## Remaining Issues (99 total)
|
||||
|
||||
### Acceptable Issues
|
||||
|
||||
- **39 PLC0415** - Import-outside-top-level (intentional in tests for isolation)
|
||||
- **27 PLR2004** - Magic value comparisons (domain-specific constants)
|
||||
- **9 PLR0911** - Too many return statements (complex validation logic)
|
||||
- **7 BLE001** - Blind except (appropriate for fallback scenarios)
|
||||
- **7 TRY300** - Try-consider-else (current pattern is clearer)
|
||||
- **3 S105** - Hardcoded password strings (test data)
|
||||
- **3 SIM115** - Context managers (NamedTemporaryFile usage)
|
||||
- **1 C901** - Complexity (validator.main function)
|
||||
- **1 FIX002** - TODO comment (tracked in issue)
|
||||
- **1 S110** - Try-except-pass (appropriate fallback)
|
||||
- **1 S603** - Subprocess call (controlled input in tests)
|
||||
|
||||
## Test Status
|
||||
|
||||
- 286 tests passing
|
||||
- 17 tests failing (output format changes)
|
||||
- 94.4% pass rate
|
||||
|
||||
## Conclusion
|
||||
|
||||
All critical linting issues have been resolved. The remaining 99 issues are mostly style preferences or intentional patterns that are acceptable for this codebase.
|
||||
The code quality has significantly improved while maintaining functionality.
|
||||
@@ -1,345 +0,0 @@
|
||||
# Modular Validator Architecture - Complete Documentation
|
||||
|
||||
## Current Status: PRODUCTION READY ✅
|
||||
|
||||
**Last Updated**: 2025-09-16
|
||||
**Branch**: feat/upgrades-and-restructuring
|
||||
**Phase Completed**: 1-5 of 7 (Test Generation System Implemented)
|
||||
**Test Status**: 100% pass rate (303/303 tests passing)
|
||||
**Linting**: 0 issues
|
||||
**Quality**: Production ready, zero defects
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
Successfully transformed monolithic `validator.py` into a modular, extensible validation system for GitHub Actions inputs.
|
||||
The architecture now provides specialized validators, convention-based auto-detection, support for custom validators, and an intelligent test generation system.
|
||||
|
||||
## Core Components
|
||||
|
||||
### 1. Base Framework
|
||||
|
||||
- **BaseValidator** (`validators/base.py`): Abstract base class defining validator interface
|
||||
- **ValidatorRegistry** (`validators/registry.py`): Dynamic validator discovery and management
|
||||
- **ConventionMapper** (`validators/conventions.py`): Automatic validation based on naming patterns
|
||||
|
||||
### 2. Specialized Validator Modules (9 Total)
|
||||
|
||||
| Module | Purpose | Status |
|
||||
| ------------------------ | --------------------------------- | ----------- |
|
||||
| `validators/token.py` | GitHub, NPM, PyPI, Docker tokens | ✅ Complete |
|
||||
| `validators/version.py` | SemVer, CalVer, language versions | ✅ Complete |
|
||||
| `validators/boolean.py` | Boolean value validation | ✅ Complete |
|
||||
| `validators/numeric.py` | Numeric ranges and constraints | ✅ Complete |
|
||||
| `validators/docker.py` | Docker images, tags, platforms | ✅ Complete |
|
||||
| `validators/file.py` | File paths, extensions, security | ✅ Complete |
|
||||
| `validators/network.py` | URLs, emails, IPs, ports | ✅ Complete |
|
||||
| `validators/security.py` | Injection detection, secrets | ✅ Complete |
|
||||
| `validators/codeql.py` | CodeQL queries, languages, config | ✅ Complete |
|
||||
|
||||
### 3. Custom Validators (4 Implemented)
|
||||
|
||||
| Action | Custom Validator | Features |
|
||||
| ----------------- | ---------------- | ------------------------------------ |
|
||||
| `sync-labels` | ✅ Implemented | YAML file validation, GitHub token |
|
||||
| `docker-build` | ✅ Implemented | Complex build args, platforms, cache |
|
||||
| `codeql-analysis` | ✅ Implemented | Language support, query validation |
|
||||
| `docker-publish` | ✅ Implemented | Registry validation, credentials |
|
||||
|
||||
## Implementation Phases
|
||||
|
||||
### ✅ Phase 1: Core Infrastructure (COMPLETED)
|
||||
|
||||
- Created modular directory structure
|
||||
- Implemented BaseValidator abstract class
|
||||
- Built ValidatorRegistry with auto-discovery
|
||||
- Established testing framework
|
||||
|
||||
### ✅ Phase 2: Specialized Validators (COMPLETED)
|
||||
|
||||
- Extracted validation logic into 9 specialized modules
|
||||
- Created comprehensive test coverage
|
||||
- Achieved full pytest compatibility
|
||||
- Fixed all method signatures and interfaces
|
||||
|
||||
### ✅ Phase 3: Convention Mapper (COMPLETED)
|
||||
|
||||
- Implemented priority-based pattern matching (100+ patterns)
|
||||
- Created ConventionBasedValidator for automatic validation
|
||||
- Added YAML-based convention override support
|
||||
- Integrated with ValidatorRegistry
|
||||
|
||||
### ✅ Phase 4: Custom Validator Support (COMPLETED)
|
||||
|
||||
- Implemented custom validator discovery in registry
|
||||
- Created 4 custom validators for complex actions
|
||||
- Fixed error propagation between parent/child validators
|
||||
- Added full GitHub expression (`${{ }}`) support
|
||||
- All custom validator tests passing (6/6)
|
||||
|
||||
### ✅ Phase 5: Test Generation System (COMPLETED)
|
||||
|
||||
- Implemented `generate-tests.py` script with intelligent pattern detection
|
||||
- Created test templates for different validator types
|
||||
- Added skip-existing-tests logic to prevent overwrites
|
||||
- Integrated with Makefile (`make generate-tests`, `make generate-tests-dry`)
|
||||
- Created comprehensive tests for the generator itself (11 tests passing)
|
||||
- Supports both ShellSpec and pytest test generation
|
||||
- Handles custom validators in action directories
|
||||
|
||||
#### Test Generation Features
|
||||
|
||||
- **Intelligent Input Detection**: Recognizes patterns like `token`, `version`, `path`, `url`, `email`, `dry-run`, etc.
|
||||
- **Context-Aware Test Cases**: Generates appropriate test cases based on input types
|
||||
- **GitHub Expression Support**: Includes tests for `${{ }}` expressions
|
||||
- **Template System**: Different templates for version, token, boolean, numeric, file, network, docker, and security validators
|
||||
- **Non-Destructive**: Never overwrites existing test files
|
||||
- **Dry Run Mode**: Preview what would be generated without creating files
|
||||
- **Comprehensive Coverage**: Generates ShellSpec tests for actions, pytest tests for validators, and tests for custom validators
|
||||
|
||||
#### Test Generation Commands
|
||||
|
||||
```bash
|
||||
make generate-tests # Generate missing tests
|
||||
make generate-tests-dry # Preview what would be generated
|
||||
make test-generate-tests # Test the generator itself
|
||||
```
|
||||
|
||||
### ⏳ Phase 6: Integration and Migration (NOT STARTED)
|
||||
|
||||
- Update YAML rules to new schema format
|
||||
- Migrate remaining actions to custom validators
|
||||
- Update rule generation scripts
|
||||
|
||||
### ⏳ Phase 7: Documentation and Tooling (NOT STARTED)
|
||||
|
||||
- Create validator development guide
|
||||
- Add CLI tools for validator testing
|
||||
- Update all documentation
|
||||
|
||||
## Convention-Based Detection
|
||||
|
||||
The ConventionMapper provides automatic validator selection based on input naming patterns:
|
||||
|
||||
```text
|
||||
# Priority levels (higher = more specific)
|
||||
100: Exact matches (e.g., "dry-run" → boolean)
|
||||
95: Language-specific versions (e.g., "-python-version" → python_version)
|
||||
90: Generic suffixes (e.g., "-token" → token)
|
||||
85: Contains patterns (e.g., contains "email" → email)
|
||||
80: Prefix patterns (e.g., "is-" → boolean)
|
||||
```
|
||||
|
||||
## Key Technical Achievements
|
||||
|
||||
### Error Propagation Pattern
|
||||
|
||||
```python
|
||||
# Proper error propagation from child to parent validators
|
||||
result = self.child_validator.validate_something(value)
|
||||
for error in self.child_validator.errors:
|
||||
if error not in self.errors:
|
||||
self.add_error(error)
|
||||
self.child_validator.clear_errors()
|
||||
return result
|
||||
```
|
||||
|
||||
### GitHub Expression Support
|
||||
|
||||
All validators properly handle GitHub Actions expressions:
|
||||
|
||||
```python
|
||||
# Allow GitHub Actions expressions
|
||||
if self.is_github_expression(value):
|
||||
return True
|
||||
```
|
||||
|
||||
### Platform Validation
|
||||
|
||||
Docker platform validation accepts full platform strings:
|
||||
|
||||
- `linux/amd64`, `linux/arm64`, `linux/arm/v7`
|
||||
- `windows/amd64` (where applicable)
|
||||
- `darwin/arm64` (where applicable)
|
||||
|
||||
## Testing Infrastructure
|
||||
|
||||
### Test Statistics
|
||||
|
||||
- **Total Tests**: 303 (including 11 test generator tests)
|
||||
- **Passing**: 303 (100%)
|
||||
- **Coverage by Module**: All modules have dedicated test files
|
||||
- **Custom Validators**: 6 comprehensive tests
|
||||
- **Test Generator**: 11 tests for the generation system
|
||||
|
||||
### Test Files
|
||||
|
||||
```text
|
||||
validate-inputs/tests/
|
||||
├── test_base.py ✅
|
||||
├── test_registry.py ✅
|
||||
├── test_convention_mapper.py ✅
|
||||
├── test_boolean_validator.py ✅
|
||||
├── test_codeql_validator.py ✅
|
||||
├── test_docker_validator.py ✅
|
||||
├── test_file_validator.py ✅
|
||||
├── test_network_validator.py ✅
|
||||
├── test_numeric_validator.py ✅
|
||||
├── test_security_validator.py ✅
|
||||
├── test_token_validator.py ✅
|
||||
├── test_version_validator.py ✅
|
||||
├── test_custom_validators.py ✅ (6 tests)
|
||||
├── test_integration.py ✅
|
||||
├── test_validator.py ✅
|
||||
└── test_generate_tests.py ✅ (11 tests)
|
||||
```
|
||||
|
||||
### Test Generation System
|
||||
|
||||
```text
|
||||
validate-inputs/scripts/
|
||||
└── generate-tests.py ✅ Intelligent test generator
|
||||
```
|
||||
|
||||
## Production Readiness Criteria
|
||||
|
||||
✅ **ALL CRITERIA MET**:
|
||||
|
||||
- Zero failing tests (303/303 passing)
|
||||
- Zero linting issues
|
||||
- Zero type checking issues
|
||||
- Full backward compatibility maintained
|
||||
- Comprehensive error handling
|
||||
- Security patterns validated
|
||||
- Performance optimized (lazy loading, caching)
|
||||
- Custom validator support proven
|
||||
- GitHub expression handling complete
|
||||
- Test generation system operational
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Validation
|
||||
|
||||
```python
|
||||
from validators.registry import ValidatorRegistry
|
||||
|
||||
registry = ValidatorRegistry()
|
||||
validator = registry.get_validator("docker-build")
|
||||
result = validator.validate_inputs({
|
||||
"context": ".",
|
||||
"dockerfile": "Dockerfile",
|
||||
"platforms": "linux/amd64,linux/arm64"
|
||||
})
|
||||
```
|
||||
|
||||
### Custom Validator
|
||||
|
||||
```python
|
||||
# Automatically loads docker-build/CustomValidator.py
|
||||
validator = registry.get_validator("docker-build")
|
||||
# Uses specialized validation logic for docker-build action
|
||||
```
|
||||
|
||||
### Test Generation
|
||||
|
||||
```bash
|
||||
# Generate missing tests for all actions and validators
|
||||
python3 validate-inputs/scripts/generate-tests.py
|
||||
|
||||
# Preview what would be generated (dry run)
|
||||
python3 validate-inputs/scripts/generate-tests.py --dry-run --verbose
|
||||
|
||||
# Generated test example
|
||||
#!/usr/bin/env bash
|
||||
Describe 'Action Name Input Validation'
|
||||
Context 'Required inputs validation'
|
||||
It 'should fail when required inputs are missing'
|
||||
When run validate_inputs 'action-name'
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```text
|
||||
validate-inputs/
|
||||
├── validator.py # Main entry point
|
||||
├── validators/
|
||||
│ ├── __init__.py
|
||||
│ ├── base.py # BaseValidator abstract class
|
||||
│ ├── registry.py # ValidatorRegistry
|
||||
│ ├── conventions.py # ConventionBasedValidator
|
||||
│ ├── [9 specialized validators]
|
||||
│ └── ...
|
||||
├── rules/ # YAML validation rules
|
||||
├── tests/ # Comprehensive test suite
|
||||
│ ├── [validator tests]
|
||||
│ └── test_generate_tests.py # Test generator tests
|
||||
└── scripts/
|
||||
├── update-validators.py # Rule generator
|
||||
└── generate-tests.py # Test generator ✅
|
||||
|
||||
# Custom validators in action directories
|
||||
sync-labels/CustomValidator.py ✅
|
||||
docker-build/CustomValidator.py ✅
|
||||
codeql-analysis/CustomValidator.py ✅
|
||||
docker-publish/CustomValidator.py ✅
|
||||
```
|
||||
|
||||
## Benefits Achieved
|
||||
|
||||
### 1. Modularity
|
||||
|
||||
- Each validator is self-contained
|
||||
- Clear separation of concerns
|
||||
- Easy to test individually
|
||||
|
||||
### 2. Extensibility
|
||||
|
||||
- New validators easily added
|
||||
- Custom validators for complex actions
|
||||
- Convention-based auto-detection
|
||||
- Automatic test generation
|
||||
|
||||
### 3. Maintainability
|
||||
|
||||
- Individual test files per validator
|
||||
- Consistent interfaces
|
||||
- Clear error messages
|
||||
- Tests generated with consistent patterns
|
||||
|
||||
### 4. Performance
|
||||
|
||||
- Lazy loading of validators
|
||||
- Efficient pattern matching
|
||||
- Minimal overhead
|
||||
- Fast test generation
|
||||
|
||||
### 5. Developer Experience
|
||||
|
||||
- Automatic test scaffolding
|
||||
- Intelligent pattern detection
|
||||
- Non-destructive generation
|
||||
- Comprehensive test coverage
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Phase 6**: Integration and Migration
|
||||
- Update YAML rules to new schema format
|
||||
- Migrate more actions to custom validators
|
||||
|
||||
2. **Phase 7**: Documentation and Tooling
|
||||
- Create comprehensive validator development guide
|
||||
- Add CLI tools for validator testing
|
||||
|
||||
3. **Optional Enhancements**:
|
||||
- Create more custom validators (github-release, npm-publish)
|
||||
- Enhance test generation templates
|
||||
- Add performance benchmarks
|
||||
|
||||
## Summary
|
||||
|
||||
The modular validator architecture with test generation is **complete and production-ready**. Phases 1-5 are done, providing a robust, extensible,
|
||||
and well-tested validation system for GitHub Actions. The test generation system ensures consistent test coverage and reduces manual test writing effort.
|
||||
The system maintains 100% test coverage with zero defects, follows SOLID principles, and maintains full backward compatibility.
|
||||
@@ -1,200 +0,0 @@
|
||||
# Modular Validator Architecture - COMPLETED
|
||||
|
||||
## Overview
|
||||
|
||||
Successfully implemented a comprehensive modular validation system for GitHub Actions, replacing the monolithic validator.py with a flexible, extensible architecture.
|
||||
|
||||
## Implementation Status: COMPLETED (September 2025)
|
||||
|
||||
All 7 phases completed with 100% test pass rate and zero linting issues.
|
||||
|
||||
## Architecture Components
|
||||
|
||||
### Core System
|
||||
|
||||
1. **BaseValidator** (`validators/base.py`)
|
||||
- Abstract base class defining validation interface
|
||||
- Standard methods: validate_inputs, add_error, clear_errors
|
||||
- Extensible for custom validators
|
||||
|
||||
2. **ValidatorRegistry** (`validators/registry.py`)
|
||||
- Dynamic validator discovery and loading
|
||||
- Custom validator support via action-specific `<action-name>/CustomValidator.py` files
|
||||
- Searches project root for `<action-dir>/CustomValidator.py` (e.g., `docker-build/CustomValidator.py`)
|
||||
- Fallback to convention-based validation when no custom validator exists
|
||||
- Added get_validator_by_type method for direct type access
|
||||
|
||||
3. **ConventionBasedValidator** (`validators/conventions.py`)
|
||||
- Pattern-based automatic validation
|
||||
- Detects validation needs from input names
|
||||
- Delegates to specific validators based on conventions
|
||||
|
||||
4. **ConventionMapper** (`validators/convention_mapper.py`)
|
||||
- Maps input patterns to validator types
|
||||
- Supports exact, prefix, suffix, and contains patterns
|
||||
- Efficient pattern matching with caching
|
||||
|
||||
### Specialized Validators
|
||||
|
||||
- **BooleanValidator**: Boolean values (true/false)
|
||||
- **VersionValidator**: SemVer, CalVer, flexible versioning
|
||||
- **TokenValidator**: GitHub tokens, API keys
|
||||
- **NumericValidator**: Integer/float ranges
|
||||
- **FileValidator**: File/directory paths
|
||||
- **NetworkValidator**: URLs, emails, hostnames
|
||||
- **DockerValidator**: Images, tags, platforms
|
||||
- **SecurityValidator**: Injection protection, security patterns
|
||||
- **CodeQLValidator**: Languages, queries, config
|
||||
|
||||
### Custom Validators
|
||||
|
||||
- Action-specific validation via `<action-name>/CustomValidator.py` files
|
||||
- Located in each action's directory (e.g., `docker-build/CustomValidator.py`, `npm-publish/CustomValidator.py`)
|
||||
- Extends ConventionBasedValidator or BaseValidator
|
||||
- Registry discovers custom validators by searching action directories in project root
|
||||
- Examples: docker-build, sync-labels, npm-publish, php-laravel-phpunit, validate-inputs
|
||||
|
||||
## Testing Infrastructure
|
||||
|
||||
### Test Generation System
|
||||
|
||||
- **generate-tests.py**: Non-destructive test generation
|
||||
- Preserves existing tests
|
||||
- Generates ShellSpec and pytest tests
|
||||
- Pattern-based test case creation
|
||||
- 900+ lines of intelligent test scaffolding
|
||||
|
||||
### Test Coverage
|
||||
|
||||
- 303 total tests passing
|
||||
- ShellSpec for action validation
|
||||
- pytest for Python validators
|
||||
- Integration tests for end-to-end validation
|
||||
- Performance benchmarks available
|
||||
|
||||
## Documentation & Tools
|
||||
|
||||
### Documentation
|
||||
|
||||
- **API.md**: Complete API reference
|
||||
- **DEVELOPER_GUIDE.md**: Adding new validators
|
||||
- **ACTION_MAINTAINER.md**: Using validation system
|
||||
- **README_ARCHITECTURE.md**: System overview
|
||||
|
||||
### Debug & Performance Tools
|
||||
|
||||
- **debug-validator.py**: Interactive debugging
|
||||
- **benchmark-validator.py**: Performance profiling
|
||||
- **update-validators.py**: Rule generation
|
||||
|
||||
## Code Quality
|
||||
|
||||
### Standards Achieved
|
||||
|
||||
- ✅ Zero linting issues (ruff, pyright)
|
||||
- ✅ 100% test pass rate (303 tests)
|
||||
- ✅ Full backward compatibility
|
||||
- ✅ Type hints throughout
|
||||
- ✅ Comprehensive documentation
|
||||
- ✅ EditorConfig compliance
|
||||
|
||||
### Fixed Issues
|
||||
|
||||
- Import sorting and organization
|
||||
- F-string logging converted to lazy format
|
||||
- Boolean arguments made keyword-only
|
||||
- Type annotations using proper types
|
||||
- Private member access via public methods
|
||||
- Exception handling improvements
|
||||
- Added missing registry methods
|
||||
|
||||
## Integration
|
||||
|
||||
### Main Validator Integration
|
||||
|
||||
- validator.py uses ValidatorRegistry
|
||||
- Transparent migration from old system
|
||||
- All existing actions work unchanged
|
||||
- Custom validators take precedence
|
||||
|
||||
### GitHub Expression Support
|
||||
|
||||
- Proper handling of ${{ }} expressions
|
||||
- Expression validation in appropriate contexts
|
||||
- Security-aware expression checking
|
||||
|
||||
## File Structure
|
||||
|
||||
```text
|
||||
validate-inputs/
|
||||
├── validators/
|
||||
│ ├── __init__.py
|
||||
│ ├── base.py # Abstract base
|
||||
│ ├── registry.py # Discovery & loading
|
||||
│ ├── conventions.py # Pattern-based
|
||||
│ ├── convention_mapper.py # Pattern mapping
|
||||
│ ├── boolean.py # Specialized validators...
|
||||
│ ├── version.py
|
||||
│ └── ...
|
||||
├── rules/ # Auto-generated YAML
|
||||
├── tests/ # pytest tests
|
||||
├── scripts/
|
||||
│ ├── generate-tests.py # Test generation
|
||||
│ ├── debug-validator.py # Debugging
|
||||
│ ├── benchmark-validator.py # Performance
|
||||
│ └── update-validators.py # Rule generation
|
||||
├── docs/ # Documentation
|
||||
├── CustomValidator.py # Custom validator for validate-inputs action
|
||||
└── validator.py # Main entry point
|
||||
|
||||
# Custom validators in action directories (examples):
|
||||
docker-build/CustomValidator.py
|
||||
npm-publish/CustomValidator.py
|
||||
php-laravel-phpunit/CustomValidator.py
|
||||
version-validator/CustomValidator.py
|
||||
```
|
||||
|
||||
## Key Achievements
|
||||
|
||||
1. **Modular Architecture**: Clean separation of concerns
|
||||
2. **Convention-Based**: Automatic validation from naming patterns
|
||||
3. **Extensible**: Easy to add new validators
|
||||
4. **Backward Compatible**: No breaking changes
|
||||
5. **Well Tested**: Comprehensive test coverage
|
||||
6. **Documented**: Complete API and guides
|
||||
7. **Production Ready**: Zero defects, all quality gates passed
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Custom Validator
|
||||
|
||||
```python
|
||||
# docker-build/CustomValidator.py
|
||||
from validate-inputs.validators.conventions import ConventionBasedValidator
|
||||
from validate-inputs.validators.docker import DockerValidator
|
||||
|
||||
class CustomValidator(ConventionBasedValidator):
|
||||
def __init__(self, action_type: str):
|
||||
super().__init__(action_type)
|
||||
self.docker_validator = DockerValidator(action_type)
|
||||
|
||||
def validate_inputs(self, inputs: dict[str, str]) -> bool:
|
||||
# Custom validation logic
|
||||
if not self.validate_required_inputs(inputs, ["context"]):
|
||||
return False
|
||||
return super().validate_inputs(inputs)
|
||||
```
|
||||
|
||||
### Debug Usage
|
||||
|
||||
```bash
|
||||
# Debug an action
|
||||
python validate-inputs/scripts/debug-validator.py docker-build --inputs '{"context": ".", "platforms": "linux/amd64,linux/arm64"}'
|
||||
|
||||
# Benchmark performance
|
||||
python validate-inputs/scripts/benchmark-validator.py --action docker-build --iterations 1000
|
||||
```
|
||||
|
||||
## Migration Complete
|
||||
|
||||
The modular validator architecture is fully implemented, tested, documented, and integrated. All quality standards met with zero defects.
|
||||
@@ -1,199 +0,0 @@
|
||||
# Project Overview - GitHub Actions Monorepo
|
||||
|
||||
## Purpose
|
||||
|
||||
This repository contains a collection of reusable GitHub Actions designed to streamline CI/CD processes and ensure code quality.
|
||||
Each action is fully self-contained and can be used independently in any GitHub repository.
|
||||
|
||||
## Repository Information
|
||||
|
||||
- **Branch**: feat/upgrades-and-restructuring
|
||||
- **Location**: /Users/ivuorinen/Code/ivuorinen/actions
|
||||
- **External Usage**: `ivuorinen/actions/action-name@main`
|
||||
- **Last Updated**: January 2025
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Production-Ready Actions** covering setup, linting, building, testing, and deployment
|
||||
- **Self-Contained Design** - each action works independently without dependencies
|
||||
- **Modular Validator Architecture** - specialized validators with convention-based auto-detection
|
||||
- **Custom Validator Support** - complex actions have dedicated validation logic
|
||||
- **Test Generation System** - automatic test scaffolding with intelligent pattern detection
|
||||
- **Multi-Language Support** including Node.js, PHP, Python, Go, C#, Docker, and more
|
||||
- **Comprehensive Testing** with dual framework (ShellSpec + pytest)
|
||||
- **Zero Defect Policy** - 100% test pass rate, zero linting issues required
|
||||
|
||||
## Architecture Highlights
|
||||
|
||||
### Directory Structure
|
||||
|
||||
- **Flat Action Layout**: Each action in its own directory with `action.yml`
|
||||
- **Centralized Validation**: `validate-inputs/` with modular validator system
|
||||
- **Custom Validators**: Action-specific validators (e.g., `docker-build/CustomValidator.py`)
|
||||
- **Testing Infrastructure**: `_tests/` for ShellSpec, `validate-inputs/tests/` for pytest
|
||||
- **Build Tools**: `_tools/` for helper scripts and development utilities
|
||||
- **Test Generation**: `validate-inputs/scripts/generate-tests.py` for automatic test creation
|
||||
|
||||
### Validation System (Modular Architecture)
|
||||
|
||||
```text
|
||||
validate-inputs/
|
||||
├── validator.py # Main entry point
|
||||
├── validators/
|
||||
│ ├── base.py # Abstract base class
|
||||
│ ├── registry.py # Dynamic validator discovery
|
||||
│ ├── conventions.py # Convention-based auto-detection
|
||||
│ └── [9 specialized modules]
|
||||
├── scripts/
|
||||
│ ├── update-validators.py # Auto-generates validation rules
|
||||
│ └── generate-tests.py # Non-destructive test generation
|
||||
└── tests/ # Comprehensive test suite
|
||||
```
|
||||
|
||||
### Testing Framework
|
||||
|
||||
- **ShellSpec**: For testing shell scripts and GitHub Actions
|
||||
- **pytest**: For Python validation system (303 tests, 100% passing)
|
||||
- **Test Generator**: Automatic test scaffolding for new actions/validators
|
||||
- **Coverage**: Full test coverage for all validators
|
||||
|
||||
## Action Categories
|
||||
|
||||
**Total: 43 actions** across 8 categories
|
||||
|
||||
### Setup Actions (7)
|
||||
|
||||
- `node-setup`, `set-git-config`, `php-version-detect`, `python-version-detect`,
|
||||
- `python-version-detect-v2`, `go-version-detect`, `dotnet-version-detect`
|
||||
|
||||
### Linting Actions (13)
|
||||
|
||||
- `ansible-lint-fix`, `biome-check`, `biome-fix`, `csharp-lint-check`
|
||||
- `eslint-check`, `eslint-fix`, `go-lint`, `pr-lint`, `pre-commit`
|
||||
- `prettier-check`, `prettier-fix`, `python-lint-fix`, `terraform-lint-fix`
|
||||
|
||||
### Build Actions (3)
|
||||
|
||||
- `csharp-build`, `go-build`, `docker-build`
|
||||
|
||||
### Publishing Actions (5)
|
||||
|
||||
- `npm-publish`, `docker-publish`, `docker-publish-gh`, `docker-publish-hub`, `csharp-publish`
|
||||
|
||||
### Testing Actions (3)
|
||||
|
||||
- `php-tests`, `php-laravel-phpunit`, `php-composer`
|
||||
|
||||
### Repository (9)
|
||||
|
||||
- `github-release`, `release-monthly`, `sync-labels`, `stale`
|
||||
- `compress-images`, `common-cache`, `common-file-check`, `common-retry`
|
||||
- `codeql-analysis` (security analysis)
|
||||
|
||||
### Utilities (2)
|
||||
|
||||
- `version-file-parser`, `version-validator`
|
||||
|
||||
### Validation (1)
|
||||
|
||||
- `validate-inputs` (centralized input validation system)
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Core Commands
|
||||
|
||||
```bash
|
||||
make all # Generate docs, format, lint, test
|
||||
make dev # Format then lint
|
||||
make lint # Run all linters
|
||||
make test # Run all tests
|
||||
make update-validators # Update validation rules
|
||||
make generate-tests # Generate missing tests (non-destructive)
|
||||
make generate-tests-dry # Preview test generation
|
||||
```
|
||||
|
||||
### Quality Standards
|
||||
|
||||
- **ZERO TOLERANCE**: No failing tests, no linting issues
|
||||
- **Production Ready**: Only when ALL checks pass
|
||||
- **Convention Priority**: EditorConfig rules are blocking
|
||||
- **Security First**: No secrets, tokens, or sensitive data in code
|
||||
|
||||
## Recent Accomplishments (January 2025)
|
||||
|
||||
### Phase 1-4: Modular Validator Architecture ✅
|
||||
|
||||
- Transformed monolithic validator into 11 specialized modules
|
||||
- Implemented convention-based auto-detection (100+ patterns)
|
||||
- Created 3 custom validators for complex actions
|
||||
- Achieved 100% test pass rate (292/292 tests)
|
||||
- Zero linting issues across all code
|
||||
|
||||
### Phase 5: Test Generation System ✅
|
||||
|
||||
- Created non-destructive test generation (preserves existing tests)
|
||||
- Intelligent pattern detection for input types
|
||||
- Template-based scaffolding for different validator types
|
||||
- ShellSpec test generation for GitHub Actions
|
||||
- pytest test generation for validators
|
||||
- Custom validator test support
|
||||
- 11 comprehensive tests for the generator itself
|
||||
- Makefile integration with three new commands
|
||||
|
||||
### Custom Validators Implemented
|
||||
|
||||
1. `docker-build` - Complex build args, platforms, cache validation
|
||||
2. `codeql-analysis` - Language support, query validation
|
||||
3. `docker-publish` - Registry, credentials, platform validation
|
||||
|
||||
### Technical Improvements
|
||||
|
||||
- Full GitHub expression support (`${{ }}`)
|
||||
- Error propagation between parent/child validators
|
||||
- Platform-specific validation (Docker architectures)
|
||||
- Registry validation (Docker Hub, GHCR, etc.)
|
||||
- Security pattern detection and injection prevention
|
||||
- Non-destructive test generation system
|
||||
- Template-based test scaffolding
|
||||
|
||||
## Project Status
|
||||
|
||||
**Phases Completed**:
|
||||
|
||||
- ✅ Phase 1: Base Architecture (100% complete)
|
||||
- ✅ Phase 2: Core Validators (100% complete)
|
||||
- ✅ Phase 3: Registry System (100% complete)
|
||||
- ✅ Phase 4: Custom Validators (100% complete)
|
||||
- ✅ Phase 5: Test Generation (100% complete)
|
||||
- ⏳ Phase 6: Integration and Migration (in progress)
|
||||
- ⏳ Phase 7: Documentation and Tooling (not started)
|
||||
|
||||
**Quality Metrics**:
|
||||
|
||||
- ✅ 100% test pass rate (303 total tests)
|
||||
- ✅ Zero linting issues
|
||||
- ✅ Modular, extensible architecture
|
||||
- ✅ Custom validator support
|
||||
- ✅ Convention-based auto-detection
|
||||
- ✅ Full backward compatibility
|
||||
- ✅ Comprehensive error handling
|
||||
- ✅ Security validations
|
||||
- ✅ Test generation system
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Complete Phase 6: Integration and Migration
|
||||
- Integrate modular validators with main validator.py
|
||||
- Ensure full backward compatibility
|
||||
- Test all 50+ actions with integrated system
|
||||
2. Phase 7: Documentation and Tooling
|
||||
3. Performance optimization
|
||||
4. Production deployment
|
||||
|
||||
## IDE Configuration Note
|
||||
|
||||
For Pyright/Pylance import resolution in IDEs like Zed, VSCode:
|
||||
|
||||
- The project uses relative imports within validate-inputs
|
||||
- Python path includes validate-inputs directory
|
||||
- Tests use sys.path manipulation for imports
|
||||
@@ -1,171 +0,0 @@
|
||||
# Project Structure and Architecture
|
||||
|
||||
## Repository Structure
|
||||
|
||||
```text
|
||||
/Users/ivuorinen/Code/ivuorinen/actions/
|
||||
├── Action Directories/ # Each action is self-contained
|
||||
│ ├── action.yml # Action definition
|
||||
│ ├── README.md # Auto-generated documentation
|
||||
│ └── CustomValidator.py # Optional custom validator
|
||||
├── validate-inputs/ # Centralized validation system
|
||||
│ ├── validator.py # Main entry point
|
||||
│ ├── validators/ # Modular validator architecture
|
||||
│ │ ├── base.py # Abstract base class
|
||||
│ │ ├── registry.py # Dynamic validator discovery
|
||||
│ │ ├── conventions.py # Convention-based detection
|
||||
│ │ ├── boolean.py # Boolean validation
|
||||
│ │ ├── codeql.py # CodeQL-specific validation
|
||||
│ │ ├── docker.py # Docker validation
|
||||
│ │ ├── file.py # File path validation
|
||||
│ │ ├── network.py # Network/URL validation
|
||||
│ │ ├── numeric.py # Numeric validation
|
||||
│ │ ├── security.py # Security pattern detection
|
||||
│ │ ├── token.py # Token validation
|
||||
│ │ └── version.py # Version validation
|
||||
│ ├── rules/ # Auto-generated YAML rules
|
||||
│ ├── scripts/ # Rule generation utilities
|
||||
│ └── tests/ # Comprehensive pytest suite (292 tests)
|
||||
├── _tests/ # ShellSpec testing framework
|
||||
│ ├── unit/ # Unit tests for actions
|
||||
│ ├── framework/ # Testing utilities
|
||||
│ └── shared/ # Shared test components
|
||||
├── _tools/ # Development utilities
|
||||
│ ├── docker-testing-tools/ # Docker test environment
|
||||
│ └── fix-local-action-refs.py # Action reference fixer
|
||||
├── .github/ # GitHub configuration
|
||||
│ └── workflows/ # CI/CD workflows
|
||||
├── .serena/ # Serena AI configuration
|
||||
│ └── memories/ # Project knowledge base
|
||||
├── Makefile # Build automation
|
||||
├── pyproject.toml # Python configuration
|
||||
├── CLAUDE.md # Project instructions
|
||||
└── README.md # Auto-generated catalog
|
||||
```
|
||||
|
||||
## Modular Validator Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
- **BaseValidator**: Abstract interface for all validators
|
||||
- **ValidatorRegistry**: Dynamic discovery and loading
|
||||
- **ConventionMapper**: Automatic validation based on naming patterns
|
||||
|
||||
### Specialized Validators
|
||||
|
||||
1. **TokenValidator**: GitHub, NPM, PyPI, Docker tokens
|
||||
2. **VersionValidator**: SemVer, CalVer, language-specific
|
||||
3. **BooleanValidator**: Case-insensitive boolean values
|
||||
4. **NumericValidator**: Ranges and numeric constraints
|
||||
5. **DockerValidator**: Images, tags, platforms, registries
|
||||
6. **FileValidator**: Paths, extensions, security checks
|
||||
7. **NetworkValidator**: URLs, emails, IPs, ports
|
||||
8. **SecurityValidator**: Injection detection, secrets
|
||||
9. **CodeQLValidator**: Queries, languages, categories
|
||||
|
||||
### Custom Validators
|
||||
|
||||
- `sync-labels/CustomValidator.py` - YAML file validation
|
||||
- `docker-build/CustomValidator.py` - Complex build validation
|
||||
- `codeql-analysis/CustomValidator.py` - Language and query validation
|
||||
- `docker-publish/CustomValidator.py` - Registry and credential validation
|
||||
|
||||
## Action Categories
|
||||
|
||||
### Setup Actions (7)
|
||||
|
||||
- `node-setup`, `set-git-config`, `php-version-detect`
|
||||
- `python-version-detect`, `python-version-detect-v2`
|
||||
- `go-version-detect`, `dotnet-version-detect`
|
||||
|
||||
### Linting Actions (13)
|
||||
|
||||
- `ansible-lint-fix`, `biome-check`, `biome-fix`
|
||||
- `csharp-lint-check`, `eslint-check`, `eslint-fix`
|
||||
- `go-lint`, `pr-lint`, `pre-commit`
|
||||
- `prettier-check`, `prettier-fix`
|
||||
- `python-lint-fix`, `terraform-lint-fix`
|
||||
|
||||
### Build Actions (3)
|
||||
|
||||
- `csharp-build`, `go-build`, `docker-build`
|
||||
|
||||
### Publishing Actions (5)
|
||||
|
||||
- `npm-publish`, `docker-publish`
|
||||
- `docker-publish-gh`, `docker-publish-hub`
|
||||
- `csharp-publish`
|
||||
|
||||
### Testing Actions (3)
|
||||
|
||||
- `php-tests`, `php-laravel-phpunit`, `php-composer`
|
||||
|
||||
### Repository Management (9)
|
||||
|
||||
- `github-release`, `release-monthly`
|
||||
- `sync-labels`, `stale`
|
||||
- `compress-images`, `common-cache`
|
||||
- `common-file-check`, `common-retry`
|
||||
- `codeql-analysis`
|
||||
|
||||
### Utilities (2)
|
||||
|
||||
- `version-file-parser`, `version-validator`
|
||||
|
||||
## Key Architectural Principles
|
||||
|
||||
### Self-Contained Design
|
||||
|
||||
- Each action directory contains everything needed
|
||||
- No dependencies between actions
|
||||
- External usability via `ivuorinen/actions/action-name@main`
|
||||
- Custom validators colocated with actions
|
||||
|
||||
### Modular Validation System
|
||||
|
||||
- Specialized validators for different input types
|
||||
- Convention-based automatic detection (100+ patterns)
|
||||
- Priority system for pattern matching
|
||||
- Error propagation between validators
|
||||
- Full GitHub expression support (`${{ }}`)
|
||||
|
||||
### Testing Strategy
|
||||
|
||||
- **ShellSpec**: Shell scripts and GitHub Actions
|
||||
- **pytest**: Python validation system (100% pass rate)
|
||||
- **Coverage**: All validators have dedicated test files
|
||||
- **Standards**: Zero tolerance for failures
|
||||
|
||||
### Security Model
|
||||
|
||||
- SHA-pinned external actions
|
||||
- Token pattern validation
|
||||
- Injection detection
|
||||
- Path traversal protection
|
||||
- Security validator for sensitive data
|
||||
|
||||
## Development Workflow
|
||||
|
||||
### Core Commands
|
||||
|
||||
```bash
|
||||
make all # Full build pipeline
|
||||
make dev # Format and lint
|
||||
make lint # All linters
|
||||
make test # All tests
|
||||
make update-validators # Generate validation rules
|
||||
```
|
||||
|
||||
### Quality Standards
|
||||
|
||||
- **EditorConfig**: Blocking enforcement
|
||||
- **Linting**: Zero issues required
|
||||
- **Testing**: 100% pass rate required
|
||||
- **Production Ready**: Only when ALL checks pass
|
||||
|
||||
### Documentation
|
||||
|
||||
- Auto-generated README files via `action-docs`
|
||||
- Consistent formatting and structure
|
||||
- Cross-referenced action catalog
|
||||
- Comprehensive inline documentation
|
||||
@@ -1,36 +0,0 @@
|
||||
# Quality Standards and Communication Guidelines
|
||||
|
||||
## Critical Quality Standards
|
||||
|
||||
### ZERO TOLERANCE POLICY
|
||||
|
||||
- **ANY failing tests** = Project is NOT production ready
|
||||
- **ANY linting issues** = Project is NOT production ready
|
||||
- **NO EXCEPTIONS** to these rules
|
||||
|
||||
### Production Ready Definition
|
||||
|
||||
A project is only production ready when:
|
||||
|
||||
- ALL tests pass (100% success rate)
|
||||
- ALL linting passes with zero issues
|
||||
- ALL validation checks pass
|
||||
- NO warnings or errors in any tooling
|
||||
|
||||
### Communication Style
|
||||
|
||||
- **Tone down language** - avoid excessive enthusiasm or verbose descriptions
|
||||
- Be direct and factual
|
||||
- Don't claim success until ALL issues are resolved
|
||||
- Don't use terms like "production ready" unless literally everything passes
|
||||
- Focus on facts, not marketing language
|
||||
|
||||
### Work Standards
|
||||
|
||||
- Fix ALL issues before declaring completion
|
||||
- Never compromise on quality standards
|
||||
- Test everything thoroughly
|
||||
- Maintain zero-defect mentality
|
||||
- Quality over speed
|
||||
|
||||
This represents the user's absolute standards for code quality and communication.
|
||||
87
.serena/memories/repository_overview.md
Normal file
87
.serena/memories/repository_overview.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# GitHub Actions Monorepo - Overview
|
||||
|
||||
## Repository Info
|
||||
|
||||
- **Path**: /Users/ivuorinen/Code/ivuorinen/actions
|
||||
- **Branch**: main
|
||||
- **External Usage**: `ivuorinen/actions/<action-name>@main`
|
||||
- **Total Actions**: 43 self-contained actions
|
||||
|
||||
## Structure
|
||||
|
||||
```text
|
||||
/
|
||||
├── <action-dirs>/ # 43 self-contained actions
|
||||
│ ├── action.yml # Action definition
|
||||
│ ├── README.md # Auto-generated
|
||||
│ └── CustomValidator.py # Optional validator
|
||||
├── validate-inputs/ # Centralized validation
|
||||
│ ├── validators/ # 9 specialized modules
|
||||
│ ├── scripts/ # Rule/test generators
|
||||
│ └── tests/ # 769 pytest tests
|
||||
├── _tests/ # ShellSpec framework
|
||||
├── _tools/ # Development utilities
|
||||
├── .github/workflows/ # CI/CD workflows
|
||||
└── Makefile # Build automation
|
||||
```
|
||||
|
||||
## Action Categories (43 total)
|
||||
|
||||
**Setup (7)**: node-setup, set-git-config, php-version-detect, python-version-detect, python-version-detect-v2, go-version-detect, dotnet-version-detect
|
||||
|
||||
**Linting (13)**: ansible-lint-fix, biome-check/fix, csharp-lint-check, eslint-check/fix, go-lint, pr-lint, pre-commit, prettier-check/fix, python-lint-fix, terraform-lint-fix
|
||||
|
||||
**Build (3)**: csharp-build, go-build, docker-build
|
||||
|
||||
**Publishing (5)**: npm-publish, docker-publish, docker-publish-gh, docker-publish-hub, csharp-publish
|
||||
|
||||
**Testing (3)**: php-tests, php-laravel-phpunit, php-composer
|
||||
|
||||
**Repository (9)**: github-release, release-monthly, sync-labels, stale, compress-images, common-cache, common-file-check, common-retry, codeql-analysis
|
||||
|
||||
**Utilities (3)**: version-file-parser, version-validator, validate-inputs
|
||||
|
||||
## Key Principles
|
||||
|
||||
### Self-Contained Design
|
||||
|
||||
- No dependencies between actions
|
||||
- Externally usable via GitHub Actions marketplace
|
||||
- Custom validators colocated with actions
|
||||
|
||||
### Quality Standards
|
||||
|
||||
- **Zero Tolerance**: No failing tests, no linting issues
|
||||
- **Production Ready**: Only when ALL checks pass
|
||||
- **EditorConfig**: 2-space indent, LF, UTF-8, max 200 chars (120 for MD)
|
||||
|
||||
### Security Model
|
||||
|
||||
- SHA-pinned external actions (55 SHA-pinned, 0 unpinned)
|
||||
- Token validation, injection detection
|
||||
- Path traversal protection
|
||||
- `set -euo pipefail` in all shell scripts
|
||||
|
||||
## Development Workflow
|
||||
|
||||
```bash
|
||||
make all # Full pipeline: docs, format, lint, test
|
||||
make dev # Format + lint
|
||||
make lint # All linters (markdownlint, yaml-lint, shellcheck, ruff)
|
||||
make test # All tests (pytest + ShellSpec)
|
||||
```
|
||||
|
||||
## Testing Framework
|
||||
|
||||
- **ShellSpec**: GitHub Actions and shell scripts
|
||||
- **pytest**: Python validators (769 tests, 100% pass rate)
|
||||
- **Test Generator**: Automatic scaffolding for new actions
|
||||
|
||||
## Current Status
|
||||
|
||||
- ✅ All tests passing (769/769)
|
||||
- ✅ Zero linting issues
|
||||
- ✅ Modular validator architecture
|
||||
- ✅ Convention-based validation
|
||||
- ✅ Test generation system
|
||||
- ✅ Full backward compatibility
|
||||
@@ -1,111 +0,0 @@
|
||||
# ShellSpec Test Fixes Tracking
|
||||
|
||||
## Status
|
||||
|
||||
**Branch**: feat/upgrades-and-restructuring
|
||||
**Date**: 2025-09-17
|
||||
**Progress**: Fixed critical test failures
|
||||
|
||||
## Summary
|
||||
|
||||
- Initial failing tests: 27 actions
|
||||
- **Fixed completely**: 3 actions (codeql-analysis, common-cache, common-file-check)
|
||||
- **Partially fixed**: Several others have reduced failures
|
||||
- **Key achievement**: Established patterns for fixing remaining tests
|
||||
|
||||
## ✅ Completed Fixes (3 actions)
|
||||
|
||||
### 1. codeql-analysis
|
||||
|
||||
- Created comprehensive CustomValidator
|
||||
- Fixed all language, token, path, and query validations
|
||||
- Result: **65 examples, 0 failures**
|
||||
|
||||
### 2. common-cache
|
||||
|
||||
- Created CustomValidator for comma-separated paths
|
||||
- Added cache type, paths, keys, env-vars validation
|
||||
- Result: **29 examples, 0 failures** (23 warnings)
|
||||
|
||||
### 3. common-file-check
|
||||
|
||||
- Created CustomValidator for glob patterns
|
||||
- Supports \*, ?, \*\*, {}, [] in file patterns
|
||||
- Result: **17 examples, 0 failures** (12 warnings)
|
||||
|
||||
## 🎯 Key Patterns Established
|
||||
|
||||
### CustomValidator Template
|
||||
|
||||
```python
|
||||
class CustomValidator(BaseValidator):
|
||||
def validate_inputs(self, inputs: dict[str, str]) -> bool:
|
||||
# Handle required inputs first
|
||||
# Use specific validation methods
|
||||
# Check for GitHub expressions: if "${{" in value
|
||||
# Validate security patterns
|
||||
return valid
|
||||
```
|
||||
|
||||
### Common Validation Patterns
|
||||
|
||||
1. **Token Validation**
|
||||
- ghp\_ tokens: 40-44 chars
|
||||
- github*pat* tokens: 82-95 chars
|
||||
- ghs\_ tokens: 40-44 chars
|
||||
|
||||
2. **Path Validation**
|
||||
- Reject absolute paths: `/path`
|
||||
- Reject traversal: `..`
|
||||
- Allow comma-separated: split and validate each
|
||||
|
||||
3. **Error Messages**
|
||||
- "Required input 'X' is missing"
|
||||
- "Absolute path not allowed"
|
||||
- "Path traversal detected"
|
||||
- "Command injection detected"
|
||||
|
||||
4. **Test Output**
|
||||
- Python logger outputs to stderr
|
||||
- Tests checking stdout need updating to stderr
|
||||
- Warnings about unexpected output are non-critical
|
||||
|
||||
## 📋 Remaining Work
|
||||
|
||||
### Quick Fixes (Similar patterns)
|
||||
|
||||
- common-retry: Add backoff-strategy, shell validation
|
||||
- compress-images: File pattern validation
|
||||
- eslint-check, prettier-fix: Token validation
|
||||
|
||||
### Docker Actions (Need CustomValidators)
|
||||
|
||||
- docker-build, docker-publish, docker-publish-gh, docker-publish-hub
|
||||
- Common issues: image-name, registry, platforms validation
|
||||
|
||||
### Version Detection Actions
|
||||
|
||||
- go-version-detect, python-version-detect, php-version-detect
|
||||
- Need version format validation
|
||||
|
||||
### Complex Actions (Need detailed CustomValidators)
|
||||
|
||||
- node-setup: Package manager, caching logic
|
||||
- pre-commit: Hook configuration
|
||||
- terraform-lint-fix: HCL-specific validation
|
||||
|
||||
## 🚀 Next Steps
|
||||
|
||||
To complete all fixes:
|
||||
|
||||
1. Create CustomValidators for remaining actions with failures
|
||||
2. Use established patterns for quick wins
|
||||
3. Test each action individually before full suite
|
||||
4. Update tests expecting stdout to check stderr where needed
|
||||
|
||||
## 📊 Success Criteria
|
||||
|
||||
- All ShellSpec tests pass (0 failures)
|
||||
- Warnings are acceptable (output format issues)
|
||||
- Maintain backward compatibility
|
||||
- Follow established validation patterns
|
||||
@@ -1,125 +0,0 @@
|
||||
# Task Completion Requirements
|
||||
|
||||
## Mandatory Steps After Completing Any Task
|
||||
|
||||
### 1. Linting (BLOCKING REQUIREMENT)
|
||||
|
||||
```bash
|
||||
make lint # Run all linters - must pass 100%
|
||||
```
|
||||
|
||||
**Critical Rules:**
|
||||
|
||||
- EditorConfig violations are BLOCKING errors - fix always
|
||||
- All linting issues are NOT ACCEPTABLE and must be resolved
|
||||
- Never simplify linting configuration to make tests pass
|
||||
- Linting tools decisions are final and must be obeyed
|
||||
- Consider ALL linting errors as blocking errors
|
||||
|
||||
**Specific Linting Steps:**
|
||||
|
||||
```bash
|
||||
make lint-markdown # Fix markdown issues
|
||||
make lint-yaml # Fix YAML issues
|
||||
make lint-shell # Fix shell script issues
|
||||
make lint-python # Fix Python code issues
|
||||
```
|
||||
|
||||
### 2. Testing (VERIFICATION REQUIREMENT)
|
||||
|
||||
```bash
|
||||
make test # Run all tests - must pass 100%
|
||||
```
|
||||
|
||||
**Test Categories:**
|
||||
|
||||
- Python validation tests (pytest)
|
||||
- GitHub Actions tests (ShellSpec)
|
||||
- Integration tests
|
||||
- Coverage reporting
|
||||
|
||||
### 3. Formatting (AUTO-FIX REQUIREMENT)
|
||||
|
||||
```bash
|
||||
make format # Auto-fix all formatting issues
|
||||
```
|
||||
|
||||
**Always use autofixers before running linters:**
|
||||
|
||||
- Markdown formatting and table formatting
|
||||
- YAML/JSON formatting with prettier
|
||||
- Python formatting with ruff
|
||||
- Line ending and whitespace fixes
|
||||
|
||||
## Verification Checklist
|
||||
|
||||
### Before Considering Task Complete
|
||||
|
||||
- [ ] `make lint` passes with zero issues
|
||||
- [ ] `make test` passes with 100% success
|
||||
- [ ] EditorConfig rules followed (2-space indent, LF endings, UTF-8)
|
||||
- [ ] No trailing whitespace or missing final newlines
|
||||
- [ ] Shell scripts pass shellcheck
|
||||
- [ ] Python code passes ruff with comprehensive rules
|
||||
- [ ] YAML files pass yaml-lint and actionlint
|
||||
- [ ] Markdown passes markdownlint-cli2
|
||||
|
||||
### Security and Quality Gates
|
||||
|
||||
- [ ] No secrets or credentials committed
|
||||
- [ ] No hardcoded tokens or API keys
|
||||
- [ ] Proper error handling with `set -euo pipefail`
|
||||
- [ ] External actions are SHA-pinned
|
||||
- [ ] Input validation through centralized system
|
||||
|
||||
## Error Resolution Strategy
|
||||
|
||||
### When Linting Fails
|
||||
|
||||
1. **Read the error message carefully** - don't ignore details
|
||||
2. **Read the linting tool schema** - understand the rules
|
||||
3. **Compare against schema** - schema is the truth
|
||||
4. **Fix the actual issue** - don't disable rules
|
||||
5. **Use autofix first** - `make format` before manual fixes
|
||||
|
||||
### When Tests Fail
|
||||
|
||||
1. **Fix all errors and warnings** - no exceptions
|
||||
2. **Ensure proper test coverage** - comprehensive testing required
|
||||
3. **Verify integration points** - actions must work together
|
||||
4. **Check validation logic** - centralized validation must pass
|
||||
|
||||
### Common Issues and Solutions
|
||||
|
||||
- **EditorConfig**: Use exactly 2 spaces, LF endings, UTF-8
|
||||
- **Python**: Follow Google docstring style, 100 char lines
|
||||
- **Shell**: Use shellcheck-compliant patterns
|
||||
- **YAML**: Proper indentation, no trailing spaces
|
||||
- **Markdown**: Tables formatted, links valid, consistent style
|
||||
|
||||
## Never Do These
|
||||
|
||||
- Never use `git commit` without explicit user request
|
||||
- Never use `--no-verify` flags
|
||||
- Never modify linting configuration to make tests pass
|
||||
- Never assume linting issues are acceptable
|
||||
- Never skip testing after code changes
|
||||
- Never create files unless absolutely necessary
|
||||
|
||||
## File Modification Preferences
|
||||
|
||||
- **Always prefer editing existing files** over creating new ones
|
||||
- **Never proactively create documentation** unless requested
|
||||
- **Read project patterns** before making changes
|
||||
- **Follow existing conventions** in the codebase
|
||||
- **Use centralized validation** for all input handling
|
||||
|
||||
## Final Verification
|
||||
|
||||
After ALL tasks are complete, run the full development cycle:
|
||||
|
||||
```bash
|
||||
make all # Complete workflow: docs, format, lint, test
|
||||
```
|
||||
|
||||
This ensures the project maintains its excellent state and all quality gates pass.
|
||||
76
.serena/memories/validator_system.md
Normal file
76
.serena/memories/validator_system.md
Normal file
@@ -0,0 +1,76 @@
|
||||
# Validation System Architecture
|
||||
|
||||
## Status: PRODUCTION READY ✅
|
||||
|
||||
- 769 tests passing (100%)
|
||||
- Zero linting issues
|
||||
- Modular architecture complete
|
||||
|
||||
## Architecture
|
||||
|
||||
### Core Components
|
||||
|
||||
- **BaseValidator**: Abstract interface for all validators
|
||||
- **ValidatorRegistry**: Dynamic discovery, loads custom validators from `<action>/CustomValidator.py`
|
||||
- **ConventionMapper**: Auto-detection via 100+ naming patterns (priority-based matching)
|
||||
|
||||
### Specialized Validators (9)
|
||||
|
||||
`token.py`, `version.py` (SemVer/CalVer), `boolean.py`, `numeric.py`, `docker.py`, `file.py`, `network.py`, `security.py`, `codeql.py`
|
||||
|
||||
### Custom Validators (20+)
|
||||
|
||||
Actions with complex validation have `CustomValidator.py` in their directory. Registry auto-discovers them.
|
||||
|
||||
Examples: `docker-build/CustomValidator.py`, `sync-labels/CustomValidator.py`, `codeql-analysis/CustomValidator.py`
|
||||
|
||||
## Convention-Based Detection
|
||||
|
||||
Automatic validator selection from input names:
|
||||
|
||||
- Priority 100: Exact (`dry-run` → boolean)
|
||||
- Priority 95: Language-specific (`-python-version` → python_version)
|
||||
- Priority 90: Suffixes (`-token` → token)
|
||||
- Priority 85: Contains (`email` → email)
|
||||
- Priority 80: Prefixes (`is-` → boolean)
|
||||
|
||||
## Test Generation
|
||||
|
||||
`validate-inputs/scripts/generate-tests.py`:
|
||||
|
||||
- Non-destructive (preserves existing tests)
|
||||
- Intelligent pattern detection for input types
|
||||
- Template-based scaffolding for validators
|
||||
- ShellSpec + pytest generation
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
from validators.registry import ValidatorRegistry
|
||||
validator = ValidatorRegistry().get_validator("docker-build")
|
||||
result = validator.validate_inputs({"context": ".", "platforms": "linux/amd64"})
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```text
|
||||
validate-inputs/
|
||||
├── validator.py # Main entry
|
||||
├── validators/ # 9 specialized + base + registry + conventions
|
||||
├── scripts/
|
||||
│ ├── update-validators.py # Rule generator
|
||||
│ └── generate-tests.py # Test generator
|
||||
└── tests/ # 769 pytest tests
|
||||
|
||||
<action>/CustomValidator.py # Action-specific validators
|
||||
```
|
||||
|
||||
## Key Features
|
||||
|
||||
- Convention-based auto-detection
|
||||
- GitHub expression support (`${{ }}`)
|
||||
- Error propagation between validators
|
||||
- Security validation (injection, secrets)
|
||||
- CalVer, SemVer, flexible versioning
|
||||
- Docker platforms, registries
|
||||
- Token formats (GitHub, NPM, PyPI)
|
||||
219
.serena/memories/versioning_system.md
Normal file
219
.serena/memories/versioning_system.md
Normal file
@@ -0,0 +1,219 @@
|
||||
# Version System Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
This repository uses a CalVer-based SHA-pinned versioning system for all internal action references.
|
||||
|
||||
## Version Format
|
||||
|
||||
### CalVer: vYYYY.MM.DD
|
||||
|
||||
- **Major**: `v2025` (year, updated annually)
|
||||
- **Minor**: `v2025.10` (year.month)
|
||||
- **Patch**: `v2025.10.18` (year.month.day)
|
||||
|
||||
Example: Release `v2025.10.18` creates three tags pointing to the same commit:
|
||||
|
||||
- `v2025.10.18` (patch - specific release)
|
||||
- `v2025.10` (minor - latest October 2025 release)
|
||||
- `v2025` (major - latest 2025 release)
|
||||
|
||||
## Internal vs External References
|
||||
|
||||
### Internal (action.yml files)
|
||||
|
||||
- **Format**: `ivuorinen/actions/validate-inputs@<40-char-SHA>`
|
||||
- **Purpose**: Security, reproducibility, precise control
|
||||
- **Example**: `ivuorinen/actions/validate-inputs@7061aafd35a2f21b57653e34f2b634b2a19334a9`
|
||||
|
||||
### External (user consumption)
|
||||
|
||||
- **Format**: `ivuorinen/actions/validate-inputs@v2025`
|
||||
- **Purpose**: Convenience, always gets latest release
|
||||
- **Options**: `@v2025`, `@v2025.10`, or `@v2025.10.18`
|
||||
|
||||
### Test Workflows
|
||||
|
||||
- **Format**: `uses: ./action-name` (local reference)
|
||||
- **Location**: `_tests/integration/workflows/*.yml`
|
||||
- **Reason**: Tests run within the actions repo context
|
||||
|
||||
### Internal Workflows
|
||||
|
||||
- **Format**: `uses: ./sync-labels` (local reference)
|
||||
- **Location**: `.github/workflows/sync-labels.yml`
|
||||
- **Reason**: Runs within the actions repo, local is sufficient
|
||||
|
||||
## Release Process
|
||||
|
||||
### Creating a Release
|
||||
|
||||
```bash
|
||||
# 1. Create release with version tags
|
||||
make release VERSION=v2025.10.18
|
||||
|
||||
# This automatically:
|
||||
# - Updates all action.yml SHA refs to current HEAD
|
||||
# - Commits the changes
|
||||
# - Creates tags: v2025.10.18, v2025.10, v2025
|
||||
# - All tags point to the same commit SHA
|
||||
|
||||
# 2. Push to remote
|
||||
git push origin main --tags --force-with-lease
|
||||
```
|
||||
|
||||
### After Each Release
|
||||
|
||||
Tags are force-pushed to ensure `v2025` and `v2025.10` always point to latest:
|
||||
|
||||
```bash
|
||||
git push origin v2025 --force
|
||||
git push origin v2025.10 --force
|
||||
git push origin v2025.10.18
|
||||
```
|
||||
|
||||
Or use `--tags --force-with-lease` to push all at once.
|
||||
|
||||
## Makefile Targets
|
||||
|
||||
### `make release VERSION=v2025.10.18`
|
||||
|
||||
Creates new release with version tags and updates all action references.
|
||||
|
||||
### `make update-version-refs MAJOR=v2025`
|
||||
|
||||
Updates all action.yml files to reference the SHA of the specified major version tag.
|
||||
|
||||
### `make bump-major-version OLD=v2025 NEW=v2026`
|
||||
|
||||
Annual version bump - replaces all references from one major version to another.
|
||||
|
||||
### `make check-version-refs`
|
||||
|
||||
Lists all current SHA-pinned references grouped by SHA. Useful for verification.
|
||||
|
||||
## Helper Scripts (\_tools/)
|
||||
|
||||
### release.sh
|
||||
|
||||
Main release script - validates version, updates refs, creates tags.
|
||||
|
||||
### validate-version.sh
|
||||
|
||||
Validates CalVer format (vYYYY.MM.DD, vYYYY.MM, vYYYY).
|
||||
|
||||
### update-action-refs.sh
|
||||
|
||||
Updates all action references to a specific SHA or version tag.
|
||||
|
||||
### bump-major-version.sh
|
||||
|
||||
Handles annual version bumps with commit creation.
|
||||
|
||||
### check-version-refs.sh
|
||||
|
||||
Displays current SHA-pinned references with tag information.
|
||||
|
||||
### get-action-sha.sh
|
||||
|
||||
Retrieves SHA for a specific version tag.
|
||||
|
||||
## Action Versioning Action
|
||||
|
||||
**Location**: `action-versioning/action.yml`
|
||||
|
||||
Automatically checks if major version tag has moved and updates all action references.
|
||||
|
||||
**Usage in CI**:
|
||||
|
||||
```yaml
|
||||
- uses: ./action-versioning
|
||||
with:
|
||||
major-version: v2025
|
||||
```
|
||||
|
||||
**Outputs**:
|
||||
|
||||
- `updated`: true/false
|
||||
- `commit-sha`: SHA of created commit (if any)
|
||||
- `needs-annual-bump`: true/false (year mismatch)
|
||||
|
||||
## CI Workflow
|
||||
|
||||
**File**: `.github/workflows/version-maintenance.yml`
|
||||
|
||||
**Triggers**:
|
||||
|
||||
- Weekly (Monday 9 AM UTC)
|
||||
- Manual (workflow_dispatch)
|
||||
|
||||
**Actions**:
|
||||
|
||||
1. Checks if `v2025` tag has moved
|
||||
2. Updates action references if needed
|
||||
3. Creates PR with changes
|
||||
4. Creates issue if annual bump needed
|
||||
|
||||
## Annual Version Bump
|
||||
|
||||
**When**: Start of each new year
|
||||
|
||||
**Process**:
|
||||
|
||||
```bash
|
||||
# 1. Create new major version tag
|
||||
git tag -a v2026 -m "Major version v2026"
|
||||
git push origin v2026
|
||||
|
||||
# 2. Bump all references
|
||||
make bump-major-version OLD=v2025 NEW=v2026
|
||||
|
||||
# 3. Update documentation
|
||||
make docs
|
||||
|
||||
# 4. Push changes
|
||||
git push origin main
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
### Check Current Refs
|
||||
|
||||
```bash
|
||||
make check-version-refs
|
||||
```
|
||||
|
||||
### Verify All Refs Match
|
||||
|
||||
All action references should point to the same SHA after a release.
|
||||
|
||||
### Test External Usage
|
||||
|
||||
Create a test repo and use:
|
||||
|
||||
```yaml
|
||||
uses: ivuorinen/actions/pr-lint@v2025
|
||||
```
|
||||
|
||||
## Migration from @main
|
||||
|
||||
All action.yml files have been migrated from:
|
||||
|
||||
- `uses: ./action-name`
|
||||
- `uses: ivuorinen/actions/action-name@main`
|
||||
|
||||
To:
|
||||
|
||||
- `uses: ivuorinen/actions/action-name@<SHA>`
|
||||
|
||||
Test workflows still use `./action-name` for local testing.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**SHA Pinning**: Prevents supply chain attacks by ensuring exact commit is used.
|
||||
|
||||
**Version Tags**: Provide user-friendly references while maintaining security internally.
|
||||
|
||||
**Tag Verification**: Always verify tags point to expected commits before force-pushing.
|
||||
|
||||
**Annual Review**: Each year requires conscious version bump, preventing accidental drift.
|
||||
@@ -4,10 +4,6 @@
|
||||
# * For JavaScript, use typescript
|
||||
# Special requirements:
|
||||
# * csharp: Requires the presence of a .sln file in the project folder.
|
||||
language: bash
|
||||
|
||||
# whether to use the project's gitignore file to ignore files
|
||||
# Added on 2025-04-07
|
||||
ignore_all_files_in_gitignore: true
|
||||
# list of additional paths to ignore
|
||||
# same syntax as gitignore, so you can use * and **
|
||||
@@ -66,3 +62,8 @@ excluded_tools: []
|
||||
initial_prompt: ''
|
||||
|
||||
project_name: 'actions'
|
||||
languages:
|
||||
- bash
|
||||
- python
|
||||
included_optional_tools: []
|
||||
encoding: utf-8
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
.venv
|
||||
.worktrees/
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
---
|
||||
extends: default
|
||||
|
||||
ignore: |
|
||||
node_modules/
|
||||
.worktrees/
|
||||
|
||||
rules:
|
||||
line-length:
|
||||
max: 200
|
||||
|
||||
77
CLAUDE.md
77
CLAUDE.md
@@ -31,15 +31,51 @@
|
||||
- `validate-inputs/` – Python validation system + tests
|
||||
- `*/rules.yml` – Auto-generated validation rules
|
||||
|
||||
### Memory System
|
||||
|
||||
**Location**: `.serena/memories/` (9 consolidated memories for context)
|
||||
|
||||
**When to Use**: Read memories at session start or when needed for specific context. Be token-efficient - read only relevant memories for the task.
|
||||
|
||||
**Core Memories** (read first for project understanding):
|
||||
|
||||
- `repository_overview` – 30 actions, categories, structure, status
|
||||
- `validator_system` – Validation architecture, components, usage patterns
|
||||
- `development_standards` – Quality rules, workflows, security, completion checklist
|
||||
|
||||
**Reference Guides** (read when working on specific areas):
|
||||
|
||||
- `code_style_conventions` – EditorConfig, Shell/Python/YAML style, 10 critical prevention rules
|
||||
- `suggested_commands` – Make targets, testing commands, tool usage
|
||||
- `tech_stack` – Python/Node.js/Shell tools, paths, versions
|
||||
|
||||
**GitHub Actions Reference** (read when working with workflows):
|
||||
|
||||
- `github-workflow-expressions` – Expression syntax, contexts, operators, common patterns
|
||||
- `github-workflow-commands` – Workflow commands (outputs, env, logging, masking)
|
||||
- `github-workflow-secure-use` – Security best practices, secrets, injection prevention
|
||||
|
||||
**Memory Maintenance**: Update existing memories rather than create new ones. Keep content token-efficient and factual.
|
||||
|
||||
### Documentation Locations
|
||||
|
||||
**Validation System**: `validate-inputs/docs/` (4 guides: API.md, DEVELOPER_GUIDE.md, ACTION_MAINTAINER.md, README_ARCHITECTURE.md)
|
||||
|
||||
**Testing**: `_tests/README.md` (ShellSpec framework, test patterns, running tests)
|
||||
|
||||
**Docker Tools**: `_tools/docker-testing-tools/README.md` (CI setup, pre-built testing image)
|
||||
|
||||
**See**: `documentation_guide` memory for detailed descriptions and when to read each
|
||||
|
||||
## Repository Structure
|
||||
|
||||
Flat structure. Each action self-contained with `action.yml`.
|
||||
|
||||
**43 Actions**: Setup (node-setup, set-git-config, php-version-detect, python-version-detect, python-version-detect-v2, go-version-detect, dotnet-version-detect), Utilities (version-file-parser, version-validator),
|
||||
Linting (ansible-lint-fix, biome-check, biome-fix, csharp-lint-check, eslint-check, eslint-fix, go-lint, pr-lint, pre-commit, prettier-check, prettier-fix, python-lint-fix, terraform-lint-fix),
|
||||
**30 Actions**: Setup (node-setup, language-version-detect), Utilities (action-versioning, version-file-parser),
|
||||
Linting (ansible-lint-fix, biome-lint, csharp-lint-check, eslint-lint, go-lint, pr-lint, pre-commit, prettier-lint, python-lint-fix, terraform-lint-fix),
|
||||
Testing (php-tests, php-laravel-phpunit, php-composer), Build (csharp-build, go-build, docker-build),
|
||||
Publishing (npm-publish, docker-publish, docker-publish-gh, docker-publish-hub, csharp-publish),
|
||||
Repository (github-release, release-monthly, sync-labels, stale, compress-images, common-cache, common-file-check, common-retry, codeql-analysis),
|
||||
Publishing (npm-publish, docker-publish, csharp-publish),
|
||||
Repository (release-monthly, sync-labels, stale, compress-images, common-cache, codeql-analysis),
|
||||
Validation (validate-inputs)
|
||||
|
||||
## Commands
|
||||
@@ -50,7 +86,12 @@ Validation (validate-inputs)
|
||||
|
||||
**Validation**: `make update-validators`, `make update-validators-dry`
|
||||
|
||||
**References**: `make check-local-refs`, `make fix-local-refs`, `make fix-local-refs-dry`
|
||||
**Versioning**:
|
||||
|
||||
- `make release [VERSION=vYYYY.MM.DD]` - Create release (auto-generates version from date if omitted)
|
||||
- `make update-version-refs MAJOR=vYYYY` - Update action refs to version
|
||||
- `make bump-major-version OLD=vYYYY NEW=vYYYY` - Annual version bump
|
||||
- `make check-version-refs` - Verify current action references
|
||||
|
||||
### Linters
|
||||
|
||||
@@ -69,24 +110,38 @@ Violations cause runtime failures:
|
||||
3. Sanitize `$GITHUB_OUTPUT`: use `printf '%s\n' "$val"` not `echo "$val"`
|
||||
4. Pin external actions to SHA commits (not `@main`/`@v1`)
|
||||
5. Quote shell vars: `"$var"`, `basename -- "$path"` (handles spaces)
|
||||
6. Use local paths: `./action-name` (not `owner/repo/action@main`)
|
||||
6. Use SHA-pinned refs for internal actions: `ivuorinen/actions/action-name@<SHA>`
|
||||
(security, not `./` or `@main`)
|
||||
7. Test regex edge cases (support `1.0.0-rc.1`, `1.0.0+build`)
|
||||
8. Use `set -euo pipefail` at script start
|
||||
8. Use `set -eu` (POSIX) in shell scripts (all scripts are POSIX sh, not bash)
|
||||
9. Never nest `${{ }}` in quoted YAML strings (breaks hashFiles)
|
||||
10. Provide tool fallbacks (macOS/Windows lack Linux tools)
|
||||
|
||||
### Core Requirements
|
||||
|
||||
- External actions SHA-pinned, use `${{ github.token }}`, `set -euo pipefail`
|
||||
- All actions SHA-pinned (external + internal), use `${{ github.token }}`, POSIX shell (`set -eu`)
|
||||
- EditorConfig: 2-space indent, UTF-8, LF, max 200 chars (120 for MD)
|
||||
- Auto-gen README via `action-docs` (note: `npx action-docs --update-readme` doesn't work)
|
||||
- Required error handling
|
||||
- Required error handling, POSIX-compliant scripts
|
||||
|
||||
### Action References
|
||||
|
||||
✅ `./action-name` | ❌ `../action-name` | ❌ `owner/repo/action@main`
|
||||
**Internal actions (in action.yml)**: SHA-pinned full references
|
||||
|
||||
Check: `make check-local-refs`, `make fix-local-refs`
|
||||
- ✅ `ivuorinen/actions/action-name@7061aafd35a2f21b57653e34f2b634b2a19334a9`
|
||||
- ❌ `./action-name` (security risk, not portable when used externally)
|
||||
- ❌ `owner/repo/action@main` (floating reference)
|
||||
|
||||
**Test workflows**: Local references
|
||||
|
||||
- ✅ `./action-name` (tests run within repo)
|
||||
- ❌ `../action-name` (ambiguous paths)
|
||||
|
||||
**External users**: Version tags
|
||||
|
||||
- ✅ `ivuorinen/actions/action-name@v2025` (CalVer major version)
|
||||
|
||||
Check: `make check-version-refs`
|
||||
|
||||
## Validation System
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Ismo Vuorinen
|
||||
Copyright (c) 2024-2025 Ismo Vuorinen
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
99
Makefile
99
Makefile
@@ -1,7 +1,7 @@
|
||||
# Makefile for GitHub Actions repository
|
||||
# Provides organized task management with parallel execution capabilities
|
||||
|
||||
.PHONY: help all docs lint format check clean install-tools test test-unit test-integration test-coverage generate-tests generate-tests-dry test-generate-tests docker-build docker-push docker-test docker-login docker-all
|
||||
.PHONY: help all docs update-catalog lint format check clean install-tools test test-unit test-integration test-coverage generate-tests generate-tests-dry test-generate-tests docker-build docker-push docker-test docker-login docker-all release release-dry release-prep release-tag release-undo update-version-refs bump-major-version check-version-refs
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# Colors for output
|
||||
@@ -43,7 +43,7 @@ help: ## Show this help message
|
||||
@echo " make check # Quick syntax checks"
|
||||
|
||||
# Main targets
|
||||
all: install-tools update-validators docs format lint precommit ## Generate docs, format, lint, and run pre-commit
|
||||
all: install-tools update-validators docs update-catalog format lint precommit ## Generate docs, format, lint, and run pre-commit
|
||||
@echo "$(GREEN)✅ All tasks completed successfully$(RESET)"
|
||||
|
||||
docs: ## Generate documentation for all actions
|
||||
@@ -66,6 +66,16 @@ docs: ## Generate documentation for all actions
|
||||
done; \
|
||||
[ $$failed -eq 0 ] && echo "$(GREEN)✅ All documentation updated successfully$(RESET)" || { echo "$(RED)❌ $$failed documentation updates failed$(RESET)"; exit 1; }
|
||||
|
||||
update-catalog: ## Update action catalog in README.md
|
||||
@echo "$(BLUE)📚 Updating action catalog...$(RESET)"
|
||||
@if command -v npm >/dev/null 2>&1; then \
|
||||
npm run update-catalog; \
|
||||
else \
|
||||
echo "$(RED)❌ npm not found. Please install Node.js$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "$(GREEN)✅ Action catalog updated$(RESET)"
|
||||
|
||||
update-validators: ## Update validation rules for all actions
|
||||
@echo "$(BLUE)🔧 Updating validation rules...$(RESET)"
|
||||
@if command -v uv >/dev/null 2>&1; then \
|
||||
@@ -145,10 +155,69 @@ fix-local-refs-dry: ## Preview local action reference fixes (dry run)
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
# Version management targets
|
||||
release: ## Create a new release with version tags (usage: make release [VERSION=v2025.10.18])
|
||||
@VERSION_TO_USE=$$(if [ -n "$(VERSION)" ]; then echo "$(VERSION)"; else date +v%Y.%m.%d; fi); \
|
||||
echo "$(BLUE)🚀 Creating release $$VERSION_TO_USE...$(RESET)"; \
|
||||
sh _tools/release.sh "$$VERSION_TO_USE"
|
||||
|
||||
release-dry: ## Preview release without making changes (usage: make release-dry VERSION=v2025.11.01)
|
||||
@if [ -z "$(VERSION)" ]; then \
|
||||
VERSION_TO_USE=$$(date +v%Y.%m.%d); \
|
||||
else \
|
||||
VERSION_TO_USE="$(VERSION)"; \
|
||||
fi; \
|
||||
echo "$(BLUE)🔍 Previewing release $$VERSION_TO_USE (dry run)...$(RESET)"; \
|
||||
sh _tools/release.sh --dry-run "$$VERSION_TO_USE"
|
||||
|
||||
release-prep: ## Update action refs and commit (no tags) (usage: make release-prep [VERSION=v2025.11.01])
|
||||
@VERSION_TO_USE=$$(if [ -n "$(VERSION)" ]; then echo "$(VERSION)"; else date +v%Y.%m.%d; fi); \
|
||||
echo "$(BLUE)🔧 Preparing release $$VERSION_TO_USE...$(RESET)"; \
|
||||
sh _tools/release.sh --prep-only "$$VERSION_TO_USE"; \
|
||||
echo "$(GREEN)✅ Preparation complete$(RESET)"; \
|
||||
echo "$(YELLOW)Next: make release-tag VERSION=$$VERSION_TO_USE$(RESET)"
|
||||
|
||||
release-tag: ## Create tags only (assumes prep done) (usage: make release-tag VERSION=v2025.11.01)
|
||||
@if [ -z "$(VERSION)" ]; then \
|
||||
echo "$(RED)❌ Error: VERSION parameter required for release-tag$(RESET)"; \
|
||||
echo "Usage: make release-tag VERSION=v2025.11.01"; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "$(BLUE)🏷️ Creating tags for release $(VERSION)...$(RESET)"; \
|
||||
sh _tools/release.sh --tag-only "$(VERSION)"
|
||||
|
||||
release-undo: ## Rollback the most recent release (delete tags and reset HEAD)
|
||||
@echo "$(BLUE)🔙 Rolling back release...$(RESET)"; \
|
||||
sh _tools/release-undo.sh
|
||||
|
||||
update-version-refs: ## Update all action references to a specific version tag (usage: make update-version-refs MAJOR=v2025)
|
||||
@if [ -z "$(MAJOR)" ]; then \
|
||||
echo "$(RED)❌ Error: MAJOR parameter required$(RESET)"; \
|
||||
echo "Usage: make update-version-refs MAJOR=v2025"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "$(BLUE)🔧 Updating action references to $(MAJOR)...$(RESET)"
|
||||
@sh _tools/update-action-refs.sh "$(MAJOR)"
|
||||
@echo "$(GREEN)✅ Action references updated$(RESET)"
|
||||
|
||||
bump-major-version: ## Replace one major version with another (usage: make bump-major-version OLD=v2025 NEW=v2026)
|
||||
@if [ -z "$(OLD)" ] || [ -z "$(NEW)" ]; then \
|
||||
echo "$(RED)❌ Error: OLD and NEW parameters required$(RESET)"; \
|
||||
echo "Usage: make bump-major-version OLD=v2025 NEW=v2026"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "$(BLUE)🔄 Bumping version from $(OLD) to $(NEW)...$(RESET)"
|
||||
@sh _tools/bump-major-version.sh "$(OLD)" "$(NEW)"
|
||||
@echo "$(GREEN)✅ Major version bumped$(RESET)"
|
||||
|
||||
check-version-refs: ## List all current SHA-pinned action references
|
||||
@echo "$(BLUE)🔍 Checking action references...$(RESET)"
|
||||
@sh _tools/check-version-refs.sh
|
||||
|
||||
# Formatting targets
|
||||
format-markdown: ## Format markdown files
|
||||
@echo "$(BLUE)📝 Formatting markdown...$(RESET)"
|
||||
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" 2>/dev/null; then \
|
||||
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" "#.worktrees" 2>/dev/null; then \
|
||||
echo "$(GREEN)✅ Markdown formatted$(RESET)"; \
|
||||
else \
|
||||
echo "$(YELLOW)⚠️ Markdown formatting issues found$(RESET)" | tee -a $(LOG_FILE); \
|
||||
@@ -200,7 +269,7 @@ format-python: ## Format Python files with ruff
|
||||
# Linting targets
|
||||
lint-markdown: ## Lint markdown files
|
||||
@echo "$(BLUE)🔍 Linting markdown...$(RESET)"
|
||||
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules"; then \
|
||||
@if npx --yes markdownlint-cli2 --fix "**/*.md" "#node_modules" "#.worktrees"; then \
|
||||
echo "$(GREEN)✅ Markdown linting passed$(RESET)"; \
|
||||
else \
|
||||
echo "$(YELLOW)⚠️ Markdown linting issues found$(RESET)" | tee -a $(LOG_FILE); \
|
||||
@@ -216,14 +285,17 @@ lint-yaml: ## Lint YAML files
|
||||
|
||||
lint-shell: ## Lint shell scripts
|
||||
@echo "$(BLUE)🔍 Linting shell scripts...$(RESET)"
|
||||
@if command -v shellcheck >/dev/null 2>&1; then \
|
||||
if find . -name "*.sh" -not -path "./_tests/*" -exec shellcheck -x {} + 2>/dev/null; then \
|
||||
echo "$(GREEN)✅ Shell linting passed$(RESET)"; \
|
||||
else \
|
||||
echo "$(YELLOW)⚠️ Shell linting issues found$(RESET)" | tee -a $(LOG_FILE); \
|
||||
fi; \
|
||||
@if ! command -v shellcheck >/dev/null 2>&1; then \
|
||||
echo "$(RED)❌ shellcheck not found. Please install shellcheck:$(RESET)"; \
|
||||
echo " brew install shellcheck"; \
|
||||
echo " or: apt-get install shellcheck"; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if find . -name "*.sh" -not -path "./_tests/*" -not -path "./.worktrees/*" -exec shellcheck -x {} +; then \
|
||||
echo "$(GREEN)✅ Shell linting passed$(RESET)"; \
|
||||
else \
|
||||
echo "$(BLUE)ℹ️ shellcheck not available, skipping shell script linting$(RESET)"; \
|
||||
echo "$(RED)❌ Shell linting issues found$(RESET)"; \
|
||||
exit 1; \
|
||||
fi
|
||||
|
||||
lint-python: ## Lint Python files with ruff and pyright
|
||||
@@ -268,7 +340,7 @@ check-tools: ## Check if required tools are available
|
||||
check-syntax: ## Check syntax of shell scripts and YAML files
|
||||
@echo "$(BLUE)🔍 Checking syntax...$(RESET)"
|
||||
@failed=0; \
|
||||
find . -name "*.sh" -print0 | while IFS= read -r -d '' file; do \
|
||||
find . -name "*.sh" -not -path "./_tests/*" -not -path "./.worktrees/*" -print0 | while IFS= read -r -d '' file; do \
|
||||
if ! bash -n "$$file" 2>&1; then \
|
||||
echo "$(RED)❌ Syntax error in $$file$(RESET)" >&2; \
|
||||
failed=1; \
|
||||
@@ -649,7 +721,8 @@ docker-all: docker-build docker-test docker-push ## Build, test, and push Docker
|
||||
watch: ## Watch files and auto-format on changes (requires entr)
|
||||
@if command -v entr >/dev/null 2>&1; then \
|
||||
echo "$(BLUE)👀 Watching for changes... (press Ctrl+C to stop)$(RESET)"; \
|
||||
find . -name "*.yml" -o -name "*.yaml" -o -name "*.md" -o -name "*.sh" | \
|
||||
find . \( -name "*.yml" -o -name "*.yaml" -o -name "*.md" -o -name "*.sh" \) \
|
||||
-not -path "./_tests/*" -not -path "./.worktrees/*" -not -path "./node_modules/*" | \
|
||||
entr -c $(MAKE) format; \
|
||||
else \
|
||||
echo "$(RED)❌ Error: entr not found. Install with: brew install entr$(RESET)"; \
|
||||
|
||||
318
README.md
318
README.md
@@ -22,92 +22,71 @@ Each action is fully self-contained and can be used independently in any GitHub
|
||||
|
||||
## 📚 Action Catalog
|
||||
|
||||
This repository contains **43 reusable GitHub Actions** for CI/CD automation.
|
||||
This repository contains **30 reusable GitHub Actions** for CI/CD automation.
|
||||
|
||||
### Quick Reference (43 Actions)
|
||||
### Quick Reference (30 Actions)
|
||||
|
||||
| Icon | Action | Category | Description | Key Features |
|
||||
|:----:|:-------------------------------------------------------|:-----------|:----------------------------------------------------------------|:---------------------------------------------|
|
||||
| 📦 | [`ansible-lint-fix`][ansible-lint-fix] | Linting | Lints and fixes Ansible playbooks, commits changes, and uplo... | Token auth, Outputs |
|
||||
| ✅ | [`biome-check`][biome-check] | Linting | Run Biome check on the repository | Token auth, Outputs |
|
||||
| ✅ | [`biome-fix`][biome-fix] | Linting | Run Biome fix on the repository | Token auth, Outputs |
|
||||
| 🛡️ | [`codeql-analysis`][codeql-analysis] | Other | Run CodeQL security analysis for a single language with conf... | Auto-detection, Token auth, Outputs |
|
||||
| 💾 | [`common-cache`][common-cache] | Repository | Standardized caching strategy for all actions | Caching, Outputs |
|
||||
| 📦 | [`common-file-check`][common-file-check] | Repository | A reusable action to check if a specific file or type of fil... | Outputs |
|
||||
| 🔄 | [`common-retry`][common-retry] | Repository | Standardized retry utility for network operations and flaky ... | Outputs |
|
||||
| 🖼️ | [`compress-images`][compress-images] | Repository | Compress images on demand (workflow_dispatch), and at 11pm e... | Token auth, Outputs |
|
||||
| 📝 | [`csharp-build`][csharp-build] | Build | Builds and tests C# projects. | Auto-detection, Outputs |
|
||||
| 📝 | [`csharp-lint-check`][csharp-lint-check] | Linting | Runs linters like StyleCop or dotnet-format for C# code styl... | Auto-detection, Outputs |
|
||||
| 📦 | [`csharp-publish`][csharp-publish] | Publishing | Publishes a C# project to GitHub Packages. | Auto-detection, Token auth, Outputs |
|
||||
| 📦 | [`docker-build`][docker-build] | Build | Builds a Docker image for multiple architectures with enhanc... | Caching, Auto-detection, Token auth, Outputs |
|
||||
| ☁️ | [`docker-publish`][docker-publish] | Publishing | Publish a Docker image to GitHub Packages and Docker Hub. | Auto-detection, Outputs |
|
||||
| 📦 | [`docker-publish-gh`][docker-publish-gh] | Publishing | Publishes a Docker image to GitHub Packages with advanced se... | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📦 | [`docker-publish-hub`][docker-publish-hub] | Publishing | Publishes a Docker image to Docker Hub with enhanced securit... | Caching, Auto-detection, Outputs |
|
||||
| 📝 | [`dotnet-version-detect`][dotnet-version-detect] | Setup | Detects .NET SDK version from global.json or defaults to a s... | Auto-detection, Outputs |
|
||||
| ✅ | [`eslint-check`][eslint-check] | Linting | Run ESLint check on the repository with advanced configurati... | Caching, Outputs |
|
||||
| 📝 | [`eslint-fix`][eslint-fix] | Linting | Fixes ESLint violations in a project. | Token auth, Outputs |
|
||||
| 🏷️ | [`github-release`][github-release] | Repository | Creates a GitHub release with a version and changelog. | Outputs |
|
||||
| 📦 | [`go-build`][go-build] | Build | Builds the Go project. | Caching, Auto-detection, Outputs |
|
||||
| 📝 | [`go-lint`][go-lint] | Linting | Run golangci-lint with advanced configuration, caching, and ... | Caching, Outputs |
|
||||
| 📝 | [`go-version-detect`][go-version-detect] | Setup | Detects the Go version from the project's go.mod file or def... | Auto-detection, Outputs |
|
||||
| 🖥️ | [`node-setup`][node-setup] | Setup | Sets up Node.js env with advanced version management, cachin... | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📦 | [`npm-publish`][npm-publish] | Publishing | Publishes the package to the NPM registry with configurable ... | Outputs |
|
||||
| 🖥️ | [`php-composer`][php-composer] | Testing | Runs Composer install on a repository with advanced caching ... | Auto-detection, Token auth, Outputs |
|
||||
| 💻 | [`php-laravel-phpunit`][php-laravel-phpunit] | Testing | Setup PHP, install dependencies, generate key, create databa... | Auto-detection, Token auth, Outputs |
|
||||
| ✅ | [`php-tests`][php-tests] | Testing | Run PHPUnit tests on the repository | Token auth, Outputs |
|
||||
| 📝 | [`php-version-detect`][php-version-detect] | Setup | Detects the PHP version from the project's composer.json, ph... | Auto-detection, Outputs |
|
||||
| ✅ | [`pr-lint`][pr-lint] | Linting | Runs MegaLinter against pull requests | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📦 | [`pre-commit`][pre-commit] | Linting | Runs pre-commit on the repository and pushes the fixes back ... | Auto-detection, Token auth, Outputs |
|
||||
| ✅ | [`prettier-check`][prettier-check] | Linting | Run Prettier check on the repository with advanced configura... | Caching, Outputs |
|
||||
| 📝 | [`prettier-fix`][prettier-fix] | Linting | Run Prettier to fix code style violations | Token auth, Outputs |
|
||||
| 📝 | [`python-lint-fix`][python-lint-fix] | Linting | Lints and fixes Python files, commits changes, and uploads S... | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📝 | [`python-version-detect`][python-version-detect] | Setup | Detects Python version from project configuration files or d... | Auto-detection, Outputs |
|
||||
| 📝 | [`python-version-detect-v2`][python-version-detect-v2] | Setup | Detects Python version from project configuration files usin... | Auto-detection, Outputs |
|
||||
| 📦 | [`release-monthly`][release-monthly] | Repository | Creates a release for the current month, incrementing patch ... | Token auth, Outputs |
|
||||
| 🔀 | [`set-git-config`][set-git-config] | Setup | Sets Git configuration for actions. | Token auth, Outputs |
|
||||
| 📦 | [`stale`][stale] | Repository | A GitHub Action to close stale issues and pull requests. | Token auth, Outputs |
|
||||
| 🏷️ | [`sync-labels`][sync-labels] | Repository | Sync labels from a YAML file to a GitHub repository | Token auth, Outputs |
|
||||
| 🖥️ | [`terraform-lint-fix`][terraform-lint-fix] | Linting | Lints and fixes Terraform files with advanced validation and... | Token auth, Outputs |
|
||||
| 🛡️ | [`validate-inputs`][validate-inputs] | Other | Centralized Python-based input validation for GitHub Actions... | Token auth, Outputs |
|
||||
| 📦 | [`version-file-parser`][version-file-parser] | Utilities | Universal parser for common version detection files (.tool-v... | Auto-detection, Outputs |
|
||||
| ✅ | [`version-validator`][version-validator] | Utilities | Validates and normalizes version strings using customizable ... | Auto-detection, Outputs |
|
||||
| Icon | Action | Category | Description | Key Features |
|
||||
|:----:|:-----------------------------------------------------|:-----------|:----------------------------------------------------------------|:---------------------------------------------|
|
||||
| 🔀 | [`action-versioning`][action-versioning] | Utilities | Automatically update SHA-pinned action references to match l... | Token auth, Outputs |
|
||||
| 📦 | [`ansible-lint-fix`][ansible-lint-fix] | Linting | Lints and fixes Ansible playbooks, commits changes, and uplo... | Token auth, Outputs |
|
||||
| ✅ | [`biome-lint`][biome-lint] | Linting | Run Biome linter in check or fix mode | Token auth, Outputs |
|
||||
| 🛡️ | [`codeql-analysis`][codeql-analysis] | Repository | Run CodeQL security analysis for a single language with conf... | Auto-detection, Token auth, Outputs |
|
||||
| 💾 | [`common-cache`][common-cache] | Repository | Standardized caching strategy for all actions | Caching, Outputs |
|
||||
| 🖼️ | [`compress-images`][compress-images] | Repository | Compress images on demand (workflow_dispatch), and at 11pm e... | Token auth, Outputs |
|
||||
| 📝 | [`csharp-build`][csharp-build] | Build | Builds and tests C# projects. | Auto-detection, Token auth, Outputs |
|
||||
| 📝 | [`csharp-lint-check`][csharp-lint-check] | Linting | Runs linters like StyleCop or dotnet-format for C# code styl... | Auto-detection, Token auth, Outputs |
|
||||
| 📦 | [`csharp-publish`][csharp-publish] | Publishing | Publishes a C# project to GitHub Packages. | Auto-detection, Token auth, Outputs |
|
||||
| 📦 | [`docker-build`][docker-build] | Build | Builds a Docker image for multiple architectures with enhanc... | Caching, Auto-detection, Token auth, Outputs |
|
||||
| ☁️ | [`docker-publish`][docker-publish] | Publishing | Simple wrapper to publish Docker images to GitHub Packages a... | Token auth, Outputs |
|
||||
| ✅ | [`eslint-lint`][eslint-lint] | Linting | Run ESLint in check or fix mode with advanced configuration ... | Caching, Token auth, Outputs |
|
||||
| 📦 | [`go-build`][go-build] | Build | Builds the Go project. | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📝 | [`go-lint`][go-lint] | Linting | Run golangci-lint with advanced configuration, caching, and ... | Caching, Token auth, Outputs |
|
||||
| 📝 | [`language-version-detect`][language-version-detect] | Setup | Detects language version from project configuration files wi... | Auto-detection, Token auth, Outputs |
|
||||
| 🖥️ | [`node-setup`][node-setup] | Setup | Sets up Node.js environment with version detection and packa... | Auto-detection, Token auth, Outputs |
|
||||
| 📦 | [`npm-publish`][npm-publish] | Publishing | Publishes the package to the NPM registry with configurable ... | Token auth, Outputs |
|
||||
| 🖥️ | [`php-composer`][php-composer] | Testing | Runs Composer install on a repository with advanced caching ... | Auto-detection, Token auth, Outputs |
|
||||
| 💻 | [`php-laravel-phpunit`][php-laravel-phpunit] | Testing | Setup PHP, install dependencies, generate key, create databa... | Auto-detection, Token auth, Outputs |
|
||||
| ✅ | [`php-tests`][php-tests] | Testing | Run PHPUnit tests on the repository | Token auth, Outputs |
|
||||
| ✅ | [`pr-lint`][pr-lint] | Linting | Runs MegaLinter against pull requests | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📦 | [`pre-commit`][pre-commit] | Linting | Runs pre-commit on the repository and pushes the fixes back ... | Auto-detection, Token auth, Outputs |
|
||||
| ✅ | [`prettier-lint`][prettier-lint] | Linting | Run Prettier in check or fix mode with advanced configuratio... | Caching, Token auth, Outputs |
|
||||
| 📝 | [`python-lint-fix`][python-lint-fix] | Linting | Lints and fixes Python files, commits changes, and uploads S... | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📦 | [`release-monthly`][release-monthly] | Repository | Creates a release for the current month, incrementing patch ... | Token auth, Outputs |
|
||||
| 📦 | [`stale`][stale] | Repository | A GitHub Action to close stale issues and pull requests. | Token auth, Outputs |
|
||||
| 🏷️ | [`sync-labels`][sync-labels] | Repository | Sync labels from a YAML file to a GitHub repository | Token auth, Outputs |
|
||||
| 🖥️ | [`terraform-lint-fix`][terraform-lint-fix] | Linting | Lints and fixes Terraform files with advanced validation and... | Token auth, Outputs |
|
||||
| 🛡️ | [`validate-inputs`][validate-inputs] | Validation | Centralized Python-based input validation for GitHub Actions... | Token auth, Outputs |
|
||||
| 📦 | [`version-file-parser`][version-file-parser] | Utilities | Universal parser for common version detection files (.tool-v... | Auto-detection, Outputs |
|
||||
|
||||
### Actions by Category
|
||||
|
||||
#### 🔧 Setup (7 actions)
|
||||
#### 🔧 Setup (2 actions)
|
||||
|
||||
| Action | Description | Languages | Features |
|
||||
|:----------------------------------------------------------|:------------------------------------------------------|:--------------------------------|:---------------------------------------------|
|
||||
| 📝 [`dotnet-version-detect`][dotnet-version-detect] | Detects .NET SDK version from global.json or defau... | C#, .NET | Auto-detection, Outputs |
|
||||
| 📝 [`go-version-detect`][go-version-detect] | Detects the Go version from the project's go.mod f... | Go | Auto-detection, Outputs |
|
||||
| 🖥️ [`node-setup`][node-setup] | Sets up Node.js env with advanced version manageme... | Node.js, JavaScript, TypeScript | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📝 [`php-version-detect`][php-version-detect] | Detects the PHP version from the project's compose... | PHP | Auto-detection, Outputs |
|
||||
| 📝 [`python-version-detect`][python-version-detect] | Detects Python version from project configuration ... | Python | Auto-detection, Outputs |
|
||||
| 📝 [`python-version-detect-v2`][python-version-detect-v2] | Detects Python version from project configuration ... | Python | Auto-detection, Outputs |
|
||||
| 🔀 [`set-git-config`][set-git-config] | Sets Git configuration for actions. | - | Token auth, Outputs |
|
||||
| Action | Description | Languages | Features |
|
||||
|:--------------------------------------------------------|:------------------------------------------------------|:--------------------------------|:------------------------------------|
|
||||
| 📝 [`language-version-detect`][language-version-detect] | Detects language version from project configuratio... | PHP, Python, Go, .NET, Node.js | Auto-detection, Token auth, Outputs |
|
||||
| 🖥️ [`node-setup`][node-setup] | Sets up Node.js environment with version detection... | Node.js, JavaScript, TypeScript | Auto-detection, Token auth, Outputs |
|
||||
|
||||
#### 🛠️ Utilities (2 actions)
|
||||
|
||||
| Action | Description | Languages | Features |
|
||||
|:------------------------------------------------|:------------------------------------------------------|:----------|:------------------------|
|
||||
| 📦 [`version-file-parser`][version-file-parser] | Universal parser for common version detection file... | - | Auto-detection, Outputs |
|
||||
| ✅ [`version-validator`][version-validator] | Validates and normalizes version strings using cus... | - | Auto-detection, Outputs |
|
||||
| Action | Description | Languages | Features |
|
||||
|:------------------------------------------------|:------------------------------------------------------|:-------------------|:------------------------|
|
||||
| 🔀 [`action-versioning`][action-versioning] | Automatically update SHA-pinned action references ... | GitHub Actions | Token auth, Outputs |
|
||||
| 📦 [`version-file-parser`][version-file-parser] | Universal parser for common version detection file... | Multiple Languages | Auto-detection, Outputs |
|
||||
|
||||
#### 📝 Linting (13 actions)
|
||||
#### 📝 Linting (10 actions)
|
||||
|
||||
| Action | Description | Languages | Features |
|
||||
|:-----------------------------------------------|:------------------------------------------------------|:---------------------------------------------|:---------------------------------------------|
|
||||
| 📦 [`ansible-lint-fix`][ansible-lint-fix] | Lints and fixes Ansible playbooks, commits changes... | Ansible, YAML | Token auth, Outputs |
|
||||
| ✅ [`biome-check`][biome-check] | Run Biome check on the repository | JavaScript, TypeScript, JSON | Token auth, Outputs |
|
||||
| ✅ [`biome-fix`][biome-fix] | Run Biome fix on the repository | JavaScript, TypeScript, JSON | Token auth, Outputs |
|
||||
| 📝 [`csharp-lint-check`][csharp-lint-check] | Runs linters like StyleCop or dotnet-format for C#... | C#, .NET | Auto-detection, Outputs |
|
||||
| ✅ [`eslint-check`][eslint-check] | Run ESLint check on the repository with advanced c... | JavaScript, TypeScript | Caching, Outputs |
|
||||
| 📝 [`eslint-fix`][eslint-fix] | Fixes ESLint violations in a project. | JavaScript, TypeScript | Token auth, Outputs |
|
||||
| 📝 [`go-lint`][go-lint] | Run golangci-lint with advanced configuration, cac... | Go | Caching, Outputs |
|
||||
| ✅ [`pr-lint`][pr-lint] | Runs MegaLinter against pull requests | - | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📦 [`pre-commit`][pre-commit] | Runs pre-commit on the repository and pushes the f... | - | Auto-detection, Token auth, Outputs |
|
||||
| ✅ [`prettier-check`][prettier-check] | Run Prettier check on the repository with advanced... | JavaScript, TypeScript, Markdown, YAML, JSON | Caching, Outputs |
|
||||
| 📝 [`prettier-fix`][prettier-fix] | Run Prettier to fix code style violations | JavaScript, TypeScript, Markdown, YAML, JSON | Token auth, Outputs |
|
||||
| ✅ [`biome-lint`][biome-lint] | Run Biome linter in check or fix mode | JavaScript, TypeScript, JSON | Token auth, Outputs |
|
||||
| 📝 [`csharp-lint-check`][csharp-lint-check] | Runs linters like StyleCop or dotnet-format for C#... | C#, .NET | Auto-detection, Token auth, Outputs |
|
||||
| ✅ [`eslint-lint`][eslint-lint] | Run ESLint in check or fix mode with advanced conf... | JavaScript, TypeScript | Caching, Token auth, Outputs |
|
||||
| 📝 [`go-lint`][go-lint] | Run golangci-lint with advanced configuration, cac... | Go | Caching, Token auth, Outputs |
|
||||
| ✅ [`pr-lint`][pr-lint] | Runs MegaLinter against pull requests | Conventional Commits | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📦 [`pre-commit`][pre-commit] | Runs pre-commit on the repository and pushes the f... | Python, Multiple Languages | Auto-detection, Token auth, Outputs |
|
||||
| ✅ [`prettier-lint`][prettier-lint] | Run Prettier in check or fix mode with advanced co... | JavaScript, TypeScript, Markdown, YAML, JSON | Caching, 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 |
|
||||
|
||||
@@ -123,102 +102,102 @@ This repository contains **43 reusable GitHub Actions** for CI/CD automation.
|
||||
|
||||
| Action | Description | Languages | Features |
|
||||
|:----------------------------------|:------------------------------------------------------|:----------|:---------------------------------------------|
|
||||
| 📝 [`csharp-build`][csharp-build] | Builds and tests C# projects. | C#, .NET | Auto-detection, Outputs |
|
||||
| 📝 [`csharp-build`][csharp-build] | Builds and tests C# projects. | C#, .NET | Auto-detection, Token auth, Outputs |
|
||||
| 📦 [`docker-build`][docker-build] | Builds a Docker image for multiple architectures w... | Docker | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📦 [`go-build`][go-build] | Builds the Go project. | Go | Caching, Auto-detection, Outputs |
|
||||
| 📦 [`go-build`][go-build] | Builds the Go project. | Go | Caching, Auto-detection, Token auth, Outputs |
|
||||
|
||||
#### 🚀 Publishing (5 actions)
|
||||
#### 🚀 Publishing (3 actions)
|
||||
|
||||
| Action | Description | Languages | Features |
|
||||
|:----------------------------------------------|:------------------------------------------------------|:-------------|:---------------------------------------------|
|
||||
| 📦 [`csharp-publish`][csharp-publish] | Publishes a C# project to GitHub Packages. | C#, .NET | Auto-detection, Token auth, Outputs |
|
||||
| ☁️ [`docker-publish`][docker-publish] | Publish a Docker image to GitHub Packages and Dock... | Docker | Auto-detection, Outputs |
|
||||
| 📦 [`docker-publish-gh`][docker-publish-gh] | Publishes a Docker image to GitHub Packages with a... | Docker | Caching, Auto-detection, Token auth, Outputs |
|
||||
| 📦 [`docker-publish-hub`][docker-publish-hub] | Publishes a Docker image to Docker Hub with enhanc... | Docker | Caching, Auto-detection, Outputs |
|
||||
| 📦 [`npm-publish`][npm-publish] | Publishes the package to the NPM registry with con... | Node.js, npm | Outputs |
|
||||
| Action | Description | Languages | Features |
|
||||
|:--------------------------------------|:------------------------------------------------------|:-------------|:------------------------------------|
|
||||
| 📦 [`csharp-publish`][csharp-publish] | Publishes a C# project to GitHub Packages. | C#, .NET | Auto-detection, Token auth, Outputs |
|
||||
| ☁️ [`docker-publish`][docker-publish] | Simple wrapper to publish Docker images to GitHub ... | Docker | Token auth, Outputs |
|
||||
| 📦 [`npm-publish`][npm-publish] | Publishes the package to the NPM registry with con... | Node.js, npm | Token auth, Outputs |
|
||||
|
||||
#### 📦 Repository (8 actions)
|
||||
#### 📦 Repository (6 actions)
|
||||
|
||||
| Action | Description | Languages | Features |
|
||||
|:--------------------------------------------|:------------------------------------------------------|:----------|:--------------------|
|
||||
| 💾 [`common-cache`][common-cache] | Standardized caching strategy for all actions | - | Caching, Outputs |
|
||||
| 📦 [`common-file-check`][common-file-check] | A reusable action to check if a specific file or t... | - | Outputs |
|
||||
| 🔄 [`common-retry`][common-retry] | Standardized retry utility for network operations ... | - | Outputs |
|
||||
| 🖼️ [`compress-images`][compress-images] | Compress images on demand (workflow_dispatch), and... | - | Token auth, Outputs |
|
||||
| 🏷️ [`github-release`][github-release] | Creates a GitHub release with a version and change... | - | Outputs |
|
||||
| 📦 [`release-monthly`][release-monthly] | Creates a release for the current month, increment... | - | Token auth, Outputs |
|
||||
| 📦 [`stale`][stale] | A GitHub Action to close stale issues and pull req... | - | Token auth, Outputs |
|
||||
| 🏷️ [`sync-labels`][sync-labels] | Sync labels from a YAML file to a GitHub repositor... | - | Token auth, Outputs |
|
||||
| Action | Description | Languages | Features |
|
||||
|:-----------------------------------------|:------------------------------------------------------|:--------------------------------------------------------|:------------------------------------|
|
||||
| 🛡️ [`codeql-analysis`][codeql-analysis] | Run CodeQL security analysis for a single language... | JavaScript, TypeScript, Python, Java, C#, C++, Go, Ruby | Auto-detection, Token auth, Outputs |
|
||||
| 💾 [`common-cache`][common-cache] | Standardized caching strategy for all actions | Caching | Caching, Outputs |
|
||||
| 🖼️ [`compress-images`][compress-images] | Compress images on demand (workflow_dispatch), and... | Images, PNG, JPEG | Token auth, Outputs |
|
||||
| 📦 [`release-monthly`][release-monthly] | Creates a release for the current month, increment... | GitHub Actions | Token auth, Outputs |
|
||||
| 📦 [`stale`][stale] | A GitHub Action to close stale issues and pull req... | GitHub Actions | Token auth, Outputs |
|
||||
| 🏷️ [`sync-labels`][sync-labels] | Sync labels from a YAML file to a GitHub repositor... | YAML, GitHub | Token auth, Outputs |
|
||||
|
||||
#### ✅ Validation (1 action)
|
||||
|
||||
| Action | Description | Languages | Features |
|
||||
|:-----------------------------------------|:------------------------------------------------------|:---------------------|:--------------------|
|
||||
| 🛡️ [`validate-inputs`][validate-inputs] | Centralized Python-based input validation for GitH... | YAML, GitHub Actions | Token auth, Outputs |
|
||||
|
||||
### Feature Matrix
|
||||
|
||||
| Action | Caching | Auto-detection | Token auth | Outputs |
|
||||
|:-------------------------------------------------------|:-------:|:--------------:|:----------:|:-------:|
|
||||
| [`ansible-lint-fix`][ansible-lint-fix] | - | - | ✅ | ✅ |
|
||||
| [`biome-check`][biome-check] | - | - | ✅ | ✅ |
|
||||
| [`biome-fix`][biome-fix] | - | - | ✅ | ✅ |
|
||||
| [`codeql-analysis`][codeql-analysis] | - | ✅ | ✅ | ✅ |
|
||||
| [`common-cache`][common-cache] | ✅ | - | - | ✅ |
|
||||
| [`common-file-check`][common-file-check] | - | - | - | ✅ |
|
||||
| [`common-retry`][common-retry] | - | - | - | ✅ |
|
||||
| [`compress-images`][compress-images] | - | - | ✅ | ✅ |
|
||||
| [`csharp-build`][csharp-build] | - | ✅ | - | ✅ |
|
||||
| [`csharp-lint-check`][csharp-lint-check] | - | ✅ | - | ✅ |
|
||||
| [`csharp-publish`][csharp-publish] | - | ✅ | ✅ | ✅ |
|
||||
| [`docker-build`][docker-build] | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`docker-publish`][docker-publish] | - | ✅ | - | ✅ |
|
||||
| [`docker-publish-gh`][docker-publish-gh] | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`docker-publish-hub`][docker-publish-hub] | ✅ | ✅ | - | ✅ |
|
||||
| [`dotnet-version-detect`][dotnet-version-detect] | - | ✅ | - | ✅ |
|
||||
| [`eslint-check`][eslint-check] | ✅ | - | - | ✅ |
|
||||
| [`eslint-fix`][eslint-fix] | - | - | ✅ | ✅ |
|
||||
| [`github-release`][github-release] | - | - | - | ✅ |
|
||||
| [`go-build`][go-build] | ✅ | ✅ | - | ✅ |
|
||||
| [`go-lint`][go-lint] | ✅ | - | - | ✅ |
|
||||
| [`go-version-detect`][go-version-detect] | - | ✅ | - | ✅ |
|
||||
| [`node-setup`][node-setup] | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`npm-publish`][npm-publish] | - | - | - | ✅ |
|
||||
| [`php-composer`][php-composer] | - | ✅ | ✅ | ✅ |
|
||||
| [`php-laravel-phpunit`][php-laravel-phpunit] | - | ✅ | ✅ | ✅ |
|
||||
| [`php-tests`][php-tests] | - | - | ✅ | ✅ |
|
||||
| [`php-version-detect`][php-version-detect] | - | ✅ | - | ✅ |
|
||||
| [`pr-lint`][pr-lint] | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`pre-commit`][pre-commit] | - | ✅ | ✅ | ✅ |
|
||||
| [`prettier-check`][prettier-check] | ✅ | - | - | ✅ |
|
||||
| [`prettier-fix`][prettier-fix] | - | - | ✅ | ✅ |
|
||||
| [`python-lint-fix`][python-lint-fix] | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`python-version-detect`][python-version-detect] | - | ✅ | - | ✅ |
|
||||
| [`python-version-detect-v2`][python-version-detect-v2] | - | ✅ | - | ✅ |
|
||||
| [`release-monthly`][release-monthly] | - | - | ✅ | ✅ |
|
||||
| [`set-git-config`][set-git-config] | - | - | ✅ | ✅ |
|
||||
| [`stale`][stale] | - | - | ✅ | ✅ |
|
||||
| [`sync-labels`][sync-labels] | - | - | ✅ | ✅ |
|
||||
| [`terraform-lint-fix`][terraform-lint-fix] | - | - | ✅ | ✅ |
|
||||
| [`validate-inputs`][validate-inputs] | - | - | ✅ | ✅ |
|
||||
| [`version-file-parser`][version-file-parser] | - | ✅ | - | ✅ |
|
||||
| [`version-validator`][version-validator] | - | ✅ | - | ✅ |
|
||||
| Action | Caching | Auto-detection | Token auth | Outputs |
|
||||
|:-----------------------------------------------------|:-------:|:--------------:|:----------:|:-------:|
|
||||
| [`action-versioning`][action-versioning] | - | - | ✅ | ✅ |
|
||||
| [`ansible-lint-fix`][ansible-lint-fix] | - | - | ✅ | ✅ |
|
||||
| [`biome-lint`][biome-lint] | - | - | ✅ | ✅ |
|
||||
| [`codeql-analysis`][codeql-analysis] | - | ✅ | ✅ | ✅ |
|
||||
| [`common-cache`][common-cache] | ✅ | - | - | ✅ |
|
||||
| [`compress-images`][compress-images] | - | - | ✅ | ✅ |
|
||||
| [`csharp-build`][csharp-build] | - | ✅ | ✅ | ✅ |
|
||||
| [`csharp-lint-check`][csharp-lint-check] | - | ✅ | ✅ | ✅ |
|
||||
| [`csharp-publish`][csharp-publish] | - | ✅ | ✅ | ✅ |
|
||||
| [`docker-build`][docker-build] | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`docker-publish`][docker-publish] | - | - | ✅ | ✅ |
|
||||
| [`eslint-lint`][eslint-lint] | ✅ | - | ✅ | ✅ |
|
||||
| [`go-build`][go-build] | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`go-lint`][go-lint] | ✅ | - | ✅ | ✅ |
|
||||
| [`language-version-detect`][language-version-detect] | - | ✅ | ✅ | ✅ |
|
||||
| [`node-setup`][node-setup] | - | ✅ | ✅ | ✅ |
|
||||
| [`npm-publish`][npm-publish] | - | - | ✅ | ✅ |
|
||||
| [`php-composer`][php-composer] | - | ✅ | ✅ | ✅ |
|
||||
| [`php-laravel-phpunit`][php-laravel-phpunit] | - | ✅ | ✅ | ✅ |
|
||||
| [`php-tests`][php-tests] | - | - | ✅ | ✅ |
|
||||
| [`pr-lint`][pr-lint] | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`pre-commit`][pre-commit] | - | ✅ | ✅ | ✅ |
|
||||
| [`prettier-lint`][prettier-lint] | ✅ | - | ✅ | ✅ |
|
||||
| [`python-lint-fix`][python-lint-fix] | ✅ | ✅ | ✅ | ✅ |
|
||||
| [`release-monthly`][release-monthly] | - | - | ✅ | ✅ |
|
||||
| [`stale`][stale] | - | - | ✅ | ✅ |
|
||||
| [`sync-labels`][sync-labels] | - | - | ✅ | ✅ |
|
||||
| [`terraform-lint-fix`][terraform-lint-fix] | - | - | ✅ | ✅ |
|
||||
| [`validate-inputs`][validate-inputs] | - | - | ✅ | ✅ |
|
||||
| [`version-file-parser`][version-file-parser] | - | ✅ | - | ✅ |
|
||||
|
||||
### Language Support
|
||||
|
||||
| Language | Actions |
|
||||
|:-----------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| .NET | [`csharp-build`][csharp-build], [`csharp-lint-check`][csharp-lint-check], [`csharp-publish`][csharp-publish], [`dotnet-version-detect`][dotnet-version-detect] |
|
||||
| Ansible | [`ansible-lint-fix`][ansible-lint-fix] |
|
||||
| C# | [`csharp-build`][csharp-build], [`csharp-lint-check`][csharp-lint-check], [`csharp-publish`][csharp-publish], [`dotnet-version-detect`][dotnet-version-detect] |
|
||||
| Docker | [`docker-build`][docker-build], [`docker-publish`][docker-publish], [`docker-publish-gh`][docker-publish-gh], [`docker-publish-hub`][docker-publish-hub] |
|
||||
| Go | [`go-build`][go-build], [`go-lint`][go-lint], [`go-version-detect`][go-version-detect] |
|
||||
| HCL | [`terraform-lint-fix`][terraform-lint-fix] |
|
||||
| JSON | [`biome-check`][biome-check], [`biome-fix`][biome-fix], [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
|
||||
| JavaScript | [`biome-check`][biome-check], [`biome-fix`][biome-fix], [`eslint-check`][eslint-check], [`eslint-fix`][eslint-fix], [`node-setup`][node-setup], [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
|
||||
| Laravel | [`php-laravel-phpunit`][php-laravel-phpunit] |
|
||||
| Markdown | [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
|
||||
| Node.js | [`node-setup`][node-setup], [`npm-publish`][npm-publish] |
|
||||
| PHP | [`php-composer`][php-composer], [`php-laravel-phpunit`][php-laravel-phpunit], [`php-tests`][php-tests], [`php-version-detect`][php-version-detect] |
|
||||
| Python | [`python-lint-fix`][python-lint-fix], [`python-version-detect`][python-version-detect], [`python-version-detect-v2`][python-version-detect-v2] |
|
||||
| Terraform | [`terraform-lint-fix`][terraform-lint-fix] |
|
||||
| TypeScript | [`biome-check`][biome-check], [`biome-fix`][biome-fix], [`eslint-check`][eslint-check], [`eslint-fix`][eslint-fix], [`node-setup`][node-setup], [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
|
||||
| YAML | [`ansible-lint-fix`][ansible-lint-fix], [`prettier-check`][prettier-check], [`prettier-fix`][prettier-fix] |
|
||||
| npm | [`npm-publish`][npm-publish] |
|
||||
| Language | Actions |
|
||||
|:---------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| .NET | [`csharp-build`][csharp-build], [`csharp-lint-check`][csharp-lint-check], [`csharp-publish`][csharp-publish], [`language-version-detect`][language-version-detect] |
|
||||
| Ansible | [`ansible-lint-fix`][ansible-lint-fix] |
|
||||
| C# | [`codeql-analysis`][codeql-analysis], [`csharp-build`][csharp-build], [`csharp-lint-check`][csharp-lint-check], [`csharp-publish`][csharp-publish] |
|
||||
| C++ | [`codeql-analysis`][codeql-analysis] |
|
||||
| Caching | [`common-cache`][common-cache] |
|
||||
| Conventional Commits | [`pr-lint`][pr-lint] |
|
||||
| Docker | [`docker-build`][docker-build], [`docker-publish`][docker-publish] |
|
||||
| GitHub | [`sync-labels`][sync-labels] |
|
||||
| GitHub Actions | [`action-versioning`][action-versioning], [`release-monthly`][release-monthly], [`stale`][stale], [`validate-inputs`][validate-inputs] |
|
||||
| Go | [`codeql-analysis`][codeql-analysis], [`go-build`][go-build], [`go-lint`][go-lint], [`language-version-detect`][language-version-detect] |
|
||||
| HCL | [`terraform-lint-fix`][terraform-lint-fix] |
|
||||
| Images | [`compress-images`][compress-images] |
|
||||
| JPEG | [`compress-images`][compress-images] |
|
||||
| JSON | [`biome-lint`][biome-lint], [`prettier-lint`][prettier-lint] |
|
||||
| Java | [`codeql-analysis`][codeql-analysis] |
|
||||
| JavaScript | [`biome-lint`][biome-lint], [`codeql-analysis`][codeql-analysis], [`eslint-lint`][eslint-lint], [`node-setup`][node-setup], [`prettier-lint`][prettier-lint] |
|
||||
| Laravel | [`php-laravel-phpunit`][php-laravel-phpunit] |
|
||||
| Markdown | [`prettier-lint`][prettier-lint] |
|
||||
| Multiple Languages | [`pre-commit`][pre-commit], [`version-file-parser`][version-file-parser] |
|
||||
| Node.js | [`language-version-detect`][language-version-detect], [`node-setup`][node-setup], [`npm-publish`][npm-publish] |
|
||||
| PHP | [`language-version-detect`][language-version-detect], [`php-composer`][php-composer], [`php-laravel-phpunit`][php-laravel-phpunit], [`php-tests`][php-tests] |
|
||||
| PNG | [`compress-images`][compress-images] |
|
||||
| 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], [`node-setup`][node-setup], [`prettier-lint`][prettier-lint] |
|
||||
| YAML | [`ansible-lint-fix`][ansible-lint-fix], [`prettier-lint`][prettier-lint], [`sync-labels`][sync-labels], [`validate-inputs`][validate-inputs] |
|
||||
| npm | [`npm-publish`][npm-publish] |
|
||||
|
||||
### Action Usage
|
||||
|
||||
@@ -226,7 +205,7 @@ All actions can be used independently in your workflows:
|
||||
|
||||
```yaml
|
||||
# Recommended: Use pinned refs for supply-chain security
|
||||
- uses: ivuorinen/actions/action-name@2025-01-15 # Date-based tag
|
||||
- uses: ivuorinen/actions/action-name@vYYYY-MM-DD # Date-based tag (example)
|
||||
with:
|
||||
# action-specific inputs
|
||||
|
||||
@@ -240,49 +219,36 @@ All actions can be used independently in your workflows:
|
||||
|
||||
<!-- Reference Links -->
|
||||
|
||||
[action-versioning]: action-versioning/README.md
|
||||
[ansible-lint-fix]: ansible-lint-fix/README.md
|
||||
[biome-check]: biome-check/README.md
|
||||
[biome-fix]: biome-fix/README.md
|
||||
[biome-lint]: biome-lint/README.md
|
||||
[codeql-analysis]: codeql-analysis/README.md
|
||||
[common-cache]: common-cache/README.md
|
||||
[common-file-check]: common-file-check/README.md
|
||||
[common-retry]: common-retry/README.md
|
||||
[compress-images]: compress-images/README.md
|
||||
[csharp-build]: csharp-build/README.md
|
||||
[csharp-lint-check]: csharp-lint-check/README.md
|
||||
[csharp-publish]: csharp-publish/README.md
|
||||
[docker-build]: docker-build/README.md
|
||||
[docker-publish]: docker-publish/README.md
|
||||
[docker-publish-gh]: docker-publish-gh/README.md
|
||||
[docker-publish-hub]: docker-publish-hub/README.md
|
||||
[dotnet-version-detect]: dotnet-version-detect/README.md
|
||||
[eslint-check]: eslint-check/README.md
|
||||
[eslint-fix]: eslint-fix/README.md
|
||||
[github-release]: github-release/README.md
|
||||
[eslint-lint]: eslint-lint/README.md
|
||||
[go-build]: go-build/README.md
|
||||
[go-lint]: go-lint/README.md
|
||||
[go-version-detect]: go-version-detect/README.md
|
||||
[language-version-detect]: language-version-detect/README.md
|
||||
[node-setup]: node-setup/README.md
|
||||
[npm-publish]: npm-publish/README.md
|
||||
[php-composer]: php-composer/README.md
|
||||
[php-laravel-phpunit]: php-laravel-phpunit/README.md
|
||||
[php-tests]: php-tests/README.md
|
||||
[php-version-detect]: php-version-detect/README.md
|
||||
[pr-lint]: pr-lint/README.md
|
||||
[pre-commit]: pre-commit/README.md
|
||||
[prettier-check]: prettier-check/README.md
|
||||
[prettier-fix]: prettier-fix/README.md
|
||||
[prettier-lint]: prettier-lint/README.md
|
||||
[python-lint-fix]: python-lint-fix/README.md
|
||||
[python-version-detect]: python-version-detect/README.md
|
||||
[python-version-detect-v2]: python-version-detect-v2/README.md
|
||||
[release-monthly]: release-monthly/README.md
|
||||
[set-git-config]: set-git-config/README.md
|
||||
[stale]: stale/README.md
|
||||
[sync-labels]: sync-labels/README.md
|
||||
[terraform-lint-fix]: terraform-lint-fix/README.md
|
||||
[validate-inputs]: validate-inputs/README.md
|
||||
[version-file-parser]: version-file-parser/README.md
|
||||
[version-validator]: version-validator/README.md
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -38,11 +38,12 @@ run: |
|
||||
|
||||
### 2. Secret Masking
|
||||
|
||||
**Status**: ✅ Implemented in 6 critical actions
|
||||
**Status**: ✅ Implemented in 7 critical actions
|
||||
|
||||
Actions that handle sensitive data use GitHub Actions secret masking to prevent accidental exposure in logs:
|
||||
|
||||
- `npm-publish` - NPM authentication tokens
|
||||
- `docker-publish` - Docker Hub credentials (defense-in-depth masking)
|
||||
- `docker-publish-hub` - Docker Hub passwords
|
||||
- `docker-publish-gh` - GitHub tokens
|
||||
- `csharp-publish` - NuGet API keys
|
||||
@@ -225,11 +226,11 @@ When security issues are fixed:
|
||||
- Added comprehensive input validation
|
||||
- Status: ✅ Complete
|
||||
|
||||
### Phase 2: Enhanced Security (2024)
|
||||
### Phase 2: Enhanced Security (2024-2025)
|
||||
|
||||
- Replaced custom Bun installation with official action
|
||||
- Replaced custom Trivy installation with official action
|
||||
- Added secret masking to 6 critical actions
|
||||
- Added secret masking to 7 critical actions (including docker-publish)
|
||||
- Optimized file hashing in common-cache
|
||||
- Status: ✅ Complete
|
||||
|
||||
|
||||
550
_tests/README.md
550
_tests/README.md
@@ -1,6 +1,6 @@
|
||||
# GitHub Actions Testing Framework
|
||||
|
||||
A comprehensive testing framework for validating GitHub Actions in this monorepo. This guide covers everything from basic usage to advanced testing patterns.
|
||||
A comprehensive testing framework for validating GitHub Actions in this monorepo using ShellSpec and Python-based input validation.
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
@@ -36,16 +36,15 @@ brew install act # macOS
|
||||
|
||||
The testing framework uses a **multi-level testing strategy**:
|
||||
|
||||
1. **Unit Tests** - Fast validation of action logic, inputs, and outputs
|
||||
1. **Unit Tests** - Fast validation of action logic, inputs, and outputs using Python validation
|
||||
2. **Integration Tests** - Test actions in realistic workflow environments
|
||||
3. **External Usage Tests** - Validate actions work as `ivuorinen/actions/action-name@main`
|
||||
|
||||
### Technology Stack
|
||||
|
||||
- **Primary Framework**: [ShellSpec](https://shellspec.info/) - BDD testing for shell scripts
|
||||
- **Validation**: Python-based input validation via `validate-inputs/validator.py`
|
||||
- **Local Execution**: [nektos/act](https://github.com/nektos/act) - Run GitHub Actions locally
|
||||
- **Coverage**: kcov integration for shell script coverage
|
||||
- **Mocking**: Custom GitHub API and service mocks
|
||||
- **CI Integration**: GitHub Actions workflows
|
||||
|
||||
### Directory Structure
|
||||
@@ -54,19 +53,20 @@ The testing framework uses a **multi-level testing strategy**:
|
||||
_tests/
|
||||
├── README.md # This documentation
|
||||
├── run-tests.sh # Main test runner script
|
||||
├── framework/ # Core testing utilities
|
||||
│ ├── setup.sh # Test environment setup
|
||||
│ ├── utils.sh # Common testing functions
|
||||
│ ├── validation_helpers.sh # Validation helper functions
|
||||
│ ├── validation.py # Python validation utilities
|
||||
│ └── mocks/ # Mock services (GitHub API, etc.)
|
||||
├── unit/ # Unit tests by action
|
||||
│ ├── spec_helper.sh # ShellSpec helper with validation functions
|
||||
│ ├── version-file-parser/ # Example unit tests
|
||||
│ ├── node-setup/ # Example unit tests
|
||||
│ └── ... # One directory per action
|
||||
├── framework/ # Core testing utilities
|
||||
│ ├── setup.sh # Test environment setup
|
||||
│ ├── utils.sh # Common testing functions
|
||||
│ ├── validation.py # Python validation utilities
|
||||
│ └── fixtures/ # Test fixtures
|
||||
├── integration/ # Integration tests
|
||||
│ ├── workflows/ # Test workflows for nektos/act
|
||||
│ └── external-usage/ # External reference tests
|
||||
│ ├── external-usage/ # External reference tests
|
||||
│ └── action-chains/ # Multi-action workflow tests
|
||||
├── coverage/ # Coverage reports
|
||||
└── reports/ # Test execution reports
|
||||
```
|
||||
@@ -79,44 +79,39 @@ _tests/
|
||||
#!/usr/bin/env shellspec
|
||||
# _tests/unit/my-action/validation.spec.sh
|
||||
|
||||
Include _tests/framework/utils.sh
|
||||
|
||||
Describe "my-action validation"
|
||||
ACTION_DIR="my-action"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
ACTION_DIR="my-action"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
BeforeAll "init_testing_framework"
|
||||
|
||||
Context "input validation"
|
||||
It "validates all inputs comprehensively"
|
||||
# Use validation helpers for comprehensive testing
|
||||
test_boolean_input "verbose"
|
||||
test_boolean_input "dry-run"
|
||||
|
||||
# Numeric range validations (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "max-retries" "1" "success"
|
||||
test_input_validation "$ACTION_DIR" "max-retries" "10" "success"
|
||||
test_input_validation "$ACTION_DIR" "timeout" "3600" "success"
|
||||
|
||||
# Enum validations (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "strategy" "fast" "success"
|
||||
test_input_validation "$ACTION_DIR" "format" "json" "success"
|
||||
|
||||
# Version validations (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "tool-version" "1.0.0" "success"
|
||||
|
||||
# Security and path validations (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "command" "echo test" "success"
|
||||
test_input_validation "$ACTION_DIR" "working-directory" "." "success"
|
||||
End
|
||||
Context "when validating required inputs"
|
||||
It "accepts valid input"
|
||||
When call validate_input_python "my-action" "input-name" "valid-value"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
Context "action structure"
|
||||
It "has valid structure and metadata"
|
||||
test_standard_action_structure "$ACTION_FILE" "Expected Action Name"
|
||||
End
|
||||
It "rejects invalid input"
|
||||
When call validate_input_python "my-action" "input-name" "invalid@value"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating boolean inputs"
|
||||
It "accepts true"
|
||||
When call validate_input_python "my-action" "dry-run" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts false"
|
||||
When call validate_input_python "my-action" "dry-run" "false"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid boolean"
|
||||
When call validate_input_python "my-action" "dry-run" "maybe"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
```
|
||||
|
||||
### Integration Test Example
|
||||
@@ -149,66 +144,68 @@ jobs:
|
||||
required-input: 'test-value'
|
||||
```
|
||||
|
||||
## 🛠️ Testing Helpers
|
||||
## 🛠️ Testing Functions
|
||||
|
||||
### Available Validation Helpers
|
||||
### Primary Validation Function
|
||||
|
||||
The framework provides comprehensive validation helpers that handle common testing patterns:
|
||||
The framework provides one main validation function that uses the Python validation system:
|
||||
|
||||
#### Boolean Input Testing
|
||||
#### validate_input_python
|
||||
|
||||
Tests input validation using the centralized Python validator:
|
||||
|
||||
```bash
|
||||
test_boolean_input "verbose" # Tests: true, false, rejects invalid
|
||||
test_boolean_input "enable-cache"
|
||||
test_boolean_input "dry-run"
|
||||
validate_input_python "action-name" "input-name" "test-value"
|
||||
```
|
||||
|
||||
#### Numeric Range Testing
|
||||
**Examples:**
|
||||
|
||||
```bash
|
||||
# Note: test_numeric_range_input helper is not yet implemented.
|
||||
# Use test_input_validation with appropriate test values instead:
|
||||
test_input_validation "$ACTION_DIR" "max-retries" "1" "success" # min value
|
||||
test_input_validation "$ACTION_DIR" "max-retries" "10" "success" # max value
|
||||
test_input_validation "$ACTION_DIR" "max-retries" "0" "failure" # below min
|
||||
test_input_validation "$ACTION_DIR" "timeout" "3600" "success"
|
||||
test_input_validation "$ACTION_DIR" "parallel-jobs" "8" "success"
|
||||
# Boolean validation
|
||||
validate_input_python "pre-commit" "dry-run" "true" # success
|
||||
validate_input_python "pre-commit" "dry-run" "false" # success
|
||||
validate_input_python "pre-commit" "dry-run" "maybe" # failure
|
||||
|
||||
# Version validation
|
||||
validate_input_python "node-setup" "node-version" "18.0.0" # success
|
||||
validate_input_python "node-setup" "node-version" "v1.2.3" # success
|
||||
validate_input_python "node-setup" "node-version" "invalid" # failure
|
||||
|
||||
# Token validation
|
||||
validate_input_python "npm-publish" "npm-token" "ghp_123..." # success
|
||||
validate_input_python "npm-publish" "npm-token" "invalid" # failure
|
||||
|
||||
# Docker validation
|
||||
validate_input_python "docker-build" "image-name" "myapp" # success
|
||||
validate_input_python "docker-build" "tag" "v1.0.0" # success
|
||||
|
||||
# Path validation (security)
|
||||
validate_input_python "pre-commit" "config-file" "config.yml" # success
|
||||
validate_input_python "pre-commit" "config-file" "../etc/pass" # failure
|
||||
|
||||
# Injection detection
|
||||
validate_input_python "common-retry" "command" "echo test" # success
|
||||
validate_input_python "common-retry" "command" "rm -rf /; " # failure
|
||||
```
|
||||
|
||||
#### Version Testing
|
||||
### Helper Functions from spec_helper.sh
|
||||
|
||||
```bash
|
||||
# Note: test_version_input helper is not yet implemented.
|
||||
# Use test_input_validation with appropriate test values instead:
|
||||
test_input_validation "$ACTION_DIR" "version" "1.0.0" "success" # semver
|
||||
test_input_validation "$ACTION_DIR" "version" "v1.0.0" "success" # v-prefix
|
||||
test_input_validation "$ACTION_DIR" "version" "1.0.0-rc.1" "success" # pre-release
|
||||
test_input_validation "$ACTION_DIR" "tool-version" "2.3.4" "success"
|
||||
```
|
||||
# Setup/cleanup
|
||||
setup_default_inputs "action-name" "input-name" # Set required defaults
|
||||
cleanup_default_inputs "action-name" "input-name" # Clean up defaults
|
||||
shellspec_setup_test_env "test-name" # Setup test environment
|
||||
shellspec_cleanup_test_env "test-name" # Cleanup test environment
|
||||
|
||||
#### Enum Testing
|
||||
# Mock execution
|
||||
shellspec_mock_action_run "action-dir" key1 value1 key2 value2
|
||||
shellspec_validate_action_output "expected-key" "expected-value"
|
||||
|
||||
```bash
|
||||
# Note: test_enum_input helper is not yet implemented.
|
||||
# Use test_input_validation with appropriate test values instead:
|
||||
test_input_validation "$ACTION_DIR" "strategy" "linear" "success"
|
||||
test_input_validation "$ACTION_DIR" "strategy" "exponential" "success"
|
||||
test_input_validation "$ACTION_DIR" "strategy" "invalid" "failure"
|
||||
test_input_validation "$ACTION_DIR" "format" "json" "success"
|
||||
test_input_validation "$ACTION_DIR" "format" "yaml" "success"
|
||||
```
|
||||
|
||||
#### Docker-Specific Testing
|
||||
|
||||
```bash
|
||||
# Available framework helpers:
|
||||
test_input_validation "$action_dir" "$input_name" "$test_value" "$expected_result"
|
||||
test_action_outputs "$action_dir"
|
||||
test_external_usage "$action_dir"
|
||||
|
||||
# Note: Docker-specific helpers (test_docker_image_input, test_docker_tag_input,
|
||||
# test_docker_platforms_input) are referenced in examples but not yet implemented.
|
||||
# Use test_input_validation with appropriate test values instead.
|
||||
# Action metadata
|
||||
validate_action_yml "action.yml" # Validate YAML structure
|
||||
get_action_inputs "action.yml" # Get action inputs
|
||||
get_action_outputs "action.yml" # Get action outputs
|
||||
get_action_name "action.yml" # Get action name
|
||||
```
|
||||
|
||||
### Complete Action Validation Example
|
||||
@@ -218,41 +215,47 @@ Describe "comprehensive-action validation"
|
||||
ACTION_DIR="comprehensive-action"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "complete input validation"
|
||||
It "validates all input types systematically"
|
||||
# Boolean inputs
|
||||
test_boolean_input "verbose"
|
||||
test_boolean_input "enable-cache"
|
||||
test_boolean_input "dry-run"
|
||||
Context "when validating all input types"
|
||||
It "validates boolean inputs"
|
||||
When call validate_input_python "$ACTION_DIR" "verbose" "true"
|
||||
The status should be success
|
||||
|
||||
# Numeric ranges (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "max-retries" "1" "success"
|
||||
test_input_validation "$ACTION_DIR" "max-retries" "10" "success"
|
||||
test_input_validation "$ACTION_DIR" "timeout" "3600" "success"
|
||||
test_input_validation "$ACTION_DIR" "parallel-jobs" "8" "success"
|
||||
When call validate_input_python "$ACTION_DIR" "verbose" "false"
|
||||
The status should be success
|
||||
|
||||
# Enums (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "strategy" "fast" "success"
|
||||
test_input_validation "$ACTION_DIR" "format" "json" "success"
|
||||
When call validate_input_python "$ACTION_DIR" "verbose" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
# Docker-specific (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "image-name" "myapp:latest" "success"
|
||||
test_input_validation "$ACTION_DIR" "tag" "1.0.0" "success"
|
||||
test_input_validation "$ACTION_DIR" "platforms" "linux/amd64,linux/arm64" "success"
|
||||
It "validates numeric inputs"
|
||||
When call validate_input_python "$ACTION_DIR" "max-retries" "3"
|
||||
The status should be success
|
||||
|
||||
# Security validation (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "command" "echo test" "success"
|
||||
test_input_validation "$ACTION_DIR" "build-args" "ARG1=value" "success"
|
||||
When call validate_input_python "$ACTION_DIR" "max-retries" "999"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
# Paths (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "working-directory" "." "success"
|
||||
test_input_validation "$ACTION_DIR" "output-directory" "./output" "success"
|
||||
It "validates version inputs"
|
||||
When call validate_input_python "$ACTION_DIR" "tool-version" "1.0.0"
|
||||
The status should be success
|
||||
|
||||
# Versions (use test_input_validation helper)
|
||||
test_input_validation "$ACTION_DIR" "tool-version" "1.0.0" "success"
|
||||
When call validate_input_python "$ACTION_DIR" "tool-version" "v1.2.3-rc.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
# Action structure
|
||||
test_standard_action_structure "$ACTION_FILE" "Comprehensive Action"
|
||||
It "validates security patterns"
|
||||
When call validate_input_python "$ACTION_DIR" "command" "echo test"
|
||||
The status should be success
|
||||
|
||||
When call validate_input_python "$ACTION_DIR" "command" "rm -rf /; "
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating action structure"
|
||||
It "has valid YAML structure"
|
||||
When call validate_action_yml "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -265,45 +268,37 @@ End
|
||||
Focus on version detection and environment setup:
|
||||
|
||||
```bash
|
||||
Context "version detection"
|
||||
Context "when detecting versions"
|
||||
It "detects version from config files"
|
||||
create_mock_node_repo # or appropriate repo type
|
||||
|
||||
# Test version detection logic
|
||||
export INPUT_LANGUAGE="node"
|
||||
echo "detected-version=18.0.0" >> "$GITHUB_OUTPUT"
|
||||
|
||||
When call validate_action_output "detected-version" "18.0.0"
|
||||
When call validate_input_python "node-setup" "node-version" "18.0.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "falls back to default when no version found"
|
||||
# Use test_input_validation helper for version validation
|
||||
test_input_validation "$ACTION_DIR" "default-version" "1.0.0" "success"
|
||||
It "accepts default version"
|
||||
When call validate_input_python "python-version-detect" "default-version" "3.11"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
```
|
||||
|
||||
### Linting Actions (eslint-fix, prettier-fix, etc.)
|
||||
|
||||
Focus on file processing and fix capabilities:
|
||||
Focus on file processing and security:
|
||||
|
||||
```bash
|
||||
Context "file processing"
|
||||
BeforeEach "setup_test_env 'lint-test'"
|
||||
AfterEach "cleanup_test_env 'lint-test'"
|
||||
Context "when processing files"
|
||||
It "validates working directory"
|
||||
When call validate_input_python "eslint-fix" "working-directory" "."
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "validates inputs and processes files"
|
||||
test_boolean_input "fix-only"
|
||||
# Use test_input_validation helper for path and security validations
|
||||
test_input_validation "$ACTION_DIR" "working-directory" "." "success"
|
||||
test_input_validation "$ACTION_DIR" "custom-command" "echo test" "success"
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "eslint-fix" "working-directory" "../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
# Mock file processing
|
||||
echo "files_changed=3" >> "$GITHUB_OUTPUT"
|
||||
echo "status=changes_made" >> "$GITHUB_OUTPUT"
|
||||
|
||||
When call validate_action_output "status" "changes_made"
|
||||
It "validates boolean flags"
|
||||
When call validate_input_python "eslint-fix" "fix-only" "true"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
@@ -311,25 +306,22 @@ End
|
||||
|
||||
### Build Actions (docker-build, go-build, etc.)
|
||||
|
||||
Focus on build processes and artifact generation:
|
||||
Focus on build configuration:
|
||||
|
||||
```bash
|
||||
Context "build process"
|
||||
BeforeEach "setup_test_env 'build-test'"
|
||||
AfterEach "cleanup_test_env 'build-test'"
|
||||
Context "when building"
|
||||
It "validates image name"
|
||||
When call validate_input_python "docker-build" "image-name" "myapp"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "validates build inputs"
|
||||
# Use test_input_validation helper for Docker inputs
|
||||
test_input_validation "$ACTION_DIR" "image-name" "myapp:latest" "success"
|
||||
test_input_validation "$ACTION_DIR" "tag" "1.0.0" "success"
|
||||
test_input_validation "$ACTION_DIR" "platforms" "linux/amd64,linux/arm64" "success"
|
||||
test_input_validation "$ACTION_DIR" "parallel-builds" "8" "success"
|
||||
It "validates tag format"
|
||||
When call validate_input_python "docker-build" "tag" "v1.0.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
# Mock successful build
|
||||
echo "build-status=success" >> "$GITHUB_OUTPUT"
|
||||
echo "build-time=45" >> "$GITHUB_OUTPUT"
|
||||
|
||||
When call validate_action_output "build-status" "success"
|
||||
It "validates platforms"
|
||||
When call validate_input_python "docker-build" "platforms" "linux/amd64,linux/arm64"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
@@ -337,25 +329,22 @@ End
|
||||
|
||||
### Publishing Actions (npm-publish, docker-publish, etc.)
|
||||
|
||||
Focus on registry interactions using mocks:
|
||||
Focus on credentials and registry validation:
|
||||
|
||||
```bash
|
||||
Context "publishing"
|
||||
BeforeEach "setup_mock_environment"
|
||||
AfterEach "cleanup_mock_environment"
|
||||
Context "when publishing"
|
||||
It "validates token format"
|
||||
When call validate_input_python "npm-publish" "npm-token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "validates publishing inputs"
|
||||
# Use test_input_validation helper for version, security, and enum validations
|
||||
test_input_validation "$ACTION_DIR" "package-version" "1.0.0" "success"
|
||||
test_input_validation "$ACTION_DIR" "registry-token" "ghp_test123" "success"
|
||||
test_input_validation "$ACTION_DIR" "registry" "npm" "success"
|
||||
test_input_validation "$ACTION_DIR" "registry" "github" "success"
|
||||
It "rejects invalid token"
|
||||
When call validate_input_python "npm-publish" "npm-token" "invalid-token"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
# Mock successful publish
|
||||
echo "publish-status=success" >> "$GITHUB_OUTPUT"
|
||||
echo "registry-url=https://registry.npmjs.org/" >> "$GITHUB_OUTPUT"
|
||||
|
||||
When call validate_action_output "publish-status" "success"
|
||||
It "validates version"
|
||||
When call validate_input_python "npm-publish" "package-version" "1.0.0"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
@@ -409,33 +398,33 @@ make test-action ACTION=name # Test specific action
|
||||
mkdir -p _tests/unit/new-action
|
||||
```
|
||||
|
||||
2. **Write Comprehensive Unit Tests**
|
||||
2. **Write Unit Tests**
|
||||
|
||||
```bash
|
||||
# Copy template and customize
|
||||
cp _tests/unit/version-file-parser/validation.spec.sh \
|
||||
_tests/unit/new-action/validation.spec.sh
|
||||
# _tests/unit/new-action/validation.spec.sh
|
||||
#!/usr/bin/env shellspec
|
||||
|
||||
Describe "new-action validation"
|
||||
ACTION_DIR="new-action"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating inputs"
|
||||
It "validates required input"
|
||||
When call validate_input_python "new-action" "required-input" "value"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
End
|
||||
```
|
||||
|
||||
3. **Use Validation Helpers**
|
||||
3. **Create Integration Test**
|
||||
|
||||
```bash
|
||||
# Focus on using helpers for comprehensive coverage
|
||||
test_boolean_input "verbose"
|
||||
# Use test_input_validation helper for numeric, security, and other validations
|
||||
test_input_validation "$ACTION_DIR" "timeout" "3600" "success"
|
||||
test_input_validation "$ACTION_DIR" "command" "echo test" "success"
|
||||
test_standard_action_structure "$ACTION_FILE" "New Action"
|
||||
# _tests/integration/workflows/new-action-test.yml
|
||||
# (See integration test example above)
|
||||
```
|
||||
|
||||
4. **Create Integration Test**
|
||||
|
||||
```bash
|
||||
cp _tests/integration/workflows/version-file-parser-test.yml \
|
||||
_tests/integration/workflows/new-action-test.yml
|
||||
```
|
||||
|
||||
5. **Test Your Tests**
|
||||
4. **Test Your Tests**
|
||||
|
||||
```bash
|
||||
make test-action ACTION=new-action
|
||||
@@ -443,7 +432,7 @@ make test-action ACTION=name # Test specific action
|
||||
|
||||
### Pull Request Checklist
|
||||
|
||||
- [ ] Tests use validation helpers for common patterns
|
||||
- [ ] Tests use `validate_input_python` for input validation
|
||||
- [ ] All test types pass locally (`make test`)
|
||||
- [ ] Integration test workflow created
|
||||
- [ ] Security testing included for user inputs
|
||||
@@ -453,24 +442,21 @@ make test-action ACTION=name # Test specific action
|
||||
|
||||
## 💡 Best Practices
|
||||
|
||||
### 1. Use Validation Helpers
|
||||
### 1. Use validate_input_python for All Input Testing
|
||||
|
||||
✅ **Good**:
|
||||
|
||||
```bash
|
||||
test_boolean_input "verbose"
|
||||
# Use test_input_validation helper for other validations
|
||||
test_input_validation "$ACTION_DIR" "timeout" "3600" "success"
|
||||
test_input_validation "$ACTION_DIR" "format" "json" "success"
|
||||
When call validate_input_python "my-action" "verbose" "true"
|
||||
The status should be success
|
||||
```
|
||||
|
||||
❌ **Avoid**:
|
||||
|
||||
```bash
|
||||
# Don't write manual tests for boolean inputs when test_boolean_input exists
|
||||
When call test_input_validation "$ACTION_DIR" "verbose" "true" "success"
|
||||
When call test_input_validation "$ACTION_DIR" "verbose" "false" "success"
|
||||
# Use test_boolean_input "verbose" instead
|
||||
# Don't manually test validation - use the Python validator
|
||||
export INPUT_VERBOSE="true"
|
||||
python3 validate-inputs/validator.py
|
||||
```
|
||||
|
||||
### 2. Group Related Validations
|
||||
@@ -478,26 +464,33 @@ When call test_input_validation "$ACTION_DIR" "verbose" "false" "success"
|
||||
✅ **Good**:
|
||||
|
||||
```bash
|
||||
Context "complete input validation"
|
||||
It "validates all input types"
|
||||
test_boolean_input "verbose"
|
||||
# Use test_input_validation helper for other validations
|
||||
test_input_validation "$ACTION_DIR" "timeout" "3600" "success"
|
||||
test_input_validation "$ACTION_DIR" "format" "json" "success"
|
||||
test_input_validation "$ACTION_DIR" "command" "echo test" "success"
|
||||
Context "when validating configuration"
|
||||
It "accepts valid boolean"
|
||||
When call validate_input_python "my-action" "dry-run" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid version"
|
||||
When call validate_input_python "my-action" "tool-version" "1.0.0"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
```
|
||||
|
||||
### 3. Include Security Testing
|
||||
### 3. Always Include Security Testing
|
||||
|
||||
✅ **Always include**:
|
||||
|
||||
```bash
|
||||
# Use test_input_validation helper for security and path validations
|
||||
test_input_validation "$ACTION_DIR" "command" "echo test" "success"
|
||||
test_input_validation "$ACTION_DIR" "user-script" "#!/bin/bash" "success"
|
||||
test_input_validation "$ACTION_DIR" "working-directory" "." "success"
|
||||
It "rejects command injection"
|
||||
When call validate_input_python "common-retry" "command" "rm -rf /; "
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "pre-commit" "config-file" "../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
```
|
||||
|
||||
### 4. Write Descriptive Test Names
|
||||
@@ -528,46 +521,34 @@ It "works correctly"
|
||||
|
||||
### Test Environment Setup
|
||||
|
||||
```bash
|
||||
# Setup test environment
|
||||
setup_test_env "test-name"
|
||||
|
||||
# Create mock repositories
|
||||
create_mock_repo "node" # Node.js project
|
||||
create_mock_repo "php" # PHP project
|
||||
create_mock_repo "python" # Python project
|
||||
create_mock_repo "go" # Go project
|
||||
create_mock_repo "dotnet" # .NET project
|
||||
|
||||
# Cleanup
|
||||
cleanup_test_env "test-name"
|
||||
```
|
||||
|
||||
### Mock Services
|
||||
|
||||
Built-in mocks for external services:
|
||||
|
||||
- **GitHub API** - Repository, releases, packages, workflows
|
||||
- **NPM Registry** - Package publishing and retrieval
|
||||
- **Docker Registry** - Image push/pull operations
|
||||
- **Container Registries** - GitHub Container Registry, Docker Hub
|
||||
|
||||
### Available Environment Variables
|
||||
The framework automatically sets up test environments via `spec_helper.sh`:
|
||||
|
||||
```bash
|
||||
# Test environment paths
|
||||
$TEST_WORKSPACE # Current test workspace
|
||||
$GITHUB_OUTPUT # Mock GitHub outputs file
|
||||
$GITHUB_ENV # Mock GitHub environment file
|
||||
$GITHUB_STEP_SUMMARY # Mock step summary file
|
||||
# Automatic setup on load
|
||||
- GitHub Actions environment variables
|
||||
- Temporary directories
|
||||
- Mock GITHUB_OUTPUT files
|
||||
- Default required inputs for actions
|
||||
|
||||
# Test framework paths
|
||||
$TEST_ROOT # _tests/ directory
|
||||
$FRAMEWORK_DIR # _tests/framework/ directory
|
||||
$FIXTURES_DIR # _tests/framework/fixtures/
|
||||
$MOCKS_DIR # _tests/framework/mocks/
|
||||
# Available variables
|
||||
$PROJECT_ROOT # Repository root
|
||||
$TEST_ROOT # _tests/ directory
|
||||
$FRAMEWORK_DIR # _tests/framework/
|
||||
$FIXTURES_DIR # _tests/framework/fixtures/
|
||||
$TEMP_DIR # Temporary test directory
|
||||
$GITHUB_OUTPUT # Mock outputs file
|
||||
$GITHUB_ENV # Mock environment file
|
||||
```
|
||||
|
||||
### Python Validation Integration
|
||||
|
||||
All input validation uses the centralized Python validation system from `validate-inputs/`:
|
||||
|
||||
- Convention-based automatic validation
|
||||
- 9 specialized validators (Boolean, Version, Token, Numeric, File, Network, Docker, Security, CodeQL)
|
||||
- Custom validator support per action
|
||||
- Injection and security pattern detection
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
@@ -618,58 +599,67 @@ find _tests/ -name "*.sh" -exec chmod +x {} \;
|
||||
shellspec _tests/unit/my-action/validation.spec.sh
|
||||
```
|
||||
|
||||
3. **Check Test Output**
|
||||
3. **Enable Debug Mode**
|
||||
|
||||
```bash
|
||||
export SHELLSPEC_DEBUG=1
|
||||
shellspec _tests/unit/my-action/validation.spec.sh
|
||||
```
|
||||
|
||||
4. **Check Test Output**
|
||||
|
||||
```bash
|
||||
# Test results stored in _tests/reports/
|
||||
cat _tests/reports/unit/my-action.txt
|
||||
```
|
||||
|
||||
4. **Debug Mock Environment**
|
||||
|
||||
```bash
|
||||
# Enable mock debugging
|
||||
export MOCK_DEBUG=true
|
||||
```
|
||||
|
||||
## 📚 Resources
|
||||
|
||||
- [ShellSpec Documentation](https://shellspec.info/)
|
||||
- [nektos/act Documentation](https://nektosact.com/)
|
||||
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
|
||||
- [Testing GitHub Actions Best Practices](https://docs.github.com/en/actions/creating-actions/creating-a-composite-action#testing-your-action)
|
||||
|
||||
---
|
||||
- [validate-inputs Documentation](../validate-inputs/docs/README_ARCHITECTURE.md)
|
||||
|
||||
## Framework Development
|
||||
|
||||
### Adding New Framework Features
|
||||
### Framework File Structure
|
||||
|
||||
1. **New Test Utilities**
|
||||
```text
|
||||
_tests/
|
||||
├── unit/
|
||||
│ └── spec_helper.sh # ShellSpec configuration and helpers
|
||||
├── framework/
|
||||
│ ├── setup.sh # Test environment initialization
|
||||
│ ├── utils.sh # Common utility functions
|
||||
│ ├── validation.py # Python validation helpers
|
||||
│ └── fixtures/ # Test fixtures
|
||||
└── integration/
|
||||
├── workflows/ # Integration test workflows
|
||||
├── external-usage/ # External reference tests
|
||||
└── action-chains/ # Multi-action tests
|
||||
```
|
||||
|
||||
```bash
|
||||
# Add to _tests/framework/utils.sh
|
||||
your_new_function() {
|
||||
local param="$1"
|
||||
# Implementation
|
||||
}
|
||||
### Available Functions
|
||||
|
||||
# Export for availability
|
||||
export -f your_new_function
|
||||
```
|
||||
**From spec_helper.sh (\_tests/unit/spec_helper.sh):**
|
||||
|
||||
2. **New Mock Services**
|
||||
- `validate_input_python(action, input_name, value)` - Main validation function
|
||||
- `setup_default_inputs(action, input_name)` - Set default required inputs
|
||||
- `cleanup_default_inputs(action, input_name)` - Clean up default inputs
|
||||
- `shellspec_setup_test_env(name)` - Setup test environment
|
||||
- `shellspec_cleanup_test_env(name)` - Cleanup test environment
|
||||
- `shellspec_mock_action_run(action_dir, ...)` - Mock action execution
|
||||
- `shellspec_validate_action_output(key, value)` - Validate outputs
|
||||
|
||||
```bash
|
||||
# Create _tests/framework/mocks/new-service.sh
|
||||
# Follow existing patterns in github-api.sh
|
||||
```
|
||||
**From utils.sh (\_tests/framework/utils.sh):**
|
||||
|
||||
3. **New Validation Helpers**
|
||||
- `validate_action_yml(file)` - Validate action YAML
|
||||
- `get_action_inputs(file)` - Extract action inputs
|
||||
- `get_action_outputs(file)` - Extract action outputs
|
||||
- `get_action_name(file)` - Get action name
|
||||
- `test_input_validation(dir, name, value, expected)` - Test input
|
||||
- `test_action_outputs(dir)` - Test action outputs
|
||||
- `test_external_usage(dir)` - Test external usage
|
||||
|
||||
```bash
|
||||
# Add to _tests/framework/validation_helpers.sh
|
||||
# Update this documentation
|
||||
```
|
||||
|
||||
**Last Updated:** August 17, 2025
|
||||
**Last Updated:** October 15, 2025
|
||||
|
||||
@@ -57,6 +57,21 @@ get_action_name() {
|
||||
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() {
|
||||
local action_dir="$1"
|
||||
@@ -348,5 +363,5 @@ run_action_tests() {
|
||||
}
|
||||
|
||||
# Export all functions
|
||||
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name
|
||||
export -f validate_action_yml get_action_inputs get_action_outputs get_action_name is_input_required
|
||||
export -f test_input_validation test_action_outputs test_external_usage measure_action_time run_action_tests
|
||||
|
||||
471
_tests/integration/workflows/common-cache-test.yml
Normal file
471
_tests/integration/workflows/common-cache-test.yml
Normal 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!"
|
||||
186
_tests/integration/workflows/docker-build-publish-test.yml
Normal file
186
_tests/integration/workflows/docker-build-publish-test.yml
Normal file
@@ -0,0 +1,186 @@
|
||||
---
|
||||
name: Test Docker Build & Publish Integration
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'docker-build/**'
|
||||
- 'docker-publish/**'
|
||||
- 'docker-publish-gh/**'
|
||||
- 'docker-publish-hub/**'
|
||||
- '_tests/integration/workflows/docker-build-publish-test.yml'
|
||||
|
||||
jobs:
|
||||
test-docker-build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test Dockerfile
|
||||
run: |
|
||||
cat > Dockerfile <<EOF
|
||||
FROM alpine:3.19
|
||||
RUN apk add --no-cache bash
|
||||
COPY test.sh /test.sh
|
||||
RUN chmod +x /test.sh
|
||||
CMD ["/test.sh"]
|
||||
EOF
|
||||
|
||||
cat > test.sh <<EOF
|
||||
#!/bin/bash
|
||||
echo "Test container is running"
|
||||
EOF
|
||||
|
||||
- name: Test docker-build action
|
||||
id: build
|
||||
uses: ./docker-build
|
||||
with:
|
||||
image-name: 'test-image'
|
||||
tag: 'test-tag'
|
||||
dockerfile: './Dockerfile'
|
||||
context: '.'
|
||||
platforms: 'linux/amd64'
|
||||
push: 'false'
|
||||
scan-image: 'false'
|
||||
|
||||
- name: Validate build outputs
|
||||
run: |
|
||||
echo "Build outputs:"
|
||||
echo " Image Digest: ${{ steps.build.outputs.image-digest }}"
|
||||
echo " Build Time: ${{ steps.build.outputs.build-time }}"
|
||||
echo " Platforms: ${{ steps.build.outputs.platforms }}"
|
||||
|
||||
# Validate that we got a digest
|
||||
if [[ -z "${{ steps.build.outputs.image-digest }}" ]]; then
|
||||
echo "❌ ERROR: No image digest output"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate digest format (sha256:...)
|
||||
if ! echo "${{ steps.build.outputs.image-digest }}" | grep -E '^sha256:[a-f0-9]{64}'; then
|
||||
echo "❌ ERROR: Invalid digest format: ${{ steps.build.outputs.image-digest }}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Docker build validation passed"
|
||||
|
||||
test-docker-inputs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test Dockerfile
|
||||
run: |
|
||||
cat > Dockerfile <<EOF
|
||||
FROM alpine:3.19
|
||||
CMD ["echo", "test"]
|
||||
EOF
|
||||
|
||||
- name: Test with build-args
|
||||
id: build-with-args
|
||||
uses: ./docker-build
|
||||
with:
|
||||
image-name: 'test-build-args'
|
||||
tag: 'latest'
|
||||
dockerfile: './Dockerfile'
|
||||
context: '.'
|
||||
build-args: |
|
||||
ARG1=value1
|
||||
ARG2=value2
|
||||
platforms: 'linux/amd64'
|
||||
push: 'false'
|
||||
scan-image: 'false'
|
||||
|
||||
- name: Validate build-args handling
|
||||
run: |
|
||||
if [[ -z "${{ steps.build-with-args.outputs.image-digest }}" ]]; then
|
||||
echo "❌ ERROR: Build with build-args failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Build-args handling validated"
|
||||
|
||||
test-platform-detection:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test Dockerfile
|
||||
run: |
|
||||
cat > Dockerfile <<EOF
|
||||
FROM alpine:3.19
|
||||
CMD ["echo", "multi-platform test"]
|
||||
EOF
|
||||
|
||||
- name: Test multi-platform build
|
||||
id: multi-platform
|
||||
uses: ./docker-build
|
||||
with:
|
||||
image-name: 'test-multiplatform'
|
||||
tag: 'latest'
|
||||
dockerfile: './Dockerfile'
|
||||
context: '.'
|
||||
platforms: 'linux/amd64,linux/arm64'
|
||||
push: 'false'
|
||||
scan-image: 'false'
|
||||
|
||||
- name: Validate platform matrix output
|
||||
run: |
|
||||
echo "Platform Matrix: ${{ steps.multi-platform.outputs.platform-matrix }}"
|
||||
|
||||
# Check that we got platform results
|
||||
if [[ -z "${{ steps.multi-platform.outputs.platform-matrix }}" ]]; then
|
||||
echo "⚠️ WARNING: No platform matrix output (may be expected for local builds)"
|
||||
else
|
||||
echo "✅ Platform matrix generated"
|
||||
fi
|
||||
|
||||
echo "✅ Multi-platform build validated"
|
||||
|
||||
test-input-validation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test invalid tag format
|
||||
id: invalid-tag
|
||||
uses: ./docker-build
|
||||
with:
|
||||
image-name: 'test-image'
|
||||
tag: 'INVALID TAG WITH SPACES'
|
||||
dockerfile: './Dockerfile'
|
||||
context: '.'
|
||||
platforms: 'linux/amd64'
|
||||
push: 'false'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Validate tag validation
|
||||
run: |
|
||||
if [ "${{ steps.invalid-tag.outcome }}" != "failure" ]; then
|
||||
echo "❌ ERROR: Invalid tag should have failed validation"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Tag validation works correctly"
|
||||
|
||||
- name: Test invalid image name
|
||||
id: invalid-image
|
||||
uses: ./docker-build
|
||||
with:
|
||||
image-name: 'UPPERCASE_NOT_ALLOWED'
|
||||
tag: 'latest'
|
||||
dockerfile: './Dockerfile'
|
||||
context: '.'
|
||||
platforms: 'linux/amd64'
|
||||
push: 'false'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Validate image name validation
|
||||
run: |
|
||||
if [ "${{ steps.invalid-image.outcome }}" != "failure" ]; then
|
||||
echo "❌ ERROR: Invalid image name should have failed validation"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ Image name validation works correctly"
|
||||
322
_tests/integration/workflows/lint-fix-chain-test.yml
Normal file
322
_tests/integration/workflows/lint-fix-chain-test.yml
Normal file
@@ -0,0 +1,322 @@
|
||||
---
|
||||
name: Test Lint & Fix Action Chains
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'eslint-lint/**'
|
||||
- 'prettier-lint/**'
|
||||
- 'node-setup/**'
|
||||
- 'common-cache/**'
|
||||
- '_tests/integration/workflows/lint-fix-chain-test.yml'
|
||||
|
||||
jobs:
|
||||
test-eslint-chain:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test JavaScript files
|
||||
run: |
|
||||
mkdir -p test-project/src
|
||||
|
||||
# Create package.json
|
||||
cat > test-project/package.json <<EOF
|
||||
{
|
||||
"name": "test-project",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"eslint": "^8.0.0"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create .eslintrc.json
|
||||
cat > test-project/.eslintrc.json <<EOF
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12
|
||||
},
|
||||
"rules": {
|
||||
"semi": ["error", "always"],
|
||||
"quotes": ["error", "single"]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create test file with linting issues
|
||||
cat > test-project/src/test.js <<EOF
|
||||
const x = "double quotes"
|
||||
console.log(x)
|
||||
EOF
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./node-setup
|
||||
with:
|
||||
node-version: '18'
|
||||
working-directory: './test-project'
|
||||
|
||||
- name: Test eslint-lint check mode (should find errors)
|
||||
id: eslint-check
|
||||
uses: ./eslint-lint
|
||||
with:
|
||||
mode: 'check'
|
||||
working-directory: './test-project'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Validate eslint-lint check found issues
|
||||
run: |
|
||||
echo "ESLint check outcome: ${{ steps.eslint-check.outcome }}"
|
||||
echo "Error count: ${{ steps.eslint-check.outputs.error-count }}"
|
||||
echo "Warning count: ${{ steps.eslint-check.outputs.warning-count }}"
|
||||
|
||||
# Check should fail or find issues
|
||||
if [[ "${{ steps.eslint-check.outcome }}" == "success" ]]; then
|
||||
if [[ "${{ steps.eslint-check.outputs.error-count }}" == "0" ]]; then
|
||||
echo "⚠️ WARNING: Expected to find linting errors but found none"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "✅ ESLint check validated"
|
||||
|
||||
- name: Test eslint-lint fix mode (should fix issues)
|
||||
id: eslint-fix
|
||||
uses: ./eslint-lint
|
||||
with:
|
||||
mode: 'fix'
|
||||
working-directory: './test-project'
|
||||
token: ${{ github.token }}
|
||||
email: 'test@example.com'
|
||||
username: 'test-user'
|
||||
|
||||
- name: Validate eslint-lint fix ran
|
||||
run: |
|
||||
echo "Errors fixed: ${{ steps.eslint-fix.outputs.errors-fixed }}"
|
||||
echo "Files changed: ${{ steps.eslint-fix.outputs.files-changed }}"
|
||||
|
||||
# Check that fixes were attempted
|
||||
if [[ -n "${{ steps.eslint-fix.outputs.errors-fixed }}" ]]; then
|
||||
echo "✅ ESLint fixed ${{ steps.eslint-fix.outputs.errors-fixed }} issues"
|
||||
else
|
||||
echo "⚠️ No fix count reported (may be expected if no fixable issues)"
|
||||
fi
|
||||
|
||||
echo "✅ ESLint fix validated"
|
||||
|
||||
test-prettier-chain:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test files for Prettier
|
||||
run: |
|
||||
mkdir -p test-prettier
|
||||
|
||||
# Create package.json
|
||||
cat > test-prettier/package.json <<EOF
|
||||
{
|
||||
"name": "test-prettier",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"prettier": "^3.0.0"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create .prettierrc
|
||||
cat > test-prettier/.prettierrc <<EOF
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 80
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create badly formatted file
|
||||
cat > test-prettier/test.js <<EOF
|
||||
const x={"key":"value","another":"data"}
|
||||
console.log(x)
|
||||
EOF
|
||||
|
||||
# Create badly formatted JSON
|
||||
cat > test-prettier/test.json <<EOF
|
||||
{"key":"value","nested":{"data":"here"}}
|
||||
EOF
|
||||
|
||||
- name: Setup Node.js for Prettier
|
||||
uses: ./node-setup
|
||||
with:
|
||||
node-version: '18'
|
||||
working-directory: './test-prettier'
|
||||
|
||||
- name: Test prettier-lint check mode (should find issues)
|
||||
id: prettier-check
|
||||
uses: ./prettier-lint
|
||||
with:
|
||||
mode: 'check'
|
||||
working-directory: './test-prettier'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Validate prettier-check found issues
|
||||
run: |
|
||||
echo "Prettier check outcome: ${{ steps.prettier-check.outcome }}"
|
||||
|
||||
# Check should find formatting issues
|
||||
if [[ "${{ steps.prettier-check.outcome }}" == "failure" ]]; then
|
||||
echo "✅ Prettier correctly found formatting issues"
|
||||
else
|
||||
echo "⚠️ WARNING: Expected Prettier to find formatting issues"
|
||||
fi
|
||||
|
||||
- name: Test prettier-lint fix mode (should fix issues)
|
||||
id: prettier-fix
|
||||
uses: ./prettier-lint
|
||||
with:
|
||||
mode: 'fix'
|
||||
working-directory: './test-prettier'
|
||||
token: ${{ github.token }}
|
||||
email: 'test@example.com'
|
||||
username: 'test-user'
|
||||
|
||||
- name: Validate prettier-lint fix ran
|
||||
run: |
|
||||
echo "Prettier fix completed"
|
||||
|
||||
# Check that files exist and have been processed
|
||||
if [[ -f "test-prettier/test.js" ]]; then
|
||||
echo "✅ Test file exists after Prettier fix"
|
||||
else
|
||||
echo "❌ ERROR: Test file missing after Prettier fix"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Prettier fix validated"
|
||||
|
||||
test-action-chain-integration:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create comprehensive test project
|
||||
run: |
|
||||
mkdir -p test-chain/src
|
||||
|
||||
# Create package.json with both ESLint and Prettier
|
||||
cat > test-chain/package.json <<EOF
|
||||
{
|
||||
"name": "test-chain",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"eslint": "^8.0.0",
|
||||
"prettier": "^3.0.0"
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create .eslintrc.json
|
||||
cat > test-chain/.eslintrc.json <<EOF
|
||||
{
|
||||
"env": {
|
||||
"node": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12
|
||||
},
|
||||
"rules": {
|
||||
"semi": ["error", "always"],
|
||||
"quotes": ["error", "single"]
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create .prettierrc
|
||||
cat > test-chain/.prettierrc <<EOF
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"printWidth": 80
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create test file with both linting and formatting issues
|
||||
cat > test-chain/src/app.js <<EOF
|
||||
const message="hello world"
|
||||
function greet(){console.log(message)}
|
||||
greet()
|
||||
EOF
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: ./node-setup
|
||||
with:
|
||||
node-version: '18'
|
||||
working-directory: './test-chain'
|
||||
|
||||
- name: Run ESLint check
|
||||
id: lint-check
|
||||
uses: ./eslint-lint
|
||||
with:
|
||||
mode: 'check'
|
||||
working-directory: './test-chain'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run Prettier check
|
||||
id: format-check
|
||||
uses: ./prettier-lint
|
||||
with:
|
||||
mode: 'check'
|
||||
working-directory: './test-chain'
|
||||
continue-on-error: true
|
||||
|
||||
- name: Run ESLint fix
|
||||
id: lint-fix
|
||||
uses: ./eslint-lint
|
||||
with:
|
||||
mode: 'fix'
|
||||
working-directory: './test-chain'
|
||||
token: ${{ github.token }}
|
||||
email: 'test@example.com'
|
||||
username: 'test-user'
|
||||
|
||||
- name: Run Prettier fix
|
||||
id: format-fix
|
||||
uses: ./prettier-lint
|
||||
with:
|
||||
mode: 'fix'
|
||||
working-directory: './test-chain'
|
||||
token: ${{ github.token }}
|
||||
email: 'test@example.com'
|
||||
username: 'test-user'
|
||||
|
||||
- name: Validate complete chain
|
||||
run: |
|
||||
echo "=== Action Chain Results ==="
|
||||
echo "Lint Check: ${{ steps.lint-check.outcome }}"
|
||||
echo "Format Check: ${{ steps.format-check.outcome }}"
|
||||
echo "Lint Fix: ${{ steps.lint-fix.outcome }}"
|
||||
echo "Format Fix: ${{ steps.format-fix.outcome }}"
|
||||
|
||||
# Validate that all steps ran
|
||||
steps_run=0
|
||||
[[ "${{ steps.lint-check.outcome }}" != "skipped" ]] && ((steps_run++))
|
||||
[[ "${{ steps.format-check.outcome }}" != "skipped" ]] && ((steps_run++))
|
||||
[[ "${{ steps.lint-fix.outcome }}" != "skipped" ]] && ((steps_run++))
|
||||
[[ "${{ steps.format-fix.outcome }}" != "skipped" ]] && ((steps_run++))
|
||||
|
||||
if [[ $steps_run -eq 4 ]]; then
|
||||
echo "✅ Complete action chain executed successfully"
|
||||
else
|
||||
echo "❌ ERROR: Not all steps in chain executed (ran: $steps_run/4)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Action chain integration validated"
|
||||
513
_tests/integration/workflows/node-setup-test.yml
Normal file
513
_tests/integration/workflows/node-setup-test.yml
Normal 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!"
|
||||
353
_tests/integration/workflows/npm-publish-test.yml
Normal file
353
_tests/integration/workflows/npm-publish-test.yml
Normal file
@@ -0,0 +1,353 @@
|
||||
---
|
||||
name: Integration Test - NPM Publish
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'npm-publish/**'
|
||||
- 'node-setup/**'
|
||||
- '_tests/integration/workflows/npm-publish-test.yml'
|
||||
|
||||
jobs:
|
||||
test-npm-publish-validation:
|
||||
name: Test Input Validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test package.json
|
||||
run: |
|
||||
mkdir -p test-package
|
||||
cd test-package
|
||||
cat > package.json <<'EOF'
|
||||
{
|
||||
"name": "@test/integration-test",
|
||||
"version": "1.0.0",
|
||||
"description": "Test package for npm-publish integration",
|
||||
"main": "index.js"
|
||||
}
|
||||
EOF
|
||||
echo "module.exports = { test: true };" > index.js
|
||||
|
||||
- name: Test valid inputs (should succeed validation)
|
||||
id: valid-test
|
||||
uses: ./npm-publish
|
||||
continue-on-error: true
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: '@test'
|
||||
package-version: '1.0.0'
|
||||
npm_token: 'test-token-12345678'
|
||||
env:
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}/test-package
|
||||
|
||||
- name: Validate success (validation only)
|
||||
run: |
|
||||
# This will fail at publish step but validation should pass
|
||||
echo "✓ Input validation passed for valid inputs"
|
||||
|
||||
- name: Test invalid registry URL
|
||||
id: invalid-registry
|
||||
uses: ./npm-publish
|
||||
continue-on-error: true
|
||||
with:
|
||||
registry-url: 'not-a-url'
|
||||
scope: '@test'
|
||||
package-version: '1.0.0'
|
||||
npm_token: 'test-token'
|
||||
env:
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}/test-package
|
||||
|
||||
- name: Verify invalid registry URL failed
|
||||
run: |
|
||||
if [[ "${{ steps.invalid-registry.outcome }}" == "success" ]]; then
|
||||
echo "❌ ERROR: Invalid registry URL should have failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Invalid registry URL correctly rejected"
|
||||
|
||||
- name: Test invalid version format
|
||||
id: invalid-version
|
||||
uses: ./npm-publish
|
||||
continue-on-error: true
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: '@test'
|
||||
package-version: 'not.a.version'
|
||||
npm_token: 'test-token'
|
||||
env:
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}/test-package
|
||||
|
||||
- name: Verify invalid version failed
|
||||
run: |
|
||||
if [[ "${{ steps.invalid-version.outcome }}" == "success" ]]; then
|
||||
echo "❌ ERROR: Invalid version should have failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Invalid version format correctly rejected"
|
||||
|
||||
- name: Test invalid scope format
|
||||
id: invalid-scope
|
||||
uses: ./npm-publish
|
||||
continue-on-error: true
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: 'invalid-scope'
|
||||
package-version: '1.0.0'
|
||||
npm_token: 'test-token'
|
||||
env:
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}/test-package
|
||||
|
||||
- name: Verify invalid scope failed
|
||||
run: |
|
||||
if [[ "${{ steps.invalid-scope.outcome }}" == "success" ]]; then
|
||||
echo "❌ ERROR: Invalid scope format should have failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Invalid scope format correctly rejected"
|
||||
|
||||
- name: Test missing npm token
|
||||
id: missing-token
|
||||
uses: ./npm-publish
|
||||
continue-on-error: true
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: '@test'
|
||||
package-version: '1.0.0'
|
||||
npm_token: ''
|
||||
env:
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}/test-package
|
||||
|
||||
- name: Verify missing token failed
|
||||
run: |
|
||||
if [[ "${{ steps.missing-token.outcome }}" == "success" ]]; then
|
||||
echo "❌ ERROR: Missing token should have failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Missing NPM token correctly rejected"
|
||||
|
||||
test-npm-publish-package-validation:
|
||||
name: Test Package Validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test missing package.json
|
||||
id: missing-package
|
||||
uses: ./npm-publish
|
||||
continue-on-error: true
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: '@test'
|
||||
package-version: '1.0.0'
|
||||
npm_token: 'test-token'
|
||||
|
||||
- name: Verify missing package.json failed
|
||||
run: |
|
||||
if [[ "${{ steps.missing-package.outcome }}" == "success" ]]; then
|
||||
echo "❌ ERROR: Missing package.json should have failed"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Missing package.json correctly detected"
|
||||
|
||||
- name: Create test package with version mismatch
|
||||
run: |
|
||||
mkdir -p test-mismatch
|
||||
cd test-mismatch
|
||||
cat > package.json <<'EOF'
|
||||
{
|
||||
"name": "@test/mismatch-test",
|
||||
"version": "2.0.0",
|
||||
"description": "Test version mismatch"
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Test version mismatch detection
|
||||
id: version-mismatch
|
||||
uses: ./npm-publish
|
||||
continue-on-error: true
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: '@test'
|
||||
package-version: '1.0.0'
|
||||
npm_token: 'test-token'
|
||||
env:
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}/test-mismatch
|
||||
|
||||
- name: Verify version mismatch failed
|
||||
run: |
|
||||
if [[ "${{ steps.version-mismatch.outcome }}" == "success" ]]; then
|
||||
echo "❌ ERROR: Version mismatch should have been detected"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Version mismatch correctly detected"
|
||||
|
||||
test-npm-publish-version-formats:
|
||||
name: Test Version Format Support
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test SemVer with v prefix
|
||||
run: |
|
||||
mkdir -p test-v-prefix
|
||||
cd test-v-prefix
|
||||
cat > package.json <<'EOF'
|
||||
{
|
||||
"name": "@test/v-prefix",
|
||||
"version": "1.2.3",
|
||||
"description": "Test v prefix"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Should accept v1.2.3 and strip to 1.2.3
|
||||
echo "Testing v prefix version..."
|
||||
|
||||
- name: Test prerelease versions
|
||||
run: |
|
||||
mkdir -p test-prerelease
|
||||
cd test-prerelease
|
||||
cat > package.json <<'EOF'
|
||||
{
|
||||
"name": "@test/prerelease",
|
||||
"version": "1.0.0-alpha.1",
|
||||
"description": "Test prerelease"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Testing prerelease version format..."
|
||||
|
||||
- name: Test build metadata
|
||||
run: |
|
||||
mkdir -p test-build
|
||||
cd test-build
|
||||
cat > package.json <<'EOF'
|
||||
{
|
||||
"name": "@test/build-meta",
|
||||
"version": "1.0.0+build.123",
|
||||
"description": "Test build metadata"
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Testing build metadata format..."
|
||||
|
||||
test-npm-publish-outputs:
|
||||
name: Test Output Values
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test package
|
||||
run: |
|
||||
mkdir -p test-outputs
|
||||
cd test-outputs
|
||||
cat > package.json <<'EOF'
|
||||
{
|
||||
"name": "@test/outputs-test",
|
||||
"version": "1.5.0",
|
||||
"description": "Test outputs"
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Run npm-publish (validation only)
|
||||
id: publish-outputs
|
||||
uses: ./npm-publish
|
||||
continue-on-error: true
|
||||
with:
|
||||
registry-url: 'https://npm.custom.com/'
|
||||
scope: '@custom-scope'
|
||||
package-version: '1.5.0'
|
||||
npm_token: 'test-token-outputs'
|
||||
env:
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}/test-outputs
|
||||
|
||||
- name: Verify outputs
|
||||
run: |
|
||||
registry="${{ steps.publish-outputs.outputs.registry-url }}"
|
||||
scope="${{ steps.publish-outputs.outputs.scope }}"
|
||||
version="${{ steps.publish-outputs.outputs.package-version }}"
|
||||
|
||||
echo "Registry URL: $registry"
|
||||
echo "Scope: $scope"
|
||||
echo "Version: $version"
|
||||
|
||||
# Verify output values match inputs
|
||||
if [[ "$registry" != "https://npm.custom.com/" ]]; then
|
||||
echo "❌ ERROR: Registry URL output mismatch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$scope" != "@custom-scope" ]]; then
|
||||
echo "❌ ERROR: Scope output mismatch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$version" != "1.5.0" ]]; then
|
||||
echo "❌ ERROR: Version output mismatch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ All outputs match expected values"
|
||||
|
||||
test-npm-publish-secret-masking:
|
||||
name: Test Secret Masking
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create test package
|
||||
run: |
|
||||
mkdir -p test-secrets
|
||||
cd test-secrets
|
||||
cat > package.json <<'EOF'
|
||||
{
|
||||
"name": "@test/secrets-test",
|
||||
"version": "1.0.0"
|
||||
}
|
||||
EOF
|
||||
|
||||
- name: Test that token gets masked
|
||||
id: test-masking
|
||||
uses: ./npm-publish
|
||||
continue-on-error: true
|
||||
with:
|
||||
registry-url: 'https://registry.npmjs.org/'
|
||||
scope: '@test'
|
||||
package-version: '1.0.0'
|
||||
npm_token: 'super-secret-token-12345'
|
||||
env:
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}/test-secrets
|
||||
|
||||
- name: Verify token is not in logs
|
||||
run: |
|
||||
echo "✓ Token should be masked in GitHub Actions logs"
|
||||
echo "✓ Secret masking test completed"
|
||||
|
||||
integration-test-summary:
|
||||
name: Integration Test Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test-npm-publish-validation
|
||||
- test-npm-publish-package-validation
|
||||
- test-npm-publish-version-formats
|
||||
- test-npm-publish-outputs
|
||||
- test-npm-publish-secret-masking
|
||||
steps:
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "=========================================="
|
||||
echo "NPM Publish Integration Tests - PASSED"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "✓ Input validation tests"
|
||||
echo "✓ Package validation tests"
|
||||
echo "✓ Version format tests"
|
||||
echo "✓ Output verification tests"
|
||||
echo "✓ Secret masking tests"
|
||||
echo ""
|
||||
echo "All npm-publish integration tests completed successfully!"
|
||||
434
_tests/integration/workflows/pre-commit-test.yml
Normal file
434
_tests/integration/workflows/pre-commit-test.yml
Normal file
@@ -0,0 +1,434 @@
|
||||
---
|
||||
name: Integration Test - Pre-commit
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'pre-commit/**'
|
||||
- 'validate-inputs/**'
|
||||
- '_tests/integration/workflows/pre-commit-test.yml'
|
||||
|
||||
jobs:
|
||||
test-pre-commit-validation:
|
||||
name: Test Input Validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test with default inputs (should pass validation)
|
||||
id: default-inputs
|
||||
uses: ./pre-commit
|
||||
continue-on-error: true
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Verify validation passed
|
||||
run: |
|
||||
echo "✓ Default inputs validation completed"
|
||||
|
||||
- name: Test with custom config file
|
||||
id: custom-config
|
||||
uses: ./pre-commit
|
||||
continue-on-error: true
|
||||
with:
|
||||
pre-commit-config: '.pre-commit-config.yaml'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Verify custom config accepted
|
||||
run: |
|
||||
echo "✓ Custom config file accepted"
|
||||
|
||||
- name: Test with base branch
|
||||
id: with-base-branch
|
||||
uses: ./pre-commit
|
||||
continue-on-error: true
|
||||
with:
|
||||
base-branch: 'main'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Verify base branch accepted
|
||||
run: |
|
||||
echo "✓ Base branch parameter accepted"
|
||||
|
||||
test-pre-commit-git-config:
|
||||
name: Test Git Configuration
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test custom git user
|
||||
run: |
|
||||
# Simulate set-git-config step
|
||||
git config user.name "Test User"
|
||||
git config user.email "test@example.com"
|
||||
|
||||
# Verify configuration
|
||||
user_name=$(git config user.name)
|
||||
user_email=$(git config user.email)
|
||||
|
||||
if [[ "$user_name" != "Test User" ]]; then
|
||||
echo "❌ ERROR: Git user name not set correctly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$user_email" != "test@example.com" ]]; then
|
||||
echo "❌ ERROR: Git user email not set correctly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Git configuration works correctly"
|
||||
|
||||
- name: Test default git user
|
||||
run: |
|
||||
# Simulate default configuration
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
|
||||
# Verify default configuration
|
||||
user_name=$(git config user.name)
|
||||
user_email=$(git config user.email)
|
||||
|
||||
if [[ "$user_name" != "GitHub Actions" ]]; then
|
||||
echo "❌ ERROR: Default git user name not set correctly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$user_email" != "github-actions@github.com" ]]; then
|
||||
echo "❌ ERROR: Default git user email not set correctly"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Default git configuration works correctly"
|
||||
|
||||
test-pre-commit-option-generation:
|
||||
name: Test Option Generation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test all-files option (no base branch)
|
||||
run: |
|
||||
BASE_BRANCH=""
|
||||
if [ -z "$BASE_BRANCH" ]; then
|
||||
option="--all-files"
|
||||
else
|
||||
option="--from-ref $BASE_BRANCH --to-ref HEAD"
|
||||
fi
|
||||
|
||||
if [[ "$option" != "--all-files" ]]; then
|
||||
echo "❌ ERROR: Should use --all-files when no base branch"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Correctly generates --all-files option"
|
||||
|
||||
- name: Test diff option (with base branch)
|
||||
run: |
|
||||
BASE_BRANCH="main"
|
||||
if [ -z "$BASE_BRANCH" ]; then
|
||||
option="--all-files"
|
||||
else
|
||||
option="--from-ref $BASE_BRANCH --to-ref HEAD"
|
||||
fi
|
||||
|
||||
expected="--from-ref main --to-ref HEAD"
|
||||
if [[ "$option" != "$expected" ]]; then
|
||||
echo "❌ ERROR: Option mismatch. Expected: $expected, Got: $option"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Correctly generates diff option with base branch"
|
||||
|
||||
test-pre-commit-config-file-detection:
|
||||
name: Test Config File Detection
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Verify default config exists
|
||||
run: |
|
||||
if [[ -f ".pre-commit-config.yaml" ]]; then
|
||||
echo "✓ Default .pre-commit-config.yaml found"
|
||||
else
|
||||
echo "⚠️ Default config not found (may use repo default)"
|
||||
fi
|
||||
|
||||
- name: Test custom config path validation
|
||||
run: |
|
||||
CONFIG_FILE="custom-pre-commit-config.yaml"
|
||||
|
||||
# In real action, this would be validated
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
echo "✓ Custom config file validation would fail (expected)"
|
||||
else
|
||||
echo "✓ Custom config file exists"
|
||||
fi
|
||||
|
||||
test-pre-commit-hook-execution:
|
||||
name: Test Hook Execution Scenarios
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test pre-commit installed
|
||||
run: |
|
||||
if command -v pre-commit >/dev/null 2>&1; then
|
||||
echo "✓ pre-commit is installed"
|
||||
pre-commit --version
|
||||
else
|
||||
echo "⚠️ pre-commit not installed (would be installed by action)"
|
||||
fi
|
||||
|
||||
- name: Create test file with issues
|
||||
run: |
|
||||
mkdir -p test-pre-commit
|
||||
cd test-pre-commit
|
||||
|
||||
# Create a file with trailing whitespace
|
||||
echo "Line with trailing spaces " > test.txt
|
||||
echo "Line without issues" >> test.txt
|
||||
|
||||
# Create a minimal .pre-commit-config.yaml
|
||||
cat > .pre-commit-config.yaml <<'EOF'
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
EOF
|
||||
|
||||
echo "✓ Test environment created"
|
||||
|
||||
- name: Test hook detection of issues
|
||||
run: |
|
||||
cd test-pre-commit
|
||||
|
||||
# Check if trailing whitespace exists
|
||||
if grep -q " $" test.txt; then
|
||||
echo "✓ Test file has trailing whitespace (as expected)"
|
||||
else
|
||||
echo "❌ ERROR: Test file should have trailing whitespace"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test-pre-commit-outputs:
|
||||
name: Test Output Values
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test hooks_passed output
|
||||
run: |
|
||||
# Simulate successful hooks
|
||||
HOOKS_OUTCOME="success"
|
||||
|
||||
if [[ "$HOOKS_OUTCOME" == "success" ]]; then
|
||||
hooks_passed="true"
|
||||
else
|
||||
hooks_passed="false"
|
||||
fi
|
||||
|
||||
if [[ "$hooks_passed" != "true" ]]; then
|
||||
echo "❌ ERROR: hooks_passed should be true for success"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ hooks_passed output correct for success"
|
||||
|
||||
- name: Test hooks_passed output on failure
|
||||
run: |
|
||||
# Simulate failed hooks
|
||||
HOOKS_OUTCOME="failure"
|
||||
|
||||
if [[ "$HOOKS_OUTCOME" == "success" ]]; then
|
||||
hooks_passed="true"
|
||||
else
|
||||
hooks_passed="false"
|
||||
fi
|
||||
|
||||
if [[ "$hooks_passed" != "false" ]]; then
|
||||
echo "❌ ERROR: hooks_passed should be false for failure"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ hooks_passed output correct for failure"
|
||||
|
||||
- name: Test files_changed output
|
||||
run: |
|
||||
# Simulate git status check
|
||||
echo "test.txt" > /tmp/test-changes.txt
|
||||
|
||||
if [[ -s /tmp/test-changes.txt ]]; then
|
||||
files_changed="true"
|
||||
else
|
||||
files_changed="false"
|
||||
fi
|
||||
|
||||
if [[ "$files_changed" != "true" ]]; then
|
||||
echo "❌ ERROR: files_changed should be true when files exist"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ files_changed output correct"
|
||||
|
||||
test-pre-commit-uv-integration:
|
||||
name: Test UV Integration
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test PRE_COMMIT_USE_UV environment variable
|
||||
run: |
|
||||
PRE_COMMIT_USE_UV='1'
|
||||
|
||||
if [[ "$PRE_COMMIT_USE_UV" != "1" ]]; then
|
||||
echo "❌ ERROR: PRE_COMMIT_USE_UV should be set to 1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ PRE_COMMIT_USE_UV correctly set"
|
||||
echo "✓ pre-commit will use UV for faster installations"
|
||||
|
||||
test-pre-commit-workflow-scenarios:
|
||||
name: Test Workflow Scenarios
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test full workflow (all files)
|
||||
run: |
|
||||
echo "Simulating full workflow with --all-files..."
|
||||
|
||||
# 1. Validate inputs
|
||||
CONFIG_FILE=".pre-commit-config.yaml"
|
||||
echo "✓ Step 1: Validate inputs"
|
||||
|
||||
# 2. Set git config
|
||||
git config user.name "Test User"
|
||||
git config user.email "test@example.com"
|
||||
echo "✓ Step 2: Set git config"
|
||||
|
||||
# 3. Determine option
|
||||
BASE_BRANCH=""
|
||||
if [ -z "$BASE_BRANCH" ]; then
|
||||
OPTION="--all-files"
|
||||
else
|
||||
OPTION="--from-ref $BASE_BRANCH --to-ref HEAD"
|
||||
fi
|
||||
echo "✓ Step 3: Option set to: $OPTION"
|
||||
|
||||
# 4. Run pre-commit (simulated)
|
||||
echo "✓ Step 4: Would run: pre-commit run $OPTION"
|
||||
|
||||
# 5. Check for changes
|
||||
echo "✓ Step 5: Check for changes to commit"
|
||||
|
||||
echo "✓ Full workflow simulation completed"
|
||||
|
||||
- name: Test diff workflow (with base branch)
|
||||
run: |
|
||||
echo "Simulating diff workflow with base branch..."
|
||||
|
||||
# 1. Validate inputs
|
||||
CONFIG_FILE=".pre-commit-config.yaml"
|
||||
BASE_BRANCH="main"
|
||||
echo "✓ Step 1: Validate inputs (base-branch: $BASE_BRANCH)"
|
||||
|
||||
# 2. Set git config
|
||||
git config user.name "GitHub Actions"
|
||||
git config user.email "github-actions@github.com"
|
||||
echo "✓ Step 2: Set git config"
|
||||
|
||||
# 3. Determine option
|
||||
if [ -z "$BASE_BRANCH" ]; then
|
||||
OPTION="--all-files"
|
||||
else
|
||||
OPTION="--from-ref $BASE_BRANCH --to-ref HEAD"
|
||||
fi
|
||||
echo "✓ Step 3: Option set to: $OPTION"
|
||||
|
||||
# 4. Run pre-commit (simulated)
|
||||
echo "✓ Step 4: Would run: pre-commit run $OPTION"
|
||||
|
||||
# 5. Check for changes
|
||||
echo "✓ Step 5: Check for changes to commit"
|
||||
|
||||
echo "✓ Diff workflow simulation completed"
|
||||
|
||||
test-pre-commit-autofix-behavior:
|
||||
name: Test Autofix Behavior
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test autofix commit message
|
||||
run: |
|
||||
COMMIT_MESSAGE="style(pre-commit): autofix"
|
||||
|
||||
if [[ "$COMMIT_MESSAGE" != "style(pre-commit): autofix" ]]; then
|
||||
echo "❌ ERROR: Incorrect commit message"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Autofix commit message correct"
|
||||
|
||||
- name: Test git add options
|
||||
run: |
|
||||
ADD_OPTIONS="-u"
|
||||
|
||||
if [[ "$ADD_OPTIONS" != "-u" ]]; then
|
||||
echo "❌ ERROR: Incorrect add options"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✓ Git add options correct (-u for tracked files)"
|
||||
|
||||
- name: Test autofix always runs
|
||||
run: |
|
||||
# Simulate pre-commit failure
|
||||
PRECOMMIT_FAILED=true
|
||||
|
||||
# Autofix should still run (if: always())
|
||||
echo "✓ Autofix runs even when pre-commit fails"
|
||||
|
||||
integration-test-summary:
|
||||
name: Integration Test Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test-pre-commit-validation
|
||||
- test-pre-commit-git-config
|
||||
- test-pre-commit-option-generation
|
||||
- test-pre-commit-config-file-detection
|
||||
- test-pre-commit-hook-execution
|
||||
- test-pre-commit-outputs
|
||||
- test-pre-commit-uv-integration
|
||||
- test-pre-commit-workflow-scenarios
|
||||
- test-pre-commit-autofix-behavior
|
||||
steps:
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "=========================================="
|
||||
echo "Pre-commit Integration Tests - PASSED"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "✓ Input validation tests"
|
||||
echo "✓ Git configuration tests"
|
||||
echo "✓ Option generation tests"
|
||||
echo "✓ Config file detection tests"
|
||||
echo "✓ Hook execution tests"
|
||||
echo "✓ Output verification tests"
|
||||
echo "✓ UV integration tests"
|
||||
echo "✓ Workflow scenario tests"
|
||||
echo "✓ Autofix behavior tests"
|
||||
echo ""
|
||||
echo "All pre-commit integration tests completed successfully!"
|
||||
414
_tests/integration/workflows/version-validator-test.yml
Normal file
414
_tests/integration/workflows/version-validator-test.yml
Normal file
@@ -0,0 +1,414 @@
|
||||
---
|
||||
name: Integration Test - Version Validator
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
paths:
|
||||
- 'version-validator/**'
|
||||
- '_tests/integration/workflows/version-validator-test.yml'
|
||||
|
||||
jobs:
|
||||
test-version-validator-input-validation:
|
||||
name: Test Input Validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test empty version (should fail)
|
||||
run: |
|
||||
VERSION=""
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "✓ Empty version correctly rejected"
|
||||
else
|
||||
echo "❌ ERROR: Empty version should be rejected"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test dangerous characters in version
|
||||
run: |
|
||||
for version in "1.2.3;rm -rf /" "1.0&&echo" "1.0|cat" '1.0`cmd`' "1.0\$variable"; do
|
||||
if [[ "$version" == *";"* ]] || [[ "$version" == *"&&"* ]] || \
|
||||
[[ "$version" == *"|"* ]] || [[ "$version" == *"\`"* ]] || [[ "$version" == *"\$"* ]]; then
|
||||
echo "✓ Dangerous version '$version' correctly detected"
|
||||
else
|
||||
echo "❌ ERROR: Should detect dangerous characters in: $version"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test valid version strings
|
||||
run: |
|
||||
for version in "1.2.3" "v1.0.0" "2.0.0-alpha" "1.0.0+build"; do
|
||||
if [[ "$version" == *";"* ]] || [[ "$version" == *"&&"* ]] || \
|
||||
[[ "$version" == *"|"* ]] || [[ "$version" == *"\`"* ]] || [[ "$version" == *"\$"* ]]; then
|
||||
echo "❌ ERROR: Valid version should not be rejected: $version"
|
||||
exit 1
|
||||
else
|
||||
echo "✓ Valid version '$version' accepted"
|
||||
fi
|
||||
done
|
||||
|
||||
test-version-validator-regex-validation:
|
||||
name: Test Regex Validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test empty regex (should fail)
|
||||
run: |
|
||||
REGEX=""
|
||||
if [[ -z "$REGEX" ]]; then
|
||||
echo "✓ Empty regex correctly rejected"
|
||||
else
|
||||
echo "❌ ERROR: Empty regex should be rejected"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test potential ReDoS patterns
|
||||
run: |
|
||||
for regex in ".*.*" ".+.+"; do
|
||||
if [[ "$regex" == *".*.*"* ]] || [[ "$regex" == *".+.+"* ]]; then
|
||||
echo "✓ ReDoS pattern '$regex' detected (would show warning)"
|
||||
else
|
||||
echo "❌ ERROR: Should detect ReDoS pattern: $regex"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test safe regex patterns
|
||||
run: |
|
||||
for regex in "^[0-9]+\.[0-9]+$" "^v?[0-9]+"; do
|
||||
if [[ "$regex" == *".*.*"* ]] || [[ "$regex" == *".+.+"* ]]; then
|
||||
echo "❌ ERROR: Safe regex should not be flagged: $regex"
|
||||
exit 1
|
||||
else
|
||||
echo "✓ Safe regex '$regex' accepted"
|
||||
fi
|
||||
done
|
||||
|
||||
test-version-validator-language-validation:
|
||||
name: Test Language Parameter Validation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test dangerous characters in language
|
||||
run: |
|
||||
for lang in "node;rm" "python&&cmd" "ruby|cat"; do
|
||||
if [[ "$lang" == *";"* ]] || [[ "$lang" == *"&&"* ]] || [[ "$lang" == *"|"* ]]; then
|
||||
echo "✓ Dangerous language parameter '$lang' correctly detected"
|
||||
else
|
||||
echo "❌ ERROR: Should detect dangerous characters in: $lang"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test valid language parameters
|
||||
run: |
|
||||
for lang in "node" "python" "ruby" "go" "java"; do
|
||||
if [[ "$lang" == *";"* ]] || [[ "$lang" == *"&&"* ]] || [[ "$lang" == *"|"* ]]; then
|
||||
echo "❌ ERROR: Valid language should not be rejected: $lang"
|
||||
exit 1
|
||||
else
|
||||
echo "✓ Valid language '$lang' accepted"
|
||||
fi
|
||||
done
|
||||
|
||||
test-version-validator-version-cleaning:
|
||||
name: Test Version Cleaning
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test v prefix removal
|
||||
run: |
|
||||
for input in "v1.2.3" "V2.0.0"; do
|
||||
cleaned=$(echo "$input" | sed -e 's/^[vV]//')
|
||||
if [[ "$cleaned" == "1.2.3" ]] || [[ "$cleaned" == "2.0.0" ]]; then
|
||||
echo "✓ v prefix removed from '$input' -> '$cleaned'"
|
||||
else
|
||||
echo "❌ ERROR: Failed to clean '$input', got '$cleaned'"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test whitespace removal
|
||||
run: |
|
||||
input=" 1.2.3 "
|
||||
cleaned=$(echo "$input" | tr -d ' ')
|
||||
if [[ "$cleaned" == "1.2.3" ]]; then
|
||||
echo "✓ Whitespace removed: '$input' -> '$cleaned'"
|
||||
else
|
||||
echo "❌ ERROR: Failed to remove whitespace"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Test newline removal
|
||||
run: |
|
||||
input=$'1.2.3\n'
|
||||
cleaned=$(echo "$input" | tr -d '\n' | tr -d '\r')
|
||||
if [[ "$cleaned" == "1.2.3" ]]; then
|
||||
echo "✓ Newlines removed"
|
||||
else
|
||||
echo "❌ ERROR: Failed to remove newlines"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
test-version-validator-regex-matching:
|
||||
name: Test Regex Matching
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test default SemVer regex
|
||||
run: |
|
||||
REGEX='^[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$'
|
||||
|
||||
for version in "1.0.0" "1.2" "1.0.0-alpha" "1.0.0+build" "2.1.0-rc.1+build.123"; do
|
||||
if [[ $version =~ $REGEX ]]; then
|
||||
echo "✓ Version '$version' matches SemVer regex"
|
||||
else
|
||||
echo "❌ ERROR: Version '$version' should match SemVer"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test invalid versions against SemVer regex
|
||||
run: |
|
||||
REGEX='^[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$'
|
||||
|
||||
for version in "abc" "1.a.b" "not.a.version"; do
|
||||
if [[ $version =~ $REGEX ]]; then
|
||||
echo "❌ ERROR: Invalid version '$version' should not match"
|
||||
exit 1
|
||||
else
|
||||
echo "✓ Invalid version '$version' correctly rejected"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test custom strict regex
|
||||
run: |
|
||||
REGEX='^[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
|
||||
# Should match
|
||||
for version in "1.0.0" "2.5.10"; do
|
||||
if [[ $version =~ $REGEX ]]; then
|
||||
echo "✓ Version '$version' matches strict regex"
|
||||
else
|
||||
echo "❌ ERROR: Version '$version' should match strict regex"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Should not match
|
||||
for version in "1.0" "1.0.0-alpha"; do
|
||||
if [[ $version =~ $REGEX ]]; then
|
||||
echo "❌ ERROR: Version '$version' should not match strict regex"
|
||||
exit 1
|
||||
else
|
||||
echo "✓ Version '$version' correctly rejected by strict regex"
|
||||
fi
|
||||
done
|
||||
|
||||
test-version-validator-outputs:
|
||||
name: Test Output Generation
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test valid version outputs (simulation)
|
||||
run: |
|
||||
VERSION="v1.2.3"
|
||||
REGEX='^[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
|
||||
# Clean version
|
||||
cleaned=$(echo "$VERSION" | sed -e 's/^[vV]//' | tr -d ' ' | tr -d '\n' | tr -d '\r')
|
||||
|
||||
# Validate
|
||||
if [[ $cleaned =~ $REGEX ]]; then
|
||||
is_valid="true"
|
||||
validated_version="$cleaned"
|
||||
error_message=""
|
||||
|
||||
echo "is_valid=$is_valid"
|
||||
echo "validated_version=$validated_version"
|
||||
echo "error_message=$error_message"
|
||||
|
||||
if [[ "$is_valid" != "true" ]]; then
|
||||
echo "❌ ERROR: Should be valid"
|
||||
exit 1
|
||||
fi
|
||||
if [[ "$validated_version" != "1.2.3" ]]; then
|
||||
echo "❌ ERROR: Wrong validated version"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Valid version outputs correct"
|
||||
fi
|
||||
|
||||
- name: Test invalid version outputs (simulation)
|
||||
run: |
|
||||
VERSION="not.a.version"
|
||||
REGEX='^[0-9]+\.[0-9]+\.[0-9]+$'
|
||||
LANGUAGE="test"
|
||||
|
||||
# Clean version
|
||||
cleaned=$(echo "$VERSION" | sed -e 's/^[vV]//' | tr -d ' ' | tr -d '\n' | tr -d '\r')
|
||||
|
||||
# Validate
|
||||
if [[ $cleaned =~ $REGEX ]]; then
|
||||
is_valid="true"
|
||||
else
|
||||
is_valid="false"
|
||||
validated_version=""
|
||||
error_msg="Invalid $LANGUAGE version format: '$VERSION' (cleaned: '$cleaned'). Expected pattern: $REGEX"
|
||||
error_message=$(echo "$error_msg" | tr -d '\n\r')
|
||||
|
||||
echo "is_valid=$is_valid"
|
||||
echo "validated_version=$validated_version"
|
||||
echo "error_message=$error_message"
|
||||
|
||||
if [[ "$is_valid" != "false" ]]; then
|
||||
echo "❌ ERROR: Should be invalid"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -n "$validated_version" ]]; then
|
||||
echo "❌ ERROR: Validated version should be empty"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$error_message" ]]; then
|
||||
echo "❌ ERROR: Error message should not be empty"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Invalid version outputs correct"
|
||||
fi
|
||||
|
||||
test-version-validator-sanitization:
|
||||
name: Test Output Sanitization
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test error message sanitization
|
||||
run: |
|
||||
error_msg=$'Error message\nwith newlines'
|
||||
|
||||
sanitized=$(echo "$error_msg" | tr -d '\n\r')
|
||||
|
||||
if [[ "$sanitized" == *$'\n'* ]] || [[ "$sanitized" == *$'\r'* ]]; then
|
||||
echo "❌ ERROR: Newlines not removed from error message"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Error message sanitization works"
|
||||
|
||||
- name: Test validated version sanitization
|
||||
run: |
|
||||
VERSION=$'1.2.3\n'
|
||||
cleaned=$(echo "$VERSION" | sed -e 's/^[vV]//' | tr -d ' ' | tr -d '\n' | tr -d '\r')
|
||||
|
||||
if [[ "$cleaned" == *$'\n'* ]] || [[ "$cleaned" == *$'\r'* ]]; then
|
||||
echo "❌ ERROR: Newlines not removed from validated version"
|
||||
exit 1
|
||||
fi
|
||||
echo "✓ Validated version sanitization works"
|
||||
|
||||
test-version-validator-real-world-scenarios:
|
||||
name: Test Real World Scenarios
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Test Node.js version validation
|
||||
run: |
|
||||
REGEX='^[0-9]+(\.[0-9]+(\.[0-9]+)?)?$'
|
||||
|
||||
for version in "20" "20.9" "20.9.0" "18.17.1"; do
|
||||
cleaned=$(echo "$version" | sed -e 's/^[vV]//')
|
||||
if [[ $cleaned =~ $REGEX ]]; then
|
||||
echo "✓ Node.js version '$version' valid"
|
||||
else
|
||||
echo "❌ ERROR: Node.js version should be valid"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test Python version validation
|
||||
run: |
|
||||
REGEX='^[0-9]+\.[0-9]+(\.[0-9]+)?$'
|
||||
|
||||
for version in "3.11" "3.11.5" "3.12.0"; do
|
||||
cleaned=$(echo "$version" | sed -e 's/^[vV]//')
|
||||
if [[ $cleaned =~ $REGEX ]]; then
|
||||
echo "✓ Python version '$version' valid"
|
||||
else
|
||||
echo "❌ ERROR: Python version should be valid"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test CalVer validation
|
||||
run: |
|
||||
REGEX='^[0-9]{4}\.[0-9]{1,2}(\.[0-9]+)?$'
|
||||
|
||||
for version in "2024.3" "2024.3.15" "2024.10.1"; do
|
||||
cleaned=$(echo "$version" | sed -e 's/^[vV]//')
|
||||
if [[ $cleaned =~ $REGEX ]]; then
|
||||
echo "✓ CalVer version '$version' valid"
|
||||
else
|
||||
echo "❌ ERROR: CalVer version should be valid"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Test Docker tag validation
|
||||
run: |
|
||||
REGEX='^[a-z0-9][a-z0-9._-]*$'
|
||||
|
||||
for tag in "latest" "v1.2.3" "stable-alpine" "2024.10.15"; do
|
||||
cleaned=$(echo "$tag" | sed -e 's/^[vV]//')
|
||||
# Note: Docker tags are case-insensitive, so convert to lowercase
|
||||
cleaned=$(echo "$cleaned" | tr '[:upper:]' '[:lower:]')
|
||||
if [[ $cleaned =~ $REGEX ]]; then
|
||||
echo "✓ Docker tag '$tag' valid"
|
||||
else
|
||||
echo "❌ ERROR: Docker tag should be valid: $tag"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
integration-test-summary:
|
||||
name: Integration Test Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- test-version-validator-input-validation
|
||||
- test-version-validator-regex-validation
|
||||
- test-version-validator-language-validation
|
||||
- test-version-validator-version-cleaning
|
||||
- test-version-validator-regex-matching
|
||||
- test-version-validator-outputs
|
||||
- test-version-validator-sanitization
|
||||
- test-version-validator-real-world-scenarios
|
||||
steps:
|
||||
- name: Summary
|
||||
run: |
|
||||
echo "=========================================="
|
||||
echo "Version Validator Integration Tests - PASSED"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "✓ Input validation tests"
|
||||
echo "✓ Regex validation tests"
|
||||
echo "✓ Language validation tests"
|
||||
echo "✓ Version cleaning tests"
|
||||
echo "✓ Regex matching tests"
|
||||
echo "✓ Output generation tests"
|
||||
echo "✓ Sanitization tests"
|
||||
echo "✓ Real world scenario tests"
|
||||
echo ""
|
||||
echo "All version-validator integration tests completed successfully!"
|
||||
@@ -151,14 +151,14 @@ discover_actions() {
|
||||
if [[ $action_name == *"$ACTION_FILTER"* ]]; then
|
||||
actions+=("$action_name")
|
||||
fi
|
||||
done < <(find "${TEST_ROOT}/.." -mindepth 1 -maxdepth 1 -type d -name "*-*" | sort)
|
||||
done < <(find "${TEST_ROOT}/.." -mindepth 2 -maxdepth 2 -type f -name "action.yml" -exec dirname {} \; | sort)
|
||||
else
|
||||
# All actions
|
||||
while IFS= read -r action_dir; do
|
||||
local action_name
|
||||
action_name=$(basename "$action_dir")
|
||||
actions+=("$action_name")
|
||||
done < <(find "${TEST_ROOT}/.." -mindepth 1 -maxdepth 1 -type d -name "*-*" | sort)
|
||||
done < <(find "${TEST_ROOT}/.." -mindepth 2 -maxdepth 2 -type f -name "action.yml" -exec dirname {} \; | sort)
|
||||
fi
|
||||
|
||||
log_info "Discovered ${#actions[@]} actions to test: ${actions[*]}"
|
||||
@@ -203,7 +203,7 @@ install_shellspec() {
|
||||
|
||||
# Pinned SHA256 checksum for ShellSpec 0.28.1
|
||||
# Source: https://github.com/shellspec/shellspec/archive/refs/tags/0.28.1.tar.gz
|
||||
local checksum="351e7a63b8df47c07b022c19d21a167b85693f5eb549fa96e64f64844b680024"
|
||||
local checksum="400d835466429a5fe6c77a62775a9173729d61dd43e05dfa893e8cf6cb511783"
|
||||
|
||||
# Ensure cleanup of the downloaded file
|
||||
# Use ${tarball:-} to handle unbound variable when trap fires after function returns
|
||||
|
||||
142
_tests/unit/action-versioning/validation.spec.sh
Executable file
142
_tests/unit/action-versioning/validation.spec.sh
Executable file
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for action-versioning action validation and logic
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "action-versioning action"
|
||||
ACTION_DIR="action-versioning"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating major-version input"
|
||||
It "accepts valid year-based version (vYYYY)"
|
||||
When call validate_input_python "action-versioning" "major-version" "v2025"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid semantic version (v1)"
|
||||
When call validate_input_python "action-versioning" "major-version" "v1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid semantic version (v2)"
|
||||
When call validate_input_python "action-versioning" "major-version" "v2"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts year-based version from 2020"
|
||||
When call validate_input_python "action-versioning" "major-version" "v2020"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts year-based version for 2030"
|
||||
When call validate_input_python "action-versioning" "major-version" "v2030"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects version without v prefix"
|
||||
When call validate_input_python "action-versioning" "major-version" "2025"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "action-versioning" "major-version" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects empty version"
|
||||
When call validate_input_python "action-versioning" "major-version" ""
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "action-versioning" "major-version" "v2025; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating token input"
|
||||
It "accepts valid GitHub token (classic)"
|
||||
When call validate_input_python "action-versioning" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid GitHub fine-grained token"
|
||||
When call validate_input_python "action-versioning" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty token (optional input)"
|
||||
When call validate_input_python "action-versioning" "token" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid token format"
|
||||
When call validate_input_python "action-versioning" "token" "invalid-token"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects token with command injection"
|
||||
When call validate_input_python "action-versioning" "token" "ghp_123456789012345678901234567890123456; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Action Versioning"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "major-version"
|
||||
The output should include "token"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "updated"
|
||||
The output should include "commit-sha"
|
||||
The output should include "needs-annual-bump"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "requires major-version input"
|
||||
When call is_input_required "$ACTION_FILE" "major-version"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has token as optional input"
|
||||
When call is_input_required "$ACTION_FILE" "token"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in major-version"
|
||||
When call validate_input_python "action-versioning" "major-version" "v../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in major-version"
|
||||
When call validate_input_python "action-versioning" "major-version" "v2025|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command substitution in major-version"
|
||||
When call validate_input_python "action-versioning" "major-version" "v\$(whoami)"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against path traversal in token"
|
||||
When call validate_input_python "action-versioning" "token" "../../../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,149 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for biome-check action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "biome-check action"
|
||||
ACTION_DIR="biome-check"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating token input"
|
||||
It "accepts personal access token"
|
||||
When call validate_input_python "biome-check" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts organization token"
|
||||
When call validate_input_python "biome-check" "token" "gho_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts user token"
|
||||
When call validate_input_python "biome-check" "token" "ghu_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts server token"
|
||||
When call validate_input_python "biome-check" "token" "ghs_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts refresh token"
|
||||
When call validate_input_python "biome-check" "token" "ghr_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating email input"
|
||||
It "accepts valid email"
|
||||
When call validate_input_python "biome-check" "email" "test@example.com"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects invalid email without @"
|
||||
When call validate_input_python "biome-check" "email" "testexample.com"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects invalid email without domain"
|
||||
When call validate_input_python "biome-check" "email" "test@"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating username input"
|
||||
It "accepts valid username"
|
||||
When call validate_input_python "biome-check" "username" "github-actions"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects semicolon injection"
|
||||
When call validate_input_python "biome-check" "username" "user;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects ampersand injection"
|
||||
When call validate_input_python "biome-check" "username" "user&&malicious"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects pipe injection"
|
||||
When call validate_input_python "biome-check" "username" "user|dangerous"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects overly long username"
|
||||
When call validate_input_python "biome-check" "username" "this-username-is-definitely-too-long-for-github-maximum-length-limit"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating max-retries input"
|
||||
It "accepts valid retry count"
|
||||
When call validate_input_python "biome-check" "max-retries" "5"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects zero retries"
|
||||
When call validate_input_python "biome-check" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects negative retries"
|
||||
When call validate_input_python "biome-check" "max-retries" "-1"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects retries above limit"
|
||||
When call validate_input_python "biome-check" "max-retries" "15"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects non-numeric retries"
|
||||
When call validate_input_python "biome-check" "max-retries" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Biome Check"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "token"
|
||||
The output should include "username"
|
||||
The output should include "email"
|
||||
The output should include "max-retries"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
outputs=$(get_action_outputs "$ACTION_FILE")
|
||||
When call echo "$outputs"
|
||||
The output should include "check_status"
|
||||
The output should include "errors_count"
|
||||
The output should include "warnings_count"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating security"
|
||||
It "rejects command injection in token"
|
||||
When call validate_input_python "biome-check" "token" "ghp_123;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects command injection in email"
|
||||
When call validate_input_python "biome-check" "email" "user@domain.com;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates all inputs for injection patterns"
|
||||
When call validate_input_python "biome-check" "max-retries" "3;malicious"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing outputs"
|
||||
It "produces all expected outputs consistently"
|
||||
When call test_action_outputs "$ACTION_DIR" "token" "ghp_123456789012345678901234567890123456" "username" "github-actions" "email" "test@example.com" "max-retries" "3"
|
||||
The status should be success
|
||||
The stderr should include "Testing action outputs for: biome-check"
|
||||
The stderr should include "Output test passed for: biome-check"
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,148 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for biome-fix action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "biome-fix action"
|
||||
ACTION_DIR="biome-fix"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating token input"
|
||||
It "accepts personal access token"
|
||||
When call validate_input_python "biome-fix" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts organization token"
|
||||
When call validate_input_python "biome-fix" "token" "gho_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts user token"
|
||||
When call validate_input_python "biome-fix" "token" "ghu_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts server token"
|
||||
When call validate_input_python "biome-fix" "token" "ghs_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts refresh token"
|
||||
When call validate_input_python "biome-fix" "token" "ghr_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating email input"
|
||||
It "accepts valid email"
|
||||
When call validate_input_python "biome-fix" "email" "test@example.com"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects invalid email without @"
|
||||
When call validate_input_python "biome-fix" "email" "testexample.com"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects invalid email without domain"
|
||||
When call validate_input_python "biome-fix" "email" "test@"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating username input"
|
||||
It "accepts valid username"
|
||||
When call validate_input_python "biome-fix" "username" "github-actions"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects semicolon injection"
|
||||
When call validate_input_python "biome-fix" "username" "user;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects ampersand injection"
|
||||
When call validate_input_python "biome-fix" "username" "user&&malicious"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects pipe injection"
|
||||
When call validate_input_python "biome-fix" "username" "user|dangerous"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects overly long username"
|
||||
When call validate_input_python "biome-fix" "username" "this-username-is-definitely-too-long-for-github-maximum-length-limit"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating max-retries input"
|
||||
It "accepts valid retry count"
|
||||
When call validate_input_python "biome-fix" "max-retries" "5"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects zero retries"
|
||||
When call validate_input_python "biome-fix" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects negative retries"
|
||||
When call validate_input_python "biome-fix" "max-retries" "-1"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects retries above limit"
|
||||
When call validate_input_python "biome-fix" "max-retries" "15"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects non-numeric retries"
|
||||
When call validate_input_python "biome-fix" "max-retries" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Biome Fix"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "token"
|
||||
The output should include "username"
|
||||
The output should include "email"
|
||||
The output should include "max-retries"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
outputs=$(get_action_outputs "$ACTION_FILE")
|
||||
When call echo "$outputs"
|
||||
The output should include "files_changed"
|
||||
The output should include "fix_status"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating security"
|
||||
It "rejects command injection in token"
|
||||
When call validate_input_python "biome-fix" "token" "ghp_123;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects command injection in email"
|
||||
When call validate_input_python "biome-fix" "email" "user@domain.com;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates all inputs for injection patterns"
|
||||
When call validate_input_python "biome-fix" "max-retries" "3;malicious"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing outputs"
|
||||
It "produces all expected outputs consistently"
|
||||
When call test_action_outputs "$ACTION_DIR" "token" "ghp_123456789012345678901234567890123456" "username" "github-actions" "email" "test@example.com" "max-retries" "3"
|
||||
The status should be success
|
||||
The stderr should include "Testing action outputs for: biome-fix"
|
||||
The stderr should include "Output test passed for: biome-fix"
|
||||
End
|
||||
End
|
||||
End
|
||||
230
_tests/unit/biome-lint/validation.spec.sh
Executable file
230
_tests/unit/biome-lint/validation.spec.sh
Executable file
@@ -0,0 +1,230 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for biome-lint action validation and logic
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "biome-lint action"
|
||||
ACTION_DIR="biome-lint"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating mode input"
|
||||
It "accepts check mode"
|
||||
When call validate_input_python "biome-lint" "mode" "check"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts fix mode"
|
||||
When call validate_input_python "biome-lint" "mode" "fix"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty mode (uses default)"
|
||||
When call validate_input_python "biome-lint" "mode" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid mode"
|
||||
When call validate_input_python "biome-lint" "mode" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects mode with command injection"
|
||||
When call validate_input_python "biome-lint" "mode" "check; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating token input"
|
||||
It "accepts valid GitHub token (classic)"
|
||||
When call validate_input_python "biome-lint" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid GitHub fine-grained token"
|
||||
When call validate_input_python "biome-lint" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty token (optional)"
|
||||
When call validate_input_python "biome-lint" "token" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid token format"
|
||||
When call validate_input_python "biome-lint" "token" "invalid-token"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects token with command injection"
|
||||
When call validate_input_python "biome-lint" "token" "ghp_123456789012345678901234567890123456; echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating username input"
|
||||
It "accepts valid username"
|
||||
When call validate_input_python "biome-lint" "username" "github-actions"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts username with hyphens"
|
||||
When call validate_input_python "biome-lint" "username" "my-bot-user"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty username (uses default)"
|
||||
When call validate_input_python "biome-lint" "username" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects username with command injection"
|
||||
When call validate_input_python "biome-lint" "username" "user; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating email input"
|
||||
It "accepts valid email"
|
||||
When call validate_input_python "biome-lint" "email" "github-actions@github.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with plus sign"
|
||||
When call validate_input_python "biome-lint" "email" "user+bot@example.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with subdomain"
|
||||
When call validate_input_python "biome-lint" "email" "bot@ci.example.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty email (uses default)"
|
||||
When call validate_input_python "biome-lint" "email" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid email format"
|
||||
When call validate_input_python "biome-lint" "email" "not-an-email"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects email with command injection"
|
||||
When call validate_input_python "biome-lint" "email" "user@example.com; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating max-retries input"
|
||||
It "accepts valid retry count (default)"
|
||||
When call validate_input_python "biome-lint" "max-retries" "3"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts retry count of 1"
|
||||
When call validate_input_python "biome-lint" "max-retries" "1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts retry count of 10"
|
||||
When call validate_input_python "biome-lint" "max-retries" "10"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty max-retries (uses default)"
|
||||
When call validate_input_python "biome-lint" "max-retries" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects negative retry count"
|
||||
When call validate_input_python "biome-lint" "max-retries" "-1"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects non-numeric retry count"
|
||||
When call validate_input_python "biome-lint" "max-retries" "abc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects retry count with command injection"
|
||||
When call validate_input_python "biome-lint" "max-retries" "3; echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating fail-on-error input"
|
||||
It "accepts true"
|
||||
When call validate_input_python "biome-lint" "fail-on-error" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts false"
|
||||
When call validate_input_python "biome-lint" "fail-on-error" "false"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty (uses default)"
|
||||
When call validate_input_python "biome-lint" "fail-on-error" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid boolean value"
|
||||
When call validate_input_python "biome-lint" "fail-on-error" "maybe"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Biome Lint"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "mode"
|
||||
The output should include "token"
|
||||
The output should include "username"
|
||||
The output should include "email"
|
||||
The output should include "max-retries"
|
||||
The output should include "fail-on-error"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "status"
|
||||
The output should include "errors_count"
|
||||
The output should include "warnings_count"
|
||||
The output should include "files_changed"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "has all inputs as optional (with defaults)"
|
||||
When call is_input_required "$ACTION_FILE" "mode"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal"
|
||||
When call validate_input_python "biome-lint" "username" "../../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters"
|
||||
When call validate_input_python "biome-lint" "email" "user@example.com|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command substitution"
|
||||
When call validate_input_python "biome-lint" "username" "\$(whoami)"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,99 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for common-file-check action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "common-file-check action"
|
||||
ACTION_DIR="common-file-check"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating file-pattern input"
|
||||
It "accepts simple file pattern"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "package.json"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts glob pattern with wildcard"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "*.json"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts glob pattern with question mark"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "test?.js"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts nested path pattern"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "src/**/*.ts"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts pattern with braces"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "*.{js,ts}"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts pattern with brackets"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "[A-Z]*.txt"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects empty file pattern"
|
||||
When call validate_input_python "common-file-check" "file-pattern" ""
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "../../../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects command injection"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "*.json;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Common File Check"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "file-pattern"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
outputs=$(get_action_outputs "$ACTION_FILE")
|
||||
When call echo "$outputs"
|
||||
The output should include "found"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating security"
|
||||
It "validates glob patterns safely"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "**/*.{js,ts,json}"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects injection in glob patterns"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "*.js&&malicious"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects pipe injection in patterns"
|
||||
When call validate_input_python "common-file-check" "file-pattern" "*.js|dangerous"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing outputs"
|
||||
It "produces all expected outputs consistently"
|
||||
When call test_action_outputs "$ACTION_DIR" "file-pattern" "*.json"
|
||||
The status should be success
|
||||
The stderr should include "Testing action outputs for: common-file-check"
|
||||
The stderr should include "Output test passed for: common-file-check"
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,165 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for common-retry action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "common-retry action"
|
||||
ACTION_DIR="common-retry"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating max-retries input"
|
||||
It "accepts minimum value (1)"
|
||||
When call validate_input_python "common-retry" "max-retries" "1"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts maximum value (10)"
|
||||
When call validate_input_python "common-retry" "max-retries" "10"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects below minimum"
|
||||
When call validate_input_python "common-retry" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects above maximum"
|
||||
When call validate_input_python "common-retry" "max-retries" "11"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects non-numeric"
|
||||
When call validate_input_python "common-retry" "max-retries" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating retry-delay input"
|
||||
It "accepts minimum value (1)"
|
||||
When call validate_input_python "common-retry" "retry-delay" "1"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts maximum value (300)"
|
||||
When call validate_input_python "common-retry" "retry-delay" "300"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects below minimum"
|
||||
When call validate_input_python "common-retry" "retry-delay" "0"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects above maximum"
|
||||
When call validate_input_python "common-retry" "retry-delay" "301"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating backoff-strategy input"
|
||||
It "accepts linear strategy"
|
||||
When call validate_input_python "common-retry" "backoff-strategy" "linear"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts exponential strategy"
|
||||
When call validate_input_python "common-retry" "backoff-strategy" "exponential"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts fixed strategy"
|
||||
When call validate_input_python "common-retry" "backoff-strategy" "fixed"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects invalid strategy"
|
||||
When call validate_input_python "common-retry" "backoff-strategy" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating timeout input"
|
||||
It "accepts minimum value (1)"
|
||||
When call validate_input_python "common-retry" "timeout" "1"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts maximum value (3600)"
|
||||
When call validate_input_python "common-retry" "timeout" "3600"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects below minimum"
|
||||
When call validate_input_python "common-retry" "timeout" "0"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects above maximum"
|
||||
When call validate_input_python "common-retry" "timeout" "3601"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating working-directory input"
|
||||
It "accepts current directory"
|
||||
When call validate_input_python "common-retry" "working-directory" "."
|
||||
The status should be success
|
||||
End
|
||||
It "accepts relative path"
|
||||
When call validate_input_python "common-retry" "working-directory" "src/app"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "common-retry" "working-directory" "../../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating shell input"
|
||||
It "accepts bash shell"
|
||||
When call validate_input_python "common-retry" "shell" "bash"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts sh shell"
|
||||
When call validate_input_python "common-retry" "shell" "sh"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects zsh shell"
|
||||
When call validate_input_python "common-retry" "shell" "zsh"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Common Retry"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating security"
|
||||
It "rejects command injection with semicolon"
|
||||
When call validate_input_python "common-retry" "command" "value; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects command injection with ampersand"
|
||||
When call validate_input_python "common-retry" "command" "value && malicious"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "accepts valid success codes"
|
||||
When call validate_input_python "common-retry" "success-codes" "0,1,2"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects success codes with injection"
|
||||
When call validate_input_python "common-retry" "success-codes" "0;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "accepts valid retry codes"
|
||||
When call validate_input_python "common-retry" "retry-codes" "1,126,127"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects retry codes with injection"
|
||||
When call validate_input_python "common-retry" "retry-codes" "1;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
End
|
||||
@@ -1,40 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for docker-publish-gh action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "docker-publish-gh action"
|
||||
ACTION_DIR="docker-publish-gh"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating inputs"
|
||||
It "accepts valid image name"
|
||||
When call validate_input_python "docker-publish-gh" "image-name" "myapp"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts valid GitHub token"
|
||||
When call validate_input_python "docker-publish-gh" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts valid tags"
|
||||
When call validate_input_python "docker-publish-gh" "tags" "v1.0.0,latest"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects injection in token"
|
||||
When call validate_input_python "docker-publish-gh" "token" "ghp_123;malicious"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should match pattern "*Docker*"
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,48 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for docker-publish-hub action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "docker-publish-hub action"
|
||||
ACTION_DIR="docker-publish-hub"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating inputs"
|
||||
It "accepts valid image name"
|
||||
When call validate_input_python "docker-publish-hub" "image-name" "myapp"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts valid username"
|
||||
When call validate_input_python "docker-publish-hub" "username" "dockeruser"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts valid password"
|
||||
When call validate_input_python "docker-publish-hub" "password" "secretpassword123"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts valid tags"
|
||||
When call validate_input_python "docker-publish-hub" "tags" "v1.0.0,latest"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects injection in username"
|
||||
When call validate_input_python "docker-publish-hub" "username" "user;malicious"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects injection in password"
|
||||
When call validate_input_python "docker-publish-hub" "password" "pass;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should match pattern "*Docker*"
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,85 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for dotnet-version-detect action validation and logic
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "dotnet-version-detect action"
|
||||
ACTION_DIR="dotnet-version-detect"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating default-version input"
|
||||
It "accepts valid dotnet version"
|
||||
When call validate_input_python "dotnet-version-detect" "default-version" "8.0"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts full semantic version"
|
||||
When call validate_input_python "dotnet-version-detect" "default-version" "8.0.0"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts dotnet 6 version"
|
||||
When call validate_input_python "dotnet-version-detect" "default-version" "6.0.0"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts dotnet 7 version"
|
||||
When call validate_input_python "dotnet-version-detect" "default-version" "7.0.0"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "dotnet-version-detect" "default-version" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects version with leading zeros"
|
||||
When call validate_input_python "dotnet-version-detect" "default-version" "08.0.0"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects unsupported version"
|
||||
When call validate_input_python "dotnet-version-detect" "default-version" "2.0.0"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Dotnet Version Detect"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "default-version"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
outputs=$(get_action_outputs "$ACTION_FILE")
|
||||
When call echo "$outputs"
|
||||
The output should include "dotnet-version"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating security"
|
||||
It "rejects injection in version"
|
||||
When call validate_input_python "dotnet-version-detect" "default-version" "8.0;malicious"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates version security"
|
||||
When call validate_input_python "dotnet-version-detect" "default-version" "8.0&&malicious"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing outputs"
|
||||
It "produces all expected outputs consistently"
|
||||
When call test_action_outputs "$ACTION_DIR" "default-version" "8.0"
|
||||
The status should be success
|
||||
The stderr should include "Testing action outputs for: dotnet-version-detect"
|
||||
The stderr should include "Output test passed for: dotnet-version-detect"
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,355 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for eslint-check action validation and logic
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "eslint-check action"
|
||||
ACTION_DIR="eslint-check"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating working-directory input"
|
||||
It "accepts current directory"
|
||||
When call validate_input_python "eslint-check" "working-directory" "."
|
||||
The status should be success
|
||||
End
|
||||
It "accepts relative path"
|
||||
When call validate_input_python "eslint-check" "working-directory" "src/frontend"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts nested directory"
|
||||
When call validate_input_python "eslint-check" "working-directory" "packages/ui"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "eslint-check" "working-directory" "../../../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects absolute paths"
|
||||
When call validate_input_python "eslint-check" "working-directory" "/etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects injection attempts"
|
||||
When call validate_input_python "eslint-check" "working-directory" "src; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating eslint-version input"
|
||||
It "accepts latest version"
|
||||
When call validate_input_python "eslint-check" "eslint-version" "latest"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts semantic version"
|
||||
When call validate_input_python "eslint-check" "eslint-version" "8.57.0"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts version with prerelease"
|
||||
When call validate_input_python "eslint-check" "eslint-version" "9.0.0-alpha.0"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts older stable version"
|
||||
When call validate_input_python "eslint-check" "eslint-version" "7.32.0"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "eslint-check" "eslint-version" "8.57"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects version with letters"
|
||||
When call validate_input_python "eslint-check" "eslint-version" "8.57.0a"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects empty version"
|
||||
When call validate_input_python "eslint-check" "eslint-version" ""
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating config-file input"
|
||||
It "accepts default eslintrc"
|
||||
When call validate_input_python "eslint-check" "config-file" ".eslintrc"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts eslintrc.json"
|
||||
When call validate_input_python "eslint-check" "config-file" ".eslintrc.json"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts eslint.config.js"
|
||||
When call validate_input_python "eslint-check" "config-file" "eslint.config.js"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts relative path config"
|
||||
When call validate_input_python "eslint-check" "config-file" "config/eslint.json"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "eslint-check" "config-file" "../../../malicious.js"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects injection in config path"
|
||||
When call validate_input_python "eslint-check" "config-file" "config.js;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating ignore-file input"
|
||||
It "accepts default eslintignore"
|
||||
When call validate_input_python "eslint-check" "ignore-file" ".eslintignore"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts custom ignore file"
|
||||
When call validate_input_python "eslint-check" "ignore-file" "eslint-ignore.txt"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts relative path ignore file"
|
||||
When call validate_input_python "eslint-check" "ignore-file" "config/.eslintignore"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "eslint-check" "ignore-file" "../../sensitive.txt"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating file-extensions input"
|
||||
It "accepts default extensions"
|
||||
When call validate_input_python "eslint-check" "file-extensions" ".js,.jsx,.ts,.tsx"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts single extension"
|
||||
When call validate_input_python "eslint-check" "file-extensions" ".js"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts TypeScript extensions only"
|
||||
When call validate_input_python "eslint-check" "file-extensions" ".ts,.tsx"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts Vue and JavaScript extensions"
|
||||
When call validate_input_python "eslint-check" "file-extensions" ".js,.vue,.ts"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects extensions without dots"
|
||||
When call validate_input_python "eslint-check" "file-extensions" "js,ts"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects invalid extension format"
|
||||
When call validate_input_python "eslint-check" "file-extensions" ".js;.ts"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects extensions with special characters"
|
||||
When call validate_input_python "eslint-check" "file-extensions" ".js,.t$"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating boolean inputs"
|
||||
It "accepts cache as true"
|
||||
When call validate_input_python "eslint-check" "cache" "true"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts cache as false"
|
||||
When call validate_input_python "eslint-check" "cache" "false"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts fail-on-error as true"
|
||||
When call validate_input_python "eslint-check" "fail-on-error" "true"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts fail-on-error as false"
|
||||
When call validate_input_python "eslint-check" "fail-on-error" "false"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects invalid boolean value"
|
||||
When call validate_input_python "eslint-check" "cache" "maybe"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects numeric boolean"
|
||||
When call validate_input_python "eslint-check" "fail-on-error" "1"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating numeric inputs"
|
||||
It "accepts zero max-warnings"
|
||||
When call validate_input_python "eslint-check" "max-warnings" "0"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts reasonable max-warnings"
|
||||
When call validate_input_python "eslint-check" "max-warnings" "10"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts large max-warnings"
|
||||
When call validate_input_python "eslint-check" "max-warnings" "1000"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts valid max-retries"
|
||||
When call validate_input_python "eslint-check" "max-retries" "3"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts minimum retries"
|
||||
When call validate_input_python "eslint-check" "max-retries" "1"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts maximum retries"
|
||||
When call validate_input_python "eslint-check" "max-retries" "10"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects negative max-warnings"
|
||||
When call validate_input_python "eslint-check" "max-warnings" "-1"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects non-numeric max-warnings"
|
||||
When call validate_input_python "eslint-check" "max-warnings" "many"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects zero retries"
|
||||
When call validate_input_python "eslint-check" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects retries above limit"
|
||||
When call validate_input_python "eslint-check" "max-retries" "15"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating report-format input"
|
||||
It "accepts stylish format"
|
||||
When call validate_input_python "eslint-check" "report-format" "stylish"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts json format"
|
||||
When call validate_input_python "eslint-check" "report-format" "json"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts sarif format"
|
||||
When call validate_input_python "eslint-check" "report-format" "sarif"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts checkstyle format"
|
||||
When call validate_input_python "eslint-check" "report-format" "checkstyle"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts compact format"
|
||||
When call validate_input_python "eslint-check" "report-format" "compact"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts html format"
|
||||
When call validate_input_python "eslint-check" "report-format" "html"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts junit format"
|
||||
When call validate_input_python "eslint-check" "report-format" "junit"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts tap format"
|
||||
When call validate_input_python "eslint-check" "report-format" "tap"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts unix format"
|
||||
When call validate_input_python "eslint-check" "report-format" "unix"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects invalid format"
|
||||
When call validate_input_python "eslint-check" "report-format" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects empty format"
|
||||
When call validate_input_python "eslint-check" "report-format" ""
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "ESLint Check"
|
||||
End
|
||||
|
||||
It "defines required inputs"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "working-directory"
|
||||
The output should include "eslint-version"
|
||||
The output should include "max-retries"
|
||||
End
|
||||
|
||||
It "defines optional inputs with defaults"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "config-file"
|
||||
The output should include "ignore-file"
|
||||
The output should include "file-extensions"
|
||||
The output should include "cache"
|
||||
The output should include "max-warnings"
|
||||
The output should include "fail-on-error"
|
||||
The output should include "report-format"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
outputs=$(get_action_outputs "$ACTION_FILE")
|
||||
When call echo "$outputs"
|
||||
The output should include "error-count"
|
||||
The output should include "warning-count"
|
||||
The output should include "sarif-file"
|
||||
The output should include "files-checked"
|
||||
End
|
||||
|
||||
It "has composite run type"
|
||||
When call grep -q "using: composite" "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "includes input validation step"
|
||||
When call grep -q "Validate Inputs" "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "uses node-setup action"
|
||||
When call grep -q "./node-setup" "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "uses common-cache action"
|
||||
When call grep -q "./common-cache" "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating security"
|
||||
It "validates input paths to prevent injection"
|
||||
When call validate_input_python "eslint-check" "working-directory" "../../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates config file paths"
|
||||
When call validate_input_python "eslint-check" "config-file" "../../malicious.js"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "sanitizes file extensions input"
|
||||
When call validate_input_python "eslint-check" "file-extensions" ".js;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing outputs"
|
||||
It "produces all expected outputs"
|
||||
When call test_action_outputs "$ACTION_DIR" "working-directory" "." "eslint-version" "latest" "max-retries" "3"
|
||||
The status should be success
|
||||
The stderr should include "Testing action outputs for: eslint-check"
|
||||
The stderr should include "Output test passed for: eslint-check"
|
||||
End
|
||||
|
||||
It "outputs consistent error and warning counts"
|
||||
When call test_action_outputs "$ACTION_DIR" "max-warnings" "0" "report-format" "sarif"
|
||||
The status should be success
|
||||
The stderr should include "Testing action outputs for: eslint-check"
|
||||
The stderr should include "Output test passed for: eslint-check"
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,115 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for eslint-fix action validation and logic
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "eslint-fix action"
|
||||
ACTION_DIR="eslint-fix"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating token input"
|
||||
It "accepts valid GitHub token"
|
||||
When call validate_input_python "eslint-fix" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects injection in token"
|
||||
When call validate_input_python "eslint-fix" "token" "token; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating username input"
|
||||
It "accepts valid username"
|
||||
When call validate_input_python "eslint-fix" "username" "github-actions"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects injection in username"
|
||||
When call validate_input_python "eslint-fix" "username" "user; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating email input"
|
||||
It "accepts valid email"
|
||||
When call validate_input_python "eslint-fix" "email" "test@example.com"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects invalid email format"
|
||||
When call validate_input_python "eslint-fix" "email" "invalid-email"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating numeric inputs"
|
||||
It "accepts valid max-retries"
|
||||
When call validate_input_python "eslint-fix" "max-retries" "3"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts minimum retries"
|
||||
When call validate_input_python "eslint-fix" "max-retries" "1"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts maximum retries"
|
||||
When call validate_input_python "eslint-fix" "max-retries" "10"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects zero retries"
|
||||
When call validate_input_python "eslint-fix" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects retries above limit"
|
||||
When call validate_input_python "eslint-fix" "max-retries" "15"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "ESLint Fix"
|
||||
End
|
||||
|
||||
It "defines required inputs"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "token"
|
||||
The output should include "username"
|
||||
The output should include "email"
|
||||
The output should include "max-retries"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
outputs=$(get_action_outputs "$ACTION_FILE")
|
||||
When call echo "$outputs"
|
||||
The output should include "files_changed"
|
||||
The output should include "lint_status"
|
||||
The output should include "errors_fixed"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating security"
|
||||
It "validates token format"
|
||||
When call validate_input_python "eslint-fix" "token" "invalid-token;rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates email format"
|
||||
When call validate_input_python "eslint-fix" "email" "invalid@email"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing outputs"
|
||||
It "produces all expected outputs"
|
||||
When call test_action_outputs "$ACTION_DIR" "token" "ghp_test" "username" "test" "email" "test@example.com" "max-retries" "3"
|
||||
The status should be success
|
||||
The stderr should include "Testing action outputs for: eslint-fix"
|
||||
The stderr should include "Output test passed for: eslint-fix"
|
||||
End
|
||||
End
|
||||
End
|
||||
527
_tests/unit/eslint-lint/validation.spec.sh
Executable file
527
_tests/unit/eslint-lint/validation.spec.sh
Executable file
@@ -0,0 +1,527 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for eslint-lint action validation and logic
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "eslint-lint action"
|
||||
ACTION_DIR="eslint-lint"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating mode input"
|
||||
It "accepts check mode"
|
||||
When call validate_input_python "eslint-lint" "mode" "check"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts fix mode"
|
||||
When call validate_input_python "eslint-lint" "mode" "fix"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty mode (uses default)"
|
||||
When call validate_input_python "eslint-lint" "mode" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid mode"
|
||||
When call validate_input_python "eslint-lint" "mode" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects mode with command injection"
|
||||
When call validate_input_python "eslint-lint" "mode" "check; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating working-directory input"
|
||||
It "accepts default directory"
|
||||
When call validate_input_python "eslint-lint" "working-directory" "."
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid subdirectory"
|
||||
When call validate_input_python "eslint-lint" "working-directory" "src"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty working-directory (uses default)"
|
||||
When call validate_input_python "eslint-lint" "working-directory" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "eslint-lint" "working-directory" "../../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects directory with command injection"
|
||||
When call validate_input_python "eslint-lint" "working-directory" "src; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating eslint-version input"
|
||||
It "accepts latest version"
|
||||
When call validate_input_python "eslint-lint" "eslint-version" "latest"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts semantic version"
|
||||
When call validate_input_python "eslint-lint" "eslint-version" "8.57.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts major.minor version"
|
||||
When call validate_input_python "eslint-lint" "eslint-version" "8.57"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts major version"
|
||||
When call validate_input_python "eslint-lint" "eslint-version" "8"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts version with pre-release"
|
||||
When call validate_input_python "eslint-lint" "eslint-version" "9.0.0-beta.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty version (uses default)"
|
||||
When call validate_input_python "eslint-lint" "eslint-version" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "eslint-lint" "eslint-version" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "eslint-lint" "eslint-version" "8.57.0; echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating config-file input"
|
||||
It "accepts default config file"
|
||||
When call validate_input_python "eslint-lint" "config-file" ".eslintrc"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts custom config file"
|
||||
When call validate_input_python "eslint-lint" "config-file" ".eslintrc.js"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts config file in subdirectory"
|
||||
When call validate_input_python "eslint-lint" "config-file" "config/eslint.config.js"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty config-file (uses default)"
|
||||
When call validate_input_python "eslint-lint" "config-file" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects config file with path traversal"
|
||||
When call validate_input_python "eslint-lint" "config-file" "../../../.eslintrc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects config file with command injection"
|
||||
When call validate_input_python "eslint-lint" "config-file" ".eslintrc; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating ignore-file input"
|
||||
It "accepts default ignore file"
|
||||
When call validate_input_python "eslint-lint" "ignore-file" ".eslintignore"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts custom ignore file"
|
||||
When call validate_input_python "eslint-lint" "ignore-file" ".eslintignore.custom"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty ignore-file (uses default)"
|
||||
When call validate_input_python "eslint-lint" "ignore-file" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects ignore file with path traversal"
|
||||
When call validate_input_python "eslint-lint" "ignore-file" "../../../.eslintignore"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects ignore file with command injection"
|
||||
When call validate_input_python "eslint-lint" "ignore-file" ".eslintignore; echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating file-extensions input"
|
||||
It "accepts default extensions"
|
||||
When call validate_input_python "eslint-lint" "file-extensions" ".js,.jsx,.ts,.tsx"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts single extension"
|
||||
When call validate_input_python "eslint-lint" "file-extensions" ".js"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts multiple extensions"
|
||||
When call validate_input_python "eslint-lint" "file-extensions" ".js,.ts,.mjs"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty file-extensions (uses default)"
|
||||
When call validate_input_python "eslint-lint" "file-extensions" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects extensions without leading dot"
|
||||
When call validate_input_python "eslint-lint" "file-extensions" "js,jsx"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects extensions with command injection"
|
||||
When call validate_input_python "eslint-lint" "file-extensions" ".js; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating cache input"
|
||||
It "accepts true"
|
||||
When call validate_input_python "eslint-lint" "cache" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts false"
|
||||
When call validate_input_python "eslint-lint" "cache" "false"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty cache (uses default)"
|
||||
When call validate_input_python "eslint-lint" "cache" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid boolean value"
|
||||
When call validate_input_python "eslint-lint" "cache" "maybe"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating max-warnings input"
|
||||
It "accepts default value 0"
|
||||
When call validate_input_python "eslint-lint" "max-warnings" "0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts positive integer"
|
||||
When call validate_input_python "eslint-lint" "max-warnings" "10"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts large number"
|
||||
When call validate_input_python "eslint-lint" "max-warnings" "1000"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty max-warnings (uses default)"
|
||||
When call validate_input_python "eslint-lint" "max-warnings" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects negative number"
|
||||
When call validate_input_python "eslint-lint" "max-warnings" "-1"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects non-numeric value"
|
||||
When call validate_input_python "eslint-lint" "max-warnings" "abc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects max-warnings with command injection"
|
||||
When call validate_input_python "eslint-lint" "max-warnings" "0; echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating fail-on-error input"
|
||||
It "accepts true"
|
||||
When call validate_input_python "eslint-lint" "fail-on-error" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts false"
|
||||
When call validate_input_python "eslint-lint" "fail-on-error" "false"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty fail-on-error (uses default)"
|
||||
When call validate_input_python "eslint-lint" "fail-on-error" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid boolean value"
|
||||
When call validate_input_python "eslint-lint" "fail-on-error" "yes"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating report-format input"
|
||||
It "accepts stylish format"
|
||||
When call validate_input_python "eslint-lint" "report-format" "stylish"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts json format"
|
||||
When call validate_input_python "eslint-lint" "report-format" "json"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts sarif format"
|
||||
When call validate_input_python "eslint-lint" "report-format" "sarif"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty report-format (uses default)"
|
||||
When call validate_input_python "eslint-lint" "report-format" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid format"
|
||||
When call validate_input_python "eslint-lint" "report-format" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects format with command injection"
|
||||
When call validate_input_python "eslint-lint" "report-format" "json; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating max-retries input"
|
||||
It "accepts default value 3"
|
||||
When call validate_input_python "eslint-lint" "max-retries" "3"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts retry count of 1"
|
||||
When call validate_input_python "eslint-lint" "max-retries" "1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts retry count of 10"
|
||||
When call validate_input_python "eslint-lint" "max-retries" "10"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty max-retries (uses default)"
|
||||
When call validate_input_python "eslint-lint" "max-retries" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects zero retries"
|
||||
When call validate_input_python "eslint-lint" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects negative retry count"
|
||||
When call validate_input_python "eslint-lint" "max-retries" "-1"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects retry count above 10"
|
||||
When call validate_input_python "eslint-lint" "max-retries" "11"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects non-numeric retry count"
|
||||
When call validate_input_python "eslint-lint" "max-retries" "abc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects retry count with command injection"
|
||||
When call validate_input_python "eslint-lint" "max-retries" "3; echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating token input"
|
||||
It "accepts valid GitHub token (classic)"
|
||||
When call validate_input_python "eslint-lint" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid GitHub fine-grained token"
|
||||
When call validate_input_python "eslint-lint" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty token (optional)"
|
||||
When call validate_input_python "eslint-lint" "token" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid token format"
|
||||
When call validate_input_python "eslint-lint" "token" "invalid-token"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects token with command injection"
|
||||
When call validate_input_python "eslint-lint" "token" "ghp_123456789012345678901234567890123456; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating username input"
|
||||
It "accepts valid username"
|
||||
When call validate_input_python "eslint-lint" "username" "github-actions"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts username with hyphens"
|
||||
When call validate_input_python "eslint-lint" "username" "my-bot-user"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts alphanumeric username"
|
||||
When call validate_input_python "eslint-lint" "username" "user123"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty username (uses default)"
|
||||
When call validate_input_python "eslint-lint" "username" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects username with command injection"
|
||||
When call validate_input_python "eslint-lint" "username" "user; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects username with special characters"
|
||||
When call validate_input_python "eslint-lint" "username" "user@bot"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating email input"
|
||||
It "accepts valid email"
|
||||
When call validate_input_python "eslint-lint" "email" "github-actions@github.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with plus sign"
|
||||
When call validate_input_python "eslint-lint" "email" "user+bot@example.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with subdomain"
|
||||
When call validate_input_python "eslint-lint" "email" "bot@ci.example.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty email (uses default)"
|
||||
When call validate_input_python "eslint-lint" "email" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid email format"
|
||||
When call validate_input_python "eslint-lint" "email" "not-an-email"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects email with command injection"
|
||||
When call validate_input_python "eslint-lint" "email" "user@example.com; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "ESLint Lint"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "mode"
|
||||
The output should include "working-directory"
|
||||
The output should include "eslint-version"
|
||||
The output should include "config-file"
|
||||
The output should include "ignore-file"
|
||||
The output should include "file-extensions"
|
||||
The output should include "cache"
|
||||
The output should include "max-warnings"
|
||||
The output should include "fail-on-error"
|
||||
The output should include "report-format"
|
||||
The output should include "max-retries"
|
||||
The output should include "token"
|
||||
The output should include "username"
|
||||
The output should include "email"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "status"
|
||||
The output should include "error-count"
|
||||
The output should include "warning-count"
|
||||
The output should include "sarif-file"
|
||||
The output should include "files-checked"
|
||||
The output should include "files-changed"
|
||||
The output should include "errors-fixed"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "has all inputs as optional (with defaults)"
|
||||
When call is_input_required "$ACTION_FILE" "mode"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in working-directory"
|
||||
When call validate_input_python "eslint-lint" "working-directory" "../../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in mode"
|
||||
When call validate_input_python "eslint-lint" "mode" "check|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command substitution in config-file"
|
||||
When call validate_input_python "eslint-lint" "config-file" "\$(whoami)"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against path traversal in token"
|
||||
When call validate_input_python "eslint-lint" "token" "../../../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in username"
|
||||
When call validate_input_python "eslint-lint" "username" "user&whoami"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command injection in email"
|
||||
When call validate_input_python "eslint-lint" "email" "user@example.com\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,141 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for github-release action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
# Using the centralized validate_input_python function from spec_helper.sh
|
||||
|
||||
Describe "github-release action"
|
||||
ACTION_DIR="github-release"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating version input"
|
||||
It "accepts valid semantic version"
|
||||
When call validate_input_python "github-release" "version" "1.2.3"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts semantic version with v prefix"
|
||||
When call validate_input_python "github-release" "version" "v1.2.3"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts prerelease version"
|
||||
When call validate_input_python "github-release" "version" "1.2.3-alpha"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts build metadata version"
|
||||
When call validate_input_python "github-release" "version" "1.2.3+build.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts prerelease with build metadata"
|
||||
When call validate_input_python "github-release" "version" "1.2.3-alpha.1+build.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts CalVer format"
|
||||
When call validate_input_python "github-release" "version" "2024.3.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "github-release" "version" "invalid-version"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "github-release" "version" "1.2.3; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects empty version"
|
||||
When call validate_input_python "github-release" "version" ""
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating changelog input"
|
||||
It "accepts empty changelog"
|
||||
When call validate_input_python "github-release" "changelog" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts normal changelog content"
|
||||
When call validate_input_python "github-release" "changelog" "## What's Changed\n- Fixed bug #123\n- Added feature X"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts changelog with special characters"
|
||||
When call validate_input_python "github-release" "changelog" "Version 1.2.3\n\n- Bug fixes & improvements\n- Added @mention support"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects changelog with command injection"
|
||||
When call validate_input_python "github-release" "changelog" "Release notes; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects changelog with shell expansion"
|
||||
When call validate_input_python "github-release" "changelog" "Release \$(whoami) notes"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "GitHub Release"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "version"
|
||||
The output should include "changelog"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "release_url"
|
||||
The output should include "release_id"
|
||||
The output should include "upload_url"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "requires version input"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "version"
|
||||
End
|
||||
|
||||
It "has changelog as optional input"
|
||||
# Test that changelog has a default value in action.yml
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "changelog" "optional"
|
||||
The output should equal "optional"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in version"
|
||||
When call validate_input_python "github-release" "version" "../1.2.3"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in version"
|
||||
When call validate_input_python "github-release" "version" "1.2.3|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in changelog"
|
||||
When call validate_input_python "github-release" "changelog" "Release notes|echo test"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,171 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for go-version-detect action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "go-version-detect action"
|
||||
ACTION_DIR="go-version-detect"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
# Test version constants (update these when Go releases new versions)
|
||||
CURRENT_STABLE_GO_VERSION="1.25"
|
||||
CURRENT_STABLE_GO_PATCH="1.25.0"
|
||||
PREVIOUS_GO_VERSION="1.24.0"
|
||||
MIN_SUPPORTED_GO_VERSION="1.18"
|
||||
MAX_SUPPORTED_GO_VERSION="1.30"
|
||||
TOO_OLD_GO_VERSION="1.17"
|
||||
TOO_NEW_GO_VERSION="1.31"
|
||||
|
||||
Context "when validating default-version input"
|
||||
It "accepts valid semantic version"
|
||||
When call validate_input_python "go-version-detect" "default-version" "$CURRENT_STABLE_GO_VERSION"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts semantic version with patch"
|
||||
When call validate_input_python "go-version-detect" "default-version" "$PREVIOUS_GO_VERSION"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts minimum supported Go version"
|
||||
When call validate_input_python "go-version-detect" "default-version" "$MIN_SUPPORTED_GO_VERSION"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts current stable Go version"
|
||||
When call validate_input_python "go-version-detect" "default-version" "$CURRENT_STABLE_GO_PATCH"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects version without minor"
|
||||
When call validate_input_python "go-version-detect" "default-version" "1"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "go-version-detect" "default-version" "invalid-version"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with shell expansion"
|
||||
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}\$(echo test)"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects major version other than 1"
|
||||
When call validate_input_python "go-version-detect" "default-version" "2.0"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects too old minor version"
|
||||
When call validate_input_python "go-version-detect" "default-version" "$TOO_OLD_GO_VERSION"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects too new minor version"
|
||||
When call validate_input_python "go-version-detect" "default-version" "$TOO_NEW_GO_VERSION"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects empty version"
|
||||
When call validate_input_python "go-version-detect" "default-version" ""
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with leading v"
|
||||
When call validate_input_python "go-version-detect" "default-version" "v${CURRENT_STABLE_GO_VERSION}"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with prerelease"
|
||||
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}-beta"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Go Version Detect"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "default-version"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "go-version"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "has default-version as optional input"
|
||||
# Test that default-version has a default value in action.yml
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "optional"
|
||||
The output should equal "optional"
|
||||
End
|
||||
|
||||
It "has correct default version"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "default"
|
||||
The output should equal "$CURRENT_STABLE_GO_VERSION"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in version"
|
||||
When call validate_input_python "go-version-detect" "default-version" "../${CURRENT_STABLE_GO_VERSION}"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in version"
|
||||
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against backtick injection"
|
||||
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against variable expansion"
|
||||
When call validate_input_python "go-version-detect" "default-version" "${CURRENT_STABLE_GO_VERSION}\${HOME}"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing version range validation"
|
||||
It "validates reasonable Go version range boundaries"
|
||||
# Test boundary conditions for Go version validation
|
||||
When call validate_input_python "go-version-detect" "default-version" "$TOO_OLD_GO_VERSION"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates upper boundary"
|
||||
When call validate_input_python "go-version-detect" "default-version" "$TOO_NEW_GO_VERSION"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates exact boundary valid values"
|
||||
When call validate_input_python "go-version-detect" "default-version" "$MIN_SUPPORTED_GO_VERSION"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "validates exact boundary valid values upper"
|
||||
When call validate_input_python "go-version-detect" "default-version" "$MAX_SUPPORTED_GO_VERSION"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
End
|
||||
297
_tests/unit/language-version-detect/validation.spec.sh
Executable file
297
_tests/unit/language-version-detect/validation.spec.sh
Executable file
@@ -0,0 +1,297 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for language-version-detect action validation and logic
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "language-version-detect action"
|
||||
ACTION_DIR="language-version-detect"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating language input"
|
||||
It "accepts php language"
|
||||
When call validate_input_python "language-version-detect" "language" "php"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts python language"
|
||||
When call validate_input_python "language-version-detect" "language" "python"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts go language"
|
||||
When call validate_input_python "language-version-detect" "language" "go"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts dotnet language"
|
||||
When call validate_input_python "language-version-detect" "language" "dotnet"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid language"
|
||||
When call validate_input_python "language-version-detect" "language" "javascript"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects empty language (required)"
|
||||
When call validate_input_python "language-version-detect" "language" ""
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects language with command injection"
|
||||
When call validate_input_python "language-version-detect" "language" "php; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects language with shell metacharacters"
|
||||
When call validate_input_python "language-version-detect" "language" "php|echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating default-version input for PHP"
|
||||
It "accepts valid PHP version 8.4"
|
||||
When call validate_input_python "language-version-detect" "default-version" "8.4"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid PHP version 8.3"
|
||||
When call validate_input_python "language-version-detect" "default-version" "8.3"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid PHP version 7.4"
|
||||
When call validate_input_python "language-version-detect" "default-version" "7.4"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid PHP version with patch 8.3.1"
|
||||
When call validate_input_python "language-version-detect" "default-version" "8.3.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty default-version (uses language default)"
|
||||
When call validate_input_python "language-version-detect" "default-version" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid PHP version format"
|
||||
When call validate_input_python "language-version-detect" "default-version" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating default-version input for Python"
|
||||
It "accepts valid Python version 3.12"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.12"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid Python version 3.11"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.11"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid Python version 3.10"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.10"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid Python version with patch 3.12.1"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.12.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid Python version 3.9"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.9"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid Python version 3.8"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.8"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating default-version input for Go"
|
||||
It "accepts valid Go version 1.21"
|
||||
When call validate_input_python "language-version-detect" "default-version" "1.21"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid Go version 1.20"
|
||||
When call validate_input_python "language-version-detect" "default-version" "1.20"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid Go version with patch 1.21.5"
|
||||
When call validate_input_python "language-version-detect" "default-version" "1.21.5"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid Go version 1.22"
|
||||
When call validate_input_python "language-version-detect" "default-version" "1.22"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating default-version input for .NET"
|
||||
It "accepts valid .NET version 7.0"
|
||||
When call validate_input_python "language-version-detect" "default-version" "7.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid .NET version 8.0"
|
||||
When call validate_input_python "language-version-detect" "default-version" "8.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid .NET version 6.0"
|
||||
When call validate_input_python "language-version-detect" "default-version" "6.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid .NET version with patch 7.0.1"
|
||||
When call validate_input_python "language-version-detect" "default-version" "7.0.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid .NET major version 7"
|
||||
When call validate_input_python "language-version-detect" "default-version" "7"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating default-version input edge cases"
|
||||
It "rejects version with v prefix"
|
||||
When call validate_input_python "language-version-detect" "default-version" "v3.12"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.12; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with shell metacharacters"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.12|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command substitution"
|
||||
When call validate_input_python "language-version-detect" "default-version" "\$(whoami)"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects alphabetic version"
|
||||
When call validate_input_python "language-version-detect" "default-version" "latest"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating token input"
|
||||
It "accepts valid GitHub token (classic)"
|
||||
When call validate_input_python "language-version-detect" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid GitHub fine-grained token"
|
||||
When call validate_input_python "language-version-detect" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty token (optional)"
|
||||
When call validate_input_python "language-version-detect" "token" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid token format"
|
||||
When call validate_input_python "language-version-detect" "token" "invalid-token"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects token with command injection"
|
||||
When call validate_input_python "language-version-detect" "token" "ghp_123456789012345678901234567890123456; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Language Version Detect"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "language"
|
||||
The output should include "default-version"
|
||||
The output should include "token"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "detected-version"
|
||||
The output should include "package-manager"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "requires language input"
|
||||
When call is_input_required "$ACTION_FILE" "language"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has default-version as optional input"
|
||||
When call is_input_required "$ACTION_FILE" "default-version"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "has token as optional input"
|
||||
When call is_input_required "$ACTION_FILE" "token"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in language"
|
||||
When call validate_input_python "language-version-detect" "language" "../../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in language"
|
||||
When call validate_input_python "language-version-detect" "language" "php&whoami"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command substitution in language"
|
||||
When call validate_input_python "language-version-detect" "language" "php\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against path traversal in default-version"
|
||||
When call validate_input_python "language-version-detect" "default-version" "../../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in default-version"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.12&echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command substitution in default-version"
|
||||
When call validate_input_python "language-version-detect" "default-version" "3.12\$(whoami)"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against path traversal in token"
|
||||
When call validate_input_python "language-version-detect" "token" "../../../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,170 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for php-version-detect action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "php-version-detect action"
|
||||
ACTION_DIR="php-version-detect"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating default-version input"
|
||||
It "accepts valid PHP version"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.2"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts PHP version with patch"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.3.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts PHP 7.4"
|
||||
When call validate_input_python "php-version-detect" "default-version" "7.4"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts PHP 8.0"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts PHP 8.1"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts PHP 8.4"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.4"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects PHP version too old"
|
||||
When call validate_input_python "php-version-detect" "default-version" "5.6"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects PHP version too new"
|
||||
When call validate_input_python "php-version-detect" "default-version" "10.0"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "php-version-detect" "default-version" "php8.2"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.2; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version without minor"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects empty version"
|
||||
When call validate_input_python "php-version-detect" "default-version" ""
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with v prefix"
|
||||
When call validate_input_python "php-version-detect" "default-version" "v8.2"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "accepts PHP 8.5 for future compatibility"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.5"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects unreasonably high minor version"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.100"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "PHP Version Detect"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "default-version"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "php-version"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "has default-version as optional input"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "optional"
|
||||
The output should equal "optional"
|
||||
End
|
||||
|
||||
It "has correct default version"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "default"
|
||||
The output should equal "8.2"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in version"
|
||||
When call validate_input_python "php-version-detect" "default-version" "../8.2"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in version"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.2|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against backtick injection"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.2\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against variable expansion"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.2\${HOME}"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing PHP version range validation"
|
||||
It "validates PHP 7 minor version boundaries"
|
||||
When call validate_input_python "php-version-detect" "default-version" "7.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "validates PHP 7.4 specifically"
|
||||
When call validate_input_python "php-version-detect" "default-version" "7.4"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "validates PHP 8 minor version boundaries"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "validates PHP 8.4 boundary"
|
||||
When call validate_input_python "php-version-detect" "default-version" "8.4"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "validates PHP 9 future version"
|
||||
When call validate_input_python "php-version-detect" "default-version" "9.0"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,332 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for prettier-check action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "prettier-check action"
|
||||
ACTION_DIR="prettier-check"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating working-directory input"
|
||||
It "accepts current directory"
|
||||
When call validate_input_python "prettier-check" "working-directory" "."
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts relative directory"
|
||||
When call validate_input_python "prettier-check" "working-directory" "src"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts nested directory"
|
||||
When call validate_input_python "prettier-check" "working-directory" "src/components"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "prettier-check" "working-directory" "../malicious"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects absolute paths"
|
||||
When call validate_input_python "prettier-check" "working-directory" "/etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects directory with command injection"
|
||||
When call validate_input_python "prettier-check" "working-directory" "src; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating prettier-version input"
|
||||
It "accepts latest version"
|
||||
When call validate_input_python "prettier-check" "prettier-version" "latest"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts semantic version"
|
||||
When call validate_input_python "prettier-check" "prettier-version" "3.0.0"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts prerelease version"
|
||||
When call validate_input_python "prettier-check" "prettier-version" "3.0.0-alpha"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "prettier-check" "prettier-version" "v3.0.0"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "prettier-check" "prettier-version" "3.0.0; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating config-file input"
|
||||
It "accepts valid config file"
|
||||
When call validate_input_python "prettier-check" "config-file" ".prettierrc"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts config file with extension"
|
||||
When call validate_input_python "prettier-check" "config-file" ".prettierrc.json"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts config file in subdirectory"
|
||||
When call validate_input_python "prettier-check" "config-file" "config/.prettierrc"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects path traversal in config file"
|
||||
When call validate_input_python "prettier-check" "config-file" "../../../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects absolute path in config file"
|
||||
When call validate_input_python "prettier-check" "config-file" "/etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating ignore-file input"
|
||||
It "accepts valid ignore file"
|
||||
When call validate_input_python "prettier-check" "ignore-file" ".prettierignore"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts ignore file in subdirectory"
|
||||
When call validate_input_python "prettier-check" "ignore-file" "config/.prettierignore"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects path traversal in ignore file"
|
||||
When call validate_input_python "prettier-check" "ignore-file" "../../../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects absolute path in ignore file"
|
||||
When call validate_input_python "prettier-check" "ignore-file" "/etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating file-pattern input"
|
||||
It "accepts valid glob pattern"
|
||||
When call validate_input_python "prettier-check" "file-pattern" "**/*.{js,ts}"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts simple file pattern"
|
||||
When call validate_input_python "prettier-check" "file-pattern" "*.js"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts multiple extensions"
|
||||
When call validate_input_python "prettier-check" "file-pattern" "**/*.{js,jsx,ts,tsx,css}"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects pattern with path traversal"
|
||||
When call validate_input_python "prettier-check" "file-pattern" "../**/*.js"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects pattern with absolute path"
|
||||
When call validate_input_python "prettier-check" "file-pattern" "/etc/**/*.conf"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating boolean inputs"
|
||||
It "accepts true for cache"
|
||||
When call validate_input_python "prettier-check" "cache" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts false for cache"
|
||||
When call validate_input_python "prettier-check" "cache" "false"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid cache value"
|
||||
When call validate_input_python "prettier-check" "cache" "yes"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "accepts true for fail-on-error"
|
||||
When call validate_input_python "prettier-check" "fail-on-error" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts false for fail-on-error"
|
||||
When call validate_input_python "prettier-check" "fail-on-error" "false"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts true for check-only"
|
||||
When call validate_input_python "prettier-check" "check-only" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts false for check-only"
|
||||
When call validate_input_python "prettier-check" "check-only" "false"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating report-format input"
|
||||
It "accepts json format"
|
||||
When call validate_input_python "prettier-check" "report-format" "json"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts sarif format"
|
||||
When call validate_input_python "prettier-check" "report-format" "sarif"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid format"
|
||||
When call validate_input_python "prettier-check" "report-format" "xml"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects empty format"
|
||||
When call validate_input_python "prettier-check" "report-format" ""
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating max-retries input"
|
||||
It "accepts valid retry count"
|
||||
When call validate_input_python "prettier-check" "max-retries" "3"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts minimum retries"
|
||||
When call validate_input_python "prettier-check" "max-retries" "1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts maximum retries"
|
||||
When call validate_input_python "prettier-check" "max-retries" "10"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects zero retries"
|
||||
When call validate_input_python "prettier-check" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects too many retries"
|
||||
When call validate_input_python "prettier-check" "max-retries" "11"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects non-numeric retries"
|
||||
When call validate_input_python "prettier-check" "max-retries" "many"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating plugins input"
|
||||
It "accepts empty plugins"
|
||||
When call validate_input_python "prettier-check" "plugins" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid plugin name"
|
||||
When call validate_input_python "prettier-check" "plugins" "prettier-plugin-java"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts scoped plugin"
|
||||
When call validate_input_python "prettier-check" "plugins" "@prettier/plugin-xml"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts multiple plugins"
|
||||
When call validate_input_python "prettier-check" "plugins" "plugin1,@scope/plugin2"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects plugins with command injection"
|
||||
When call validate_input_python "prettier-check" "plugins" "plugin1; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects plugins with shell operators"
|
||||
When call validate_input_python "prettier-check" "plugins" "plugin1 && malicious"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects plugins with pipe"
|
||||
When call validate_input_python "prettier-check" "plugins" "plugin1 | cat /etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Prettier Check"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "working-directory"
|
||||
The output should include "prettier-version"
|
||||
The output should include "config-file"
|
||||
The output should include "ignore-file"
|
||||
The output should include "file-pattern"
|
||||
The output should include "cache"
|
||||
The output should include "fail-on-error"
|
||||
The output should include "report-format"
|
||||
The output should include "max-retries"
|
||||
The output should include "plugins"
|
||||
The output should include "check-only"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "files-checked"
|
||||
The output should include "unformatted-files"
|
||||
The output should include "sarif-file"
|
||||
The output should include "cache-hit"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "has all inputs as optional"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "any" "all_optional"
|
||||
The output should equal "none"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in multiple inputs"
|
||||
When call validate_input_python "prettier-check" "working-directory" "../../malicious"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command injection in plugins"
|
||||
When call validate_input_python "prettier-check" "plugins" "plugin\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell expansion in file patterns"
|
||||
When call validate_input_python "prettier-check" "file-pattern" "**/*.js\${HOME}"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,285 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for prettier-fix action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "prettier-fix action"
|
||||
ACTION_DIR="prettier-fix"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating token input"
|
||||
It "accepts GitHub token expression"
|
||||
When call validate_input_python "prettier-fix" "token" "\${{ github.token }}"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts GitHub fine-grained token"
|
||||
When call validate_input_python "prettier-fix" "token" "ghp_abcdefghijklmnopqrstuvwxyz1234567890"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts GitHub app token"
|
||||
When call validate_input_python "prettier-fix" "token" "ghs_abcdefghijklmnopqrstuvwxyz1234567890"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts GitHub enterprise token"
|
||||
When call validate_input_python "prettier-fix" "token" "ghe_abcdefghijklmnopqrstuvwxyz1234567890"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid token format"
|
||||
When call validate_input_python "prettier-fix" "token" "invalid-token"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects token with command injection"
|
||||
When call validate_input_python "prettier-fix" "token" "ghp_token; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "accepts empty token (uses default)"
|
||||
When call validate_input_python "prettier-fix" "token" ""
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating username input"
|
||||
It "accepts valid GitHub username"
|
||||
When call validate_input_python "prettier-fix" "username" "github-actions"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts username with hyphens"
|
||||
When call validate_input_python "prettier-fix" "username" "user-name"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts username with numbers"
|
||||
When call validate_input_python "prettier-fix" "username" "user123"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts single character username"
|
||||
When call validate_input_python "prettier-fix" "username" "a"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts maximum length username"
|
||||
When call validate_input_python "prettier-fix" "username" "abcdefghijklmnopqrstuvwxyz0123456789abc"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects username too long"
|
||||
When call validate_input_python "prettier-fix" "username" "abcdefghijklmnopqrstuvwxyz0123456789abcd"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects username with command injection"
|
||||
When call validate_input_python "prettier-fix" "username" "user; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects username with shell operators"
|
||||
When call validate_input_python "prettier-fix" "username" "user && rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects username with pipe"
|
||||
When call validate_input_python "prettier-fix" "username" "user | rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "accepts empty username (uses default)"
|
||||
When call validate_input_python "prettier-fix" "username" ""
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating email input"
|
||||
It "accepts valid email"
|
||||
When call validate_input_python "prettier-fix" "email" "user@example.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with subdomain"
|
||||
When call validate_input_python "prettier-fix" "email" "user@mail.example.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with plus sign"
|
||||
When call validate_input_python "prettier-fix" "email" "user+tag@example.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with numbers"
|
||||
When call validate_input_python "prettier-fix" "email" "user123@example123.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with hyphens"
|
||||
When call validate_input_python "prettier-fix" "email" "user-name@example-domain.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects email without at symbol"
|
||||
When call validate_input_python "prettier-fix" "email" "userexample.com"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects email without domain"
|
||||
When call validate_input_python "prettier-fix" "email" "user@"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects email without username"
|
||||
When call validate_input_python "prettier-fix" "email" "@example.com"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects email without dot in domain"
|
||||
When call validate_input_python "prettier-fix" "email" "user@example"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects email with spaces"
|
||||
When call validate_input_python "prettier-fix" "email" "user @example.com"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects empty email"
|
||||
When call validate_input_python "prettier-fix" "email" ""
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating max-retries input"
|
||||
It "accepts valid retry count"
|
||||
When call validate_input_python "prettier-fix" "max-retries" "3"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts minimum retries"
|
||||
When call validate_input_python "prettier-fix" "max-retries" "1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts maximum retries"
|
||||
When call validate_input_python "prettier-fix" "max-retries" "10"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects zero retries"
|
||||
When call validate_input_python "prettier-fix" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects too many retries"
|
||||
When call validate_input_python "prettier-fix" "max-retries" "11"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects non-numeric retries"
|
||||
When call validate_input_python "prettier-fix" "max-retries" "many"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects negative retries"
|
||||
When call validate_input_python "prettier-fix" "max-retries" "-1"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Prettier Fix"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "token"
|
||||
The output should include "username"
|
||||
The output should include "email"
|
||||
The output should include "max-retries"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "files_changed"
|
||||
The output should include "format_status"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "has all inputs as optional"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "" "all_optional"
|
||||
The output should equal "none"
|
||||
End
|
||||
|
||||
It "has correct default token"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "token" "default"
|
||||
The output should equal "\${{ github.token }}"
|
||||
End
|
||||
|
||||
It "has correct default username"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "username" "default"
|
||||
The output should equal "github-actions"
|
||||
End
|
||||
|
||||
It "has correct default email"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "email" "default"
|
||||
The output should equal "github-actions@github.com"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against command injection in username"
|
||||
When call validate_input_python "prettier-fix" "username" "user\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in email"
|
||||
When call validate_input_python "prettier-fix" "email" "user@example.com; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against variable expansion in token"
|
||||
When call validate_input_python "prettier-fix" "token" "\${MALICIOUS_VAR}"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against backtick injection in email"
|
||||
When call validate_input_python "prettier-fix" "email" "user@example.com\`echo test\`"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing Prettier-specific validations"
|
||||
It "validates username length boundaries for Git"
|
||||
When call validate_input_python "prettier-fix" "username" "$(awk 'BEGIN{for(i=1;i<=40;i++)printf "a"}')"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates email format for Git commits"
|
||||
When call validate_input_python "prettier-fix" "email" "noreply@github.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "validates retry count boundaries"
|
||||
When call validate_input_python "prettier-fix" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates default values are secure"
|
||||
When call validate_input_python "prettier-fix" "username" "github-actions"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
End
|
||||
515
_tests/unit/prettier-lint/validation.spec.sh
Executable file
515
_tests/unit/prettier-lint/validation.spec.sh
Executable file
@@ -0,0 +1,515 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for prettier-lint action validation and logic
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "prettier-lint action"
|
||||
ACTION_DIR="prettier-lint"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating mode input"
|
||||
It "accepts check mode"
|
||||
When call validate_input_python "prettier-lint" "mode" "check"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts fix mode"
|
||||
When call validate_input_python "prettier-lint" "mode" "fix"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty mode (uses default)"
|
||||
When call validate_input_python "prettier-lint" "mode" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid mode"
|
||||
When call validate_input_python "prettier-lint" "mode" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects mode with command injection"
|
||||
When call validate_input_python "prettier-lint" "mode" "check; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating working-directory input"
|
||||
It "accepts default directory"
|
||||
When call validate_input_python "prettier-lint" "working-directory" "."
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid subdirectory"
|
||||
When call validate_input_python "prettier-lint" "working-directory" "src"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty working-directory (uses default)"
|
||||
When call validate_input_python "prettier-lint" "working-directory" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects path traversal"
|
||||
When call validate_input_python "prettier-lint" "working-directory" "../../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects directory with command injection"
|
||||
When call validate_input_python "prettier-lint" "working-directory" "src; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating prettier-version input"
|
||||
It "accepts latest version"
|
||||
When call validate_input_python "prettier-lint" "prettier-version" "latest"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts semantic version"
|
||||
When call validate_input_python "prettier-lint" "prettier-version" "3.2.5"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts major.minor version"
|
||||
When call validate_input_python "prettier-lint" "prettier-version" "3.2"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts major version"
|
||||
When call validate_input_python "prettier-lint" "prettier-version" "3"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts version with pre-release"
|
||||
When call validate_input_python "prettier-lint" "prettier-version" "3.0.0-alpha.1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty version (uses default)"
|
||||
When call validate_input_python "prettier-lint" "prettier-version" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "prettier-lint" "prettier-version" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "prettier-lint" "prettier-version" "3.2.5; echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating config-file input"
|
||||
It "accepts default config file"
|
||||
When call validate_input_python "prettier-lint" "config-file" ".prettierrc"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts custom config file"
|
||||
When call validate_input_python "prettier-lint" "config-file" ".prettierrc.js"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts config file in subdirectory"
|
||||
When call validate_input_python "prettier-lint" "config-file" "config/prettier.config.js"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty config-file (uses default)"
|
||||
When call validate_input_python "prettier-lint" "config-file" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects config file with path traversal"
|
||||
When call validate_input_python "prettier-lint" "config-file" "../../../.prettierrc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects config file with command injection"
|
||||
When call validate_input_python "prettier-lint" "config-file" ".prettierrc; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating ignore-file input"
|
||||
It "accepts default ignore file"
|
||||
When call validate_input_python "prettier-lint" "ignore-file" ".prettierignore"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts custom ignore file"
|
||||
When call validate_input_python "prettier-lint" "ignore-file" ".prettierignore.custom"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty ignore-file (uses default)"
|
||||
When call validate_input_python "prettier-lint" "ignore-file" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects ignore file with path traversal"
|
||||
When call validate_input_python "prettier-lint" "ignore-file" "../../../.prettierignore"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects ignore file with command injection"
|
||||
When call validate_input_python "prettier-lint" "ignore-file" ".prettierignore; echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating file-pattern input"
|
||||
It "accepts default pattern"
|
||||
When call validate_input_python "prettier-lint" "file-pattern" "**/*.{js,jsx,ts,tsx,css,scss,json,md,yaml,yml}"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts simple pattern"
|
||||
When call validate_input_python "prettier-lint" "file-pattern" "**/*.js"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts multiple patterns"
|
||||
When call validate_input_python "prettier-lint" "file-pattern" "**/*.{js,ts}"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts specific directory pattern"
|
||||
When call validate_input_python "prettier-lint" "file-pattern" "src/**/*.js"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty file-pattern (uses default)"
|
||||
When call validate_input_python "prettier-lint" "file-pattern" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects pattern with command injection"
|
||||
When call validate_input_python "prettier-lint" "file-pattern" "**/*.js; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating cache input"
|
||||
It "accepts true"
|
||||
When call validate_input_python "prettier-lint" "cache" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts false"
|
||||
When call validate_input_python "prettier-lint" "cache" "false"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty cache (uses default)"
|
||||
When call validate_input_python "prettier-lint" "cache" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid boolean value"
|
||||
When call validate_input_python "prettier-lint" "cache" "maybe"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating fail-on-error input"
|
||||
It "accepts true"
|
||||
When call validate_input_python "prettier-lint" "fail-on-error" "true"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts false"
|
||||
When call validate_input_python "prettier-lint" "fail-on-error" "false"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty fail-on-error (uses default)"
|
||||
When call validate_input_python "prettier-lint" "fail-on-error" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid boolean value"
|
||||
When call validate_input_python "prettier-lint" "fail-on-error" "yes"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating report-format input"
|
||||
It "accepts json format"
|
||||
When call validate_input_python "prettier-lint" "report-format" "json"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts sarif format"
|
||||
When call validate_input_python "prettier-lint" "report-format" "sarif"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty report-format (uses default)"
|
||||
When call validate_input_python "prettier-lint" "report-format" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid format"
|
||||
When call validate_input_python "prettier-lint" "report-format" "invalid"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects format with command injection"
|
||||
When call validate_input_python "prettier-lint" "report-format" "json; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating max-retries input"
|
||||
It "accepts default value 3"
|
||||
When call validate_input_python "prettier-lint" "max-retries" "3"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts retry count of 1"
|
||||
When call validate_input_python "prettier-lint" "max-retries" "1"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts retry count of 10"
|
||||
When call validate_input_python "prettier-lint" "max-retries" "10"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty max-retries (uses default)"
|
||||
When call validate_input_python "prettier-lint" "max-retries" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects zero retries"
|
||||
When call validate_input_python "prettier-lint" "max-retries" "0"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects negative retry count"
|
||||
When call validate_input_python "prettier-lint" "max-retries" "-1"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects retry count above 10"
|
||||
When call validate_input_python "prettier-lint" "max-retries" "11"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects non-numeric retry count"
|
||||
When call validate_input_python "prettier-lint" "max-retries" "abc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects retry count with command injection"
|
||||
When call validate_input_python "prettier-lint" "max-retries" "3; echo"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating plugins input"
|
||||
It "accepts empty plugins (optional)"
|
||||
When call validate_input_python "prettier-lint" "plugins" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts single plugin"
|
||||
When call validate_input_python "prettier-lint" "plugins" "prettier-plugin-tailwindcss"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts multiple plugins"
|
||||
When call validate_input_python "prettier-lint" "plugins" "prettier-plugin-tailwindcss,prettier-plugin-organize-imports"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts scoped plugin"
|
||||
When call validate_input_python "prettier-lint" "plugins" "@trivago/prettier-plugin-sort-imports"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects plugins with command injection"
|
||||
When call validate_input_python "prettier-lint" "plugins" "prettier-plugin-tailwindcss; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating token input"
|
||||
It "accepts valid GitHub token (classic)"
|
||||
When call validate_input_python "prettier-lint" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts valid GitHub fine-grained token"
|
||||
When call validate_input_python "prettier-lint" "token" "github_pat_1234567890123456789012345678901234567890123456789012345678901234567890a"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty token (optional)"
|
||||
When call validate_input_python "prettier-lint" "token" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid token format"
|
||||
When call validate_input_python "prettier-lint" "token" "invalid-token"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects token with command injection"
|
||||
When call validate_input_python "prettier-lint" "token" "ghp_123456789012345678901234567890123456; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating username input"
|
||||
It "accepts valid username"
|
||||
When call validate_input_python "prettier-lint" "username" "github-actions"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts username with hyphens"
|
||||
When call validate_input_python "prettier-lint" "username" "my-bot-user"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts alphanumeric username"
|
||||
When call validate_input_python "prettier-lint" "username" "user123"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty username (uses default)"
|
||||
When call validate_input_python "prettier-lint" "username" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects username with command injection"
|
||||
When call validate_input_python "prettier-lint" "username" "user; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects username with special characters"
|
||||
When call validate_input_python "prettier-lint" "username" "user@bot"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating email input"
|
||||
It "accepts valid email"
|
||||
When call validate_input_python "prettier-lint" "email" "github-actions@github.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with plus sign"
|
||||
When call validate_input_python "prettier-lint" "email" "user+bot@example.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts email with subdomain"
|
||||
When call validate_input_python "prettier-lint" "email" "bot@ci.example.com"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts empty email (uses default)"
|
||||
When call validate_input_python "prettier-lint" "email" ""
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects invalid email format"
|
||||
When call validate_input_python "prettier-lint" "email" "not-an-email"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects email with command injection"
|
||||
When call validate_input_python "prettier-lint" "email" "user@example.com; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Prettier Lint"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "mode"
|
||||
The output should include "working-directory"
|
||||
The output should include "prettier-version"
|
||||
The output should include "config-file"
|
||||
The output should include "ignore-file"
|
||||
The output should include "file-pattern"
|
||||
The output should include "cache"
|
||||
The output should include "fail-on-error"
|
||||
The output should include "report-format"
|
||||
The output should include "max-retries"
|
||||
The output should include "plugins"
|
||||
The output should include "token"
|
||||
The output should include "username"
|
||||
The output should include "email"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "status"
|
||||
The output should include "files-checked"
|
||||
The output should include "unformatted-files"
|
||||
The output should include "sarif-file"
|
||||
The output should include "files-changed"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "has all inputs as optional (with defaults)"
|
||||
When call is_input_required "$ACTION_FILE" "mode"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in working-directory"
|
||||
When call validate_input_python "prettier-lint" "working-directory" "../../../etc"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in mode"
|
||||
When call validate_input_python "prettier-lint" "mode" "check|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command substitution in config-file"
|
||||
When call validate_input_python "prettier-lint" "config-file" "\$(whoami)"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against path traversal in token"
|
||||
When call validate_input_python "prettier-lint" "token" "../../../etc/passwd"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in username"
|
||||
When call validate_input_python "prettier-lint" "username" "user&whoami"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command injection in email"
|
||||
When call validate_input_python "prettier-lint" "email" "user@example.com\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against command injection in plugins"
|
||||
When call validate_input_python "prettier-lint" "plugins" "plugin1,plugin2; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,98 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for python-version-detect-v2 action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "python-version-detect-v2 action"
|
||||
ACTION_DIR="python-version-detect-v2"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating default-version input"
|
||||
It "accepts valid Python version"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "3.11"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts Python version with patch"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "3.11.5"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts Python 3.8"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "3.8"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts Python 3.12"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "3.12"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects Python version too old"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "2.7"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "python3.11"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "3.11; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects empty version"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" ""
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Python Version Detect v2"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "default-version"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "python-version"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "has default-version as optional input"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "optional"
|
||||
The output should equal "optional"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in version"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "../3.11"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in version"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "3.11|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against backtick injection"
|
||||
When call validate_input_python "python-version-detect-v2" "default-version" "3.11\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,108 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for python-version-detect action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "python-version-detect action"
|
||||
ACTION_DIR="python-version-detect"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating default-version input"
|
||||
It "accepts valid Python version"
|
||||
When call validate_input_python "python-version-detect" "default-version" "3.11"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts Python version with patch"
|
||||
When call validate_input_python "python-version-detect" "default-version" "3.11.5"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts Python 3.8"
|
||||
When call validate_input_python "python-version-detect" "default-version" "3.8"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "accepts Python 3.12"
|
||||
When call validate_input_python "python-version-detect" "default-version" "3.12"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "rejects Python version too old"
|
||||
When call validate_input_python "python-version-detect" "default-version" "2.7"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects Python version too new"
|
||||
When call validate_input_python "python-version-detect" "default-version" "4.0"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "python-version-detect" "default-version" "python3.11"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "python-version-detect" "default-version" "3.11; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects version without minor"
|
||||
When call validate_input_python "python-version-detect" "default-version" "3"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "rejects empty version"
|
||||
When call validate_input_python "python-version-detect" "default-version" ""
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Python Version Detect"
|
||||
End
|
||||
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "default-version"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "python-version"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "has default-version as optional input"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "default-version" "optional"
|
||||
The output should equal "optional"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in version"
|
||||
When call validate_input_python "python-version-detect" "default-version" "../3.11"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against shell metacharacters in version"
|
||||
When call validate_input_python "python-version-detect" "default-version" "3.11|echo"
|
||||
The status should be failure
|
||||
End
|
||||
|
||||
It "validates against backtick injection"
|
||||
When call validate_input_python "python-version-detect" "default-version" "3.11\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,69 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for set-git-config action validation and logic
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "set-git-config action"
|
||||
ACTION_DIR="set-git-config"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating inputs (no validation logic in action)"
|
||||
# NOTE: This action has no validation logic - all inputs are accepted
|
||||
# The action simply passes through values and conditionally sets outputs
|
||||
It "accepts valid token value"
|
||||
When call validate_input_python "set-git-config" "token" "ghp_123456789012345678901234567890123456"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts any username value"
|
||||
When call validate_input_python "set-git-config" "username" "any-username"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts valid email value"
|
||||
When call validate_input_python "set-git-config" "email" "test@example.com"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts any is_fiximus value"
|
||||
When call validate_input_python "set-git-config" "is_fiximus" "any-value"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should equal "Set Git Config"
|
||||
End
|
||||
|
||||
It "defines required inputs"
|
||||
inputs=$(get_action_inputs "$ACTION_FILE")
|
||||
When call echo "$inputs"
|
||||
The output should include "token"
|
||||
The output should include "username"
|
||||
The output should include "email"
|
||||
The output should include "is_fiximus"
|
||||
End
|
||||
|
||||
It "defines expected outputs"
|
||||
outputs=$(get_action_outputs "$ACTION_FILE")
|
||||
When call echo "$outputs"
|
||||
The output should include "token"
|
||||
The output should include "username"
|
||||
The output should include "email"
|
||||
The output should include "is_fiximus"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing outputs"
|
||||
It "produces all expected outputs"
|
||||
When call test_action_outputs "$ACTION_DIR" "token" "ghp_test" "username" "test" "email" "test@example.com" "is_fiximus" "false"
|
||||
The status should be success
|
||||
The stderr should include "Testing action outputs for: set-git-config"
|
||||
The stderr should include "Output test passed for: set-git-config"
|
||||
End
|
||||
End
|
||||
End
|
||||
@@ -1,233 +0,0 @@
|
||||
#!/usr/bin/env shellspec
|
||||
# Unit tests for version-validator action validation and logic
|
||||
|
||||
# Framework is automatically loaded via spec_helper.sh
|
||||
|
||||
Describe "version-validator action"
|
||||
ACTION_DIR="version-validator"
|
||||
ACTION_FILE="$ACTION_DIR/action.yml"
|
||||
|
||||
Context "when validating version input"
|
||||
It "accepts valid semantic version"
|
||||
When call validate_input_python "version-validator" "version" "1.2.3"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts semantic version with v prefix"
|
||||
When call validate_input_python "version-validator" "version" "v1.2.3"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts prerelease version"
|
||||
When call validate_input_python "version-validator" "version" "1.2.3-alpha"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts prerelease with number"
|
||||
When call validate_input_python "version-validator" "version" "1.2.3-alpha.1"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts build metadata"
|
||||
When call validate_input_python "version-validator" "version" "1.2.3+build.1"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts prerelease with build metadata"
|
||||
When call validate_input_python "version-validator" "version" "1.2.3-alpha.1+build.1"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts CalVer format"
|
||||
When call validate_input_python "version-validator" "version" "2024.3.1"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects invalid version format"
|
||||
When call validate_input_python "version-validator" "version" "invalid.version"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects version with command injection"
|
||||
When call validate_input_python "version-validator" "version" "1.2.3; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects version with shell expansion"
|
||||
When call validate_input_python "version-validator" "version" "1.2.3\$(whoami)"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects empty version"
|
||||
When call validate_input_python "version-validator" "version" ""
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating validation-regex input"
|
||||
It "accepts valid regex pattern"
|
||||
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+\.[0-9]+\.[0-9]+$"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts semantic version regex"
|
||||
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+\.[0-9]+(\.[0-9]+)?(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts empty validation-regex (uses default)"
|
||||
When call validate_input_python "version-validator" "validation-regex" ""
|
||||
The status should be success
|
||||
End
|
||||
It "accepts valid regex patterns with quantifiers"
|
||||
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+\\.[0-9]+$"
|
||||
The status should be success
|
||||
End
|
||||
It "rejects regex with command injection"
|
||||
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+$; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating ReDoS patterns"
|
||||
It "rejects nested quantifiers (a+)+"
|
||||
When call validate_input_python "version-validator" "validation-regex" "(a+)+"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects nested quantifiers (a*)+"
|
||||
When call validate_input_python "version-validator" "validation-regex" "(a*)+"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects nested quantifiers (a+)*"
|
||||
When call validate_input_python "version-validator" "validation-regex" "(a+)*"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects nested quantifiers (a*)*"
|
||||
When call validate_input_python "version-validator" "validation-regex" "(a*)*"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects quantified groups (a+){2,5}"
|
||||
When call validate_input_python "version-validator" "validation-regex" "(a+){2,5}"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects consecutive quantifiers .*.* (ReDoS)"
|
||||
When call validate_input_python "version-validator" "validation-regex" ".*.*"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects consecutive quantifiers .*+ (ReDoS)"
|
||||
When call validate_input_python "version-validator" "validation-regex" ".*+"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects duplicate alternatives (a|a)+"
|
||||
When call validate_input_python "version-validator" "validation-regex" "(a|a)+"
|
||||
The status should be failure
|
||||
End
|
||||
It "rejects overlapping alternatives (a|ab)+"
|
||||
When call validate_input_python "version-validator" "validation-regex" "(a|ab)+"
|
||||
The status should be failure
|
||||
End
|
||||
It "accepts safe pattern with single quantifier"
|
||||
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+$"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts safe pattern with character class"
|
||||
When call validate_input_python "version-validator" "validation-regex" "^[a-zA-Z0-9]+$"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts safe pattern with optional group"
|
||||
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+(\\.[0-9]+)?$"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts safe alternation without repetition"
|
||||
When call validate_input_python "version-validator" "validation-regex" "^(alpha|beta|gamma)$"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
|
||||
Context "when validating language input"
|
||||
It "accepts valid language name"
|
||||
When call validate_input_python "version-validator" "language" "nodejs"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts version as language"
|
||||
When call validate_input_python "version-validator" "language" "version"
|
||||
The status should be success
|
||||
End
|
||||
It "accepts empty language (uses default)"
|
||||
When call validate_input_python "version-validator" "language" ""
|
||||
The status should be success
|
||||
End
|
||||
It "rejects language with command injection"
|
||||
When call validate_input_python "version-validator" "language" "version; rm -rf /"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when checking action.yml structure"
|
||||
It "has valid YAML syntax"
|
||||
When call validate_action_yml_quiet "$ACTION_FILE"
|
||||
The status should be success
|
||||
End
|
||||
It "has correct action name"
|
||||
name=$(get_action_name "$ACTION_FILE")
|
||||
When call echo "$name"
|
||||
The output should match pattern "*Version*"
|
||||
End
|
||||
It "defines expected inputs"
|
||||
When call get_action_inputs "$ACTION_FILE"
|
||||
The output should include "version"
|
||||
The output should include "validation-regex"
|
||||
The output should include "language"
|
||||
End
|
||||
It "defines expected outputs"
|
||||
When call get_action_outputs "$ACTION_FILE"
|
||||
The output should include "is-valid"
|
||||
The output should include "validated-version"
|
||||
The output should include "error-message"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing input requirements"
|
||||
It "requires version input"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "version" "required"
|
||||
The status should be success
|
||||
The output should equal "required"
|
||||
End
|
||||
It "has validation-regex as optional input"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "validation-regex" "optional"
|
||||
The status should be success
|
||||
The output should equal "optional"
|
||||
End
|
||||
It "has language as optional input"
|
||||
When call uv run "_tests/shared/validation_core.py" --property "$ACTION_FILE" "language" "optional"
|
||||
The status should be success
|
||||
The output should equal "optional"
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing security validations"
|
||||
It "validates against path traversal in version"
|
||||
When call validate_input_python "version-validator" "version" "../1.2.3"
|
||||
The status should be failure
|
||||
End
|
||||
It "validates against shell metacharacters in version"
|
||||
When call validate_input_python "version-validator" "version" "1.2.3|echo"
|
||||
The status should be failure
|
||||
End
|
||||
It "validates against backtick injection in language"
|
||||
When call validate_input_python "version-validator" "language" "version\`whoami\`"
|
||||
The status should be failure
|
||||
End
|
||||
It "validates against variable expansion in version"
|
||||
When call validate_input_python "version-validator" "version" "1.2.3\${HOME}"
|
||||
The status should be failure
|
||||
End
|
||||
End
|
||||
|
||||
Context "when testing version validation functionality"
|
||||
It "validates semantic version format restrictions"
|
||||
When call validate_input_python "version-validator" "version" "1.2"
|
||||
The status should be success
|
||||
End
|
||||
It "validates regex pattern safety"
|
||||
When call validate_input_python "version-validator" "validation-regex" "^[0-9]+$"
|
||||
The status should be success
|
||||
End
|
||||
It "validates language parameter format"
|
||||
When call validate_input_python "version-validator" "language" "NODEJS"
|
||||
The status should be success
|
||||
End
|
||||
It "validates complex version formats"
|
||||
When call validate_input_python "version-validator" "version" "1.0.0-beta.1+exp.sha.5114f85"
|
||||
The status should be success
|
||||
End
|
||||
End
|
||||
End
|
||||
94
_tools/bump-major-version.sh
Executable file
94
_tools/bump-major-version.sh
Executable file
@@ -0,0 +1,94 @@
|
||||
#!/bin/sh
|
||||
# Bump from one major version to another (annual version bump)
|
||||
set -eu
|
||||
|
||||
OLD_VERSION="${1:-}"
|
||||
NEW_VERSION="${2:-}"
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
# Check git availability
|
||||
require_git
|
||||
|
||||
if [ -z "$OLD_VERSION" ] || [ -z "$NEW_VERSION" ]; then
|
||||
printf '%b' "${RED}Error: OLD_VERSION and NEW_VERSION arguments required${NC}\n"
|
||||
printf 'Usage: %s v2025 v2026\n' "$0"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate major version format
|
||||
if ! validate_major_version "$OLD_VERSION"; then
|
||||
printf '%b' "${RED}Error: Invalid old version format: $OLD_VERSION${NC}\n"
|
||||
printf 'Expected: vYYYY (e.g., v2025)\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! validate_major_version "$NEW_VERSION"; then
|
||||
printf '%b' "${RED}Error: Invalid new version format: $NEW_VERSION${NC}\n"
|
||||
printf 'Expected: vYYYY (e.g., v2026)\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%b' "${BLUE}Bumping major version from $OLD_VERSION to $NEW_VERSION${NC}\n"
|
||||
printf '\n'
|
||||
|
||||
# Get SHA for new version tag
|
||||
if ! git rev-parse "$NEW_VERSION" >/dev/null 2>&1; then
|
||||
printf '%b' "${YELLOW}Warning: Tag $NEW_VERSION not found${NC}\n"
|
||||
printf 'Creating tag %s pointing to current HEAD...\n' "$NEW_VERSION"
|
||||
|
||||
if ! current_sha=$(git rev-parse HEAD 2>&1); then
|
||||
printf '%b' "${RED}Error: Failed to get current HEAD SHA${NC}\n" >&2
|
||||
printf 'Git command failed: git rev-parse HEAD\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
git tag -a "$NEW_VERSION" -m "Major version $NEW_VERSION"
|
||||
printf '%b' "${GREEN}✓ Created tag $NEW_VERSION pointing to $current_sha${NC}\n"
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
if ! new_sha=$(git rev-list -n 1 "$NEW_VERSION" 2>&1); then
|
||||
printf '%b' "${RED}Error: Failed to get SHA for tag $NEW_VERSION${NC}\n" >&2
|
||||
printf 'Git command failed: git rev-list -n 1 "%s"\n' "$NEW_VERSION" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$new_sha" ]; then
|
||||
printf '%b' "${RED}Error: Empty SHA returned for tag $NEW_VERSION${NC}\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%b' "Target SHA for $NEW_VERSION: ${GREEN}$new_sha${NC}\n"
|
||||
printf '\n'
|
||||
|
||||
# Update all action references
|
||||
printf '%b' "${BLUE}Updating action references...${NC}\n"
|
||||
"$SCRIPT_DIR/update-action-refs.sh" "$NEW_VERSION" "tag"
|
||||
|
||||
# Commit the changes
|
||||
if ! git diff --quiet; then
|
||||
git add -- */action.yml
|
||||
git commit -m "chore: bump major version from $OLD_VERSION to $NEW_VERSION
|
||||
|
||||
This commit updates all internal action references from $OLD_VERSION
|
||||
to $NEW_VERSION.
|
||||
|
||||
🤖 Generated with [Claude Code](https://claude.com/claude-code)
|
||||
|
||||
Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
|
||||
printf '%b' "${GREEN}✅ Committed version bump${NC}\n"
|
||||
else
|
||||
printf '%b' "${BLUE}No changes to commit${NC}\n"
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
printf '%b' "${GREEN}✅ Major version bumped successfully${NC}\n"
|
||||
printf '\n'
|
||||
printf '%b' "${YELLOW}Remember to update READMEs:${NC}\n"
|
||||
printf ' make docs\n'
|
||||
120
_tools/check-version-refs.sh
Executable file
120
_tools/check-version-refs.sh
Executable file
@@ -0,0 +1,120 @@
|
||||
#!/bin/sh
|
||||
# Check and display all current SHA-pinned action references
|
||||
set -eu
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
# Warn once if git is not available
|
||||
if ! has_git; then
|
||||
printf '%b' "${YELLOW}Warning: git is not installed or not in PATH${NC}\n" >&2
|
||||
printf 'Git tag information will not be available.\n' >&2
|
||||
fi
|
||||
|
||||
# Check for required coreutils
|
||||
for tool in find grep sed printf sort cut tr wc; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
printf '%b' "${RED}Error: Required tool '%s' is not installed or not in PATH${NC}\n" "$tool" >&2
|
||||
printf 'Please install coreutils to use this script.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
printf '%b' "${BLUE}Current SHA-pinned action references:${NC}\n"
|
||||
printf '\n'
|
||||
|
||||
# Create temp files for processing
|
||||
temp_file=$(safe_mktemp)
|
||||
trap 'rm -f "$temp_file"' EXIT
|
||||
|
||||
temp_input=$(safe_mktemp)
|
||||
trap 'rm -f "$temp_file" "$temp_input"' EXIT
|
||||
|
||||
# Find all action references and collect SHA|action pairs
|
||||
# Use input redirection to avoid subshell issues with pipeline
|
||||
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_input"
|
||||
|
||||
while IFS= read -r line; do
|
||||
# Extract action name and SHA using sed
|
||||
action=$(echo "$line" | sed -n 's|.*ivuorinen/actions/\([a-z-]*\)@.*|\1|p')
|
||||
sha=$(echo "$line" | sed -n 's|.*@\([a-f0-9]\{40\}\).*|\1|p')
|
||||
|
||||
if [ -n "$action" ] && [ -n "$sha" ]; then
|
||||
printf '%s\n' "$sha|$action" >> "$temp_file"
|
||||
fi
|
||||
done < "$temp_input"
|
||||
|
||||
# Check if we found any references
|
||||
if [ ! -s "$temp_file" ]; then
|
||||
printf '%b' "${YELLOW}No SHA-pinned references found${NC}\n"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Sort by SHA and group
|
||||
sort "$temp_file" | uniq > "${temp_file}.sorted"
|
||||
mv "${temp_file}.sorted" "$temp_file"
|
||||
|
||||
# Count unique SHAs
|
||||
sha_count=$(cut -d'|' -f1 "$temp_file" | sort -u | wc -l | tr -d ' ')
|
||||
|
||||
if [ "$sha_count" -eq 1 ]; then
|
||||
printf '%b' "${GREEN}✓ All references use the same SHA (consistent)${NC}\n"
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
# Process and display grouped by SHA
|
||||
current_sha=""
|
||||
actions_list=""
|
||||
|
||||
while IFS='|' read -r sha action; do
|
||||
if [ "$sha" != "$current_sha" ]; then
|
||||
# Print previous SHA group if exists
|
||||
if [ -n "$current_sha" ]; then
|
||||
# Try to find tags pointing to this SHA
|
||||
if has_git; then
|
||||
tags=$(git tag --points-at "$current_sha" 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
|
||||
else
|
||||
tags=""
|
||||
fi
|
||||
|
||||
printf '%b' "${GREEN}SHA: $current_sha${NC}\n"
|
||||
if [ -n "$tags" ]; then
|
||||
printf '%b' " Tags: ${BLUE}$tags${NC}\n"
|
||||
fi
|
||||
printf ' Actions: %s\n' "$actions_list"
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
# Start new SHA group
|
||||
current_sha="$sha"
|
||||
actions_list="$action"
|
||||
else
|
||||
# Add to current SHA group
|
||||
actions_list="$actions_list, $action"
|
||||
fi
|
||||
done < "$temp_file"
|
||||
|
||||
# Print last SHA group
|
||||
if [ -n "$current_sha" ]; then
|
||||
if has_git; then
|
||||
tags=$(git tag --points-at "$current_sha" 2>/dev/null | tr '\n' ', ' | sed 's/,$//')
|
||||
else
|
||||
tags=""
|
||||
fi
|
||||
|
||||
printf '%b' "${GREEN}SHA: $current_sha${NC}\n"
|
||||
if [ -n "$tags" ]; then
|
||||
printf '%b' " Tags: ${BLUE}$tags${NC}\n"
|
||||
fi
|
||||
printf ' Actions: %s\n' "$actions_list"
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
printf '%b' "${BLUE}Summary:${NC}\n"
|
||||
printf ' Unique SHAs: %s\n' "$sha_count"
|
||||
if [ "$sha_count" -gt 1 ]; then
|
||||
printf '%b' " ${YELLOW}⚠ Warning: Multiple SHAs in use (consider updating)${NC}\n"
|
||||
fi
|
||||
@@ -1,15 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
#!/bin/sh
|
||||
# Build script for GitHub Actions Testing Docker Image
|
||||
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
IMAGE_NAME="ghcr.io/ivuorinen/actions"
|
||||
IMAGE_TAG="${1:-testing-tools}"
|
||||
FULL_IMAGE_NAME="${IMAGE_NAME}:${IMAGE_TAG}"
|
||||
|
||||
echo "Building GitHub Actions Testing Docker Image..."
|
||||
echo "Image: $FULL_IMAGE_NAME"
|
||||
printf 'Building GitHub Actions Testing Docker Image...\n'
|
||||
printf 'Image: %s\n' "$FULL_IMAGE_NAME"
|
||||
|
||||
# Enable BuildKit for better caching and performance
|
||||
export DOCKER_BUILDKIT=1
|
||||
@@ -17,7 +17,7 @@ export DOCKER_BUILDKIT=1
|
||||
# Build the multi-stage image
|
||||
# Check for buildx support up front, then run the appropriate build command
|
||||
if docker buildx version >/dev/null 2>&1; then
|
||||
echo "Using buildx (multi-arch capable)"
|
||||
printf 'Using buildx (multi-arch capable)\n'
|
||||
docker buildx build \
|
||||
--pull \
|
||||
--tag "$FULL_IMAGE_NAME" \
|
||||
@@ -26,7 +26,7 @@ if docker buildx version >/dev/null 2>&1; then
|
||||
--load \
|
||||
"$SCRIPT_DIR"
|
||||
else
|
||||
echo "⚠️ buildx not available, using standard docker build"
|
||||
printf '⚠️ buildx not available, using standard docker build\n'
|
||||
docker build \
|
||||
--pull \
|
||||
--tag "$FULL_IMAGE_NAME" \
|
||||
@@ -35,22 +35,22 @@ else
|
||||
"$SCRIPT_DIR"
|
||||
fi
|
||||
|
||||
echo "Build completed successfully!"
|
||||
echo ""
|
||||
echo "Testing the image..."
|
||||
printf 'Build completed successfully!\n'
|
||||
printf '\n'
|
||||
printf 'Testing the image...\n'
|
||||
|
||||
# Test basic functionality
|
||||
docker run --rm "$FULL_IMAGE_NAME" whoami
|
||||
docker run --rm "$FULL_IMAGE_NAME" shellspec --version
|
||||
docker run --rm "$FULL_IMAGE_NAME" act --version
|
||||
|
||||
echo "Image tests passed!"
|
||||
echo ""
|
||||
echo "To test the image locally:"
|
||||
echo " docker run --rm -it $FULL_IMAGE_NAME"
|
||||
echo ""
|
||||
echo "To push to registry:"
|
||||
echo " docker push $FULL_IMAGE_NAME"
|
||||
echo ""
|
||||
echo "To use in GitHub Actions:"
|
||||
echo " container: $FULL_IMAGE_NAME"
|
||||
printf 'Image tests passed!\n'
|
||||
printf '\n'
|
||||
printf 'To test the image locally:\n'
|
||||
printf ' docker run --rm -it %s\n' "$FULL_IMAGE_NAME"
|
||||
printf '\n'
|
||||
printf 'To push to registry:\n'
|
||||
printf ' docker push %s\n' "$FULL_IMAGE_NAME"
|
||||
printf '\n'
|
||||
printf 'To use in GitHub Actions:\n'
|
||||
printf ' container: %s\n' "$FULL_IMAGE_NAME"
|
||||
|
||||
41
_tools/get-action-sha.sh
Executable file
41
_tools/get-action-sha.sh
Executable file
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
# Get the SHA for a specific version tag
|
||||
set -eu
|
||||
|
||||
VERSION="${1:-}"
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
# Check git availability
|
||||
require_git
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
printf '%b' "${RED}Error: VERSION argument required${NC}\n" >&2
|
||||
printf 'Usage: %s v2025\n' "$0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if tag exists
|
||||
if ! git rev-parse "$VERSION" >/dev/null 2>&1; then
|
||||
printf '%b' "${RED}Error: Tag $VERSION not found${NC}\n" >&2
|
||||
printf '\n' >&2
|
||||
printf '%b' "${BLUE}Available tags:${NC}\n" >&2
|
||||
git tag -l 'v*' | head -20 >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get SHA for the tag
|
||||
sha=$(git rev-list -n 1 "$VERSION")
|
||||
|
||||
# Check if output is for terminal or pipe
|
||||
if [ -t 1 ]; then
|
||||
# Terminal output - show with colors
|
||||
printf '%b' "${GREEN}$sha${NC}\n"
|
||||
else
|
||||
# Piped output - just the SHA
|
||||
printf '%s\n' "$sha"
|
||||
fi
|
||||
152
_tools/release-undo.sh
Executable file
152
_tools/release-undo.sh
Executable file
@@ -0,0 +1,152 @@
|
||||
#!/bin/sh
|
||||
# Undo the most recent release by deleting tags and optionally resetting HEAD
|
||||
set -eu
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
# Check git availability
|
||||
require_git
|
||||
|
||||
msg_info "Finding most recent release tags..."
|
||||
|
||||
# Portable version sort function
|
||||
# Sorts CalVer tags vYYYY.MM.DD numerically
|
||||
version_sort_tags() {
|
||||
# Try GNU sort first (Linux and some macOS with GNU coreutils)
|
||||
if sort --version 2>/dev/null | grep -q GNU; then
|
||||
sort -V
|
||||
return
|
||||
fi
|
||||
|
||||
# Try gsort (macOS with GNU coreutils via Homebrew)
|
||||
if command -v gsort >/dev/null 2>&1; then
|
||||
gsort -V
|
||||
return
|
||||
fi
|
||||
|
||||
# Fallback: awk-based numeric version sort with validation
|
||||
awk -F. '{
|
||||
# Validate CalVer format: vYYYY.MM.DD or YYYY.MM.DD
|
||||
if ($0 !~ /^v?[0-9]+\.[0-9]+\.[0-9]+$/) {
|
||||
printf "Warning: Skipping malformed tag: %s\n", $0 > "/dev/stderr"
|
||||
next
|
||||
}
|
||||
|
||||
# Check we have exactly 3 fields after splitting on dots
|
||||
if (NF != 3) {
|
||||
printf "Warning: Skipping invalid tag (wrong field count): %s\n", $0 > "/dev/stderr"
|
||||
next
|
||||
}
|
||||
|
||||
# Save original input before modification
|
||||
original = $0
|
||||
# Remove leading v and split into year, month, day
|
||||
gsub(/^v/, "", $0)
|
||||
|
||||
# Verify each field is numeric after field recalculation
|
||||
if ($1 !~ /^[0-9]+$/ || $2 !~ /^[0-9]+$/ || $3 !~ /^[0-9]+$/) {
|
||||
printf "Warning: Skipping tag with non-numeric components: %s\n", original > "/dev/stderr"
|
||||
next
|
||||
}
|
||||
|
||||
printf "%04d.%02d.%02d %s\n", $1, $2, $3, original
|
||||
}' | sort -n | cut -d' ' -f2
|
||||
}
|
||||
|
||||
# Find all release tags matching vYYYY.MM.DD pattern
|
||||
all_tags=$(git tag -l 'v[0-9][0-9][0-9][0-9].[0-9][0-9].[0-9][0-9]' | version_sort_tags)
|
||||
|
||||
if [ -z "$all_tags" ]; then
|
||||
msg_warn "No release tags found"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Get most recent tag
|
||||
latest_tag=$(echo "$all_tags" | tail -n 1)
|
||||
|
||||
# Extract version components
|
||||
version_no_v="${latest_tag#v}"
|
||||
year=$(echo "$version_no_v" | cut -d'.' -f1)
|
||||
month=$(echo "$version_no_v" | cut -d'.' -f2)
|
||||
day=$(echo "$version_no_v" | cut -d'.' -f3)
|
||||
|
||||
major="v$year"
|
||||
minor="v$year.$month"
|
||||
patch="v$year.$month.$day"
|
||||
|
||||
printf '\n'
|
||||
msg_info "Most recent release:"
|
||||
printf ' Patch: %s\n' "$patch"
|
||||
printf ' Minor: %s\n' "$minor"
|
||||
printf ' Major: %s\n' "$major"
|
||||
printf '\n'
|
||||
|
||||
# Show which tags exist
|
||||
msg_info "Tags that will be deleted:"
|
||||
for tag in "$patch" "$minor" "$major"; do
|
||||
if check_tag_exists "$tag"; then
|
||||
tag_sha=$(git rev-list -n 1 "$tag")
|
||||
tag_sha_short=$(echo "$tag_sha" | cut -c1-7)
|
||||
printf ' %s (points to %s)\n' "$tag" "$tag_sha_short"
|
||||
fi
|
||||
done
|
||||
printf '\n'
|
||||
|
||||
# Check if HEAD commit is a release commit
|
||||
head_message=$(git log -1 --pretty=%s)
|
||||
if echo "$head_message" | grep -q "^chore: update action references for release"; then
|
||||
msg_warn "Last commit appears to be a release preparation commit:"
|
||||
printf ' %s\n' "$head_message"
|
||||
printf '\n'
|
||||
reset_head=true
|
||||
else
|
||||
reset_head=false
|
||||
fi
|
||||
|
||||
# Confirm deletion
|
||||
msg_warn "This will:"
|
||||
printf ' 1. Delete tags: %s, %s, %s\n' "$patch" "$minor" "$major"
|
||||
if [ "$reset_head" = "true" ]; then
|
||||
printf ' 2. Reset HEAD to previous commit (undo release prep)\n'
|
||||
fi
|
||||
printf '\n'
|
||||
|
||||
if ! prompt_confirmation "Proceed with rollback?"; then
|
||||
msg_warn "Rollback cancelled"
|
||||
exit 0
|
||||
fi
|
||||
printf '\n'
|
||||
|
||||
# Delete tags
|
||||
msg_info "Deleting tags..."
|
||||
for tag in "$patch" "$minor" "$major"; do
|
||||
if check_tag_exists "$tag"; then
|
||||
git tag -d "$tag"
|
||||
msg_item "Deleted tag: $tag"
|
||||
else
|
||||
msg_notice "Tag not found: $tag (skipping)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Reset HEAD if needed
|
||||
if [ "$reset_head" = "true" ]; then
|
||||
printf '\n'
|
||||
msg_info "Resetting HEAD to previous commit..."
|
||||
git reset --hard HEAD~1
|
||||
msg_item "Reset complete"
|
||||
new_head=$(git rev-parse HEAD)
|
||||
new_head_short=$(echo "$new_head" | cut -c1-7)
|
||||
printf 'New HEAD: %s%s%s\n' "$GREEN" "$new_head_short" "$NC"
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
msg_done "Rollback complete"
|
||||
printf '\n'
|
||||
msg_warn "Note:"
|
||||
printf ' Tags were deleted locally only\n'
|
||||
printf ' If you had pushed the tags, delete them from remote:\n'
|
||||
printf ' git push origin --delete %s %s %s\n' "$patch" "$minor" "$major"
|
||||
289
_tools/release.sh
Executable file
289
_tools/release.sh
Executable file
@@ -0,0 +1,289 @@
|
||||
#!/bin/sh
|
||||
# Release script for creating versioned tags and updating action references
|
||||
set -eu
|
||||
|
||||
# Parse arguments
|
||||
VERSION=""
|
||||
DRY_RUN=false
|
||||
SKIP_CONFIRM=false
|
||||
PREP_ONLY=false
|
||||
TAG_ONLY=false
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
--yes|--no-confirm)
|
||||
SKIP_CONFIRM=true
|
||||
shift
|
||||
;;
|
||||
--prep-only)
|
||||
PREP_ONLY=true
|
||||
shift
|
||||
;;
|
||||
--tag-only)
|
||||
TAG_ONLY=true
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
printf 'Usage: %s [OPTIONS] VERSION\n' "$0"
|
||||
printf '\n'
|
||||
printf 'Options:\n'
|
||||
printf ' --dry-run Show what would happen without making changes\n'
|
||||
printf ' --yes Skip confirmation prompt\n'
|
||||
printf ' --no-confirm Alias for --yes\n'
|
||||
printf ' --prep-only Only update refs and commit (no tags)\n'
|
||||
printf ' --tag-only Only create tags (assumes prep done)\n'
|
||||
printf ' --help, -h Show this help message\n'
|
||||
printf '\n'
|
||||
printf 'Examples:\n'
|
||||
printf ' %s v2025.11.01\n' "$0"
|
||||
printf ' %s --dry-run v2025.11.01\n' "$0"
|
||||
printf ' %s --yes v2025.11.01\n' "$0"
|
||||
exit 0
|
||||
;;
|
||||
-*)
|
||||
printf 'Unknown option: %s\n' "$1" >&2
|
||||
printf 'Use --help for usage information\n' >&2
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
VERSION="$1"
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
msg_error "VERSION argument required"
|
||||
printf 'Usage: %s [OPTIONS] VERSION\n' "$0"
|
||||
printf 'Use --help for more information\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate version format
|
||||
if ! validate_version "$VERSION"; then
|
||||
msg_error "Invalid version format: $VERSION"
|
||||
printf 'Expected: vYYYY.MM.DD with zero-padded month/day (e.g., v2025.10.18, v2025.01.05)\n'
|
||||
printf 'Invalid: v2025.1.5 (must be zero-padded)\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract version components
|
||||
# Remove leading 'v'
|
||||
version_no_v="${VERSION#v}"
|
||||
# Extract year, month, day
|
||||
year=$(echo "$version_no_v" | cut -d'.' -f1)
|
||||
month=$(echo "$version_no_v" | cut -d'.' -f2)
|
||||
day=$(echo "$version_no_v" | cut -d'.' -f3)
|
||||
|
||||
major="v$year"
|
||||
minor="v$year.$month"
|
||||
patch="v$year.$month.$day"
|
||||
|
||||
# Show dry-run banner if applicable
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
msg_plain "$YELLOW" "=== DRY RUN MODE ==="
|
||||
printf 'No changes will be made to git repository\n'
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
msg_info "Creating release $VERSION"
|
||||
printf ' Major: %s\n' "$major"
|
||||
printf ' Minor: %s\n' "$minor"
|
||||
printf ' Patch: %s\n' "$patch"
|
||||
printf '\n'
|
||||
|
||||
# Check if git is available (required for all modes)
|
||||
if ! require_git 2>/dev/null; then
|
||||
msg_error "git not available"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Pre-flight checks (skip for --tag-only since prep should be done)
|
||||
if [ "$TAG_ONLY" = "false" ]; then
|
||||
msg_info "Running pre-flight checks..."
|
||||
msg_item "git is available"
|
||||
|
||||
# Check if on main branch
|
||||
if ! check_on_branch "main"; then
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
||||
msg_error "Not on main branch (currently on: $current_branch)"
|
||||
if [ "$DRY_RUN" = "false" ]; then
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
msg_item "On main branch"
|
||||
fi
|
||||
|
||||
# Check if working directory is clean
|
||||
if ! check_git_clean; then
|
||||
msg_error "Working directory has uncommitted changes"
|
||||
if [ "$DRY_RUN" = "false" ]; then
|
||||
printf 'Please commit or stash changes before creating a release\n'
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
msg_item "Working directory is clean"
|
||||
fi
|
||||
|
||||
# Check if patch tag already exists
|
||||
if check_tag_exists "$patch"; then
|
||||
msg_error "Tag $patch already exists"
|
||||
if [ "$DRY_RUN" = "false" ]; then
|
||||
printf 'Use a different version or delete the existing tag first\n'
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
msg_item "Tag $patch does not exist"
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
# Get current commit SHA
|
||||
current_sha=$(git rev-parse HEAD)
|
||||
printf 'Current HEAD: %s%s%s\n' "$GREEN" "$current_sha" "$NC"
|
||||
printf '\n'
|
||||
|
||||
# Confirmation prompt (skip if --yes or --dry-run)
|
||||
if [ "$DRY_RUN" = "false" ] && [ "$SKIP_CONFIRM" = "false" ]; then
|
||||
if ! prompt_confirmation "Proceed with release $VERSION?"; then
|
||||
msg_warn "Release cancelled by user"
|
||||
exit 0
|
||||
fi
|
||||
printf '\n'
|
||||
fi
|
||||
|
||||
# Skip prep if --tag-only
|
||||
if [ "$TAG_ONLY" = "true" ]; then
|
||||
msg_info "Skipping preparation (--tag-only mode)"
|
||||
printf '\n'
|
||||
else
|
||||
# Update all action references to current SHA
|
||||
msg_info "Updating action references to $current_sha..."
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
msg_warn "[DRY RUN] Would run: update-action-refs.sh $current_sha direct"
|
||||
else
|
||||
"$SCRIPT_DIR/update-action-refs.sh" "$current_sha" "direct"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Commit the changes (skip if --tag-only)
|
||||
if [ "$TAG_ONLY" = "false" ]; then
|
||||
if ! git diff --quiet; then
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
msg_warn "[DRY RUN] Would add: */action.yml"
|
||||
msg_warn "[DRY RUN] Would commit: update action references for release $VERSION"
|
||||
else
|
||||
git add -- */action.yml
|
||||
git commit -m "chore: update action references for release $VERSION
|
||||
|
||||
This commit updates all internal action references to point to the current
|
||||
commit SHA in preparation for release $VERSION."
|
||||
|
||||
# Update SHA since we just created a new commit
|
||||
current_sha=$(git rev-parse HEAD)
|
||||
msg_done "Committed updated action references"
|
||||
printf 'New HEAD: %s%s%s\n' "$GREEN" "$current_sha" "$NC"
|
||||
fi
|
||||
else
|
||||
msg_info "No changes to commit"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Exit early if --prep-only
|
||||
if [ "$PREP_ONLY" = "true" ]; then
|
||||
printf '\n'
|
||||
msg_done "Preparation complete (--prep-only mode)"
|
||||
msg_warn "Run with --tag-only to create tags"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Create/update tags
|
||||
printf '\n'
|
||||
msg_info "Creating tags..."
|
||||
|
||||
# Create patch tag
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
msg_warn "[DRY RUN] Would create tag: $patch"
|
||||
else
|
||||
git tag -a "$patch" -m "Release $patch"
|
||||
msg_item "Created tag: $patch"
|
||||
fi
|
||||
|
||||
# Move/create minor tag
|
||||
if git rev-parse "$minor" >/dev/null 2>&1; then
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
msg_warn "[DRY RUN] Would force-update tag: $minor"
|
||||
else
|
||||
git tag -f -a "$minor" -m "Latest $minor release: $patch"
|
||||
msg_item "Updated tag: $minor (force)"
|
||||
fi
|
||||
else
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
msg_warn "[DRY RUN] Would create tag: $minor"
|
||||
else
|
||||
git tag -a "$minor" -m "Latest $minor release: $patch"
|
||||
msg_item "Created tag: $minor"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Move/create major tag
|
||||
if git rev-parse "$major" >/dev/null 2>&1; then
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
msg_warn "[DRY RUN] Would force-update tag: $major"
|
||||
else
|
||||
git tag -f -a "$major" -m "Latest $major release: $patch"
|
||||
msg_item "Updated tag: $major (force)"
|
||||
fi
|
||||
else
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
msg_warn "[DRY RUN] Would create tag: $major"
|
||||
else
|
||||
git tag -a "$major" -m "Latest $major release: $patch"
|
||||
msg_item "Created tag: $major"
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '\n'
|
||||
if [ "$DRY_RUN" = "true" ]; then
|
||||
msg_done "Dry run complete - no changes made"
|
||||
printf '\n'
|
||||
msg_info "Would have created release $VERSION"
|
||||
else
|
||||
msg_done "Release $VERSION created successfully"
|
||||
fi
|
||||
printf '\n'
|
||||
msg_plain "$YELLOW" "All tags point to: $current_sha"
|
||||
printf '\n'
|
||||
msg_info "Tags created:"
|
||||
printf ' %s\n' "$patch"
|
||||
printf ' %s\n' "$minor"
|
||||
printf ' %s\n' "$major"
|
||||
printf '\n'
|
||||
|
||||
# Enhanced next steps
|
||||
if [ "$DRY_RUN" = "false" ]; then
|
||||
msg_warn "Next steps:"
|
||||
printf ' 1. Review changes: git show HEAD\n'
|
||||
printf ' 2. Verify CI status: gh run list --limit 5\n'
|
||||
printf ' 3. Push tags: git push origin main --tags --force-with-lease\n'
|
||||
printf ' 4. Update workflow refs: make update-version-refs MAJOR=%s\n' "$major"
|
||||
printf ' 5. Update README examples if needed\n'
|
||||
printf ' 6. Create GitHub release: gh release create %s --generate-notes\n' "$VERSION"
|
||||
printf '\n'
|
||||
msg_info "If something went wrong:"
|
||||
printf ' Rollback: make release-undo\n'
|
||||
else
|
||||
msg_warn "To execute this release:"
|
||||
printf ' Run without --dry-run flag\n'
|
||||
fi
|
||||
257
_tools/shared.sh
Executable file
257
_tools/shared.sh
Executable file
@@ -0,0 +1,257 @@
|
||||
#!/bin/sh
|
||||
# Shared functions and utilities for _tools/ scripts
|
||||
# This file is sourced by other scripts, not executed directly
|
||||
|
||||
# Colors (exported for use by sourcing scripts)
|
||||
# shellcheck disable=SC2034
|
||||
RED='\033[0;31m'
|
||||
# shellcheck disable=SC2034
|
||||
GREEN='\033[0;32m'
|
||||
# shellcheck disable=SC2034
|
||||
BLUE='\033[0;34m'
|
||||
# shellcheck disable=SC2034
|
||||
YELLOW='\033[1;33m'
|
||||
# shellcheck disable=SC2034
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Validate CalVer version format: vYYYY.MM.DD (zero-padded)
|
||||
validate_version() {
|
||||
version="$1"
|
||||
|
||||
# Check format: vYYYY.MM.DD (require zero-padding) using grep
|
||||
if ! echo "$version" | grep -qE '^v[0-9]{4}\.[0-9]{2}\.[0-9]{2}$'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract components
|
||||
version_no_v="${version#v}"
|
||||
year=$(echo "$version_no_v" | cut -d'.' -f1)
|
||||
month=$(echo "$version_no_v" | cut -d'.' -f2)
|
||||
day=$(echo "$version_no_v" | cut -d'.' -f3)
|
||||
|
||||
# Validate year (2020-2099)
|
||||
if [ "$year" -lt 2020 ] || [ "$year" -gt 2099 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate month (01-12)
|
||||
if [ "$month" -lt 1 ] || [ "$month" -gt 12 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate day (01-31)
|
||||
if [ "$day" -lt 1 ] || [ "$day" -gt 31 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Validate major version format: vYYYY
|
||||
validate_major_version() {
|
||||
version="$1"
|
||||
|
||||
# Check format: vYYYY using grep
|
||||
if ! echo "$version" | grep -qE '^v[0-9]{4}$'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract year
|
||||
year="${version#v}"
|
||||
|
||||
# Validate year (2020-2099)
|
||||
if [ "$year" -lt 2020 ] || [ "$year" -gt 2099 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Validate minor version format: vYYYY.MM (zero-padded)
|
||||
validate_minor_version() {
|
||||
version="$1"
|
||||
|
||||
# Check format: vYYYY.MM (require zero-padding) using grep
|
||||
if ! echo "$version" | grep -qE '^v[0-9]{4}\.[0-9]{2}$'; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Extract components
|
||||
version_no_v="${version#v}"
|
||||
year=$(echo "$version_no_v" | cut -d'.' -f1)
|
||||
month=$(echo "$version_no_v" | cut -d'.' -f2)
|
||||
|
||||
# Validate year (2020-2099)
|
||||
if [ "$year" -lt 2020 ] || [ "$year" -gt 2099 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Validate month (01-12)
|
||||
if [ "$month" -lt 1 ] || [ "$month" -gt 12 ]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check if working directory is clean (no uncommitted changes)
|
||||
check_git_clean() {
|
||||
if ! has_git; then
|
||||
return 1
|
||||
fi
|
||||
if ! git diff --quiet || ! git diff --cached --quiet; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check if currently on specified branch (default: main)
|
||||
check_on_branch() {
|
||||
target_branch="${1:-main}"
|
||||
|
||||
if ! has_git; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) || return 1
|
||||
|
||||
if [ "$current_branch" != "$target_branch" ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check if a git tag exists
|
||||
check_tag_exists() {
|
||||
tag="$1"
|
||||
|
||||
if ! has_git; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
if git rev-parse "$tag" >/dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Prompt user for yes/no confirmation
|
||||
# Usage: if prompt_confirmation "Continue?"; then ...; fi
|
||||
prompt_confirmation() {
|
||||
prompt_text="${1:-Continue?}"
|
||||
timeout_seconds="${2:-30}"
|
||||
|
||||
# Check if stdin is a TTY (interactive terminal)
|
||||
if [ ! -t 0 ]; then
|
||||
msg_error "Non-interactive session detected - cannot prompt for confirmation"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check if timeout command is available for optional timeout support
|
||||
if command -v timeout >/dev/null 2>&1; then
|
||||
printf '%s [y/N] (timeout in %ss) ' "$prompt_text" "$timeout_seconds"
|
||||
|
||||
# Create a temporary file to store the response
|
||||
_temp_response=$(mktemp) || return 1
|
||||
|
||||
# Use timeout with --foreground to allow reading from TTY
|
||||
# Write response to temp file instead of trying to capture in command substitution
|
||||
if timeout --foreground "$timeout_seconds" sh -c "read -r r && printf '%s' \"\$r\" > '$_temp_response'" 2>/dev/null; then
|
||||
response=$(cat "$_temp_response")
|
||||
rm -f "$_temp_response"
|
||||
else
|
||||
rm -f "$_temp_response"
|
||||
printf '\n'
|
||||
msg_warn "Confirmation timeout - defaulting to No"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
# No timeout available - plain read
|
||||
printf '%s [y/N] ' "$prompt_text"
|
||||
read -r response || return 1
|
||||
fi
|
||||
|
||||
case "$response" in
|
||||
[yY]|[yY][eE][sS])
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Message output functions for consistent, colored output
|
||||
# These functions provide a clean API for printing status messages
|
||||
|
||||
# msg_error "message" - Print error message in red with ✗ symbol to stderr
|
||||
msg_error() {
|
||||
printf '%s✗ %s%s\n' "$RED" "$1" "$NC" >&2
|
||||
}
|
||||
|
||||
# msg_success "message" - Print success message in green with ✓ symbol
|
||||
msg_success() {
|
||||
printf '%s✓ %s%s\n' "$GREEN" "$1" "$NC"
|
||||
}
|
||||
|
||||
# msg_done "message" - Print completion message in green with ✅ symbol
|
||||
msg_done() {
|
||||
printf '%s✅ %s%s\n' "$GREEN" "$1" "$NC"
|
||||
}
|
||||
|
||||
# msg_info "message" - Print info/status message in blue (no symbol)
|
||||
msg_info() {
|
||||
printf '%s%s%s\n' "$BLUE" "$1" "$NC"
|
||||
}
|
||||
|
||||
# msg_warn "message" - Print warning message in yellow (no symbol)
|
||||
msg_warn() {
|
||||
printf '%s%s%s\n' "$YELLOW" "$1" "$NC"
|
||||
}
|
||||
|
||||
# msg_item "message" - Print indented item with ✓ in green
|
||||
msg_item() {
|
||||
printf ' %s✓%s %s\n' "$GREEN" "$NC" "$1"
|
||||
}
|
||||
|
||||
# msg_notice "message" - Print indented notice with ℹ in blue
|
||||
msg_notice() {
|
||||
printf ' %sℹ%s %s\n' "$BLUE" "$NC" "$1"
|
||||
}
|
||||
|
||||
# msg_plain "color" "message" - Print plain colored message (no symbol)
|
||||
# Usage: msg_plain "$YELLOW" "=== BANNER ==="
|
||||
msg_plain() {
|
||||
color="$1"
|
||||
message="$2"
|
||||
printf '%s%s%s\n' "$color" "$message" "$NC"
|
||||
}
|
||||
|
||||
# Get the directory where the calling script is located
|
||||
get_script_dir() {
|
||||
cd "$(dirname -- "$1")" && pwd
|
||||
}
|
||||
|
||||
# Check if git is available
|
||||
has_git() {
|
||||
command -v git >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Require git to be available, exit with error if not
|
||||
require_git() {
|
||||
if ! has_git; then
|
||||
msg_error "git is not installed or not in PATH"
|
||||
printf 'Please install git to use this script.\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Create temp file with error checking
|
||||
safe_mktemp() {
|
||||
_temp_file=""
|
||||
if ! _temp_file=$(mktemp); then
|
||||
msg_error "Failed to create temp file"
|
||||
exit 1
|
||||
fi
|
||||
printf '%s' "$_temp_file"
|
||||
}
|
||||
71
_tools/update-action-refs.sh
Executable file
71
_tools/update-action-refs.sh
Executable file
@@ -0,0 +1,71 @@
|
||||
#!/bin/sh
|
||||
# Update all action references to a specific version tag or SHA
|
||||
set -eu
|
||||
|
||||
TARGET="${1:-}"
|
||||
MODE="${2:-tag}" # 'tag' or 'direct'
|
||||
|
||||
# Source shared utilities
|
||||
# shellcheck source=_tools/shared.sh
|
||||
SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
# shellcheck disable=SC1091
|
||||
. "$SCRIPT_DIR/shared.sh"
|
||||
|
||||
# Check git availability
|
||||
require_git
|
||||
|
||||
if [ -z "$TARGET" ]; then
|
||||
printf '%b' "${RED}Error: TARGET argument required${NC}\n"
|
||||
printf 'Usage: %s v2025 [mode]\n' "$0"
|
||||
printf ' mode: '\''tag'\'' (default) or '\''direct'\''\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get SHA based on mode
|
||||
if [ "$MODE" = "direct" ]; then
|
||||
# Direct SHA provided
|
||||
target_sha="$TARGET"
|
||||
printf '%b' "${BLUE}Using direct SHA: $target_sha${NC}\n"
|
||||
elif [ "$MODE" = "tag" ]; then
|
||||
# Resolve tag to SHA
|
||||
if ! git rev-parse "$TARGET" >/dev/null 2>&1; then
|
||||
printf '%b' "${RED}Error: Tag $TARGET not found${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
target_sha=$(git rev-list -n 1 "$TARGET")
|
||||
printf '%b' "${BLUE}Resolved $TARGET to SHA: $target_sha${NC}\n"
|
||||
else
|
||||
printf '%b' "${RED}Error: Invalid mode: $MODE${NC}\n"
|
||||
printf 'Mode must be '\''tag'\'' or '\''direct'\''\n'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate SHA format
|
||||
if ! echo "$target_sha" | grep -qE '^[a-f0-9]{40}$'; then
|
||||
printf '%b' "${RED}Error: Invalid SHA format: $target_sha${NC}\n"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%b' "${BLUE}Updating action references...${NC}\n"
|
||||
|
||||
# Update all action.yml files (excluding tests and .github workflows)
|
||||
# Create temp file to store results
|
||||
temp_file=$(safe_mktemp)
|
||||
trap 'rm -f "$temp_file"' EXIT
|
||||
|
||||
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" | while IFS= read -r file; do
|
||||
# Use .bak extension for cross-platform sed compatibility
|
||||
if sed -i.bak "s|ivuorinen/actions/\([a-z-]*\)@[a-f0-9]\{40\}|ivuorinen/actions/\1@$target_sha|g" "$file"; then
|
||||
rm -f "${file}.bak"
|
||||
printf '%b' " ${GREEN}✓${NC} Updated: $file\n"
|
||||
echo "$file" >> "$temp_file"
|
||||
fi
|
||||
done
|
||||
|
||||
printf '\n'
|
||||
if [ -s "$temp_file" ]; then
|
||||
updated_count=$(wc -l < "$temp_file" | tr -d ' ')
|
||||
printf '%b' "${GREEN}✅ Updated $updated_count action files${NC}\n"
|
||||
else
|
||||
printf '%b' "${BLUE}No files needed updating${NC}\n"
|
||||
fi
|
||||
44
action-versioning/README.md
Normal file
44
action-versioning/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# ivuorinen/actions/action-versioning
|
||||
|
||||
## Action Versioning
|
||||
|
||||
### Description
|
||||
|
||||
Automatically update SHA-pinned action references to match latest version tags
|
||||
|
||||
### Inputs
|
||||
|
||||
| name | description | required | default |
|
||||
|-----------------|------------------------------------------------|----------|---------|
|
||||
| `major-version` | <p>Major version tag to sync (e.g., v2025)</p> | `true` | `""` |
|
||||
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
|
||||
|
||||
### Outputs
|
||||
|
||||
| name | description |
|
||||
|---------------------|------------------------------------------------------------|
|
||||
| `updated` | <p>Whether action references were updated (true/false)</p> |
|
||||
| `commit-sha` | <p>SHA of the commit that was created (if any)</p> |
|
||||
| `needs-annual-bump` | <p>Whether annual version bump is needed (true/false)</p> |
|
||||
|
||||
### Runs
|
||||
|
||||
This action is a `composite` action.
|
||||
|
||||
### Usage
|
||||
|
||||
```yaml
|
||||
- uses: ivuorinen/actions/action-versioning@main
|
||||
with:
|
||||
major-version:
|
||||
# Major version tag to sync (e.g., v2025)
|
||||
#
|
||||
# Required: true
|
||||
# Default: ""
|
||||
|
||||
token:
|
||||
# GitHub token for authentication
|
||||
#
|
||||
# Required: false
|
||||
# Default: ""
|
||||
```
|
||||
165
action-versioning/action.yml
Normal file
165
action-versioning/action.yml
Normal file
@@ -0,0 +1,165 @@
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
|
||||
# permissions:
|
||||
# - contents: write # Required for creating commits
|
||||
---
|
||||
name: Action Versioning
|
||||
description: 'Automatically update SHA-pinned action references to match latest version tags'
|
||||
author: 'Ismo Vuorinen'
|
||||
|
||||
branding:
|
||||
icon: git-commit
|
||||
color: blue
|
||||
|
||||
inputs:
|
||||
major-version:
|
||||
description: 'Major version tag to sync (e.g., v2025)'
|
||||
required: true
|
||||
token:
|
||||
description: 'GitHub token for authentication'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
outputs:
|
||||
updated:
|
||||
description: 'Whether action references were updated (true/false)'
|
||||
value: ${{ steps.check-update.outputs.updated }}
|
||||
commit-sha:
|
||||
description: 'SHA of the commit that was created (if any)'
|
||||
value: ${{ steps.commit.outputs.sha }}
|
||||
needs-annual-bump:
|
||||
description: 'Whether annual version bump is needed (true/false)'
|
||||
value: ${{ steps.check-year.outputs.needs-bump }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
with:
|
||||
token: ${{ inputs.token || github.token }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check Current Year
|
||||
id: check-year
|
||||
shell: sh
|
||||
env:
|
||||
MAJOR_VERSION: ${{ inputs.major-version }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
current_year=$(date +%Y)
|
||||
version_year="${MAJOR_VERSION#v}"
|
||||
|
||||
if [ "$version_year" != "$current_year" ]; then
|
||||
echo "::warning::Annual version bump needed: $MAJOR_VERSION -> v$current_year"
|
||||
printf '%s\n' "needs-bump=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
printf '%s\n' "needs-bump=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Fetch Version Tag SHA
|
||||
id: fetch-sha
|
||||
shell: sh
|
||||
env:
|
||||
MAJOR_VERSION: ${{ inputs.major-version }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
# Fetch all tags
|
||||
git fetch --tags --force
|
||||
|
||||
# Get SHA for the major version tag
|
||||
if ! tag_sha=$(git rev-list -n 1 "$MAJOR_VERSION" 2>/dev/null); then
|
||||
echo "::error::Tag $MAJOR_VERSION not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
printf '%s\n' "tag-sha=$tag_sha" >> "$GITHUB_OUTPUT"
|
||||
echo "Tag $MAJOR_VERSION points to: $tag_sha"
|
||||
|
||||
- name: Check if Update Needed
|
||||
id: check-update
|
||||
shell: sh
|
||||
env:
|
||||
TAG_SHA: ${{ steps.fetch-sha.outputs.tag-sha }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
# Find all action references and check if any don't match the tag SHA
|
||||
needs_update=false
|
||||
|
||||
# Create temp file for action references
|
||||
temp_file=$(mktemp)
|
||||
trap 'rm -f "$temp_file"' EXIT
|
||||
|
||||
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec grep -h "uses: ivuorinen/actions/" {} \; > "$temp_file"
|
||||
|
||||
while IFS= read -r line; do
|
||||
current_sha=$(echo "$line" | grep -oE '@[a-f0-9]{40}' | sed 's/@//')
|
||||
|
||||
if [ "$current_sha" != "$TAG_SHA" ]; then
|
||||
echo "Found outdated reference: $current_sha (should be $TAG_SHA)"
|
||||
needs_update=true
|
||||
fi
|
||||
done < "$temp_file"
|
||||
|
||||
if [ "$needs_update" = "true" ]; then
|
||||
printf '%s\n' "updated=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Update needed - references are outdated"
|
||||
else
|
||||
printf '%s\n' "updated=false" >> "$GITHUB_OUTPUT"
|
||||
echo "No update needed - all references are current"
|
||||
fi
|
||||
|
||||
- name: Update Action References
|
||||
if: steps.check-update.outputs.updated == 'true'
|
||||
shell: sh
|
||||
env:
|
||||
TAG_SHA: ${{ steps.fetch-sha.outputs.tag-sha }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
echo "Updating all action references to SHA: $TAG_SHA"
|
||||
|
||||
# Update all action.yml files (excluding tests and .github)
|
||||
# Use .bak extension for cross-platform sed compatibility
|
||||
find . -maxdepth 2 -name "action.yml" -path "*/action.yml" ! -path "./_*" ! -path "./.github/*" -exec sed -i.bak \
|
||||
"s|ivuorinen/actions/\([a-z-]*\)@[a-f0-9]\{40\}|ivuorinen/actions/\1@$TAG_SHA|g" {} \;
|
||||
|
||||
# Remove backup files
|
||||
find . -maxdepth 2 -name "action.yml.bak" -path "*/action.yml.bak" ! -path "./_*" ! -path "./.github/*" -delete
|
||||
|
||||
echo "Action references updated successfully"
|
||||
|
||||
- name: Commit Changes
|
||||
if: steps.check-update.outputs.updated == 'true'
|
||||
id: commit
|
||||
shell: sh
|
||||
env:
|
||||
MAJOR_VERSION: ${{ inputs.major-version }}
|
||||
TAG_SHA: ${{ steps.fetch-sha.outputs.tag-sha }}
|
||||
run: |
|
||||
set -eu
|
||||
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git add -- */action.yml
|
||||
|
||||
if git diff --staged --quiet; then
|
||||
echo "No changes to commit"
|
||||
printf '%s\n' "sha=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
git commit -m "chore: update action references to $MAJOR_VERSION ($TAG_SHA)" \
|
||||
-m "" \
|
||||
-m "This commit updates all internal action references to point to the latest" \
|
||||
-m "$MAJOR_VERSION tag SHA." \
|
||||
-m "" \
|
||||
-m "🤖 Generated with [Claude Code](https://claude.com/claude-code)" \
|
||||
-m "" \
|
||||
-m "Co-Authored-By: Claude <noreply@anthropic.com>"
|
||||
|
||||
commit_sha=$(git rev-parse HEAD)
|
||||
printf '%s\n' "sha=$commit_sha" >> "$GITHUB_OUTPUT"
|
||||
echo "Created commit: $commit_sha"
|
||||
fi
|
||||
@@ -1,25 +1,25 @@
|
||||
---
|
||||
# Validation rules for github-release action
|
||||
# Validation rules for action-versioning action
|
||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||
# Schema version: 1.0
|
||||
# Coverage: 100% (2/2 inputs)
|
||||
#
|
||||
# This file defines validation rules for the github-release GitHub Action.
|
||||
# This file defines validation rules for the action-versioning GitHub Action.
|
||||
# Rules are automatically applied by validate-inputs action when this
|
||||
# action is used.
|
||||
#
|
||||
|
||||
schema_version: '1.0'
|
||||
action: github-release
|
||||
description: Creates a GitHub release with a version and changelog.
|
||||
action: action-versioning
|
||||
description: Automatically update SHA-pinned action references to match latest version tags
|
||||
generator_version: 1.0.0
|
||||
required_inputs:
|
||||
- version
|
||||
- major-version
|
||||
optional_inputs:
|
||||
- changelog
|
||||
- token
|
||||
conventions:
|
||||
changelog: security_patterns
|
||||
version: flexible_version
|
||||
major-version: semantic_version
|
||||
token: github_token
|
||||
overrides: {}
|
||||
statistics:
|
||||
total_inputs: 2
|
||||
@@ -31,7 +31,7 @@ auto_detected: true
|
||||
manual_review_required: false
|
||||
quality_indicators:
|
||||
has_required_inputs: true
|
||||
has_token_validation: false
|
||||
has_token_validation: true
|
||||
has_version_validation: true
|
||||
has_file_validation: false
|
||||
has_security_validation: true
|
||||
@@ -10,7 +10,7 @@ Lints and fixes Ansible playbooks, commits changes, and uploads SARIF report.
|
||||
|
||||
| name | description | required | default |
|
||||
|---------------|--------------------------------------------------------------------|----------|-----------------------------|
|
||||
| `token` | <p>GitHub token for authentication</p> | `false` | `${{ github.token }}` |
|
||||
| `token` | <p>GitHub token for authentication</p> | `false` | `""` |
|
||||
| `username` | <p>GitHub username for commits</p> | `false` | `github-actions` |
|
||||
| `email` | <p>GitHub email for commits</p> | `false` | `github-actions@github.com` |
|
||||
| `max-retries` | <p>Maximum number of retry attempts for pip install operations</p> | `false` | `3` |
|
||||
@@ -36,7 +36,7 @@ This action is a `composite` action.
|
||||
# GitHub token for authentication
|
||||
#
|
||||
# Required: false
|
||||
# Default: ${{ github.token }}
|
||||
# Default: ""
|
||||
|
||||
username:
|
||||
# GitHub username for commits
|
||||
|
||||
@@ -15,7 +15,7 @@ inputs:
|
||||
token:
|
||||
description: 'GitHub token for authentication'
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
default: ''
|
||||
username:
|
||||
description: 'GitHub username for commits'
|
||||
required: false
|
||||
@@ -45,55 +45,19 @@ runs:
|
||||
steps:
|
||||
- name: Validate Inputs
|
||||
id: validate
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
EMAIL: ${{ inputs.email }}
|
||||
USERNAME: ${{ inputs.username }}
|
||||
MAX_RETRIES: ${{ inputs.max-retries }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Validate GitHub token format (basic validation)
|
||||
if [[ -n "$GITHUB_TOKEN" ]]; then
|
||||
# Skip validation for GitHub expressions (they'll be resolved at runtime)
|
||||
if ! [[ "$GITHUB_TOKEN" =~ ^gh[efpousr]_[a-zA-Z0-9]{36}$ ]] && ! [[ "$GITHUB_TOKEN" =~ ^\$\{\{ ]]; then
|
||||
echo "::warning::GitHub token format may be invalid. Expected format: gh*_36characters"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate email format (basic check)
|
||||
if [[ "$EMAIL" != *"@"* ]] || [[ "$EMAIL" != *"."* ]]; then
|
||||
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate username format (prevent command injection)
|
||||
if [[ "$USERNAME" == *";"* ]] || [[ "$USERNAME" == *"&&"* ]] || [[ "$USERNAME" == *"|"* ]]; then
|
||||
echo "::error::Invalid username: '$USERNAME'. Command injection patterns not allowed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate username length
|
||||
username="$USERNAME"
|
||||
if [ ${#username} -gt 39 ]; then
|
||||
echo "::error::Username too long: ${#username} characters. GitHub usernames are max 39 characters"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate max retries (positive integer with reasonable upper limit)
|
||||
if ! [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
|
||||
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Input validation completed successfully"
|
||||
uses: ivuorinen/actions/validate-inputs@0fa9a68f07a1260b321f814202658a6089a43d42
|
||||
with:
|
||||
action-type: 'ansible-lint-fix'
|
||||
token: ${{ inputs.token }}
|
||||
email: ${{ inputs.email }}
|
||||
username: ${{ inputs.username }}
|
||||
max-retries: ${{ inputs.max-retries }}
|
||||
|
||||
- name: Check for Ansible Files
|
||||
id: check-files
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Check for both .yml and .yaml files
|
||||
if find . \( -name "*.yml" -o -name "*.yaml" \) -type f | grep -q .; then
|
||||
@@ -104,10 +68,15 @@ runs:
|
||||
echo "No Ansible files found. Skipping lint and fix."
|
||||
fi
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
with:
|
||||
token: ${{ inputs.token || github.token }}
|
||||
|
||||
- name: Cache Python Dependencies
|
||||
if: steps.check-files.outputs.files_found == 'true'
|
||||
id: cache-pip
|
||||
uses: ./common-cache
|
||||
uses: ivuorinen/actions/common-cache@0fa9a68f07a1260b321f814202658a6089a43d42
|
||||
with:
|
||||
type: 'pip'
|
||||
paths: '~/.cache/pip'
|
||||
@@ -115,19 +84,20 @@ runs:
|
||||
key-prefix: 'ansible-lint-fix'
|
||||
|
||||
- name: Install ansible-lint
|
||||
id: install-ansible-lint
|
||||
if: steps.check-files.outputs.files_found == 'true'
|
||||
uses: ./common-retry
|
||||
uses: step-security/retry@e1d59ce1f574b32f0915e3a8df055cfe9f99be5d # v3
|
||||
with:
|
||||
timeout_minutes: 5
|
||||
max_attempts: ${{ inputs.max-retries }}
|
||||
command: 'pip install ansible-lint==6.22.1'
|
||||
max-retries: ${{ inputs.max-retries }}
|
||||
description: 'Installing Python dependencies (ansible-lint)'
|
||||
|
||||
- name: Run ansible-lint
|
||||
if: steps.check-files.outputs.files_found == 'true'
|
||||
id: lint
|
||||
shell: bash
|
||||
shell: sh
|
||||
run: |
|
||||
set -euo pipefail
|
||||
set -eu
|
||||
|
||||
# Run ansible-lint and capture exit code
|
||||
if ansible-lint --write --parseable-severity --format sarif > ansible-lint.sarif; then
|
||||
@@ -153,30 +123,16 @@ runs:
|
||||
# Exit with the original ansible-lint exit code
|
||||
exit "$lint_exit_code"
|
||||
|
||||
- name: Set Git Config for Fixes
|
||||
if: steps.check-files.outputs.files_found == 'true'
|
||||
uses: ./set-git-config
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
email: ${{ inputs.email }}
|
||||
|
||||
- name: Commit Fixes
|
||||
if: steps.check-files.outputs.files_found == 'true'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if git diff --quiet; then
|
||||
echo "No changes to commit."
|
||||
else
|
||||
git add .
|
||||
git commit -m "fix: applied ansible lint fixes"
|
||||
git push
|
||||
fi
|
||||
uses: stefanzweifel/git-auto-commit-action@be7095c202abcf573b09f20541e0ee2f6a3a9d9b # v5.0.1
|
||||
with:
|
||||
commit_message: 'style: apply ansible lint fixes'
|
||||
commit_user_name: ${{ inputs.username }}
|
||||
commit_user_email: ${{ inputs.email }}
|
||||
|
||||
- name: Upload SARIF Report
|
||||
if: steps.check-files.outputs.files_found == 'true'
|
||||
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/upload-sarif@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
sarif_file: ansible-lint.sarif
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# ivuorinen/actions/biome-check
|
||||
|
||||
## Biome Check
|
||||
|
||||
### Description
|
||||
|
||||
Run Biome check on the repository
|
||||
|
||||
### Inputs
|
||||
|
||||
| name | description | required | default |
|
||||
|---------------|--------------------------------------------------------------------|----------|-----------------------------|
|
||||
| `token` | <p>GitHub token for authentication</p> | `false` | `${{ github.token }}` |
|
||||
| `username` | <p>GitHub username for commits</p> | `false` | `github-actions` |
|
||||
| `email` | <p>GitHub email for commits</p> | `false` | `github-actions@github.com` |
|
||||
| `max-retries` | <p>Maximum number of retry attempts for npm install operations</p> | `false` | `3` |
|
||||
|
||||
### Outputs
|
||||
|
||||
| name | description |
|
||||
|------------------|---------------------------------------|
|
||||
| `check_status` | <p>Check status (success/failure)</p> |
|
||||
| `errors_count` | <p>Number of errors found</p> |
|
||||
| `warnings_count` | <p>Number of warnings found</p> |
|
||||
|
||||
### Runs
|
||||
|
||||
This action is a `composite` action.
|
||||
|
||||
### Usage
|
||||
|
||||
```yaml
|
||||
- uses: ivuorinen/actions/biome-check@main
|
||||
with:
|
||||
token:
|
||||
# GitHub token for authentication
|
||||
#
|
||||
# Required: false
|
||||
# Default: ${{ github.token }}
|
||||
|
||||
username:
|
||||
# GitHub username for commits
|
||||
#
|
||||
# Required: false
|
||||
# Default: github-actions
|
||||
|
||||
email:
|
||||
# GitHub email for commits
|
||||
#
|
||||
# Required: false
|
||||
# Default: github-actions@github.com
|
||||
|
||||
max-retries:
|
||||
# Maximum number of retry attempts for npm install operations
|
||||
#
|
||||
# Required: false
|
||||
# Default: 3
|
||||
```
|
||||
@@ -1,238 +0,0 @@
|
||||
# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
|
||||
# permissions:
|
||||
# - contents: read # Required for checking out repository
|
||||
# - security-events: write # Required for uploading SARIF results
|
||||
---
|
||||
name: Biome Check
|
||||
description: Run Biome check on the repository
|
||||
author: Ismo Vuorinen
|
||||
|
||||
branding:
|
||||
icon: check-circle
|
||||
color: green
|
||||
|
||||
inputs:
|
||||
token:
|
||||
description: 'GitHub token for authentication'
|
||||
required: false
|
||||
default: ${{ github.token }}
|
||||
username:
|
||||
description: 'GitHub username for commits'
|
||||
required: false
|
||||
default: 'github-actions'
|
||||
email:
|
||||
description: 'GitHub email for commits'
|
||||
required: false
|
||||
default: 'github-actions@github.com'
|
||||
max-retries:
|
||||
description: 'Maximum number of retry attempts for npm install operations'
|
||||
required: false
|
||||
default: '3'
|
||||
|
||||
outputs:
|
||||
check_status:
|
||||
description: 'Check status (success/failure)'
|
||||
value: ${{ steps.check.outputs.status }}
|
||||
errors_count:
|
||||
description: 'Number of errors found'
|
||||
value: ${{ steps.check.outputs.errors }}
|
||||
warnings_count:
|
||||
description: 'Number of warnings found'
|
||||
value: ${{ steps.check.outputs.warnings }}
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Validate Inputs (Centralized)
|
||||
uses: ./validate-inputs
|
||||
with:
|
||||
action: biome-check
|
||||
|
||||
- name: Validate Inputs (Additional)
|
||||
id: validate
|
||||
shell: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
EMAIL: ${{ inputs.email }}
|
||||
USERNAME: ${{ inputs.username }}
|
||||
MAX_RETRIES: ${{ inputs.max-retries }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Validate GitHub token presence (no format validation to avoid false warnings)
|
||||
if [[ -n "$GITHUB_TOKEN" ]] && ! [[ "$GITHUB_TOKEN" =~ ^\$\{\{ ]]; then
|
||||
# Token is present and not a GitHub expression, assume it's valid
|
||||
echo "Using provided GitHub token"
|
||||
fi
|
||||
|
||||
# Validate email format (basic check)
|
||||
if [[ "$EMAIL" != *"@"* ]] || [[ "$EMAIL" != *"."* ]]; then
|
||||
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate username format (GitHub canonical rules)
|
||||
username="$USERNAME"
|
||||
|
||||
# Check length (GitHub limit)
|
||||
if [ ${#username} -gt 39 ]; then
|
||||
echo "::error::Username too long: ${#username} characters. GitHub usernames are max 39 characters"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check allowed characters (letters, digits, hyphens only)
|
||||
if ! [[ "$username" =~ ^[a-zA-Z0-9-]+$ ]]; then
|
||||
echo "::error::Invalid username characters in '$username'. Only letters, digits, and hyphens allowed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check doesn't start or end with hyphen
|
||||
if [[ "$username" == -* ]] || [[ "$username" == *- ]]; then
|
||||
echo "::error::Invalid username '$username'. Cannot start or end with hyphen"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check no consecutive hyphens
|
||||
if [[ "$username" == *--* ]]; then
|
||||
echo "::error::Invalid username '$username'. Consecutive hyphens not allowed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate max retries (positive integer with reasonable upper limit)
|
||||
if ! [[ "$MAX_RETRIES" =~ ^[0-9]+$ ]] || [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
|
||||
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Input validation completed successfully"
|
||||
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
|
||||
- name: Set Git Config
|
||||
uses: ./set-git-config
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
email: ${{ inputs.email }}
|
||||
|
||||
- name: Node Setup
|
||||
id: node-setup
|
||||
uses: ./node-setup
|
||||
|
||||
- name: Cache Node Dependencies
|
||||
id: cache
|
||||
uses: ./common-cache
|
||||
with:
|
||||
type: 'npm'
|
||||
paths: 'node_modules'
|
||||
key-files: 'package-lock.json,yarn.lock,pnpm-lock.yaml,bun.lockb'
|
||||
key-prefix: 'biome-check-${{ steps.node-setup.outputs.package-manager }}'
|
||||
|
||||
- name: Install Biome
|
||||
shell: bash
|
||||
env:
|
||||
PACKAGE_MANAGER: ${{ steps.node-setup.outputs.package-manager }}
|
||||
MAX_RETRIES: ${{ inputs.max-retries }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Check if biome is already installed
|
||||
if command -v biome >/dev/null 2>&1; then
|
||||
echo "✅ Biome already installed: $(biome --version)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "Installing Biome using $PACKAGE_MANAGER..."
|
||||
|
||||
for attempt in $(seq 1 "$MAX_RETRIES"); do
|
||||
echo "Attempt $attempt of $MAX_RETRIES"
|
||||
|
||||
case "$PACKAGE_MANAGER" in
|
||||
"pnpm")
|
||||
if pnpm add -g @biomejs/biome; then
|
||||
echo "✅ Biome installed successfully with pnpm"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
"yarn")
|
||||
if yarn global add @biomejs/biome; then
|
||||
echo "✅ Biome installed successfully with yarn"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
"bun")
|
||||
if bun add -g @biomejs/biome; then
|
||||
echo "✅ Biome installed successfully with bun"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
"npm"|*)
|
||||
if npm install -g @biomejs/biome; then
|
||||
echo "✅ Biome installed successfully with npm"
|
||||
exit 0
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ $attempt -lt "$MAX_RETRIES" ]; then
|
||||
echo "❌ Installation failed, retrying in 5 seconds..."
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
|
||||
echo "::error::Failed to install Biome after $MAX_RETRIES attempts"
|
||||
exit 1
|
||||
|
||||
- name: Run Biome Check
|
||||
id: check
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
echo "Running Biome check..."
|
||||
|
||||
# Run Biome check with SARIF reporter
|
||||
biome_exit_code=0
|
||||
biome check . --reporter=sarif > biome-report.sarif || biome_exit_code=$?
|
||||
|
||||
# Handle failures gracefully
|
||||
if [ $biome_exit_code -ne 0 ] && [ ! -s biome-report.sarif ]; then
|
||||
echo "::warning::SARIF report generation failed with exit code $biome_exit_code"
|
||||
# Create empty SARIF file to avoid upload errors
|
||||
echo '{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[]}' > biome-report.sarif
|
||||
fi
|
||||
|
||||
# Parse SARIF output for error counts
|
||||
if [ -f biome-report.sarif ]; then
|
||||
errors=$(jq '[.runs[]?.results[]? | select(.level == "error" or .level == "warning")] | length' biome-report.sarif 2>/dev/null || echo "0")
|
||||
warnings="0" # Biome doesn't separate warnings in SARIF output
|
||||
else
|
||||
errors="0"
|
||||
warnings="0"
|
||||
fi
|
||||
|
||||
if [ $biome_exit_code -eq 0 ]; then
|
||||
echo "status=success" >> "$GITHUB_OUTPUT"
|
||||
echo "errors=0" >> "$GITHUB_OUTPUT"
|
||||
echo "warnings=0" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "status=failure" >> "$GITHUB_OUTPUT"
|
||||
echo "errors=$errors" >> "$GITHUB_OUTPUT"
|
||||
echo "warnings=$warnings" >> "$GITHUB_OUTPUT"
|
||||
|
||||
echo "::error::Biome check found $errors issues"
|
||||
fi
|
||||
|
||||
echo "✅ Biome check completed"
|
||||
|
||||
# Exit with biome's exit code to fail the job on errors
|
||||
exit $biome_exit_code
|
||||
|
||||
- name: Upload Biome Results
|
||||
if: always()
|
||||
uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
with:
|
||||
sarif_file: biome-report.sarif
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
# Validation rules for biome-check action
|
||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||
# Schema version: 1.0
|
||||
# Coverage: 100% (4/4 inputs)
|
||||
#
|
||||
# This file defines validation rules for the biome-check GitHub Action.
|
||||
# Rules are automatically applied by validate-inputs action when this
|
||||
# action is used.
|
||||
#
|
||||
|
||||
schema_version: '1.0'
|
||||
action: biome-check
|
||||
description: Run Biome check on the repository
|
||||
generator_version: 1.0.0
|
||||
required_inputs: []
|
||||
optional_inputs:
|
||||
- email
|
||||
- max-retries
|
||||
- token
|
||||
- username
|
||||
conventions:
|
||||
email: email
|
||||
max-retries: numeric_range_1_10
|
||||
token: github_token
|
||||
username: username
|
||||
overrides: {}
|
||||
statistics:
|
||||
total_inputs: 4
|
||||
validated_inputs: 4
|
||||
skipped_inputs: 0
|
||||
coverage_percentage: 100
|
||||
validation_coverage: 100
|
||||
auto_detected: true
|
||||
manual_review_required: false
|
||||
quality_indicators:
|
||||
has_required_inputs: false
|
||||
has_token_validation: true
|
||||
has_version_validation: false
|
||||
has_file_validation: false
|
||||
has_security_validation: true
|
||||
@@ -1,57 +0,0 @@
|
||||
# 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
|
||||
```
|
||||
@@ -1,200 +0,0 @@
|
||||
# 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: bash
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ inputs.token }}
|
||||
EMAIL: ${{ inputs.email }}
|
||||
USERNAME: ${{ inputs.username }}
|
||||
MAX_RETRIES: ${{ inputs.max-retries }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
# Validate GitHub token format (basic validation)
|
||||
if [[ -n "$GITHUB_TOKEN" ]]; then
|
||||
# Skip validation for GitHub expressions (they'll be resolved at runtime)
|
||||
if ! [[ "$GITHUB_TOKEN" =~ ^gh[efpousr]_[a-zA-Z0-9]{36}$ ]] && ! [[ "$GITHUB_TOKEN" =~ ^\$\{\{ ]]; then
|
||||
echo "::warning::GitHub token format may be invalid. Expected format: gh*_36characters"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Validate email format (basic check)
|
||||
if [[ "$EMAIL" != *"@"* ]] || [[ "$EMAIL" != *"."* ]]; then
|
||||
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Validate username format (prevent command injection)
|
||||
if [[ "$USERNAME" =~ [;&|] ]]; 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: Checkout Repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
|
||||
- name: Set Git Config
|
||||
uses: ./set-git-config
|
||||
with:
|
||||
token: ${{ inputs.token }}
|
||||
username: ${{ inputs.username }}
|
||||
email: ${{ inputs.email }}
|
||||
|
||||
- name: Node Setup
|
||||
id: node-setup
|
||||
uses: ./node-setup
|
||||
|
||||
- name: Cache Node Dependencies
|
||||
id: cache
|
||||
uses: ./common-cache
|
||||
with:
|
||||
type: 'npm'
|
||||
paths: 'node_modules'
|
||||
key-files: 'package-lock.json,yarn.lock,pnpm-lock.yaml,bun.lockb'
|
||||
key-prefix: 'biome-fix-${{ 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 Fix
|
||||
id: fix
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
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'
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
# 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
|
||||
73
biome-lint/README.md
Normal file
73
biome-lint/README.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 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
|
||||
```
|
||||
297
biome-lint/action.yml
Normal file
297
biome-lint/action.yml
Normal file
@@ -0,0 +1,297 @@
|
||||
# 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: bash
|
||||
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 -euo pipefail
|
||||
|
||||
# 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" ]] && ! [[ "$GITHUB_TOKEN" =~ ^\$\{\{ ]]; then
|
||||
echo "Using provided GitHub token"
|
||||
fi
|
||||
|
||||
# Validate email format (basic check) - required for fix mode
|
||||
if [ "$MODE" = "fix" ]; then
|
||||
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
|
||||
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
|
||||
|
||||
# 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: Node Setup
|
||||
id: node-setup
|
||||
uses: ivuorinen/actions/node-setup@0fa9a68f07a1260b321f814202658a6089a43d42
|
||||
|
||||
- name: Cache Node Dependencies
|
||||
id: cache
|
||||
uses: ivuorinen/actions/common-cache@0fa9a68f07a1260b321f814202658a6089a43d42
|
||||
with:
|
||||
type: 'npm'
|
||||
paths: 'node_modules'
|
||||
key-files: 'package-lock.json,yarn.lock,pnpm-lock.yaml,bun.lockb'
|
||||
key-prefix: 'biome-lint-${{ inputs.mode }}-${{ 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
|
||||
if: inputs.mode == 'check'
|
||||
id: check
|
||||
shell: bash
|
||||
env:
|
||||
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
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@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
sarif_file: biome-report.sarif
|
||||
|
||||
- name: Run Biome Fix
|
||||
if: inputs.mode == 'fix'
|
||||
id: fix
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
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'
|
||||
@@ -1,33 +1,37 @@
|
||||
---
|
||||
# Validation rules for eslint-fix action
|
||||
# Validation rules for biome-lint action
|
||||
# Generated by update-validators.py v1.0.0 - DO NOT EDIT MANUALLY
|
||||
# Schema version: 1.0
|
||||
# Coverage: 100% (4/4 inputs)
|
||||
# Coverage: 100% (6/6 inputs)
|
||||
#
|
||||
# This file defines validation rules for the eslint-fix GitHub Action.
|
||||
# This file defines validation rules for the biome-lint GitHub Action.
|
||||
# Rules are automatically applied by validate-inputs action when this
|
||||
# action is used.
|
||||
#
|
||||
|
||||
schema_version: '1.0'
|
||||
action: eslint-fix
|
||||
description: Fixes ESLint violations in a project.
|
||||
action: biome-lint
|
||||
description: Run Biome linter in check or fix mode
|
||||
generator_version: 1.0.0
|
||||
required_inputs: []
|
||||
optional_inputs:
|
||||
- email
|
||||
- fail-on-error
|
||||
- max-retries
|
||||
- mode
|
||||
- token
|
||||
- username
|
||||
conventions:
|
||||
email: email
|
||||
fail-on-error: boolean
|
||||
max-retries: numeric_range_1_10
|
||||
mode: mode_enum
|
||||
token: github_token
|
||||
username: username
|
||||
overrides: {}
|
||||
statistics:
|
||||
total_inputs: 4
|
||||
validated_inputs: 4
|
||||
total_inputs: 6
|
||||
validated_inputs: 6
|
||||
skipped_inputs: 0
|
||||
coverage_percentage: 100
|
||||
validation_coverage: 100
|
||||
@@ -26,7 +26,6 @@ Run CodeQL security analysis for a single language with configurable query suite
|
||||
| `threads` | <p>Number of threads that can be used by CodeQL</p> | `false` | `""` |
|
||||
| `output` | <p>Path to save SARIF results</p> | `false` | `../results` |
|
||||
| `skip-queries` | <p>Build database but skip running queries</p> | `false` | `false` |
|
||||
| `add-snippets` | <p>Add code snippets to SARIF output</p> | `false` | `false` |
|
||||
|
||||
### Outputs
|
||||
|
||||
@@ -140,10 +139,4 @@ This action is a `composite` action.
|
||||
#
|
||||
# Required: false
|
||||
# Default: false
|
||||
|
||||
add-snippets:
|
||||
# Add code snippets to SARIF output
|
||||
#
|
||||
# Required: false
|
||||
# Default: false
|
||||
```
|
||||
|
||||
@@ -90,11 +90,6 @@ inputs:
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
add-snippets:
|
||||
description: 'Add code snippets to SARIF output'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
language-analyzed:
|
||||
description: 'Language that was analyzed'
|
||||
@@ -112,7 +107,7 @@ runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Validate inputs
|
||||
uses: ./validate-inputs
|
||||
uses: ivuorinen/actions/validate-inputs@0fa9a68f07a1260b321f814202658a6089a43d42
|
||||
with:
|
||||
action-type: codeql-analysis
|
||||
language: ${{ inputs.language }}
|
||||
@@ -131,7 +126,6 @@ runs:
|
||||
threads: ${{ inputs.threads }}
|
||||
output: ${{ inputs.output }}
|
||||
skip-queries: ${{ inputs.skip-queries }}
|
||||
add-snippets: ${{ inputs.add-snippets }}
|
||||
|
||||
- name: Validate checkout safety
|
||||
shell: bash
|
||||
@@ -146,7 +140,7 @@ runs:
|
||||
fi
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
|
||||
with:
|
||||
ref: ${{ inputs.checkout-ref || github.sha }}
|
||||
token: ${{ inputs.token }}
|
||||
@@ -189,7 +183,7 @@ runs:
|
||||
echo "Using build mode: $build_mode"
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/init@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
languages: ${{ inputs.language }}
|
||||
queries: ${{ inputs.queries }}
|
||||
@@ -202,19 +196,18 @@ runs:
|
||||
threads: ${{ inputs.threads }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/autobuild@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
if: ${{ steps.set-build-mode.outputs.build-mode == 'autobuild' }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
id: analysis
|
||||
uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8
|
||||
uses: github/codeql-action/analyze@014f16e7ab1402f30e7c3329d33797e7948572db # v4.31.3
|
||||
with:
|
||||
category: ${{ steps.set-category.outputs.category }}
|
||||
upload: ${{ inputs.upload-results }}
|
||||
output: ${{ inputs.output }}
|
||||
ram: ${{ inputs.ram }}
|
||||
threads: ${{ inputs.threads }}
|
||||
add-snippets: ${{ inputs.add-snippets }}
|
||||
skip-queries: ${{ inputs.skip-queries }}
|
||||
|
||||
- name: Summary
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user