@@ -188,6 +188,107 @@ func Pluralize(count int, singular, plural string) string {
188188 return plural
189189}
190190
191+ // runPrettierCheck runs Prettier formatting check/fix for a given directory.
192+ // extensions are the file extensions to count (e.g., []string{"*.ts", "*.svelte", "*.css", "*.js"}).
193+ func runPrettierCheck (ctx * CheckContext , dir string , extensions []string ) (CheckResult , error ) {
194+ // Count files that prettier would check
195+ findArgs := buildFindArgs ("src" , extensions )
196+ findCmd := exec .Command ("find" , findArgs ... )
197+ findCmd .Dir = dir
198+ findOutput , _ := RunCommand (findCmd , true )
199+ fileCount := 0
200+ if strings .TrimSpace (findOutput ) != "" {
201+ fileCount = len (strings .Split (strings .TrimSpace (findOutput ), "\n " ))
202+ }
203+
204+ // Check which files need formatting (--list-different lists them)
205+ // Note: prettier exits with code 1 if files differ, so we ignore the error
206+ // Prettier's default behavior respects .gitignore files in current dir and parents
207+ checkCmd := exec .Command ("pnpm" , "exec" , "prettier" , "--list-different" , "." )
208+ checkCmd .Dir = dir
209+ checkOutput , _ := RunCommand (checkCmd , true )
210+
211+ // Parse files that need formatting
212+ var needsFormat []string
213+ if strings .TrimSpace (checkOutput ) != "" {
214+ needsFormat = strings .Split (strings .TrimSpace (checkOutput ), "\n " )
215+ }
216+
217+ if ctx .CI {
218+ if len (needsFormat ) > 0 {
219+ return CheckResult {}, fmt .Errorf ("code is not formatted, run pnpm format locally\n %s" , indentOutput (checkOutput ))
220+ }
221+ return Success (fmt .Sprintf ("%d %s already formatted" , fileCount , Pluralize (fileCount , "file" , "files" ))), nil
222+ }
223+
224+ // Non-CI mode: format if needed
225+ if len (needsFormat ) > 0 {
226+ fmtCmd := exec .Command ("pnpm" , "format" )
227+ fmtCmd .Dir = dir
228+ output , err := RunCommand (fmtCmd , true )
229+ if err != nil {
230+ return CheckResult {}, fmt .Errorf ("prettier formatting failed\n %s" , indentOutput (output ))
231+ }
232+ return SuccessWithChanges (fmt .Sprintf ("Formatted %d of %d %s" , len (needsFormat ), fileCount , Pluralize (fileCount , "file" , "files" ))), nil
233+ }
234+
235+ return Success (fmt .Sprintf ("%d %s already formatted" , fileCount , Pluralize (fileCount , "file" , "files" ))), nil
236+ }
237+
238+ // runESLintCheck runs ESLint check/fix for a given directory.
239+ // extensions are the file extensions to count (e.g., []string{"*.ts", "*.svelte", "*.js"}).
240+ // If requireConfig is true, skips when eslint.config.js is missing.
241+ func runESLintCheck (ctx * CheckContext , dir string , extensions []string , requireConfig bool ) (CheckResult , error ) {
242+ if requireConfig {
243+ if _ , err := os .Stat (filepath .Join (dir , "eslint.config.js" )); os .IsNotExist (err ) {
244+ return Skipped ("no eslint.config.js" ), nil
245+ }
246+ }
247+
248+ // Count lintable files
249+ findArgs := buildFindArgs ("src" , extensions )
250+ findCmd := exec .Command ("find" , findArgs ... )
251+ findCmd .Dir = dir
252+ findOutput , _ := RunCommand (findCmd , true )
253+ fileCount := 0
254+ if strings .TrimSpace (findOutput ) != "" {
255+ fileCount = len (strings .Split (strings .TrimSpace (findOutput ), "\n " ))
256+ }
257+
258+ var cmd * exec.Cmd
259+ if ctx .CI {
260+ cmd = exec .Command ("pnpm" , "lint" )
261+ } else {
262+ cmd = exec .Command ("pnpm" , "lint:fix" )
263+ }
264+ cmd .Dir = dir
265+ output , err := RunCommand (cmd , true )
266+ if err != nil {
267+ if ctx .CI {
268+ return CheckResult {}, fmt .Errorf ("lint errors found, run pnpm lint:fix locally\n %s" , indentOutput (output ))
269+ }
270+ return CheckResult {}, fmt .Errorf ("eslint found unfixable errors\n %s" , indentOutput (output ))
271+ }
272+
273+ if fileCount > 0 {
274+ return Success (fmt .Sprintf ("%d %s passed" , fileCount , Pluralize (fileCount , "file" , "files" ))), nil
275+ }
276+ return Success ("All files passed" ), nil
277+ }
278+
279+ // buildFindArgs constructs arguments for a find command to locate files with given extensions.
280+ func buildFindArgs (searchDir string , extensions []string ) []string {
281+ args := []string {searchDir , "-type" , "f" , "(" }
282+ for i , ext := range extensions {
283+ if i > 0 {
284+ args = append (args , "-o" )
285+ }
286+ args = append (args , "-name" , ext )
287+ }
288+ args = append (args , ")" )
289+ return args
290+ }
291+
191292// GetGoDirectories returns all directories in the repo that contain Go code.
192293// Each returned path is relative to rootDir.
193294func GetGoDirectories () []string {
0 commit comments