Skip to content

Nextflow lint GHA workflow: check for formatting changes too #39

Nextflow lint GHA workflow: check for formatting changes too

Nextflow lint GHA workflow: check for formatting changes too #39

Workflow file for this run

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