Skip to content

Merge pull request #98 from XAOSTECH:anglicise/20260316-032939 #238

Merge pull request #98 from XAOSTECH:anglicise/20260316-032939

Merge pull request #98 from XAOSTECH:anglicise/20260316-032939 #238

name: Bash Linting with Auto-Fix Suggestions
on:
push:
branches:
- main
- master
- develop
paths:
- '**.sh'
- '.github/workflows/bash-lint-advanced.yml'
pull_request:
branches:
- main
- master
- develop
paths:
- '**.sh'
workflow_dispatch:
inputs:
auto_fix:
description: 'Attempt automatic fixes for common issues'
required: false
default: 'false'
permissions:
contents: read
pull-requests: write
checks: write
actions: read
jobs:
shellcheck-analysis:
name: ShellCheck Analysis & Suggestions
runs-on: ubuntu-latest
outputs:
errors: ${{ steps.shellcheck.outputs.errors }}
warnings: ${{ steps.shellcheck.outputs.warnings }}
permissions:
contents: read
pull-requests: write
checks: write
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install tools
run: |
sudo apt-get update
sudo apt-get install -y shellcheck
- name: Run ShellCheck with JSON output
id: shellcheck
continue-on-error: true
run: |
mkdir -p /tmp/lint
# Run ShellCheck for all scripts
find . -type f -name "*.sh" ! -path "./.git/*" ! -path "./node_modules/*" ! -path "./vendor/*" \
-exec shellcheck -f json {} + > /tmp/lint/results.json 2>&1 || true
# Count issues
ERRORS=$(jq '[.[] | select(.level=="error")] | length' /tmp/lint/results.json 2>/dev/null || echo "0")
WARNINGS=$(jq '[.[] | select(.level=="warning")] | length' /tmp/lint/results.json 2>/dev/null || echo "0")
echo "errors=$ERRORS" >> $GITHUB_OUTPUT
echo "warnings=$WARNINGS" >> $GITHUB_OUTPUT
cat /tmp/lint/results.json
- name: Parse and annotate issues
if: always()
run: |
if [[ ! -f /tmp/lint/results.json ]]; then
echo "No linting results found"
exit 0
fi
jq -r '.[] | "::notice file=\(.file),line=\(.line),col=\(.column)::[\(.code)] \(.message) - See: https://www.shellcheck.net/wiki/\(.code)"' /tmp/lint/results.json 2>/dev/null || true
- name: Generate suggestions markdown
id: suggestions
run: |
{
echo "# 🔍 Bash Linting Report"
echo ""
echo "**Errors**: ${{ steps.shellcheck.outputs.errors }}"
echo "**Warnings**: ${{ steps.shellcheck.outputs.warnings }}"
echo ""
if [[ -f /tmp/lint/results.json ]]; then
echo "## Issues Found:"
echo ""
jq -r '.[] |
"### [\(.code)] \(.message)\n" +
"- **File**: `\(.file)` (line \(.line), col \(.column))\n" +
"- **Severity**: \(.level)\n" +
"- **Wiki**: https://www.shellcheck.net/wiki/\(.code)\n"' /tmp/lint/results.json 2>/dev/null || echo "No issues parsed"
fi
} > /tmp/lint/suggestions.md
cat /tmp/lint/suggestions.md
- name: Suggest fixes on PR
if: github.event_name == 'pull_request' && (steps.shellcheck.outputs.errors > 0 || steps.shellcheck.outputs.warnings > 0)
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
const results = JSON.parse(fs.readFileSync('/tmp/lint/results.json', 'utf8'));
if (results.length === 0) return;
// Group issues by file
const issuesByFile = {};
results.forEach(issue => {
if (!issuesByFile[issue.file]) {
issuesByFile[issue.file] = [];
}
issuesByFile[issue.file].push(issue);
});
// Create review with suggestions
const reviewComments = [];
Object.entries(issuesByFile).forEach(([file, issues]) => {
issues.forEach(issue => {
reviewComments.push({
path: file,
line: issue.line,
side: 'RIGHT',
body: `**[SC${issue.code}]** ${issue.message}\n\n[📖 Read more](https://www.shellcheck.net/wiki/${issue.code})`
});
});
});
if (reviewComments.length > 0) {
github.rest.pulls.createReview({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.issue.number,
event: 'COMMENT',
comments: reviewComments.slice(0, 50) // GitHub limit: 50 comments per review
}).catch(err => {
console.log('Note: Could not create inline review (file may not be in diff)');
console.log(err.message);
});
}
create-fix-pr:
name: Create Auto-Fix PR
runs-on: ubuntu-latest
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install tools
run: |
sudo apt-get update
sudo apt-get install -y shellcheck shfmt jq
- name: Generate security app token
id: app_token
uses: actions/create-github-app-token@v2
with:
app-id: ${{ secrets['XSS_AI'] }}
private-key: ${{ secrets['XSS_PK'] }}
- name: Setup bot identity for signing
id: identity
uses: XAOSTECH/dev-control/.github/actions/identity@b067f72ca7f849b734a45634f23581a739d5146f # v2.0.0
with:
gpg-private-key: ${{ secrets['XB_GK'] }}
gpg-passphrase: ${{ secrets['XB_GP'] }}
user-token: ${{ secrets['XB_UT'] }}
bot-name: xaos-bot
- name: Run ShellCheck to find issues
id: check
continue-on-error: true
run: |
find . -type f -name "*.sh" ! -path "./.git/*" ! -path "./node_modules/*" ! -path "./vendor/*" \
-exec shellcheck -f json {} + > /tmp/lint/results.json 2>&1 || true
ERRORS=$(jq '[.[] | select(.level=="error")] | length' /tmp/lint/results.json 2>/dev/null || echo "0")
echo "errors=$ERRORS" >> $GITHUB_OUTPUT
[[ "$ERRORS" -gt 0 ]] && echo "has_fixes=true" >> $GITHUB_OUTPUT || echo "has_fixes=false" >> $GITHUB_OUTPUT
- name: Apply style fixes with shfmt
if: steps.check.outputs.has_fixes == 'true'
run: |
echo "Applying style fixes..."
find . -type f -name "*.sh" ! -path "./.git/*" ! -path "./node_modules/*" ! -path "./vendor/*" \
-exec shfmt -i 4 -bn -ci -w {} +
echo "Style fixes applied"
- name: Create fix PR
if: steps.check.outputs.has_fixes == 'true' && steps.identity.outputs.gpg-outcome == 'success'
run: |
set -euo pipefail
# Configure git for commits
git config user.name "xaos-bot"
git config user.email "dev@xaostech.com"
git config user.signingkey "$(git config user.signingkey)"
# Check if there are changes
if git diff --quiet; then
echo "No changes to commit"
exit 0
fi
# Create feature branch
BRANCH="fix/bash-linting-$(date +%s)"
git checkout -b "$BRANCH"
# Commit fixes with GPG signature
git add -A
git commit -S -m "fix: Apply bash linting auto-fixes\n\n- ShellCheck findings addressed via shellcheck analysis\n- Code style standardised with shfmt\n- Auto-generated by bash-lint-advanced workflow"
# Push with security app token
git push origin "$BRANCH" --force-with-lease
# Create PR using security app token
gh pr create \
--repo "$GITHUB_REPOSITORY" \
--base main \
--head "$BRANCH" \
--title "🔧 Bash Linting Auto-Fixes" \
--body "Automated fixes from bash linting analysis.\n\n## Changes\n- ShellCheck findings addressed\n- Code style standardised with shfmt\n\nAuto-generated by bash-lint-advanced workflow. Please review before merging." \
--label "automated" \
--label "bash-linting" \
|| echo "PR may already exist for this branch"
env:
GH_TOKEN: ${{ steps.app_token.outputs.token }}
summary:
name: Linting Summary
runs-on: ubuntu-latest
needs: [shellcheck-analysis]
if: always()
steps:
- name: Create summary
run: |
ERRORS="${{ needs.shellcheck-analysis.outputs.errors }}"
WARNINGS="${{ needs.shellcheck-analysis.outputs.warnings }}"
echo "## 📊 Bash Linting Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Errors**: ${ERRORS:-0}" >> $GITHUB_STEP_SUMMARY
echo "**Warnings**: ${WARNINGS:-0}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Next Steps:" >> $GITHUB_STEP_SUMMARY
echo "1. Review issues in workflow annotations" >> $GITHUB_STEP_SUMMARY
echo "2. Check PR comments for detailed suggestions" >> $GITHUB_STEP_SUMMARY
echo "3. Follow linked wiki pages for explanations" >> $GITHUB_STEP_SUMMARY
echo "4. Create PR with fixes" >> $GITHUB_STEP_SUMMARY
# Fail if there are errors
if [[ "${ERRORS:-0}" -gt 0 ]]; then
echo "" >> $GITHUB_STEP_SUMMARY
echo "❌ **Errors found - please fix**" >> $GITHUB_STEP_SUMMARY
exit 1
fi