--- # yaml-language-server: $schema=https://json.schemastore.org/github-action.json name: Prettier Check description: 'Run Prettier check on the repository with advanced configuration and reporting' author: Ismo Vuorinen branding: icon: check-circle color: green inputs: working-directory: description: 'Directory containing files to check' required: false default: '.' prettier-version: description: 'Prettier version to use' required: false default: 'latest' config-file: description: 'Path to Prettier config file' required: false default: '.prettierrc' ignore-file: description: 'Path to Prettier ignore file' required: false default: '.prettierignore' file-pattern: description: 'Files to include (glob pattern)' required: false default: '**/*.{js,jsx,ts,tsx,css,scss,json,md,yaml,yml}' cache: description: 'Enable Prettier caching' required: false default: 'true' fail-on-error: description: 'Fail workflow if issues are found' required: false default: 'true' report-format: description: 'Output format (json, sarif)' required: false default: 'sarif' max-retries: description: 'Maximum number of retry attempts' required: false default: '3' plugins: description: 'Comma-separated list of Prettier plugins to install' required: false default: '' check-only: description: 'Only check for formatting issues without fixing' required: false default: 'true' outputs: files-checked: description: 'Number of files checked' value: ${{ steps.check.outputs.files_checked }} unformatted-files: description: 'Number of files with formatting issues' value: ${{ steps.check.outputs.unformatted_files }} sarif-file: description: 'Path to SARIF report file' value: ${{ steps.check.outputs.sarif_file }} cache-hit: description: 'Indicates if there was a cache hit' value: ${{ steps.cache.outputs.cache-hit }} runs: using: composite steps: - name: Validate Inputs id: validate shell: bash run: | set -euo pipefail # Validate working directory if [ ! -d "${{ inputs.working-directory }}" ]; then echo "::error::Working directory does not exist: ${{ inputs.working-directory }}" exit 1 fi # Validate glob pattern if ! echo "${{ inputs.file-pattern }}" | grep -qE '^[*{}\[\].,a-zA-Z0-9/_-]+$'; then echo "::error::Invalid file pattern format" exit 1 fi # Validate plugins format if provided if [ -n "${{ inputs.plugins }}" ]; then if ! echo "${{ inputs.plugins }}" | grep -qE '^[a-zA-Z0-9/@._,-]+$'; then echo "::error::Invalid plugins format" exit 1 fi fi - name: Setup Node.js uses: ivuorinen/actions/node-setup@main - name: Set up Cache id: cache uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 if: inputs.cache == 'true' with: path: | node_modules/.cache/prettier .prettier-cache key: ${{ runner.os }}-prettier-${{ hashFiles('**/package.json', '**/package-lock.json', '${{ inputs.config-file }}') }} restore-keys: | ${{ runner.os }}-prettier- - name: Install Dependencies shell: bash run: | set -euo pipefail cd ${{ inputs.working-directory }} # Function to install with retries install_with_retries() { local attempt=1 local max_attempts=${{ inputs.max-retries }} while [ $attempt -le $max_attempts ]; do echo "Installation attempt $attempt of $max_attempts" # Install Prettier and base dependencies if npm install \ prettier@${{ inputs.prettier-version }} \ @prettier/plugin-xml \ prettier-plugin-packagejson \ prettier-plugin-sh; then # Install additional plugins if specified if [ -n "${{ inputs.plugins }}" ]; then IFS=',' read -ra PLUGINS <<< "${{ inputs.plugins }}" for plugin in "${PLUGINS[@]}"; do if ! npm install "$plugin"; then return 1 fi done fi return 0 fi attempt=$((attempt + 1)) if [ $attempt -le $max_attempts ]; then echo "Installation failed, waiting 10 seconds before retry..." sleep 10 fi done echo "::error::Failed to install dependencies after $max_attempts attempts" return 1 } install_with_retries - name: Prepare Configuration id: config shell: bash run: | set -euo pipefail cd ${{ inputs.working-directory }} # Create default config if none exists if [ ! -f "${{ inputs.config-file }}" ]; then echo "Creating default Prettier configuration..." cat > "${{ inputs.config-file }}" < "${{ inputs.ignore-file }}" < "$output_file" local first=true while IFS= read -r line; do if [ "$first" = true ]; then first=false else echo "," >> "$output_file" fi echo "{ \"level\": \"error\", \"message\": { \"text\": \"File is not formatted according to Prettier rules\" }, \"locations\": [ { \"physicalLocation\": { \"artifactLocation\": { \"uri\": \"$line\" } } } ] }" >> "$output_file" done < "$input_file" echo ']}]}' >> "$output_file" } # Run Prettier echo "Running Prettier check..." unformatted_files=$(mktemp) if [ "${{ inputs.check-only }}" = "true" ]; then npx prettier \ --check \ --config "${{ inputs.config-file }}" \ --ignore-path "${{ inputs.ignore-file }}" \ ${{ inputs.cache == 'true' && '--cache --cache-location=.prettier-cache' || '' }} \ --no-error-on-unmatched-pattern \ "${{ inputs.file-pattern }}" 2>&1 | \ grep -oE '[^ ]+\.[a-zA-Z]+$' > "$unformatted_files" || true else npx prettier \ --write \ --list-different \ --config "${{ inputs.config-file }}" \ --ignore-path "${{ inputs.ignore-file }}" \ ${{ inputs.cache == 'true' && '--cache --cache-location=.prettier-cache' || '' }} \ --no-error-on-unmatched-pattern \ "${{ inputs.file-pattern }}" > "$unformatted_files" || true fi # Count files files_checked=$(find . -type f -name "${{ inputs.file-pattern }}" -not -path "*/node_modules/*" | wc -l) unformatted_count=$(wc -l < "$unformatted_files") echo "files_checked=${files_checked}" >> $GITHUB_OUTPUT echo "unformatted_files=${unformatted_count}" >> $GITHUB_OUTPUT # Generate SARIF report if requested if [ "${{ inputs.report-format }}" = "sarif" ]; then prettier_to_sarif "$unformatted_files" "reports/prettier.sarif" echo "sarif_file=reports/prettier.sarif" >> $GITHUB_OUTPUT fi # Clean up temporary file rm "$unformatted_files" # Exit with error if issues found and fail-on-error is true if [ "${{ inputs.fail-on-error }}" = "true" ] && [ "$unformatted_count" -gt 0 ]; then echo "::error::Found $unformatted_count files with formatting issues" exit 1 fi - name: Upload Prettier Results if: always() && inputs.report-format == 'sarif' uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 with: sarif_file: ${{ inputs.working-directory }}/reports/prettier.sarif category: prettier - name: Cleanup if: always() shell: bash run: | set -euo pipefail cd ${{ inputs.working-directory }} # Remove temporary files rm -rf reports/ # Clean cache if exists and not being preserved if [ "${{ inputs.cache }}" != "true" ]; then rm -rf .prettier-cache rm -rf node_modules/.cache/prettier fi