import { gunzipSync } from "node:zlib"; import { mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const RELEASE = "v3.2-export.7"; const SOURCE_URL = `https://github.com/dr5hn/countries-states-cities-database/releases/download/${RELEASE}/json-countries%2Bstates%2Bcities.json.gz`; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const outputDir = resolve(root, "public/data/world-locations"); const indexPath = resolve(root, "src/data/world-countries.json"); async function loadSource() { const localPath = process.env.GLOBAL_LOCATION_SOURCE_FILE; if (localPath) return JSON.parse(await readFile(localPath, "utf8")); const response = await fetch(SOURCE_URL, { headers: { "user-agent": "Jyotisha location-data refresh" } }); if (!response.ok) throw new Error(`Unable to download global location data: ${response.status} ${response.statusText}`); return JSON.parse(gunzipSync(Buffer.from(await response.arrayBuffer())).toString("utf8")); } function coordinate(value, label) { const result = Number(value); if (!Number.isFinite(result)) throw new Error(`Missing valid ${label}: ${value}`); return result; } function regionCode(state) { return String(state.iso3166_2 || state.iso2 || state.id); } function compactCity(city) { return { code: String(city.id), name: city.name, nativeName: typeof city.native === "string" && city.native.trim() ? city.native : undefined, latitude: coordinate(city.latitude, `latitude for ${city.name}`), longitude: coordinate(city.longitude, `longitude for ${city.name}`), timezone: city.timezone, }; } function compactRegion(state) { return { code: regionCode(state), name: state.name, nativeName: typeof state.native === "string" && state.native.trim() ? state.native : undefined, latitude: coordinate(state.latitude, `latitude for ${state.name}`), longitude: coordinate(state.longitude, `longitude for ${state.name}`), timezone: state.timezone, cities: (Array.isArray(state.cities) ? state.cities : []).map(compactCity), }; } const countries = await loadSource(); if (!Array.isArray(countries)) throw new Error("Global location source is not an array"); await mkdir(outputDir, { recursive: true }); const index = []; let stateCount = 0; let cityCount = 0; for (const country of countries) { const code = typeof country.iso2 === "string" ? country.iso2.trim().toUpperCase() : ""; if (!/^[A-Z]{2}$/.test(code)) continue; const regions = (Array.isArray(country.states) ? country.states : []).map(compactRegion); if (regions.length === 0) continue; const payload = { code, name: country.name, nativeName: typeof country.native === "string" && country.native.trim() ? country.native : undefined, regions, }; await writeFile(resolve(outputDir, `${code.toLowerCase()}.json`), `${JSON.stringify(payload)}\n`, "utf8"); index.push({ code, name: country.name, nativeName: payload.nativeName, regionCount: regions.length }); stateCount += regions.length; cityCount += regions.reduce((total, region) => total + region.cities.length, 0); } index.sort((a, b) => a.name.localeCompare(b.name)); await writeFile(indexPath, `${JSON.stringify({ source: { repository: "dr5hn/countries-states-cities-database", release: RELEASE, url: SOURCE_URL, license: "ODbL-1.0" }, countries: index })}\n`, "utf8"); console.log(`Wrote ${indexPath}: ${index.length} countries, ${stateCount} regions, ${cityCount} cities.`);