Files
actions/biome-lint/action.yml
Ismo Vuorinen 900dd96797 feat: add action-validator and clean up CI workflows (#513)
* chore(pre-commit): update hooks and add action-validator

Update uv-pre-commit 0.10.9→0.10.11 and checkov 3.2.508→3.2.510.
Normalize single quotes to double quotes in hook args.
Add action-validator v0.8.0 hook for GitHub Actions validation.

* fix(ci): clean up workflow path filters

Remove non-existent action.yaml paths from action-security workflow.
Fix glob patterns (**.md → **/*.md) in pr-lint workflow.
Remove unused trigger paths (yarn.lock, pnpm-lock.yaml,
requirements.txt, .github/labels.yml, docs/**) from security-suite
and sync-labels workflows.

* feat(make): add lint-actions target for action-validator

Add lint-actions target that runs action-validator via pre-commit.
Include it in the lint dependency list and .PHONY declaration.

* docs: add context-mode routing rules to CLAUDE.md

Add mandatory routing rules section for context-mode MCP plugin,
documenting blocked commands, redirected tools, tool selection
hierarchy, and output constraints.

* fix(lint): resolve action-validator failure on language-version-detect

- Remove unsupported `deprecated: true` from language-version-detect/action.yml
  (deprecation already communicated via description field)
- Scope action-validator pre-commit hook to workflow and action.yml files only
- Make missing pre-commit a hard error in lint-actions target

* fix(deps): update action pins and fix trivy-action version comment

Update SHA-pinned action references to latest versions:
- github/codeql-action v4.32.6 → v4.33.0
- nick-fields/retry v3.0.2 → v4.0.0
- actions/cache v5.0.3 → v5.0.4
- oven-sh/setup-bun v2.1.3 → v2.2.0
- softprops/action-gh-release v2.5.0 → v2.6.1
- github/issue-metrics v4.1.0 → v4.1.1
- shivammathur/setup-php 2.36.0 → 2.37.0
- astral-sh/setup-uv v7.5.0 → v7.6.0
- terraform-linters/setup-tflint v6.2.1 → v6.2.2
- aquasecurity/trivy-action: pin from master to v0.35.0

Fix pinact warning in docker-build by adding missing v prefix
to trivy-action version comment (0.35.0 → v0.35.0).
2026-03-20 13:01:24 +02:00

374 lines
12 KiB
YAML

# yaml-language-server: $schema=https://json.schemastore.org/github-action.json
# permissions:
# - contents: write # Required for fix mode to push changes
# - security-events: write # Required for check mode to upload SARIF
---
name: Biome Lint
description: Run Biome linter in check or fix mode
author: Ismo Vuorinen
branding:
icon: check-circle
color: green
inputs:
mode:
description: 'Mode to run (check or fix)'
required: false
default: 'check'
token:
description: 'GitHub token for authentication'
required: false
default: ''
username:
description: 'GitHub username for commits (fix mode only)'
required: false
default: 'github-actions'
email:
description: 'GitHub email for commits (fix mode only)'
required: false
default: 'github-actions@github.com'
max-retries:
description: 'Maximum number of retry attempts for npm install operations'
required: false
default: '3'
fail-on-error:
description: 'Whether to fail the action if linting errors are found (check mode only)'
required: false
default: 'true'
outputs:
status:
description: 'Overall status (success/failure)'
value: ${{ steps.check.outputs.status || steps.fix.outputs.status }}
errors_count:
description: 'Number of errors found (check mode only)'
value: ${{ steps.check.outputs.errors }}
warnings_count:
description: 'Number of warnings found (check mode only)'
value: ${{ steps.check.outputs.warnings }}
files_changed:
description: 'Number of files changed (fix mode only)'
value: ${{ steps.fix.outputs.files_changed }}
runs:
using: composite
steps:
- name: Validate Inputs
id: validate
shell: sh
env:
MODE: ${{ inputs.mode }}
GITHUB_TOKEN: ${{ inputs.token }}
EMAIL: ${{ inputs.email }}
USERNAME: ${{ inputs.username }}
MAX_RETRIES: ${{ inputs.max-retries }}
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
run: |
set -eu
# Validate mode
case "$MODE" in
"check"|"fix")
echo "Mode: $MODE"
;;
*)
echo "::error::Invalid mode: '$MODE'. Must be 'check' or 'fix'"
exit 1
;;
esac
# Validate GitHub token presence if provided
if [ -n "$GITHUB_TOKEN" ]; then
case "$GITHUB_TOKEN" in
\$\{\{*)
# Token is a GitHub Actions expression, skip validation
;;
*)
echo "Using provided GitHub token"
;;
esac
fi
# Validate email format (basic check) - required for fix mode
if [ "$MODE" = "fix" ]; then
case "$EMAIL" in
*@*.*) ;;
*)
echo "::error::Invalid email format: '$EMAIL'. Expected valid email address"
exit 1
;;
esac
# Validate username format (GitHub canonical rules)
username="$USERNAME"
# Check length (GitHub limit)
if [ ${#username} -gt 39 ]; then
echo "::error::Username too long: ${#username} characters. GitHub usernames are max 39 characters"
exit 1
fi
# Check allowed characters (letters, digits, hyphens only)
case "$username" in
*[!a-zA-Z0-9-]*)
echo "::error::Invalid username characters in '$username'. Only letters, digits, and hyphens allowed"
exit 1
;;
esac
# Check doesn't start or end with hyphen
case "$username" in
-*|*-)
echo "::error::Invalid username '$username'. Cannot start or end with hyphen"
exit 1
;;
esac
# Check no consecutive hyphens
case "$username" in
*--*)
echo "::error::Invalid username '$username'. Consecutive hyphens not allowed"
exit 1
;;
esac
fi
# Validate max retries (positive integer with reasonable upper limit)
case "$MAX_RETRIES" in
''|*[!0-9]*)
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
exit 1
;;
esac
if [ "$MAX_RETRIES" -le 0 ] || [ "$MAX_RETRIES" -gt 10 ]; then
echo "::error::Invalid max-retries: '$MAX_RETRIES'. Must be a positive integer between 1 and 10"
exit 1
fi
# Validate fail-on-error (boolean)
if [ "$FAIL_ON_ERROR" != "true" ] && [ "$FAIL_ON_ERROR" != "false" ]; then
echo "::error::Invalid fail-on-error value: '$FAIL_ON_ERROR'. Must be 'true' or 'false'"
exit 1
fi
echo "Input validation completed successfully"
- name: Checkout Repository
uses: actions/checkout@71cf2267d89c5cb81562390fa70a37fa40b1305e # v6-beta
with:
token: ${{ inputs.token || github.token }}
- name: Detect Package Manager
id: detect-pm
shell: sh
run: |
set -eu
# Detect package manager from lockfiles
if [ -f bun.lockb ]; then
package_manager="bun"
elif [ -f pnpm-lock.yaml ]; then
package_manager="pnpm"
elif [ -f yarn.lock ]; then
package_manager="yarn"
else
package_manager="npm"
fi
printf 'package-manager=%s\n' "$package_manager" >> "$GITHUB_OUTPUT"
echo "Detected package manager: $package_manager"
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '24'
- name: Enable Corepack
shell: sh
run: |
set -eu
corepack enable
- name: Install Package Manager
shell: sh
env:
PACKAGE_MANAGER: ${{ steps.detect-pm.outputs.package-manager }}
run: |
set -eu
case "$PACKAGE_MANAGER" in
pnpm)
corepack prepare pnpm@latest --activate
;;
yarn)
corepack prepare yarn@stable --activate
;;
bun|npm)
# Bun installed separately, npm built-in
;;
esac
- name: Setup Bun
if: steps.detect-pm.outputs.package-manager == 'bun'
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest
- name: Cache Node Dependencies
id: cache
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: node_modules
key: ${{ runner.os }}-biome-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
restore-keys: |
${{ runner.os }}-biome-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-
${{ runner.os }}-biome-lint-${{ inputs.mode }}-
- name: Install Biome
shell: sh
env:
PACKAGE_MANAGER: ${{ steps.detect-pm.outputs.package-manager }}
MAX_RETRIES: ${{ inputs.max-retries }}
run: |
set -eu
# Check if biome is already installed
if command -v biome >/dev/null 2>&1; then
echo "✅ Biome already installed: $(biome --version)"
exit 0
fi
echo "Installing Biome using $PACKAGE_MANAGER..."
for attempt in $(seq 1 "$MAX_RETRIES"); do
echo "Attempt $attempt of $MAX_RETRIES"
case "$PACKAGE_MANAGER" in
"pnpm")
if pnpm add -g @biomejs/biome; then
echo "✅ Biome installed successfully with pnpm"
exit 0
fi
;;
"yarn")
if yarn global add @biomejs/biome; then
echo "✅ Biome installed successfully with yarn"
exit 0
fi
;;
"bun")
if bun add -g @biomejs/biome; then
echo "✅ Biome installed successfully with bun"
exit 0
fi
;;
"npm"|*)
if npm install -g @biomejs/biome; then
echo "✅ Biome installed successfully with npm"
exit 0
fi
;;
esac
if [ $attempt -lt "$MAX_RETRIES" ]; then
echo "❌ Installation failed, retrying in 5 seconds..."
sleep 5
fi
done
echo "::error::Failed to install Biome after $MAX_RETRIES attempts"
exit 1
- name: Run Biome Check
if: inputs.mode == 'check'
id: check
shell: sh
env:
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
run: |
set -eu
echo "Running Biome check mode..."
# Run Biome check with SARIF reporter
biome_exit_code=0
biome check . --reporter=sarif > biome-report.sarif || biome_exit_code=$?
# Handle failures gracefully
if [ $biome_exit_code -ne 0 ] && [ ! -s biome-report.sarif ]; then
echo "::warning::SARIF report generation failed with exit code $biome_exit_code"
# Create empty SARIF file to avoid upload errors
echo '{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[]}' > biome-report.sarif
fi
# Parse SARIF output for error counts
if [ -f biome-report.sarif ]; then
errors=$(jq '[.runs[]?.results[]? | select(.level == "error" or .level == "warning")] | length' biome-report.sarif 2>/dev/null || echo "0")
warnings="0" # Biome doesn't separate warnings in SARIF output
else
errors="0"
warnings="0"
fi
if [ $biome_exit_code -eq 0 ]; then
echo "status=success" >> "$GITHUB_OUTPUT"
echo "errors=0" >> "$GITHUB_OUTPUT"
echo "warnings=0" >> "$GITHUB_OUTPUT"
echo "✅ Biome check completed successfully"
else
echo "status=failure" >> "$GITHUB_OUTPUT"
echo "errors=$errors" >> "$GITHUB_OUTPUT"
echo "warnings=$warnings" >> "$GITHUB_OUTPUT"
echo "::error::Biome check found $errors issues"
fi
# Exit with biome's exit code if fail-on-error is true
if [ "$FAIL_ON_ERROR" = "true" ]; then
exit $biome_exit_code
fi
- name: Upload SARIF Report
if: inputs.mode == 'check' && always()
uses: github/codeql-action/upload-sarif@b1bff81932f5cdfc8695c7752dcee935dcd061c8 # v4.33.0
with:
sarif_file: biome-report.sarif
- name: Run Biome Fix
if: inputs.mode == 'fix'
id: fix
shell: sh
run: |
set -eu
echo "Running Biome fix mode..."
# Run Biome fix and capture exit code
biome_exit_code=0
biome check --write . || biome_exit_code=$?
# Count changed files using git diff
files_changed=$(git diff --name-only | wc -l | tr -d ' ')
# Set status based on biome check result and changes
if [ $biome_exit_code -eq 0 ] && [ "$files_changed" -eq 0 ]; then
status="success"
echo "✅ No changes needed"
else
status="failure"
echo "⚠️ Fixed $files_changed file(s)"
fi
printf '%s\n' "files_changed=$files_changed" >> "$GITHUB_OUTPUT"
printf '%s\n' "status=$status" >> "$GITHUB_OUTPUT"
- name: Commit and Push Fixes
if: inputs.mode == 'fix' && success()
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
with:
commit_message: 'style: autofix Biome violations'
commit_user_name: ${{ inputs.username }}
commit_user_email: ${{ inputs.email }}
add_options: '-u'