package main import ( "context" "encoding/json" "flag" "fmt" "os" "path/filepath" ) // Config structs for version_groups.json type VersionGroupInfo struct { Versions []string `json:"versions"` Generation int `json:"generation"` Region string `json:"region"` RegionID int `json:"region_id"` ExtraRegions []int `json:"extra_regions"` Games map[string]GameInfo `json:"games"` } type GameInfo struct { Name string `json:"name"` Slug string `json:"slug"` ReleaseYear int `json:"release_year"` Color *string `json:"color"` } // Config structs for route_order.json type RouteOrderFile struct { Routes map[string][]string `json:"routes"` Aliases map[string]string `json:"aliases"` } // Config structs for special_encounters.json type SpecialEncountersFile struct { Encounters map[string]map[string][]EncounterOutput `json:"encounters"` Aliases map[string]string `json:"aliases"` } func getSpecialEncounters(se *SpecialEncountersFile, vgKey string) map[string][]EncounterOutput { if se == nil { return nil } if data, ok := se.Encounters[vgKey]; ok { return data } if alias, ok := se.Aliases[vgKey]; ok { if data, ok := se.Encounters[alias]; ok { return data } } return nil } func main() { clearCache := flag.Bool("clear-cache", false, "Delete cached API responses before fetching") flag.Parse() pokeapiURL := os.Getenv("POKEAPI_URL") if pokeapiURL == "" { pokeapiURL = "http://localhost:8000/api/v2" } // Resolve paths relative to this tool's location or use the standard layout seedsDir := findSeedsDir() dataDir := filepath.Join(seedsDir, "data") cacheDir := filepath.Join(seedsDir, ".pokeapi_cache") client := NewClient(pokeapiURL, cacheDir, 50) if *clearCache { if err := client.ClearCache(); err != nil { fmt.Fprintf(os.Stderr, "Warning: could not clear cache: %v\n", err) } else { fmt.Println("Cleared API cache.") } } ctx := context.Background() // Connectivity check fmt.Printf("Connecting to PokeAPI at %s...\n", pokeapiURL) if _, err := client.Get(ctx, "pokemon-species/1"); err != nil { fmt.Fprintf(os.Stderr, "Error: Cannot connect to PokeAPI at %s\n %v\nStart the local PokeAPI server or set POKEAPI_URL.\n", pokeapiURL, err) os.Exit(1) } // Load configs versionGroups, err := loadJSON[map[string]VersionGroupInfo](filepath.Join(seedsDir, "version_groups.json")) if err != nil { fmt.Fprintf(os.Stderr, "Error loading version_groups.json: %v\n", err) os.Exit(1) } routeOrder, err := loadRouteOrder(filepath.Join(seedsDir, "route_order.json")) if err != nil { fmt.Fprintf(os.Stderr, "Error loading route_order.json: %v\n", err) os.Exit(1) } specialEnc, err := loadJSON[SpecialEncountersFile](filepath.Join(seedsDir, "special_encounters.json")) if err != nil { fmt.Fprintf(os.Stderr, "Warning: could not load special_encounters.json: %v\n", err) // Continue without special encounters } if err := os.MkdirAll(dataDir, 0o755); err != nil { fmt.Fprintf(os.Stderr, "Error creating data dir: %v\n", err) os.Exit(1) } // Build games.json var games []GameOutput for _, vgInfo := range *versionGroups { for _, gameInfo := range vgInfo.Games { games = append(games, GameOutput{ Name: gameInfo.Name, Slug: gameInfo.Slug, Generation: vgInfo.Generation, Region: vgInfo.Region, ReleaseYear: gameInfo.ReleaseYear, Color: gameInfo.Color, }) } } writeJSON(filepath.Join(dataDir, "games.json"), games) fmt.Printf("Wrote %d games to games.json\n", len(games)) // Process each version pokeIDCollector := NewPokeIDCollector() for vgKey, vgInfo := range *versionGroups { if vgInfo.RegionID == 0 { fmt.Printf("\nSkipping %s (no PokeAPI region, data managed manually)\n", vgKey) continue } for _, verName := range vgInfo.Versions { routes, err := processVersion(ctx, client, verName, vgInfo, vgKey, routeOrder, specialEnc, pokeIDCollector) if err != nil { fmt.Fprintf(os.Stderr, "Error processing %s: %v\n", verName, err) os.Exit(1) } writeJSON(filepath.Join(dataDir, verName+".json"), routes) } } // Fetch all species data (reused for pokemon discovery + evolutions) speciesData, err := fetchAllSpecies(ctx, client) if err != nil { fmt.Fprintf(os.Stderr, "Error fetching species: %v\n", err) os.Exit(1) } // Fetch all Pokemon (base + all forms) pokemonList, err := fetchAllPokemon(ctx, client, speciesData, pokeIDCollector.IDs()) if err != nil { fmt.Fprintf(os.Stderr, "Error fetching pokemon: %v\n", err) os.Exit(1) } writeJSON(filepath.Join(dataDir, "pokemon.json"), pokemonList) fmt.Printf("\nWrote %d Pokemon to pokemon.json\n", len(pokemonList)) // Build set of all seeded PokeAPI IDs for evolution filtering allSeededDex := make(map[int]bool) for _, p := range pokemonList { allSeededDex[p.PokeAPIID] = true } // Fetch evolution chains evolutions, err := fetchEvolutionData(ctx, client, speciesData, allSeededDex) if err != nil { fmt.Fprintf(os.Stderr, "Error fetching evolutions: %v\n", err) os.Exit(1) } evolutions, err = applyEvolutionOverrides(evolutions, filepath.Join(dataDir, "evolution_overrides.json")) if err != nil { fmt.Fprintf(os.Stderr, "Error applying evolution overrides: %v\n", err) os.Exit(1) } writeJSON(filepath.Join(dataDir, "evolutions.json"), evolutions) fmt.Printf("\nWrote %d evolution pairs to evolutions.json\n", len(evolutions)) fmt.Println("\nDone! JSON files written to seeds/data/") fmt.Println("Review route ordering and curate as needed.") } // findSeedsDir locates the backend/src/app/seeds directory. func findSeedsDir() string { // Try relative to CWD (from repo root) candidates := []string{ "backend/src/app/seeds", "../../backend/src/app/seeds", // from tools/fetch-pokeapi/ } for _, c := range candidates { if _, err := os.Stat(filepath.Join(c, "version_groups.json")); err == nil { abs, _ := filepath.Abs(c) return abs } } // Fallback: try to find from executable location exe, _ := os.Executable() exeDir := filepath.Dir(exe) rel := filepath.Join(exeDir, "../../backend/src/app/seeds") if _, err := os.Stat(filepath.Join(rel, "version_groups.json")); err == nil { abs, _ := filepath.Abs(rel) return abs } // Default abs, _ := filepath.Abs("backend/src/app/seeds") return abs } func loadJSON[T any](path string) (*T, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var result T if err := json.Unmarshal(data, &result); err != nil { return nil, err } return &result, nil } func loadRouteOrder(path string) (map[string][]string, error) { data, err := os.ReadFile(path) if err != nil { return nil, err } var rof RouteOrderFile if err := json.Unmarshal(data, &rof); err != nil { return nil, err } routes := make(map[string][]string) for k, v := range rof.Routes { routes[k] = v } for alias, target := range rof.Aliases { routes[alias] = routes[target] } return routes, nil } func writeJSON(path string, data interface{}) { content, err := json.MarshalIndent(data, "", " ") if err != nil { fmt.Fprintf(os.Stderr, "Error marshaling JSON for %s: %v\n", path, err) os.Exit(1) } content = append(content, '\n') if err := os.WriteFile(path, content, 0o644); err != nil { fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", path, err) os.Exit(1) } fmt.Printf(" -> %s\n", path) }