Skip to content

Commit d76bedc

Browse files
Enhance linting script and workflow (#117)
* fix: add missing permissions for lint jobs in workflow * ci: posting results of linting as sticky PR comment * feat: enhance linting script - improved error handling - a running count for each issue - adding emojis for each type of syntax issue
1 parent 41af375 commit d76bedc

File tree

2 files changed

+113
-20
lines changed

2 files changed

+113
-20
lines changed

.github/scripts/lint-readme.js

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,20 @@ function escapeRegExp(string) {
1919
const readmePath = path.join(directoryPath, 'README.md');
2020

2121
// Read README.md content
22+
if (!fs.existsSync(readmePath)) {
23+
console.log(`README.md not found at ${readmePath}`);
24+
process.exit(1);
25+
}
2226
const readme = fs.readFileSync(readmePath, 'utf8');
2327

2428
// Get all files tracked by Git
25-
const gitFiles = execSync('git ls-files', { cwd: directoryPath, encoding: 'utf-8' });
29+
let gitFiles;
30+
try {
31+
gitFiles = execSync('git ls-files', { cwd: directoryPath, encoding: 'utf-8' });
32+
} catch (error) {
33+
console.log(`Error running git ls-files in ${directoryPath}: ${error.message}`);
34+
process.exit(1);
35+
}
2636

2737
// Split the output into an array of file paths
2838
const files = gitFiles.split('\n');
@@ -49,19 +59,23 @@ const allScripts = filteredFiles.concat(subdirectories);
4959
// Initialize a counter for the number of issues
5060
let issueCount = 0;
5161

62+
// Helper function to log numbered issues
63+
function logIssue(message) {
64+
issueCount++;
65+
console.log(`${issueCount}. ${message}`);
66+
}
67+
5268
// Check if each .sh file is mentioned in the README.md
5369
allScripts.forEach(file => {
5470
if (!readme.includes(`${headingLevel} ${file}`)) {
55-
console.log(`The file ${file} is not mentioned in the README.md`);
56-
issueCount++;
71+
logIssue(`📝 The file ${file} is not mentioned in the README.md`);
5772
}
5873
});
5974

6075
// Check that all files follow the kebab-case naming convention
6176
allScripts.forEach(file => {
6277
if (!/^([a-z0-9]+-)*[a-z0-9]+(\.[a-z0-9]+)*$/.test(file)) {
63-
console.log(`The file ${file} does not follow the kebab-case naming convention`);
64-
issueCount++;
78+
logIssue(`🔤 The file ${file} does not follow the kebab-case naming convention`);
6579
}
6680
});
6781

@@ -76,8 +90,7 @@ allScripts.forEach(file => {
7690
const isExecutable = (stats.mode & fs.constants.X_OK) !== 0;
7791

7892
if (!isExecutable) {
79-
console.log(`The file ${file} does not have execution permissions`);
80-
issueCount++;
93+
logIssue(`🔒 The file ${file} does not have execution permissions`);
8194
}
8295
});
8396

@@ -91,24 +104,33 @@ allScripts.forEach(file => {
91104
try {
92105
execSync(`bash -n "${filePath}"`, { stdio: 'pipe' });
93106
} catch (error) {
94-
console.log(`Bash syntax error in ${file}: ${error.stderr.toString().trim()}`);
95-
issueCount++;
107+
logIssue(`🐛 The file ${file} has a bash syntax error`);
108+
const errorLines = error.stderr.toString().trim().split('\n');
109+
errorLines.forEach(line => console.log(` ${line}`));
96110
}
97111
});
98112

99113
// Extract the part of the README under the ## Scripts heading
100114
const scriptsSection = readme.split(`${parentHeading}\n`)[1];
115+
if (!scriptsSection) {
116+
console.log(`Section "${parentHeading}" not found in README.md`);
117+
process.exit(1);
118+
}
101119

102120
// Extract all ### headings from the scripts section
103121
const regex = new RegExp(`${escapeRegExp(headingLevel)} .*`, 'g');
104122
const headings = scriptsSection.match(regex);
105123

124+
if (!headings || headings.length === 0) {
125+
console.log(`No headings found with level "${headingLevel}" in the scripts section`);
126+
process.exit(1);
127+
}
128+
106129
// Check that all scripts mentioned in the README.md actually exist in the repository
107130
headings.forEach(heading => {
108131
const script = heading.slice(headingLevel.length + 1); // Remove the '### ' prefix
109132
if (!allScripts.includes(script)) {
110-
console.log(`The script ${script} is mentioned in the README.md but does not exist in the repository`);
111-
issueCount++;
133+
logIssue(`📁 The script ${script} is mentioned in the README.md but does not exist in the repository`);
112134
}
113135
});
114136

@@ -124,8 +146,7 @@ allScripts.forEach(file => {
124146
Object.keys(shortWords).forEach(word => {
125147
const regex = new RegExp(`\\b${word}\\b`, 'g');
126148
if (regex.test(file)) {
127-
console.log(`The file name "${file}" uses the short word "${word}". Consider using "${shortWords[word]}" instead.`);
128-
issueCount++;
149+
logIssue(`📏 The file name "${file}" uses the short word "${word}". Consider using "${shortWords[word]}" instead.`);
129150
}
130151
});
131152
});
@@ -136,16 +157,16 @@ for (let i = 0; i < headings.length - 1; i++) {
136157
const next = headings[i + 1].toLowerCase();
137158

138159
if (next.startsWith(current + '-') || (current > next && !current.startsWith(next + '-'))) {
139-
console.log(`The heading "${headings[i + 1]}" is out of order. It should come before "${headings[i]}".`);
140-
issueCount++;
160+
logIssue(`📋 The heading "${headings[i + 1]}" is out of order. It should come before "${headings[i]}".`);
141161
break;
142162
}
143163
}
144164

145-
// If there are no issues, print a message
165+
// Output final summary
146166
if (issueCount === 0) {
147-
console.log('No issues found.');
167+
console.log('No issues found.');
148168
} else {
149-
console.log(`Found ${issueCount} issue(s). ❌`);
169+
console.log('');
170+
console.log(`❌ Found ${issueCount} issue${issueCount === 1 ? '' : 's'} that need to be addressed.`);
150171
process.exit(1);
151172
}

.github/workflows/lint-readme.yml

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,93 @@ jobs:
1212
lint-gh-cli-readme:
1313
name: Lint ./gh-cli/README.md
1414
runs-on: ubuntu-latest
15+
permissions:
16+
contents: read
1517

1618
steps:
1719
- name: Checkout code
1820
uses: actions/checkout@v4
1921

2022
- name: Lint ./gh-cli/README.md
21-
run: node ./.github/scripts/lint-readme.js
23+
id: lint
24+
run: node ./.github/scripts/lint-readme.js | tee gh-cli-readme-lint-results.txt
25+
26+
- name: Upload lint results
27+
if: steps.lint.outcome == 'failure' || steps.lint.outcome == 'success'
28+
uses: actions/upload-artifact@v4
29+
with:
30+
name: gh-cli-readme-lint-results
31+
path: gh-cli-readme-lint-results.txt
2232

2333
lint-scripts-readme:
2434
name: Lint ./scripts/README.md
2535
runs-on: ubuntu-latest
36+
permissions:
37+
contents: read
2638

2739
steps:
2840
- name: Checkout code
2941
uses: actions/checkout@v4
3042

3143
- name: Lint ./scripts/README.md
32-
run: node ./.github/scripts/lint-readme.js ./scripts '##' '# scripts'
44+
id: lint
45+
run: node ./.github/scripts/lint-readme.js ./scripts '##' '# scripts' | tee scripts-readme-lint-results.txt
46+
47+
- name: Upload lint results
48+
if: steps.lint.outcome == 'failure' || steps.lint.outcome == 'success'
49+
uses: actions/upload-artifact@v4
50+
with:
51+
name: scripts-readme-lint-results
52+
path: scripts-readme-lint-results.txt
53+
54+
post-results:
55+
name: Post Lint Results as PR Comment
56+
runs-on: ubuntu-latest
57+
if: always()
58+
needs:
59+
- lint-gh-cli-readme
60+
- lint-scripts-readme
61+
permissions:
62+
pull-requests: write
63+
64+
steps:
65+
- name: Download lint results
66+
uses: actions/download-artifact@v5
67+
with:
68+
path: ./lint-results/
69+
70+
- name: Process Lint Results
71+
id: process-results
72+
run: |
73+
echo "## 📋 README Lint Results" > comment.md
74+
echo "" >> comment.md
75+
76+
# Function to process lint results
77+
process_lint_results() {
78+
local title="$1"
79+
local file="$2"
80+
81+
echo "### $title" >> comment.md
82+
echo "\`\`\`" >> comment.md
83+
if [ -f "$file" ]; then
84+
cat "$file" >> comment.md
85+
else
86+
echo "❌ No results available" >> comment.md
87+
fi
88+
echo "\`\`\`" >> comment.md
89+
echo "" >> comment.md
90+
}
91+
92+
# Process both README results
93+
process_lint_results "⚡ gh-cli/README.md" "./lint-results/gh-cli-readme-lint-results/gh-cli-readme-lint-results.txt"
94+
process_lint_results "🔧 scripts/README.md" "./lint-results/scripts-readme-lint-results/scripts-readme-lint-results.txt"
95+
96+
echo "---" >> comment.md
97+
echo "*Lint results updated at $(date)*" >> comment.md
98+
99+
- name: Post Sticky PR Comment
100+
if: github.event_name == 'pull_request'
101+
uses: marocchino/sticky-pull-request-comment@v2
102+
with:
103+
header: readme-lint-results
104+
path: comment.md

0 commit comments

Comments
 (0)