37
37
// Split the output into an array of file paths
38
38
const files = gitFiles . split ( '\n' ) ;
39
39
40
- // Filter out .sh files in the root directory (excluding those in subdirectories)
40
+ // Filter files in the root directory (excluding those in subdirectories)
41
41
const fileExtensions = [ '.sh' , '.ps1' , '.js' , '.mjs' , '.py' ] ;
42
42
43
43
const filteredFiles = files . filter ( file => {
@@ -65,46 +65,76 @@ function logIssue(message) {
65
65
console . log ( `${ issueCount } . ${ message } ` ) ;
66
66
}
67
67
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` ) ;
72
86
}
73
87
} ) ;
74
88
75
89
// 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` ) ;
79
95
}
80
96
} ) ;
81
97
82
98
// 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' ) ) {
85
101
return ;
86
102
}
87
103
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 ) ;
90
113
const isExecutable = ( stats . mode & fs . constants . X_OK ) !== 0 ;
91
114
92
115
if ( ! isExecutable ) {
93
- logIssue ( `🔒 The file ${ file } does not have execution permissions` ) ;
116
+ logIssue ( `🔒 The file ${ item } does not have execution permissions` ) ;
94
117
}
95
118
} ) ;
96
119
97
120
// 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' ) ) {
100
123
return ;
101
124
}
102
125
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
+
104
134
try {
105
- execSync ( `bash -n "${ filePath } "` , { stdio : 'pipe' } ) ;
135
+ execSync ( `bash -n "${ itemPath } "` , { stdio : 'pipe' } ) ;
106
136
} catch ( error ) {
107
- logIssue ( `🐛 The file ${ file } has a bash syntax error` ) ;
137
+ logIssue ( `🐛 The file ${ item } has a bash syntax error` ) ;
108
138
const errorLines = error . stderr . toString ( ) . trim ( ) . split ( '\n' ) ;
109
139
errorLines . forEach ( line => console . log ( ` ${ line } ` ) ) ;
110
140
}
@@ -126,32 +156,36 @@ if (!headings || headings.length === 0) {
126
156
process . exit ( 1 ) ;
127
157
}
128
158
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
130
160
headings . 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 ) } " ` ) ;
134
164
}
135
165
} ) ;
136
166
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
138
168
const shortWords = {
139
169
'repo' : 'repository' ,
140
170
'repos' : 'repositories' ,
141
171
'org' : 'organization' ,
142
172
'orgs' : 'organizations'
143
173
} ;
144
174
145
- allScripts . forEach ( file => {
175
+ allScripts . forEach ( item => {
146
176
Object . keys ( shortWords ) . forEach ( word => {
147
177
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.` ) ;
150
182
}
151
183
} ) ;
152
184
} ) ;
153
185
154
186
// 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)
155
189
for ( let i = 0 ; i < headings . length - 1 ; i ++ ) {
156
190
const current = headings [ i ] . toLowerCase ( ) ;
157
191
const next = headings [ i + 1 ] . toLowerCase ( ) ;
0 commit comments