From 72d2953358d3f144dcf28d2453eec92c27570a21 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Sat, 23 Aug 2025 08:19:22 -0500 Subject: [PATCH 1/3] fix: add missing permissions for lint jobs in workflow --- .github/workflows/lint-readme.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/lint-readme.yml b/.github/workflows/lint-readme.yml index e6c2453..c2ce64d 100644 --- a/.github/workflows/lint-readme.yml +++ b/.github/workflows/lint-readme.yml @@ -12,6 +12,8 @@ jobs: lint-gh-cli-readme: name: Lint ./gh-cli/README.md runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code @@ -23,6 +25,8 @@ jobs: lint-scripts-readme: name: Lint ./scripts/README.md runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout code From e18c383fec0c7476e174ee35f01a094fa9b26889 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Sat, 23 Aug 2025 08:19:42 -0500 Subject: [PATCH 2/3] ci: posting results of linting as sticky PR comment --- .github/workflows/lint-readme.yml | 72 ++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint-readme.yml b/.github/workflows/lint-readme.yml index c2ce64d..03cb31c 100644 --- a/.github/workflows/lint-readme.yml +++ b/.github/workflows/lint-readme.yml @@ -20,7 +20,15 @@ jobs: uses: actions/checkout@v4 - name: Lint ./gh-cli/README.md - run: node ./.github/scripts/lint-readme.js + id: lint + run: node ./.github/scripts/lint-readme.js | tee gh-cli-readme-lint-results.txt + + - name: Upload lint results + if: steps.lint.outcome == 'failure' || steps.lint.outcome == 'success' + uses: actions/upload-artifact@v4 + with: + name: gh-cli-readme-lint-results + path: gh-cli-readme-lint-results.txt lint-scripts-readme: name: Lint ./scripts/README.md @@ -33,4 +41,64 @@ jobs: uses: actions/checkout@v4 - name: Lint ./scripts/README.md - run: node ./.github/scripts/lint-readme.js ./scripts '##' '# scripts' + id: lint + run: node ./.github/scripts/lint-readme.js ./scripts '##' '# scripts' | tee scripts-readme-lint-results.txt + + - name: Upload lint results + if: steps.lint.outcome == 'failure' || steps.lint.outcome == 'success' + uses: actions/upload-artifact@v4 + with: + name: scripts-readme-lint-results + path: scripts-readme-lint-results.txt + + post-results: + name: Post Lint Results as PR Comment + runs-on: ubuntu-latest + if: always() + needs: + - lint-gh-cli-readme + - lint-scripts-readme + permissions: + pull-requests: write + + steps: + - name: Download lint results + uses: actions/download-artifact@v5 + with: + path: ./lint-results/ + + - name: Process Lint Results + id: process-results + run: | + echo "## 📋 README Lint Results" > comment.md + echo "" >> comment.md + + # Function to process lint results + process_lint_results() { + local title="$1" + local file="$2" + + echo "### $title" >> comment.md + echo "\`\`\`" >> comment.md + if [ -f "$file" ]; then + cat "$file" >> comment.md + else + echo "❌ No results available" >> comment.md + fi + echo "\`\`\`" >> comment.md + echo "" >> comment.md + } + + # Process both README results + process_lint_results "⚡ gh-cli/README.md" "./lint-results/gh-cli-readme-lint-results/gh-cli-readme-lint-results.txt" + process_lint_results "🔧 scripts/README.md" "./lint-results/scripts-readme-lint-results/scripts-readme-lint-results.txt" + + echo "---" >> comment.md + echo "*Lint results updated at $(date)*" >> comment.md + + - name: Post Sticky PR Comment + if: github.event_name == 'pull_request' + uses: marocchino/sticky-pull-request-comment@v2 + with: + header: readme-lint-results + path: comment.md From dd160a5fda79d417b2846f0d71dce371988dc3c0 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Sat, 23 Aug 2025 08:43:51 -0500 Subject: [PATCH 3/3] feat: enhance linting script - improved error handling - a running count for each issue - adding emojis for each type of syntax issue --- .github/scripts/lint-readme.js | 57 +++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/.github/scripts/lint-readme.js b/.github/scripts/lint-readme.js index 9b044a3..8ea2104 100644 --- a/.github/scripts/lint-readme.js +++ b/.github/scripts/lint-readme.js @@ -19,10 +19,20 @@ function escapeRegExp(string) { const readmePath = path.join(directoryPath, 'README.md'); // Read README.md content +if (!fs.existsSync(readmePath)) { + console.log(`README.md not found at ${readmePath}`); + process.exit(1); +} const readme = fs.readFileSync(readmePath, 'utf8'); // Get all files tracked by Git -const gitFiles = execSync('git ls-files', { cwd: directoryPath, encoding: 'utf-8' }); +let gitFiles; +try { + gitFiles = execSync('git ls-files', { cwd: directoryPath, encoding: 'utf-8' }); +} catch (error) { + console.log(`Error running git ls-files in ${directoryPath}: ${error.message}`); + process.exit(1); +} // Split the output into an array of file paths const files = gitFiles.split('\n'); @@ -49,19 +59,23 @@ const allScripts = filteredFiles.concat(subdirectories); // Initialize a counter for the number of issues let issueCount = 0; +// Helper function to log numbered issues +function logIssue(message) { + issueCount++; + console.log(`${issueCount}. ${message}`); +} + // Check if each .sh file is mentioned in the README.md allScripts.forEach(file => { if (!readme.includes(`${headingLevel} ${file}`)) { - console.log(`The file ${file} is not mentioned in the README.md`); - issueCount++; + logIssue(`📝 The file ${file} is not mentioned in the README.md`); } }); // Check that all files follow the kebab-case naming convention allScripts.forEach(file => { if (!/^([a-z0-9]+-)*[a-z0-9]+(\.[a-z0-9]+)*$/.test(file)) { - console.log(`The file ${file} does not follow the kebab-case naming convention`); - issueCount++; + logIssue(`🔤 The file ${file} does not follow the kebab-case naming convention`); } }); @@ -76,8 +90,7 @@ allScripts.forEach(file => { const isExecutable = (stats.mode & fs.constants.X_OK) !== 0; if (!isExecutable) { - console.log(`The file ${file} does not have execution permissions`); - issueCount++; + logIssue(`🔒 The file ${file} does not have execution permissions`); } }); @@ -91,24 +104,33 @@ allScripts.forEach(file => { try { execSync(`bash -n "${filePath}"`, { stdio: 'pipe' }); } catch (error) { - console.log(`Bash syntax error in ${file}: ${error.stderr.toString().trim()}`); - issueCount++; + logIssue(`🐛 The file ${file} has a bash syntax error`); + const errorLines = error.stderr.toString().trim().split('\n'); + errorLines.forEach(line => console.log(` ${line}`)); } }); // Extract the part of the README under the ## Scripts heading const scriptsSection = readme.split(`${parentHeading}\n`)[1]; +if (!scriptsSection) { + console.log(`Section "${parentHeading}" not found in README.md`); + process.exit(1); +} // Extract all ### headings from the scripts section const regex = new RegExp(`${escapeRegExp(headingLevel)} .*`, 'g'); const headings = scriptsSection.match(regex); +if (!headings || headings.length === 0) { + console.log(`No headings found with level "${headingLevel}" in the scripts section`); + process.exit(1); +} + // Check that all scripts mentioned in the README.md actually exist in the repository headings.forEach(heading => { const script = heading.slice(headingLevel.length + 1); // Remove the '### ' prefix if (!allScripts.includes(script)) { - console.log(`The script ${script} is mentioned in the README.md but does not exist in the repository`); - issueCount++; + logIssue(`📁 The script ${script} is mentioned in the README.md but does not exist in the repository`); } }); @@ -124,8 +146,7 @@ allScripts.forEach(file => { Object.keys(shortWords).forEach(word => { const regex = new RegExp(`\\b${word}\\b`, 'g'); if (regex.test(file)) { - console.log(`The file name "${file}" uses the short word "${word}". Consider using "${shortWords[word]}" instead.`); - issueCount++; + logIssue(`📏 The file name "${file}" uses the short word "${word}". Consider using "${shortWords[word]}" instead.`); } }); }); @@ -136,16 +157,16 @@ for (let i = 0; i < headings.length - 1; i++) { const next = headings[i + 1].toLowerCase(); if (next.startsWith(current + '-') || (current > next && !current.startsWith(next + '-'))) { - console.log(`The heading "${headings[i + 1]}" is out of order. It should come before "${headings[i]}".`); - issueCount++; + logIssue(`📋 The heading "${headings[i + 1]}" is out of order. It should come before "${headings[i]}".`); break; } } -// If there are no issues, print a message +// Output final summary if (issueCount === 0) { - console.log('No issues found. ✅'); + console.log('✅ No issues found.'); } else { - console.log(`Found ${issueCount} issue(s). ❌`); + console.log(''); + console.log(`❌ Found ${issueCount} issue${issueCount === 1 ? '' : 's'} that need to be addressed.`); process.exit(1); }