From 1ae662722e535f80e29c75656e446fa00f85967c Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Thu, 2 Oct 2025 13:43:25 -0500 Subject: [PATCH 1/7] ci: update linting error message to distinguish between files and directories --- .github/scripts/lint-readme.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/scripts/lint-readme.js b/.github/scripts/lint-readme.js index 8ea2104..fdce3b6 100644 --- a/.github/scripts/lint-readme.js +++ b/.github/scripts/lint-readme.js @@ -65,10 +65,14 @@ function logIssue(message) { console.log(`${issueCount}. ${message}`); } -// Check if each .sh file is mentioned in the README.md +// Check if each file/directory is mentioned in the README.md allScripts.forEach(file => { if (!readme.includes(`${headingLevel} ${file}`)) { - logIssue(`📝 The file ${file} is not mentioned in the README.md`); + // Check if it's a directory or a file + const filePath = path.join(directoryPath, file); + const isDirectory = fs.existsSync(filePath) && fs.statSync(filePath).isDirectory(); + const type = isDirectory ? 'directory' : 'file'; + logIssue(`📝 The ${type} ${file} is not mentioned in the README.md`); } }); From 11bb59bd3cf2a6078aa5ce7aa013b9b2c598643c Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Thu, 2 Oct 2025 13:43:42 -0500 Subject: [PATCH 2/7] fix: add existence checks for files tracked in Git before validation --- .github/scripts/lint-readme.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/scripts/lint-readme.js b/.github/scripts/lint-readme.js index fdce3b6..d6b51d3 100644 --- a/.github/scripts/lint-readme.js +++ b/.github/scripts/lint-readme.js @@ -90,6 +90,13 @@ allScripts.forEach(file => { } const filePath = path.join(directoryPath, file); + + // Check if file exists before trying to stat it + if (!fs.existsSync(filePath)) { + logIssue(`⚠️ The file ${file} is tracked in Git but does not exist on the filesystem`); + return; + } + const stats = fs.statSync(filePath); const isExecutable = (stats.mode & fs.constants.X_OK) !== 0; @@ -105,6 +112,13 @@ allScripts.forEach(file => { } const filePath = path.join(directoryPath, file); + + // Check if file exists before trying to validate syntax + if (!fs.existsSync(filePath)) { + // Already reported in the execution permissions check + return; + } + try { execSync(`bash -n "${filePath}"`, { stdio: 'pipe' }); } catch (error) { From 5eab436fac0d61ac80fe145bd60ca6d3363ce8c9 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Thu, 2 Oct 2025 13:52:14 -0500 Subject: [PATCH 3/7] fix: improve file/directory type detection in README.md validation --- .github/scripts/lint-readme.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/scripts/lint-readme.js b/.github/scripts/lint-readme.js index d6b51d3..7061a2d 100644 --- a/.github/scripts/lint-readme.js +++ b/.github/scripts/lint-readme.js @@ -68,10 +68,13 @@ function logIssue(message) { // Check if each file/directory is mentioned in the README.md allScripts.forEach(file => { if (!readme.includes(`${headingLevel} ${file}`)) { - // Check if it's a directory or a file const filePath = path.join(directoryPath, file); - const isDirectory = fs.existsSync(filePath) && fs.statSync(filePath).isDirectory(); - const type = isDirectory ? 'directory' : 'file'; + let type = 'file'; + if (fs.existsSync(filePath)) { + type = fs.statSync(filePath).isDirectory() ? 'directory' : 'file'; + } else { + type = 'file/directory'; // or 'script' as generic term + } logIssue(`📝 The ${type} ${file} is not mentioned in the README.md`); } }); From 9f42fbcb7ccef88ca59bf77822b9b3a0f921b5aa Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Thu, 2 Oct 2025 13:57:52 -0500 Subject: [PATCH 4/7] fix: refactor item type detection for README.md validation and improve naming checks --- .github/scripts/lint-readme.js | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/.github/scripts/lint-readme.js b/.github/scripts/lint-readme.js index 7061a2d..4313bd3 100644 --- a/.github/scripts/lint-readme.js +++ b/.github/scripts/lint-readme.js @@ -65,24 +65,29 @@ function logIssue(message) { console.log(`${issueCount}. ${message}`); } +// Helper function to determine if a path is a directory, file, or doesn't exist +function getItemType(itemPath) { + if (!fs.existsSync(itemPath)) { + return null; // doesn't exist + } + return fs.statSync(itemPath).isDirectory() ? 'directory' : 'file'; +} + // Check if each file/directory is mentioned in the README.md allScripts.forEach(file => { if (!readme.includes(`${headingLevel} ${file}`)) { const filePath = path.join(directoryPath, file); - let type = 'file'; - if (fs.existsSync(filePath)) { - type = fs.statSync(filePath).isDirectory() ? 'directory' : 'file'; - } else { - type = 'file/directory'; // or 'script' as generic term - } + const type = getItemType(filePath) || 'file/directory'; logIssue(`📝 The ${type} ${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)) { - logIssue(`🔤 The file ${file} does not follow the kebab-case naming convention`); +allScripts.forEach(item => { + if (!/^([a-z0-9]+-)*[a-z0-9]+(\.[a-z0-9]+)*$/.test(item)) { + const itemPath = path.join(directoryPath, item); + const type = getItemType(itemPath) || 'file'; + logIssue(`🔤 The ${type} ${item} does not follow the kebab-case naming convention`); } }); @@ -155,7 +160,7 @@ headings.forEach(heading => { } }); -// Check that certain short words are not used in the .sh file names +// Check that certain short words are not used in file/directory names const shortWords = { 'repo': 'repository', 'repos': 'repositories', @@ -163,11 +168,13 @@ const shortWords = { 'orgs': 'organizations' }; -allScripts.forEach(file => { +allScripts.forEach(item => { Object.keys(shortWords).forEach(word => { const regex = new RegExp(`\\b${word}\\b`, 'g'); - if (regex.test(file)) { - logIssue(`📏 The file name "${file}" uses the short word "${word}". Consider using "${shortWords[word]}" instead.`); + if (regex.test(item)) { + const itemPath = path.join(directoryPath, item); + const type = getItemType(itemPath) || 'file'; + logIssue(`📏 The ${type} name "${item}" uses the short word "${word}". Consider using "${shortWords[word]}" instead.`); } }); }); From 5634490b5e6969a42bb05f0df65b55c2373d87e0 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Thu, 2 Oct 2025 14:01:05 -0500 Subject: [PATCH 5/7] fix: unify variable naming for file and directory checks in README.md validation --- .github/scripts/lint-readme.js | 44 +++++++++++++++++----------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/.github/scripts/lint-readme.js b/.github/scripts/lint-readme.js index 4313bd3..b225110 100644 --- a/.github/scripts/lint-readme.js +++ b/.github/scripts/lint-readme.js @@ -74,11 +74,11 @@ function getItemType(itemPath) { } // Check if each file/directory is mentioned in the README.md -allScripts.forEach(file => { - if (!readme.includes(`${headingLevel} ${file}`)) { - const filePath = path.join(directoryPath, file); - const type = getItemType(filePath) || 'file/directory'; - logIssue(`📝 The ${type} ${file} is not mentioned in the README.md`); +allScripts.forEach(item => { + if (!readme.includes(`${headingLevel} ${item}`)) { + const itemPath = path.join(directoryPath, item); + const type = getItemType(itemPath) || 'file/directory'; + logIssue(`📝 The ${type} ${item} is not mentioned in the README.md`); } }); @@ -92,45 +92,45 @@ allScripts.forEach(item => { }); // Check that all .sh files have execution permissions -allScripts.forEach(file => { - if (!file.endsWith('.sh')) { +allScripts.forEach(item => { + if (!item.endsWith('.sh')) { return; } - const filePath = path.join(directoryPath, file); + const itemPath = path.join(directoryPath, item); // Check if file exists before trying to stat it - if (!fs.existsSync(filePath)) { - logIssue(`⚠️ The file ${file} is tracked in Git but does not exist on the filesystem`); + if (!fs.existsSync(itemPath)) { + logIssue(`⚠️ The file ${item} is tracked in Git but does not exist on the filesystem`); return; } - const stats = fs.statSync(filePath); + const stats = fs.statSync(itemPath); const isExecutable = (stats.mode & fs.constants.X_OK) !== 0; if (!isExecutable) { - logIssue(`🔒 The file ${file} does not have execution permissions`); + logIssue(`🔒 The file ${item} does not have execution permissions`); } }); // Check bash syntax for all .sh files -allScripts.forEach(file => { - if (!file.endsWith('.sh')) { +allScripts.forEach(item => { + if (!item.endsWith('.sh')) { return; } - const filePath = path.join(directoryPath, file); + const itemPath = path.join(directoryPath, item); // Check if file exists before trying to validate syntax - if (!fs.existsSync(filePath)) { + if (!fs.existsSync(itemPath)) { // Already reported in the execution permissions check return; } try { - execSync(`bash -n "${filePath}"`, { stdio: 'pipe' }); + execSync(`bash -n "${itemPath}"`, { stdio: 'pipe' }); } catch (error) { - logIssue(`🐛 The file ${file} has a bash syntax error`); + logIssue(`🐛 The file ${item} has a bash syntax error`); const errorLines = error.stderr.toString().trim().split('\n'); errorLines.forEach(line => console.log(` ${line}`)); } @@ -152,11 +152,11 @@ if (!headings || headings.length === 0) { process.exit(1); } -// Check that all scripts mentioned in the README.md actually exist in the repository +// Check that all items 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)) { - logIssue(`📁 The script ${script} is mentioned in the README.md but does not exist in the repository`); + const item = heading.slice(headingLevel.length + 1); // Remove the '### ' prefix + if (!allScripts.includes(item)) { + logIssue(`📁 The item "${item}" is mentioned in the README.md but does not exist in the repository`); } }); From f7156efc1cc11a57dbd1a7f921cd1a811cc07220 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Thu, 2 Oct 2025 14:01:38 -0500 Subject: [PATCH 6/7] fix: clarify comment for filtering files in README.md validation --- .github/scripts/lint-readme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/lint-readme.js b/.github/scripts/lint-readme.js index b225110..d6f1479 100644 --- a/.github/scripts/lint-readme.js +++ b/.github/scripts/lint-readme.js @@ -37,7 +37,7 @@ try { // Split the output into an array of file paths const files = gitFiles.split('\n'); -// Filter out .sh files in the root directory (excluding those in subdirectories) +// Filter files in the root directory (excluding those in subdirectories) const fileExtensions = ['.sh', '.ps1', '.js', '.mjs', '.py']; const filteredFiles = files.filter(file => { From 7fcc153f7e776e2ca516866c27728fc7f1422142 Mon Sep 17 00:00:00 2001 From: Josh Johanning Date: Thu, 2 Oct 2025 14:09:25 -0500 Subject: [PATCH 7/7] fix: enhance README.md validation with improved item path logging and additional comments --- .github/scripts/lint-readme.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/scripts/lint-readme.js b/.github/scripts/lint-readme.js index d6f1479..6037012 100644 --- a/.github/scripts/lint-readme.js +++ b/.github/scripts/lint-readme.js @@ -65,7 +65,11 @@ function logIssue(message) { console.log(`${issueCount}. ${message}`); } -// Helper function to determine if a path is a directory, file, or doesn't exist +/** + * Determines if a path is a file, directory, or doesn't exist + * @param {string} itemPath - Path to check + * @returns {string|null} - 'file', 'directory', or null if doesn't exist + */ function getItemType(itemPath) { if (!fs.existsSync(itemPath)) { return null; // doesn't exist @@ -98,7 +102,7 @@ allScripts.forEach(item => { } const itemPath = path.join(directoryPath, item); - + // Check if file exists before trying to stat it if (!fs.existsSync(itemPath)) { logIssue(`⚠️ The file ${item} is tracked in Git but does not exist on the filesystem`); @@ -120,7 +124,7 @@ allScripts.forEach(item => { } const itemPath = path.join(directoryPath, item); - + // Check if file exists before trying to validate syntax if (!fs.existsSync(itemPath)) { // Already reported in the execution permissions check @@ -156,7 +160,7 @@ if (!headings || headings.length === 0) { headings.forEach(heading => { const item = heading.slice(headingLevel.length + 1); // Remove the '### ' prefix if (!allScripts.includes(item)) { - logIssue(`📁 The item "${item}" is mentioned in the README.md but does not exist in the repository`); + logIssue(`📁 The item "${item}" is mentioned in the README.md but does not exist at "${path.join(directoryPath, item)}"`); } }); @@ -180,6 +184,8 @@ allScripts.forEach(item => { }); // Check if the headings are in alphabetical order +// Special handling: prefixed items (e.g., 'add-user') should come after their prefix base (e.g., 'add') +// but 'add-team-to-repository' should come before 'add-user' (standard alphabetical) for (let i = 0; i < headings.length - 1; i++) { const current = headings[i].toLowerCase(); const next = headings[i + 1].toLowerCase();