|
| 1 | +name: 'ci-doctor' |
| 2 | +description: 'Audit GitHub Actions workflows for waste, cost, and security gaps. 16 rules, SARIF + PR comment.' |
| 3 | +author: 'depmedic' |
| 4 | + |
| 5 | +branding: |
| 6 | + icon: 'activity' |
| 7 | + color: 'green' |
| 8 | + |
| 9 | +inputs: |
| 10 | + path: |
| 11 | + description: 'Path to scan. Default = repo root, which auto-discovers .github/workflows.' |
| 12 | + required: false |
| 13 | + default: '.' |
| 14 | + fail-on: |
| 15 | + description: 'Severity threshold for non-zero exit: info | warn | error. Default = error (only hard-fails on error-level findings).' |
| 16 | + required: false |
| 17 | + default: 'error' |
| 18 | + upload-sarif: |
| 19 | + description: 'Upload SARIF to GitHub Code Scanning so findings show under the Security tab. Requires security-events: write permission.' |
| 20 | + required: false |
| 21 | + default: 'true' |
| 22 | + comment-on-pr: |
| 23 | + description: 'Post a markdown table of findings as a PR comment. Requires pull-requests: write permission.' |
| 24 | + required: false |
| 25 | + default: 'true' |
| 26 | + ci-doctor-version: |
| 27 | + description: 'npm version of ci-doctor to run. Pin to a SHA-tag for reproducibility.' |
| 28 | + required: false |
| 29 | + default: 'latest' |
| 30 | + only: |
| 31 | + description: 'Comma-separated list of rule IDs to run exclusively.' |
| 32 | + required: false |
| 33 | + default: '' |
| 34 | + disable: |
| 35 | + description: 'Comma-separated list of rule IDs to skip.' |
| 36 | + required: false |
| 37 | + default: '' |
| 38 | + |
| 39 | +outputs: |
| 40 | + sarif-path: |
| 41 | + description: 'Path to the SARIF file produced by ci-doctor.' |
| 42 | + value: ${{ steps.sarif.outputs.sarif-path }} |
| 43 | + markdown-path: |
| 44 | + description: 'Path to the markdown report produced by ci-doctor.' |
| 45 | + value: ${{ steps.markdown.outputs.markdown-path }} |
| 46 | + finding-count: |
| 47 | + description: 'Total number of findings (parsed from JSON report).' |
| 48 | + value: ${{ steps.json.outputs.finding-count }} |
| 49 | + |
| 50 | +runs: |
| 51 | + using: 'composite' |
| 52 | + steps: |
| 53 | + - name: Resolve flags |
| 54 | + id: flags |
| 55 | + shell: bash |
| 56 | + run: | |
| 57 | + FLAGS="" |
| 58 | + if [ -n "${{ inputs.only }}" ]; then FLAGS="$FLAGS --only=${{ inputs.only }}"; fi |
| 59 | + if [ -n "${{ inputs.disable }}" ]; then FLAGS="$FLAGS --disable=${{ inputs.disable }}"; fi |
| 60 | + echo "flags=$FLAGS" >> "$GITHUB_OUTPUT" |
| 61 | + echo "ci-doctor flags: $FLAGS" |
| 62 | +
|
| 63 | + - name: Run ci-doctor (markdown report) |
| 64 | + id: markdown |
| 65 | + shell: bash |
| 66 | + run: | |
| 67 | + set +e |
| 68 | + npx --yes ci-doctor@${{ inputs.ci-doctor-version }} ${{ steps.flags.outputs.flags }} --markdown "${{ inputs.path }}" > ci-doctor.md |
| 69 | + echo "markdown-path=ci-doctor.md" >> "$GITHUB_OUTPUT" |
| 70 | + echo "::group::ci-doctor markdown" |
| 71 | + cat ci-doctor.md || true |
| 72 | + echo "::endgroup::" |
| 73 | +
|
| 74 | + - name: Run ci-doctor (SARIF for code scanning) |
| 75 | + id: sarif |
| 76 | + shell: bash |
| 77 | + run: | |
| 78 | + set +e |
| 79 | + npx --yes ci-doctor@${{ inputs.ci-doctor-version }} ${{ steps.flags.outputs.flags }} --sarif "${{ inputs.path }}" > ci-doctor.sarif |
| 80 | + echo "sarif-path=ci-doctor.sarif" >> "$GITHUB_OUTPUT" |
| 81 | +
|
| 82 | + - name: Run ci-doctor (JSON for finding-count output) |
| 83 | + id: json |
| 84 | + shell: bash |
| 85 | + run: | |
| 86 | + set +e |
| 87 | + npx --yes ci-doctor@${{ inputs.ci-doctor-version }} ${{ steps.flags.outputs.flags }} --json "${{ inputs.path }}" > ci-doctor.json |
| 88 | + if [ -s ci-doctor.json ]; then |
| 89 | + COUNT=$(node -e "try{const j=JSON.parse(require('fs').readFileSync('ci-doctor.json','utf8'));console.log(Array.isArray(j.findings)?j.findings.length:(j.summary&&j.summary.total||0))}catch(e){console.log(0)}") |
| 90 | + else |
| 91 | + COUNT=0 |
| 92 | + fi |
| 93 | + echo "finding-count=$COUNT" >> "$GITHUB_OUTPUT" |
| 94 | + echo "Findings: $COUNT" |
| 95 | +
|
| 96 | + - name: Upload SARIF to GitHub Code Scanning |
| 97 | + if: ${{ inputs.upload-sarif == 'true' && hashFiles('ci-doctor.sarif') != '' }} |
| 98 | + uses: github/codeql-action/upload-sarif@1b1aada464948af03b950897e5eb522f92603cc2 # v3.27.0 |
| 99 | + with: |
| 100 | + sarif_file: ci-doctor.sarif |
| 101 | + category: ci-doctor |
| 102 | + continue-on-error: true |
| 103 | + |
| 104 | + - name: Comment on pull request |
| 105 | + if: ${{ inputs.comment-on-pr == 'true' && github.event_name == 'pull_request' && hashFiles('ci-doctor.md') != '' }} |
| 106 | + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 |
| 107 | + with: |
| 108 | + script: | |
| 109 | + const fs = require('fs'); |
| 110 | + let body = ''; |
| 111 | + try { body = fs.readFileSync('ci-doctor.md', 'utf8'); } catch { return; } |
| 112 | + body = body.trim(); |
| 113 | + if (!body) return; |
| 114 | + if (body.length > 60000) body = body.slice(0, 60000) + '\n\n_truncated_'; |
| 115 | + const marker = '<!-- ci-doctor-bot -->'; |
| 116 | + const header = `${marker}\n\n## ci-doctor findings\n\n`; |
| 117 | + const full = header + body + '\n\n---\n_Powered by [ci-doctor](https://www.npmjs.com/package/ci-doctor) — [docs](https://depmedicdev-byte.github.io/)_'; |
| 118 | + const { data: comments } = await github.rest.issues.listComments({ |
| 119 | + owner: context.repo.owner, |
| 120 | + repo: context.repo.repo, |
| 121 | + issue_number: context.issue.number, |
| 122 | + per_page: 100, |
| 123 | + }); |
| 124 | + const existing = comments.find((c) => c.body && c.body.includes(marker)); |
| 125 | + if (existing) { |
| 126 | + await github.rest.issues.updateComment({ |
| 127 | + owner: context.repo.owner, |
| 128 | + repo: context.repo.repo, |
| 129 | + comment_id: existing.id, |
| 130 | + body: full, |
| 131 | + }); |
| 132 | + } else { |
| 133 | + await github.rest.issues.createComment({ |
| 134 | + owner: context.repo.owner, |
| 135 | + repo: context.repo.repo, |
| 136 | + issue_number: context.issue.number, |
| 137 | + body: full, |
| 138 | + }); |
| 139 | + } |
| 140 | + continue-on-error: true |
| 141 | + |
| 142 | + - name: Enforce severity threshold |
| 143 | + shell: bash |
| 144 | + run: | |
| 145 | + npx --yes ci-doctor@${{ inputs.ci-doctor-version }} ${{ steps.flags.outputs.flags }} --severity=${{ inputs.fail-on }} "${{ inputs.path }}" |
0 commit comments