Nextflow lint GHA workflow: check for formatting changes too #39
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Nextflow Lint | |
| on: | |
| push: | |
| pull_request: | |
| jobs: | |
| lint: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Nextflow | |
| uses: nf-core/setup-nextflow@v2 | |
| - name: Run Nextflow lint | |
| run: | | |
| mkdir -p lint-results | |
| # TODO: Remove '|| true' to fail CI when linting errors are fixed | |
| nextflow lint **/solutions/* -o json > lint-results/lint-output.json 2>&1 || true | |
| - name: Check formatting changes | |
| if: always() | |
| run: | | |
| # Run formatter (modifies files in place) | |
| nextflow lint **/solutions/* -format || true | |
| # Count changed files and save diffs | |
| changed_files=$(git diff --name-only) | |
| if [ -n "$changed_files" ]; then | |
| echo "$changed_files" | wc -l | xargs > lint-results/format-count.txt | |
| echo "$changed_files" > lint-results/format-files.txt | |
| # Save individual diffs for each file | |
| mkdir -p lint-results/diffs | |
| for file in $changed_files; do | |
| # Create safe filename for the diff | |
| safe_name=$(echo "$file" | tr '/' '_') | |
| git diff "$file" > "lint-results/diffs/${safe_name}.diff" | |
| done | |
| else | |
| echo "0" > lint-results/format-count.txt | |
| echo "" > lint-results/format-files.txt | |
| fi | |
| # Reset changes so they don't affect other steps | |
| git checkout -- . || true | |
| - name: Generate markdown report | |
| if: always() | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const lintOutput = JSON.parse(fs.readFileSync('lint-results/lint-output.json', 'utf8')); | |
| const { summary, errors, warnings } = lintOutput; | |
| const sha = context.sha; | |
| const repo = `${context.repo.owner}/${context.repo.repo}`; | |
| // Handle warnings array (added in future Nextflow version) | |
| const warningsArray = warnings || []; | |
| const warningCount = summary.warnings || 0; | |
| // Read formatting results | |
| const formatCount = parseInt(fs.readFileSync('lint-results/format-count.txt', 'utf8').trim()) || 0; | |
| const formatFiles = fs.readFileSync('lint-results/format-files.txt', 'utf8').trim().split('\n').filter(f => f); | |
| let markdown = '**Nextflow linting complete!**\n\n'; | |
| const hasIssues = summary.errors > 0 || warningCount > 0; | |
| if (!hasIssues) { | |
| markdown += `✅ ${summary.filesWithoutErrors} files had no errors\n`; | |
| } else { | |
| if (summary.errors > 0) { | |
| markdown += `❌ ${summary.filesWithErrors} files had ${summary.errors} errors\n`; | |
| } | |
| if (warningCount > 0) { | |
| const filesWithWarnings = summary.filesWithWarnings || 'N/A'; | |
| markdown += `⚠️ ${filesWithWarnings} files had ${warningCount} warnings\n`; | |
| } | |
| markdown += `✅ ${summary.filesWithoutErrors} files had no errors\n`; | |
| } | |
| // Add formatting status line | |
| if (formatCount > 0) { | |
| markdown += `🔧 ${formatCount} file${formatCount === 1 ? '' : 's'} would be changed by auto-formatting\n`; | |
| } else { | |
| markdown += `✨ No formatting changes needed\n`; | |
| } | |
| // Add lint issues details section | |
| if (hasIssues) { | |
| markdown += '\n'; | |
| // Combine errors and warnings with type labels | |
| const allIssues = [ | |
| ...errors.map(e => ({ ...e, type: 'Error' })), | |
| ...warningsArray.map(w => ({ ...w, type: 'Warning' })) | |
| ]; | |
| // Group by file | |
| const grouped = {}; | |
| for (const issue of allIssues) { | |
| if (!grouped[issue.filename]) { | |
| grouped[issue.filename] = []; | |
| } | |
| grouped[issue.filename].push(issue); | |
| } | |
| // Build table rows | |
| const rows = []; | |
| for (const [filename, fileIssues] of Object.entries(grouped).sort()) { | |
| for (const issue of fileIssues) { | |
| const location = `${issue.startLine}:${issue.startColumn}`; | |
| const link = `[${filename}:${location}](https://github.com/${repo}/blob/${sha}/${filename}#L${issue.startLine})`; | |
| rows.push(`| ${issue.type} | ${link} | ${issue.message} |`); | |
| } | |
| } | |
| const totalIssues = summary.errors + warningCount; | |
| markdown += `<sup>💡 Tip: Click filename locations to go directly to that code.</sup>\n\n`; | |
| markdown += `<details>\n<summary>View all ${totalIssues} issues</summary>\n\n`; | |
| markdown += `| Type | Location | Message |\n|------|----------|---------|\n`; | |
| markdown += rows.join('\n'); | |
| markdown += `\n\n</details>\n`; | |
| } | |
| // Add formatting changes details section | |
| if (formatCount > 0) { | |
| markdown += `\n<details>\n<summary>View formatting changes</summary>\n\n`; | |
| const maxDiffLines = 30; | |
| const maxTotalChars = 15000; | |
| let totalChars = 0; | |
| let truncatedDueToSize = false; | |
| for (const file of formatFiles) { | |
| if (truncatedDueToSize) { | |
| markdown += `\n_...and more files (output truncated)_\n`; | |
| break; | |
| } | |
| const safeName = file.replace(/\//g, '_'); | |
| const diffPath = `lint-results/diffs/${safeName}.diff`; | |
| try { | |
| let diffContent = fs.readFileSync(diffPath, 'utf8'); | |
| const lines = diffContent.split('\n'); | |
| let fileTruncated = false; | |
| if (lines.length > maxDiffLines) { | |
| diffContent = lines.slice(0, maxDiffLines).join('\n'); | |
| fileTruncated = true; | |
| } | |
| // Check if adding this would exceed total size | |
| if (totalChars + diffContent.length > maxTotalChars) { | |
| truncatedDueToSize = true; | |
| markdown += `\n_...and more files (output truncated due to size)_\n`; | |
| break; | |
| } | |
| totalChars += diffContent.length; | |
| markdown += `<details>\n<summary>${file}</summary>\n\n`; | |
| markdown += '```diff\n'; | |
| markdown += diffContent; | |
| if (fileTruncated) { | |
| markdown += '\n... (truncated)\n'; | |
| } | |
| markdown += '```\n\n</details>\n\n'; | |
| } catch (e) { | |
| markdown += `<details>\n<summary>${file}</summary>\n\n`; | |
| markdown += '_Could not read diff_\n\n</details>\n\n'; | |
| } | |
| } | |
| markdown += `</details>\n`; | |
| } | |
| // Write to file | |
| fs.writeFileSync('lint-results/report.md', markdown); | |
| // Add to job summary | |
| await core.summary | |
| .addRaw(markdown) | |
| .write(); | |
| - name: Save PR info | |
| if: always() && github.event_name == 'pull_request' | |
| run: | | |
| echo "${{ github.event.pull_request.number }}" > lint-results/pr-number.txt | |
| echo "${{ github.sha }}" > lint-results/head-sha.txt | |
| - name: Upload lint results | |
| if: always() && github.event_name == 'pull_request' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: lint-results | |
| path: lint-results/ | |
| retention-days: 1 |