mirror of
https://github.com/ivuorinen/actions.git
synced 2026-01-26 03:23:59 +00:00
Signed-off-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
374 lines
12 KiB
YAML
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@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
|
with:
|
|
node-version: '24'
|
|
|
|
- name: Enable Corepack
|
|
shell: sh
|
|
run: |
|
|
set -eu
|
|
corepack enable
|
|
|
|
- name: Install Package Manager
|
|
shell: sh
|
|
env:
|
|
PACKAGE_MANAGER: ${{ steps.detect-pm.outputs.package-manager }}
|
|
run: |
|
|
set -eu
|
|
|
|
case "$PACKAGE_MANAGER" in
|
|
pnpm)
|
|
corepack prepare pnpm@latest --activate
|
|
;;
|
|
yarn)
|
|
corepack prepare yarn@stable --activate
|
|
;;
|
|
bun|npm)
|
|
# Bun installed separately, npm built-in
|
|
;;
|
|
esac
|
|
|
|
- name: Setup Bun
|
|
if: steps.detect-pm.outputs.package-manager == 'bun'
|
|
uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2.0.2
|
|
with:
|
|
bun-version: latest
|
|
|
|
- name: Cache Node Dependencies
|
|
id: cache
|
|
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
|
with:
|
|
path: node_modules
|
|
key: ${{ runner.os }}-biome-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-${{ hashFiles('package-lock.json', 'yarn.lock', 'pnpm-lock.yaml', 'bun.lockb') }}
|
|
restore-keys: |
|
|
${{ runner.os }}-biome-lint-${{ inputs.mode }}-${{ steps.detect-pm.outputs.package-manager }}-
|
|
${{ runner.os }}-biome-lint-${{ inputs.mode }}-
|
|
|
|
- name: Install Biome
|
|
shell: sh
|
|
env:
|
|
PACKAGE_MANAGER: ${{ steps.detect-pm.outputs.package-manager }}
|
|
MAX_RETRIES: ${{ inputs.max-retries }}
|
|
run: |
|
|
set -eu
|
|
|
|
# Check if biome is already installed
|
|
if command -v biome >/dev/null 2>&1; then
|
|
echo "✅ Biome already installed: $(biome --version)"
|
|
exit 0
|
|
fi
|
|
|
|
echo "Installing Biome using $PACKAGE_MANAGER..."
|
|
|
|
for attempt in $(seq 1 "$MAX_RETRIES"); do
|
|
echo "Attempt $attempt of $MAX_RETRIES"
|
|
|
|
case "$PACKAGE_MANAGER" in
|
|
"pnpm")
|
|
if pnpm add -g @biomejs/biome; then
|
|
echo "✅ Biome installed successfully with pnpm"
|
|
exit 0
|
|
fi
|
|
;;
|
|
"yarn")
|
|
if yarn global add @biomejs/biome; then
|
|
echo "✅ Biome installed successfully with yarn"
|
|
exit 0
|
|
fi
|
|
;;
|
|
"bun")
|
|
if bun add -g @biomejs/biome; then
|
|
echo "✅ Biome installed successfully with bun"
|
|
exit 0
|
|
fi
|
|
;;
|
|
"npm"|*)
|
|
if npm install -g @biomejs/biome; then
|
|
echo "✅ Biome installed successfully with npm"
|
|
exit 0
|
|
fi
|
|
;;
|
|
esac
|
|
|
|
if [ $attempt -lt "$MAX_RETRIES" ]; then
|
|
echo "❌ Installation failed, retrying in 5 seconds..."
|
|
sleep 5
|
|
fi
|
|
done
|
|
|
|
echo "::error::Failed to install Biome after $MAX_RETRIES attempts"
|
|
exit 1
|
|
|
|
- name: Run Biome Check
|
|
if: inputs.mode == 'check'
|
|
id: check
|
|
shell: sh
|
|
env:
|
|
FAIL_ON_ERROR: ${{ inputs.fail-on-error }}
|
|
run: |
|
|
set -eu
|
|
|
|
echo "Running Biome check mode..."
|
|
|
|
# Run Biome check with SARIF reporter
|
|
biome_exit_code=0
|
|
biome check . --reporter=sarif > biome-report.sarif || biome_exit_code=$?
|
|
|
|
# Handle failures gracefully
|
|
if [ $biome_exit_code -ne 0 ] && [ ! -s biome-report.sarif ]; then
|
|
echo "::warning::SARIF report generation failed with exit code $biome_exit_code"
|
|
# Create empty SARIF file to avoid upload errors
|
|
echo '{"version":"2.1.0","$schema":"https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json","runs":[]}' > biome-report.sarif
|
|
fi
|
|
|
|
# Parse SARIF output for error counts
|
|
if [ -f biome-report.sarif ]; then
|
|
errors=$(jq '[.runs[]?.results[]? | select(.level == "error" or .level == "warning")] | length' biome-report.sarif 2>/dev/null || echo "0")
|
|
warnings="0" # Biome doesn't separate warnings in SARIF output
|
|
else
|
|
errors="0"
|
|
warnings="0"
|
|
fi
|
|
|
|
if [ $biome_exit_code -eq 0 ]; then
|
|
echo "status=success" >> "$GITHUB_OUTPUT"
|
|
echo "errors=0" >> "$GITHUB_OUTPUT"
|
|
echo "warnings=0" >> "$GITHUB_OUTPUT"
|
|
echo "✅ Biome check completed successfully"
|
|
else
|
|
echo "status=failure" >> "$GITHUB_OUTPUT"
|
|
echo "errors=$errors" >> "$GITHUB_OUTPUT"
|
|
echo "warnings=$warnings" >> "$GITHUB_OUTPUT"
|
|
echo "::error::Biome check found $errors issues"
|
|
fi
|
|
|
|
# Exit with biome's exit code if fail-on-error is true
|
|
if [ "$FAIL_ON_ERROR" = "true" ]; then
|
|
exit $biome_exit_code
|
|
fi
|
|
|
|
- name: Upload SARIF Report
|
|
if: inputs.mode == 'check' && always()
|
|
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
|
with:
|
|
sarif_file: biome-report.sarif
|
|
|
|
- name: Run Biome Fix
|
|
if: inputs.mode == 'fix'
|
|
id: fix
|
|
shell: sh
|
|
run: |
|
|
set -eu
|
|
|
|
echo "Running Biome fix mode..."
|
|
|
|
# Run Biome fix and capture exit code
|
|
biome_exit_code=0
|
|
biome check --write . || biome_exit_code=$?
|
|
|
|
# Count changed files using git diff
|
|
files_changed=$(git diff --name-only | wc -l | tr -d ' ')
|
|
|
|
# Set status based on biome check result and changes
|
|
if [ $biome_exit_code -eq 0 ] && [ "$files_changed" -eq 0 ]; then
|
|
status="success"
|
|
echo "✅ No changes needed"
|
|
else
|
|
status="failure"
|
|
echo "⚠️ Fixed $files_changed file(s)"
|
|
fi
|
|
|
|
printf '%s\n' "files_changed=$files_changed" >> "$GITHUB_OUTPUT"
|
|
printf '%s\n' "status=$status" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Commit and Push Fixes
|
|
if: inputs.mode == 'fix' && success()
|
|
uses: stefanzweifel/git-auto-commit-action@04702edda442b2e678b25b537cec683a1493fcb9 # v7.1.0
|
|
with:
|
|
commit_message: 'style: autofix Biome violations'
|
|
commit_user_name: ${{ inputs.username }}
|
|
commit_user_email: ${{ inputs.email }}
|
|
add_options: '-u'
|