3737// Split the output into an array of file paths
3838const files = gitFiles . split ( '\n' ) ;
3939
40- // Filter out .sh files in the root directory (excluding those in subdirectories)
40+ // Filter files in the root directory (excluding those in subdirectories)
4141const fileExtensions = [ '.sh' , '.ps1' , '.js' , '.mjs' , '.py' ] ;
4242
4343const filteredFiles = files . filter ( file => {
@@ -65,46 +65,76 @@ function logIssue(message) {
6565 console . log ( `${ issueCount } . ${ message } ` ) ;
6666}
6767
68- // Check if each .sh file is mentioned in the README.md
69- allScripts . forEach ( file => {
70- if ( ! readme . includes ( `${ headingLevel } ${ file } ` ) ) {
71- logIssue ( `📝 The file ${ file } is not mentioned in the README.md` ) ;
68+ /**
69+ * Determines if a path is a file, directory, or doesn't exist
70+ * @param {string } itemPath - Path to check
71+ * @returns {string|null } - 'file', 'directory', or null if doesn't exist
72+ */
73+ function getItemType ( itemPath ) {
74+ if ( ! fs . existsSync ( itemPath ) ) {
75+ return null ; // doesn't exist
76+ }
77+ return fs . statSync ( itemPath ) . isDirectory ( ) ? 'directory' : 'file' ;
78+ }
79+
80+ // Check if each file/directory is mentioned in the README.md
81+ allScripts . forEach ( item => {
82+ if ( ! readme . includes ( `${ headingLevel } ${ item } ` ) ) {
83+ const itemPath = path . join ( directoryPath , item ) ;
84+ const type = getItemType ( itemPath ) || 'file/directory' ;
85+ logIssue ( `📝 The ${ type } ${ item } is not mentioned in the README.md` ) ;
7286 }
7387} ) ;
7488
7589// Check that all files follow the kebab-case naming convention
76- allScripts . forEach ( file => {
77- if ( ! / ^ ( [ a - z 0 - 9 ] + - ) * [ a - z 0 - 9 ] + ( \. [ a - z 0 - 9 ] + ) * $ / . test ( file ) ) {
78- logIssue ( `🔤 The file ${ file } does not follow the kebab-case naming convention` ) ;
90+ allScripts . forEach ( item => {
91+ if ( ! / ^ ( [ a - z 0 - 9 ] + - ) * [ a - z 0 - 9 ] + ( \. [ a - z 0 - 9 ] + ) * $ / . test ( item ) ) {
92+ const itemPath = path . join ( directoryPath , item ) ;
93+ const type = getItemType ( itemPath ) || 'file' ;
94+ logIssue ( `🔤 The ${ type } ${ item } does not follow the kebab-case naming convention` ) ;
7995 }
8096} ) ;
8197
8298// Check that all .sh files have execution permissions
83- allScripts . forEach ( file => {
84- if ( ! file . endsWith ( '.sh' ) ) {
99+ allScripts . forEach ( item => {
100+ if ( ! item . endsWith ( '.sh' ) ) {
85101 return ;
86102 }
87103
88- const filePath = path . join ( directoryPath , file ) ;
89- const stats = fs . statSync ( filePath ) ;
104+ const itemPath = path . join ( directoryPath , item ) ;
105+
106+ // Check if file exists before trying to stat it
107+ if ( ! fs . existsSync ( itemPath ) ) {
108+ logIssue ( `⚠️ The file ${ item } is tracked in Git but does not exist on the filesystem` ) ;
109+ return ;
110+ }
111+
112+ const stats = fs . statSync ( itemPath ) ;
90113 const isExecutable = ( stats . mode & fs . constants . X_OK ) !== 0 ;
91114
92115 if ( ! isExecutable ) {
93- logIssue ( `🔒 The file ${ file } does not have execution permissions` ) ;
116+ logIssue ( `🔒 The file ${ item } does not have execution permissions` ) ;
94117 }
95118} ) ;
96119
97120// Check bash syntax for all .sh files
98- allScripts . forEach ( file => {
99- if ( ! file . endsWith ( '.sh' ) ) {
121+ allScripts . forEach ( item => {
122+ if ( ! item . endsWith ( '.sh' ) ) {
100123 return ;
101124 }
102125
103- const filePath = path . join ( directoryPath , file ) ;
126+ const itemPath = path . join ( directoryPath , item ) ;
127+
128+ // Check if file exists before trying to validate syntax
129+ if ( ! fs . existsSync ( itemPath ) ) {
130+ // Already reported in the execution permissions check
131+ return ;
132+ }
133+
104134 try {
105- execSync ( `bash -n "${ filePath } "` , { stdio : 'pipe' } ) ;
135+ execSync ( `bash -n "${ itemPath } "` , { stdio : 'pipe' } ) ;
106136 } catch ( error ) {
107- logIssue ( `🐛 The file ${ file } has a bash syntax error` ) ;
137+ logIssue ( `🐛 The file ${ item } has a bash syntax error` ) ;
108138 const errorLines = error . stderr . toString ( ) . trim ( ) . split ( '\n' ) ;
109139 errorLines . forEach ( line => console . log ( ` ${ line } ` ) ) ;
110140 }
@@ -126,32 +156,36 @@ if (!headings || headings.length === 0) {
126156 process . exit ( 1 ) ;
127157}
128158
129- // Check that all scripts mentioned in the README.md actually exist in the repository
159+ // Check that all items mentioned in the README.md actually exist in the repository
130160headings . forEach ( heading => {
131- const script = heading . slice ( headingLevel . length + 1 ) ; // Remove the '### ' prefix
132- if ( ! allScripts . includes ( script ) ) {
133- logIssue ( `📁 The script ${ script } is mentioned in the README.md but does not exist in the repository ` ) ;
161+ const item = heading . slice ( headingLevel . length + 1 ) ; // Remove the '### ' prefix
162+ if ( ! allScripts . includes ( item ) ) {
163+ logIssue ( `📁 The item " ${ item } " is mentioned in the README.md but does not exist at " ${ path . join ( directoryPath , item ) } " ` ) ;
134164 }
135165} ) ;
136166
137- // Check that certain short words are not used in the .sh file names
167+ // Check that certain short words are not used in file/directory names
138168const shortWords = {
139169 'repo' : 'repository' ,
140170 'repos' : 'repositories' ,
141171 'org' : 'organization' ,
142172 'orgs' : 'organizations'
143173} ;
144174
145- allScripts . forEach ( file => {
175+ allScripts . forEach ( item => {
146176 Object . keys ( shortWords ) . forEach ( word => {
147177 const regex = new RegExp ( `\\b${ word } \\b` , 'g' ) ;
148- if ( regex . test ( file ) ) {
149- logIssue ( `📏 The file name "${ file } " uses the short word "${ word } ". Consider using "${ shortWords [ word ] } " instead.` ) ;
178+ if ( regex . test ( item ) ) {
179+ const itemPath = path . join ( directoryPath , item ) ;
180+ const type = getItemType ( itemPath ) || 'file' ;
181+ logIssue ( `📏 The ${ type } name "${ item } " uses the short word "${ word } ". Consider using "${ shortWords [ word ] } " instead.` ) ;
150182 }
151183 } ) ;
152184} ) ;
153185
154186// Check if the headings are in alphabetical order
187+ // Special handling: prefixed items (e.g., 'add-user') should come after their prefix base (e.g., 'add')
188+ // but 'add-team-to-repository' should come before 'add-user' (standard alphabetical)
155189for ( let i = 0 ; i < headings . length - 1 ; i ++ ) {
156190 const current = headings [ i ] . toLowerCase ( ) ;
157191 const next = headings [ i + 1 ] . toLowerCase ( ) ;
0 commit comments