package main import ( "bytes" "encoding/json" "fmt" "os" "path/filepath" "strings" "git.dev.alexdunmow.com/block/check-safety/internal/theme" ) type presetViolation struct { file string message string } type presetJSON struct { ID string `json:"id"` Name string `json:"name"` Description string `json:"description"` Theme theme.Theme `json:"theme"` } func checkPresets(root string) []presetViolation { var violations []presetViolation presetsFiles := findPresetsFiles(root) for _, path := range presetsFiles { relPath, _ := filepath.Rel(root, path) if relPath == "" { relPath = path } data, err := os.ReadFile(path) if err != nil { violations = append(violations, presetViolation{ file: relPath, message: fmt.Sprintf("cannot read file: %v", err), }) continue } // Strict unmarshal: catches type mismatches (e.g., int where string expected) var presets []presetJSON decoder := json.NewDecoder(bytes.NewReader(data)) decoder.DisallowUnknownFields() if err := decoder.Decode(&presets); err != nil { // Distinguish between unknown fields (warning-worthy) and hard parse errors if isUnknownFieldError(err) { // Re-try without strict mode to get the actual parse result var lenient []presetJSON if lenientErr := json.Unmarshal(data, &lenient); lenientErr != nil { violations = append(violations, presetViolation{ file: relPath, message: fmt.Sprintf("invalid JSON: %v", lenientErr), }) } else { violations = append(violations, presetViolation{ file: relPath, message: fmt.Sprintf("unknown fields (stale preset data): %v", err), }) } } else { violations = append(violations, presetViolation{ file: relPath, message: fmt.Sprintf("invalid JSON: %v", err), }) } continue } for i, p := range presets { if p.ID == "" { violations = append(violations, presetViolation{ file: relPath, message: fmt.Sprintf("preset[%d]: missing \"id\" field", i), }) } if p.Name == "" { violations = append(violations, presetViolation{ file: relPath, message: fmt.Sprintf("preset[%d]: missing \"name\" field", i), }) } } } return violations } func findPresetsFiles(root string) []string { var files []string _ = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() { if info != nil && info.IsDir() { switch info.Name() { case ".git", ".worktrees", "vendor", "node_modules", "web", "dist": return filepath.SkipDir } } return nil } if info.Name() == "presets.json" { files = append(files, path) } return nil }) return files } func isUnknownFieldError(err error) bool { return strings.Contains(err.Error(), "unknown field") }