@@ -83,39 +83,58 @@ function resolveDomainSelection(options: CLIOptions): {
8383 return { selectedDomains, shellDomainExplicitlyRequested } ;
8484}
8585
86- type MainDeps = {
87- runESLint : typeof utils . runESLint ;
88- findUserESLintConfig: typeof utils . findUserESLintConfig ;
89- collectMarkdown: typeof utils . collectMarkdown ;
90- commandExists: typeof utils . commandExists ;
91- execFileSync: typeof childProcess . execFileSync ;
92- fileExists: typeof fs . existsSync ;
93- parseAsync: ( argv : string [ ] ) => Promise < void > ;
94- opts: ( ) => CLIOptions ;
95- } ;
96-
97- const defaultMainDeps : MainDeps = {
98- runESLint : utils . runESLint ,
99- findUserESLintConfig : utils . findUserESLintConfig ,
100- collectMarkdown : utils . collectMarkdown ,
101- commandExists : utils . commandExists ,
102- execFileSync : ( ( ...args : unknown [ ] ) =>
103- childProcess . execFileSync (
104- args [ 0 ] as string ,
105- args [ 1 ] as ReadonlyArray < string > | undefined ,
106- args [ 2 ] as childProcess . ExecFileSyncOptions ,
107- ) ) as typeof childProcess . execFileSync ,
108- fileExists : fs . existsSync ,
109- parseAsync : async ( argv ) => {
110- await program . parseAsync ( argv ) ;
111- } ,
112- opts : ( ) => program . opts < CLIOptions > ( ) ,
113- } ;
86+ const SHELL_FILE_PATTERN = / \. s h $ / i;
87+
88+ function collectShellFiles ( searchRoots : string [ ] ) : string [ ] {
89+ const shellFiles = new Set < string > ( ) ;
90+
91+ const visitPath = ( entryPath : string ) : void => {
92+ let entryStats : fs . Stats ;
93+ try {
94+ entryStats = fs . statSync ( entryPath ) ;
95+ } catch {
96+ return ;
97+ }
98+
99+ if ( entryStats . isFile ( ) ) {
100+ if ( SHELL_FILE_PATTERN . test ( entryPath ) ) {
101+ shellFiles . add ( entryPath ) ;
102+ }
103+ return ;
104+ }
105+
106+ if ( ! entryStats . isDirectory ( ) ) {
107+ return ;
108+ }
109+
110+ let dirEntries : fs . Dirent [ ] ;
111+ try {
112+ dirEntries = fs . readdirSync ( entryPath , { withFileTypes : true } ) ;
113+ } catch {
114+ return ;
115+ }
116+
117+ for ( const dirEntry of dirEntries ) {
118+ const childPath = path . join ( entryPath , dirEntry . name ) ;
119+ if ( dirEntry . isDirectory ( ) ) {
120+ visitPath ( childPath ) ;
121+ } else if ( dirEntry . isFile ( ) && SHELL_FILE_PATTERN . test ( dirEntry . name ) ) {
122+ shellFiles . add ( childPath ) ;
123+ }
124+ }
125+ } ;
126+
127+ for ( const searchRoot of searchRoots ) {
128+ visitPath ( searchRoot ) ;
129+ }
130+
131+ return [ ...shellFiles ] . sort ( ) ;
132+ }
114133
115134/* eslint-disable no-console */
116135async function main ( argv = process . argv ) {
117- await defaultMainDeps . parseAsync ( argv ) ;
118- const options = defaultMainDeps . opts ( ) ;
136+ await program . parseAsync ( argv ) ;
137+ const options = program . opts < CLIOptions > ( ) ;
119138
120139 const fix = Boolean ( options . fix ) ;
121140 const useUserConfig = Boolean ( options . userConfig ) ;
@@ -135,7 +154,7 @@ async function main(argv = process.argv) {
135154 if ( explicitConfigPath !== undefined ) {
136155 const absolutePath = path . resolve ( explicitConfigPath ) ;
137156
138- if ( ! defaultMainDeps . fileExists ( absolutePath ) ) {
157+ if ( ! fs . existsSync ( absolutePath ) ) {
139158 console . error (
140159 `--config points to "${ explicitConfigPath } ", but that file does not exist.` ,
141160 ) ;
@@ -145,7 +164,7 @@ async function main(argv = process.argv) {
145164 chosenConfig = absolutePath ;
146165 }
147166 } else if ( useUserConfig ) {
148- chosenConfig = defaultMainDeps . findUserESLintConfig ( ) ;
167+ chosenConfig = utils . findUserESLintConfig ( ) ;
149168 if ( chosenConfig === undefined ) {
150169 console . error (
151170 '--user-config given but no local ESLint config was found. Falling back to built-in config.' ,
@@ -159,7 +178,7 @@ async function main(argv = process.argv) {
159178 hadFailure = true ;
160179 } else {
161180 try {
162- const hadLintingErrors = await defaultMainDeps . runESLint ( {
181+ const hadLintingErrors = await utils . runESLint ( {
163182 fix,
164183 configPath : chosenConfig ,
165184 explicitGlobs : eslintPatterns ,
@@ -176,60 +195,51 @@ async function main(argv = process.argv) {
176195 }
177196
178197 if ( selectedDomains . has ( 'shell' ) ) {
179- const hasFind = defaultMainDeps . commandExists ( 'find' ) ;
180- const hasShellcheck = defaultMainDeps . commandExists ( 'shellcheck' ) ;
198+ const hasShellcheck = utils . commandExists ( 'shellcheck' ) ;
181199
182- if ( ! ( hasFind && hasShellcheck ) ) {
200+ if ( ! hasShellcheck ) {
183201 if ( shellDomainExplicitlyRequested ) {
184202 console . error (
185- 'Shell domain requested explicitly, but find or shellcheck was not found in environment.' ,
203+ 'Shell domain requested explicitly, but shellcheck was not found in environment.' ,
186204 ) ;
187205 hadFailure = true ;
188206 } else {
189207 console . warn (
190- 'Skipping shellcheck: find or shellcheck not found in environment.' ,
208+ 'Skipping shellcheck: shellcheck not found in environment.' ,
191209 ) ;
192210 }
193211 } else {
194212 const searchRoots = (
195213 shellPatterns ?. length ? shellPatterns : DEFAULT_SHELLCHECK_SEARCH_ROOTS
196214 )
197215 . map ( ( p ) => path . resolve ( process . cwd ( ) , p ) )
198- . filter ( ( p ) => defaultMainDeps . fileExists ( p ) ) ;
199-
200- // Linting shell scripts (this does not have auto-fixing)
201- const shellCheckArgs = [
202- ...searchRoots ,
203- '-type' ,
204- 'f' ,
205- '-regextype' ,
206- 'posix-extended' ,
207- '-regex' ,
208- '.*\\.(sh)' ,
209- '-exec' ,
210- 'shellcheck' ,
211- '{}' ,
212- '+' ,
213- ] ;
216+ . filter ( ( p ) => fs . existsSync ( p ) ) ;
214217
215218 console . error ( 'Running shellcheck:' ) ;
216219 if ( searchRoots . length === 0 ) {
217220 console . warn (
218221 'No search roots found for shellcheck. Skipping shellcheck.' ,
219222 ) ;
220223 } else {
221- console . error ( ' ' + [ 'find' , ...shellCheckArgs ] . join ( ' ' ) ) ;
222- try {
223- defaultMainDeps . execFileSync ( 'find' , shellCheckArgs , {
224- stdio : [ 'inherit' , 'inherit' , 'inherit' ] ,
225- windowsHide : true ,
226- encoding : 'utf-8' ,
227- shell : platform === 'win32' ? true : false ,
228- cwd : process . cwd ( ) ,
229- } ) ;
230- } catch ( err ) {
231- console . error ( 'Shellcheck failed. ' + err ) ;
232- hadFailure = true ;
224+ const shellFiles = collectShellFiles ( searchRoots ) ;
225+ if ( shellFiles . length === 0 ) {
226+ console . warn (
227+ 'No shell script files found for shellcheck. Skipping shellcheck.' ,
228+ ) ;
229+ } else {
230+ console . error ( ' ' + [ 'shellcheck' , ...shellFiles ] . join ( ' ' ) ) ;
231+ try {
232+ childProcess . execFileSync ( 'shellcheck' , shellFiles , {
233+ stdio : [ 'inherit' , 'inherit' , 'inherit' ] ,
234+ windowsHide : true ,
235+ encoding : 'utf-8' ,
236+ shell : platform === 'win32' ,
237+ cwd : process . cwd ( ) ,
238+ } ) ;
239+ } catch ( err ) {
240+ console . error ( 'Shellcheck failed. ' + err ) ;
241+ hadFailure = true ;
242+ }
233243 }
234244 }
235245 }
@@ -239,12 +249,12 @@ async function main(argv = process.argv) {
239249 // Linting markdown files
240250 // Always include README if it exists
241251 const markdownFiles : string [ ] = [ ] ;
242- if ( defaultMainDeps . fileExists ( 'README.md' ) ) {
252+ if ( fs . existsSync ( 'README.md' ) ) {
243253 markdownFiles . push ( 'README.md' ) ;
244254 }
245255 for ( const dir of [ 'pages' , 'blog' , 'docs' ] ) {
246- if ( defaultMainDeps . fileExists ( dir ) ) {
247- markdownFiles . push ( ...defaultMainDeps . collectMarkdown ( dir ) ) ;
256+ if ( fs . existsSync ( dir ) ) {
257+ markdownFiles . push ( ...utils . collectMarkdown ( dir ) ) ;
248258 }
249259 }
250260 if ( markdownFiles . length === 0 ) {
@@ -274,7 +284,7 @@ async function main(argv = process.argv) {
274284 try {
275285 if ( prettierBin ) {
276286 console . error ( ` ${ prettierBin } \n ${ prettierArgs . join ( '\n' + ' ' ) } ` ) ;
277- defaultMainDeps . execFileSync (
287+ childProcess . execFileSync (
278288 process . execPath ,
279289 [ prettierBin , ...prettierArgs ] ,
280290 {
@@ -286,7 +296,7 @@ async function main(argv = process.argv) {
286296 ) ;
287297 } else {
288298 console . error ( 'prettier' + prettierArgs . join ( '\n' + ' ' ) ) ;
289- defaultMainDeps . execFileSync ( 'prettier' , prettierArgs , {
299+ childProcess . execFileSync ( 'prettier' , prettierArgs , {
290300 stdio : 'inherit' ,
291301 windowsHide : true ,
292302 encoding : 'utf-8' ,
@@ -314,21 +324,6 @@ async function main(argv = process.argv) {
314324}
315325/* eslint-enable no-console */
316326
317- async function mainWithDeps (
318- argv : string [ ] ,
319- deps : Partial < MainDeps > ,
320- ) : Promise < void > {
321- const previousDeps = {
322- ...defaultMainDeps ,
323- } ;
324- Object . assign ( defaultMainDeps , deps ) ;
325- try {
326- await main ( argv ) ;
327- } finally {
328- Object . assign ( defaultMainDeps , previousDeps ) ;
329- }
330- }
331-
332327if ( import . meta. url . startsWith ( 'file:' ) ) {
333328 const modulePath = url . fileURLToPath ( import . meta. url ) ;
334329 if ( process . argv [ 1 ] === modulePath ) {
@@ -337,4 +332,3 @@ if (import.meta.url.startsWith('file:')) {
337332}
338333
339334export default main ;
340- export { mainWithDeps } ;
0 commit comments