geo-data-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mohammed
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,225 @@
1
+ # 🌍 geo-data
2
+
3
+ Copy only the countries you need. No bloat.
4
+
5
+ ## The Problem
6
+
7
+ ```bash
8
+ # Installing country data for your app?
9
+ npm install country-city-multilanguage # 6MB of world data 😱
10
+ ```
11
+
12
+ You only need Saudi Arabia and UAE, but you're shipping data for 250 countries.
13
+
14
+ ## The Solution
15
+
16
+ ```bash
17
+ npx geo-data add sa ae # ~65KB instead of 6MB ✨
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```bash
23
+ # 1. Initialize in your project
24
+ npx geo-data init
25
+
26
+ # 2. Add countries you need
27
+ npx geo-data add sa ae qa
28
+
29
+ # 3. Use in your code
30
+ ```
31
+
32
+ ```tsx
33
+ import { getCities, getLocalizedName } from './src/data/geo';
34
+
35
+ const cities = getCities('SA');
36
+ // → [{ name: { en: "Riyadh", ar: "الرياض" }, ... }]
37
+
38
+ // With i18n
39
+ const cityName = getLocalizedName(city, 'ar'); // → "الرياض"
40
+ ```
41
+
42
+ ## Features
43
+
44
+ - 🎯 **Only what you need** — install 1 country, not 250
45
+ - 🌐 **Multilingual** — English, Arabic, French, Spanish, and 13 more languages
46
+ - 📦 **No runtime dependency** — data lives in your project
47
+ - 🔧 **Fully customizable** — it's just JSON, edit as needed
48
+ - ⚡ **TypeScript ready** — full type safety out of the box
49
+ - 🔄 **Offline support** — works offline after first download
50
+ - 🗑️ **Easy management** — add, update, or remove countries anytime
51
+
52
+ ## Commands
53
+
54
+ ```bash
55
+ # Initialize configuration
56
+ npx geo-data init
57
+
58
+ # Add countries
59
+ npx geo-data add sa qa ae
60
+ npx geo-data add sa --force # Overwrite existing
61
+ npx geo-data add sa --dry-run # Preview changes
62
+
63
+ # List countries
64
+ npx geo-data list # All available (218 countries)
65
+ npx geo-data list --installed # Only installed
66
+
67
+ # Update installed countries
68
+ npx geo-data update # Re-download with current config
69
+ npx geo-data update --dry-run # Preview what would update
70
+
71
+ # Remove countries
72
+ npx geo-data remove sa # Remove with confirmation
73
+ npx geo-data rm sa --force # Remove without confirmation
74
+
75
+ # Manage cache
76
+ npx geo-data cache # Show cache info
77
+ npx geo-data cache clear # Clear offline cache
78
+ ```
79
+
80
+ ## Configuration
81
+
82
+ After `init`, you'll have a `geo-data.json`:
83
+
84
+ ```json
85
+ {
86
+ "$schema": "https://geo-data.dev/schema.json",
87
+ "outputDir": "./src/data/geo",
88
+ "languages": ["en", "ar"],
89
+ "includeCoordinates": true,
90
+ "typescript": true
91
+ }
92
+ ```
93
+
94
+ | Option | Description | Default |
95
+ |--------|-------------|---------|
96
+ | `outputDir` | Where to put the data files | `./src/data/geo` |
97
+ | `languages` | Languages to include | `["en"]` |
98
+ | `includeCoordinates` | Include lat/lng for cities | `true` |
99
+ | `typescript` | Generate TypeScript types | `true` |
100
+
101
+ ### Available Languages
102
+
103
+ `en`, `ar`, `de`, `es`, `fr`, `hi`, `it`, `ja`, `ko`, `nl`, `pl`, `pt`, `pt-BR`, `ru`, `tr`, `uk`, `zh`
104
+
105
+ ## Data Schema
106
+
107
+ Each country file contains:
108
+
109
+ ```json
110
+ {
111
+ "code": "SA",
112
+ "name": { "en": "Saudi Arabia", "ar": "المملكة العربية السعودية" },
113
+ "phone": "+966",
114
+ "currency": "SAR",
115
+ "timezone": "Asia/Riyadh",
116
+ "flag": "🇸🇦",
117
+ "regions": [
118
+ {
119
+ "code": "SA-01",
120
+ "name": { "en": "Riyadh Region", "ar": "منطقة الرياض" },
121
+ "cities": [
122
+ {
123
+ "name": { "en": "Riyadh", "ar": "الرياض" },
124
+ "latitude": 24.7136,
125
+ "longitude": 46.6753
126
+ }
127
+ ]
128
+ }
129
+ ]
130
+ }
131
+ ```
132
+
133
+ ## Generated Helpers
134
+
135
+ The CLI generates an `index.ts` with helpful functions:
136
+
137
+ ```typescript
138
+ import {
139
+ countries,
140
+ getCountry,
141
+ getRegions,
142
+ getCities,
143
+ getAllCities,
144
+ getLocalizedName,
145
+ getCountryCodes,
146
+ isValidCountryCode
147
+ } from './src/data/geo';
148
+
149
+ // Get all cities in a country
150
+ const cities = getCities('SA');
151
+
152
+ // Get cities in a specific region
153
+ const riyadhCities = getCities('SA', 'SA-01');
154
+
155
+ // Get localized name
156
+ const name = getLocalizedName(city, 'ar'); // "الرياض"
157
+
158
+ // Type-safe country codes
159
+ type CountryCode = 'SA' | 'AE' | 'QA'; // Based on what you installed
160
+
161
+ // Validation
162
+ if (isValidCountryCode(userInput)) {
163
+ const country = getCountry(userInput);
164
+ }
165
+ ```
166
+
167
+ ## Framework Examples
168
+
169
+ ### React
170
+
171
+ ```tsx
172
+ import { getCities, getLocalizedName } from '@/data/geo';
173
+ import { useTranslation } from 'react-i18next';
174
+
175
+ function CitySelect({ country }: { country: 'SA' | 'AE' }) {
176
+ const { i18n } = useTranslation();
177
+ const cities = getCities(country);
178
+
179
+ return (
180
+ <select>
181
+ {cities.map(city => (
182
+ <option key={city.name.en} value={city.name.en}>
183
+ {getLocalizedName(city, i18n.language)}
184
+ </option>
185
+ ))}
186
+ </select>
187
+ );
188
+ }
189
+ ```
190
+
191
+ ### Vue
192
+
193
+ ```vue
194
+ <script setup lang="ts">
195
+ import { computed } from 'vue';
196
+ import { getCities, getLocalizedName } from '@/data/geo';
197
+ import { useI18n } from 'vue-i18n';
198
+
199
+ const props = defineProps<{ country: 'SA' | 'AE' }>();
200
+ const { locale } = useI18n();
201
+ const cities = computed(() => getCities(props.country));
202
+ </script>
203
+
204
+ <template>
205
+ <select>
206
+ <option v-for="city in cities" :key="city.name.en" :value="city.name.en">
207
+ {{ getLocalizedName(city, locale) }}
208
+ </option>
209
+ </select>
210
+ </template>
211
+ ```
212
+
213
+ ## Data Sources
214
+
215
+ This package combines data from:
216
+ - [dr5hn/countries-states-cities-database](https://github.com/dr5hn/countries-states-cities-database) — Base country/city data
217
+ - [GeoNames](https://www.geonames.org/) — Arabic translations for cities worldwide
218
+
219
+ ## Contributing
220
+
221
+ Found incorrect data? PRs welcome! Each country is a separate JSON file in `registry/countries/`.
222
+
223
+ ## License
224
+
225
+ MIT
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node
package/dist/index.js ADDED
@@ -0,0 +1,1021 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { createRequire } from "module";
5
+ import chalk3 from "chalk";
6
+ import { Command } from "commander";
7
+
8
+ // src/commands/add.ts
9
+ import fs5 from "fs-extra";
10
+ import path4 from "path";
11
+ import { findBestMatch } from "string-similarity";
12
+
13
+ // src/utils/codegen.ts
14
+ import fs3 from "fs-extra";
15
+ import path2 from "path";
16
+
17
+ // src/utils/helpers.ts
18
+ import fs2 from "fs-extra";
19
+
20
+ // src/utils/ui.ts
21
+ import * as p from "@clack/prompts";
22
+ import chalk from "chalk";
23
+ import figlet from "figlet";
24
+ import gradient from "gradient-string";
25
+ var brandGradient = gradient(["#a855f7", "#6366f1", "#06b6d4"]);
26
+ function banner() {
27
+ console.log();
28
+ console.log(brandGradient(figlet.textSync("geodata", { font: "Big" })));
29
+ console.log();
30
+ }
31
+ function intro2(message) {
32
+ console.log();
33
+ p.intro(chalk.bgHex("#6366f1").white(` ${message || "geo-data"} `));
34
+ }
35
+ function outro2(message) {
36
+ p.outro(chalk.hex("#a855f7")(message));
37
+ }
38
+ function cancel2(message = "Operation cancelled.") {
39
+ p.cancel(message);
40
+ }
41
+
42
+ // src/utils/config.ts
43
+ import * as p2 from "@clack/prompts";
44
+ import fs from "fs-extra";
45
+ import path from "path";
46
+
47
+ // src/utils/schemas.ts
48
+ import { z } from "zod";
49
+ var CitySchema = z.object({
50
+ name: z.record(z.string(), z.string()),
51
+ latitude: z.number().optional(),
52
+ longitude: z.number().optional()
53
+ });
54
+ var RegionSchema = z.object({
55
+ code: z.string(),
56
+ name: z.record(z.string(), z.string()),
57
+ cities: z.array(CitySchema)
58
+ });
59
+ var CountryDataSchema = z.object({
60
+ code: z.string(),
61
+ iso3: z.string().optional(),
62
+ name: z.record(z.string(), z.string()),
63
+ phone: z.string(),
64
+ currency: z.string(),
65
+ timezone: z.string(),
66
+ flag: z.string(),
67
+ regions: z.array(RegionSchema)
68
+ });
69
+ var RegistryIndexSchema = z.object({
70
+ version: z.string(),
71
+ countries: z.record(
72
+ z.string(),
73
+ z.object({
74
+ name: z.object({ en: z.string(), ar: z.string().optional() }),
75
+ flag: z.string(),
76
+ languages: z.array(z.string())
77
+ })
78
+ )
79
+ });
80
+ var VALID_LANGUAGES = [
81
+ "en",
82
+ "ar",
83
+ "de",
84
+ "es",
85
+ "fr",
86
+ "hi",
87
+ "it",
88
+ "ja",
89
+ "ko",
90
+ "nl",
91
+ "pl",
92
+ "pt",
93
+ "pt-BR",
94
+ "ru",
95
+ "tr",
96
+ "uk",
97
+ "zh"
98
+ ];
99
+ var ConfigSchema = z.object({
100
+ $schema: z.string().optional(),
101
+ outputDir: z.string().min(1, "outputDir must be a non-empty string"),
102
+ languages: z.array(z.string()).min(1, "languages must be a non-empty array"),
103
+ includeCoordinates: z.boolean(),
104
+ typescript: z.boolean()
105
+ });
106
+
107
+ // src/utils/config.ts
108
+ var CONFIG_FILE = "geo-data.json";
109
+ function validateConfig(config) {
110
+ const result = ConfigSchema.safeParse(config);
111
+ if (!result.success) {
112
+ throw new Error(result.error.issues[0]?.message ?? "Invalid config");
113
+ }
114
+ for (const lang of result.data.languages) {
115
+ if (!VALID_LANGUAGES.includes(lang)) {
116
+ p2.log.warn(`Unknown language code "${lang}"`);
117
+ }
118
+ }
119
+ return result.data;
120
+ }
121
+ async function getConfigResult() {
122
+ const configPath = path.resolve(process.cwd(), CONFIG_FILE);
123
+ if (!await fs.pathExists(configPath)) {
124
+ return { status: "not_found" };
125
+ }
126
+ try {
127
+ const raw = await fs.readJson(configPath);
128
+ const config = validateConfig(raw);
129
+ return { status: "ok", config };
130
+ } catch (error) {
131
+ return { status: "invalid", error: errorMessage(error) };
132
+ }
133
+ }
134
+ async function getConfig() {
135
+ const result = await getConfigResult();
136
+ if (result.status === "ok") return result.config;
137
+ if (result.status === "invalid") {
138
+ p2.log.error(`Error reading ${CONFIG_FILE}: ${result.error}`);
139
+ }
140
+ return null;
141
+ }
142
+
143
+ // src/utils/helpers.ts
144
+ function errorMessage(error) {
145
+ return error instanceof Error ? error.message : String(error);
146
+ }
147
+ async function requireConfig(commandName) {
148
+ const result = await getConfigResult();
149
+ if (result.status === "ok") return result.config;
150
+ intro2(commandName);
151
+ if (result.status === "not_found") {
152
+ p.log.error("No geo-data.json found. Run 'npx geo-data init' first.");
153
+ } else {
154
+ p.log.error(`Invalid geo-data.json: ${result.error}`);
155
+ }
156
+ console.log();
157
+ return null;
158
+ }
159
+ async function getInstalledCountries(outputDir) {
160
+ const files = await fs2.readdir(outputDir).catch(() => []);
161
+ return files.filter((f) => f.endsWith(".json") && f !== "index.json").map((f) => f.replace(".json", ""));
162
+ }
163
+ function formatCountryDisplay(code, info) {
164
+ if (info) {
165
+ return `${info.flag} ${code.toUpperCase()} - ${info.name.en}`;
166
+ }
167
+ return code.toUpperCase();
168
+ }
169
+
170
+ // src/utils/codegen.ts
171
+ async function generateIndex(config) {
172
+ const codes = await getInstalledCountries(config.outputDir);
173
+ const validCodePattern = /^[a-z]{2}$/;
174
+ const safeCodes = codes.filter((code) => {
175
+ if (!validCodePattern.test(code)) {
176
+ console.warn(`Skipping invalid country code: ${code}`);
177
+ return false;
178
+ }
179
+ return true;
180
+ });
181
+ const ext = config.typescript ? "ts" : "js";
182
+ const indexPath = path2.join(config.outputDir, `index.${ext}`);
183
+ if (safeCodes.length === 0) {
184
+ const emptyContent = config.typescript ? `// Auto-generated by geo-data - no countries installed yet
185
+ // Run: npx geo-data add <country>
186
+
187
+ export const countries = {} as const;
188
+ export type CountryCode = never;
189
+ ` : `// Auto-generated by geo-data - no countries installed yet
190
+ // Run: npx geo-data add <country>
191
+
192
+ export const countries = {};
193
+ `;
194
+ await fs3.writeFile(indexPath, emptyContent);
195
+ return;
196
+ }
197
+ safeCodes.sort();
198
+ const imports = safeCodes.map((code) => `import ${code}Data from './${code}.json' with { type: "json" };`).join("\n");
199
+ const countriesObj = safeCodes.map((code) => ` ${code.toUpperCase()}: ${code}Data,`).join("\n");
200
+ const types = config.typescript ? `
201
+ export type CountryCode = keyof typeof countries;
202
+ export type Country = (typeof countries)[CountryCode];
203
+ export type Region = Country['regions'][number];
204
+ export type City = Region['cities'][number];
205
+ export type LocalizedName = { name: { [key: string]: string | undefined; en: string } };
206
+ ` : "";
207
+ const content = `// Auto-generated by geo-data - do not edit manually
208
+ // Regenerate with: npx geo-data update
209
+ ${imports}
210
+
211
+ export const countries = {
212
+ ${countriesObj}
213
+ } as const;
214
+ ${types}
215
+ export function getCountry${config.typescript ? "(code: CountryCode)" : "(code)"} {
216
+ return countries[code];
217
+ }
218
+
219
+ export function getRegions${config.typescript ? "(code: CountryCode)" : "(code)"} {
220
+ return countries[code].regions;
221
+ }
222
+
223
+ export function getCities${config.typescript ? "(code: CountryCode, regionCode?: string)" : "(code, regionCode)"} {
224
+ const regions = getRegions(code);
225
+ if (regionCode) {
226
+ const region = regions.find(r => r.code === regionCode);
227
+ return region?.cities ?? [];
228
+ }
229
+ return regions.flatMap(r => r.cities);
230
+ }
231
+
232
+ export function getAllCities${config.typescript ? "(code: CountryCode)" : "(code)"} {
233
+ return getRegions(code).flatMap(r => r.cities);
234
+ }
235
+
236
+ export function getLocalizedName${config.typescript ? '(item: { name: Record<string, string | undefined> & { en: string } }, lang: string = "en"): string' : '(item, lang = "en")'} {
237
+ return item.name[lang] ?? item.name.en;
238
+ }
239
+
240
+ export function getCountryCodes()${config.typescript ? ": CountryCode[]" : ""} {
241
+ return Object.keys(countries)${config.typescript ? " as CountryCode[]" : ""};
242
+ }
243
+
244
+ export function isValidCountryCode${config.typescript ? "(code: string): code is CountryCode" : "(code)"} {
245
+ return code in countries;
246
+ }
247
+ `;
248
+ await fs3.writeFile(indexPath, content.trim() + "\n");
249
+ }
250
+
251
+ // src/utils/registry.ts
252
+ import * as p3 from "@clack/prompts";
253
+ import fs4 from "fs-extra";
254
+ import os from "os";
255
+ import path3 from "path";
256
+ var REGISTRY_BASE = process.env.GEO_DATA_REGISTRY || "https://cdn.jsdelivr.net/gh/H4ck3r-x0/geo-data@main/registry";
257
+ var CACHE_DIR = path3.join(os.homedir(), ".cache", "geo-data");
258
+ function isRemoteRegistry(url) {
259
+ return url.startsWith("http://") || url.startsWith("https://");
260
+ }
261
+ async function readCache(cacheKey, maxAgeMs) {
262
+ const cachePath = path3.join(CACHE_DIR, `${cacheKey}.json`);
263
+ if (await fs4.pathExists(cachePath)) {
264
+ const stat = await fs4.stat(cachePath);
265
+ if (Date.now() - stat.mtimeMs < maxAgeMs) {
266
+ return fs4.readJson(cachePath);
267
+ }
268
+ }
269
+ return null;
270
+ }
271
+ async function writeCache(cacheKey, data) {
272
+ await fs4.ensureDir(CACHE_DIR);
273
+ await fs4.writeJson(path3.join(CACHE_DIR, `${cacheKey}.json`), data);
274
+ }
275
+ async function readStaleCache(cacheKey) {
276
+ const cachePath = path3.join(CACHE_DIR, `${cacheKey}.json`);
277
+ if (await fs4.pathExists(cachePath)) {
278
+ return fs4.readJson(cachePath);
279
+ }
280
+ return null;
281
+ }
282
+ async function fetchJson(urlOrPath) {
283
+ if (path3.isAbsolute(urlOrPath) || urlOrPath.startsWith(".")) {
284
+ return fs4.readJson(urlOrPath);
285
+ }
286
+ const controller = new AbortController();
287
+ const timeout = setTimeout(() => controller.abort(), 3e4);
288
+ try {
289
+ const response = await fetch(urlOrPath, { signal: controller.signal });
290
+ if (!response.ok) {
291
+ throw new Error(`Failed to fetch ${urlOrPath}: ${response.status}`);
292
+ }
293
+ return response.json();
294
+ } finally {
295
+ clearTimeout(timeout);
296
+ }
297
+ }
298
+ async function fetchRegistry() {
299
+ const url = isRemoteRegistry(REGISTRY_BASE) ? `${REGISTRY_BASE}/index.json` : path3.join(REGISTRY_BASE, "index.json");
300
+ let data;
301
+ if (isRemoteRegistry(REGISTRY_BASE)) {
302
+ const cached = await readCache("registry-index", 60 * 60 * 1e3);
303
+ if (cached) {
304
+ data = cached;
305
+ } else {
306
+ try {
307
+ data = await fetchJson(url);
308
+ } catch (error) {
309
+ const stale = await readStaleCache("registry-index");
310
+ if (stale) {
311
+ p3.log.warn("Network unavailable, using cached data");
312
+ data = stale;
313
+ } else {
314
+ throw error;
315
+ }
316
+ }
317
+ }
318
+ } else {
319
+ data = await fetchJson(url);
320
+ }
321
+ const result = RegistryIndexSchema.safeParse(data);
322
+ if (!result.success) {
323
+ p3.log.warn(`Registry index failed validation: ${result.error.issues[0]?.message}`);
324
+ return null;
325
+ }
326
+ if (isRemoteRegistry(REGISTRY_BASE)) {
327
+ await writeCache("registry-index", result.data);
328
+ }
329
+ return result.data;
330
+ }
331
+ async function fetchCountry(code, config) {
332
+ const url = isRemoteRegistry(REGISTRY_BASE) ? `${REGISTRY_BASE}/countries/${code}.json` : path3.join(REGISTRY_BASE, "countries", `${code}.json`);
333
+ let raw;
334
+ if (isRemoteRegistry(REGISTRY_BASE)) {
335
+ const cached = await readCache(`country-${code}`, 24 * 60 * 60 * 1e3);
336
+ if (cached) {
337
+ raw = cached;
338
+ } else {
339
+ try {
340
+ raw = await fetchJson(url);
341
+ } catch (error) {
342
+ const stale = await readStaleCache(`country-${code}`);
343
+ if (stale) {
344
+ p3.log.warn("Network unavailable, using cached data");
345
+ raw = stale;
346
+ } else {
347
+ throw error;
348
+ }
349
+ }
350
+ }
351
+ } else {
352
+ raw = await fetchJson(url);
353
+ }
354
+ const result = CountryDataSchema.safeParse(raw);
355
+ if (!result.success) {
356
+ p3.log.warn(`Country data for "${code}" failed validation: ${result.error.issues[0]?.message}`);
357
+ return null;
358
+ }
359
+ if (isRemoteRegistry(REGISTRY_BASE)) {
360
+ await writeCache(`country-${code}`, result.data);
361
+ }
362
+ return filterCountryData(result.data, config);
363
+ }
364
+ function filterCountryData(data, config) {
365
+ const filterName = (name) => {
366
+ const filtered = {};
367
+ for (const lang of config.languages) {
368
+ if (name[lang]) {
369
+ filtered[lang] = name[lang];
370
+ }
371
+ }
372
+ if (!filtered.en && name.en) {
373
+ filtered.en = name.en;
374
+ }
375
+ return filtered;
376
+ };
377
+ return {
378
+ ...data,
379
+ name: filterName(data.name),
380
+ regions: data.regions.map((region) => ({
381
+ ...region,
382
+ name: filterName(region.name),
383
+ cities: region.cities.map((city) => {
384
+ const filteredCity = {
385
+ name: filterName(city.name)
386
+ };
387
+ if (config.includeCoordinates && city.latitude !== void 0 && city.longitude !== void 0) {
388
+ filteredCity.latitude = city.latitude;
389
+ filteredCity.longitude = city.longitude;
390
+ }
391
+ return filteredCity;
392
+ })
393
+ }))
394
+ };
395
+ }
396
+ async function clearCache() {
397
+ await fs4.remove(CACHE_DIR);
398
+ }
399
+ async function getCacheInfo() {
400
+ if (!await fs4.pathExists(CACHE_DIR)) {
401
+ return null;
402
+ }
403
+ const files = await fs4.readdir(CACHE_DIR);
404
+ let size = 0;
405
+ for (const file of files) {
406
+ const stat = await fs4.stat(path3.join(CACHE_DIR, file));
407
+ size += stat.size;
408
+ }
409
+ return { size, files: files.length };
410
+ }
411
+
412
+ // src/commands/add.ts
413
+ async function add(countryCodes, options = {}) {
414
+ const config = await requireConfig("Add Countries");
415
+ if (!config) {
416
+ process.exitCode = 1;
417
+ return;
418
+ }
419
+ intro2("Add Countries");
420
+ const codes = countryCodes.map((c) => c.toLowerCase());
421
+ const s = p.spinner();
422
+ s.start("Fetching registry");
423
+ try {
424
+ const registry = await fetchRegistry();
425
+ if (!registry) {
426
+ s.stop("Failed");
427
+ p.log.error("Could not load registry \u2014 invalid data from remote");
428
+ console.log();
429
+ process.exitCode = 1;
430
+ return;
431
+ }
432
+ const invalid = codes.filter((c) => !registry.countries[c]);
433
+ if (invalid.length > 0) {
434
+ s.stop("Registry loaded");
435
+ const allCodes = Object.keys(registry.countries);
436
+ for (const code of invalid) {
437
+ p.log.error(`Unknown country code: ${code.toUpperCase()}`);
438
+ const match = findBestMatch(code.toLowerCase(), allCodes);
439
+ if (match.bestMatch.rating > 0.3) {
440
+ const suggested = match.bestMatch.target;
441
+ const info = registry.countries[suggested];
442
+ p.log.info(`Did you mean ${info.flag} ${suggested.toUpperCase()} (${info.name.en})?`);
443
+ }
444
+ }
445
+ p.log.message("Run: npx geo-data list");
446
+ console.log();
447
+ process.exitCode = 1;
448
+ return;
449
+ }
450
+ const existing = [];
451
+ for (const code of codes) {
452
+ const outputPath = path4.join(config.outputDir, `${code}.json`);
453
+ if (await fs5.pathExists(outputPath)) {
454
+ existing.push(code);
455
+ }
456
+ }
457
+ s.stop("Registry loaded");
458
+ if (existing.length > 0 && !options.force && !options.dryRun) {
459
+ const existingList = existing.map((code) => formatCountryDisplay(code, registry.countries[code])).join("\n");
460
+ p.note(existingList, "Already installed");
461
+ const overwrite = await p.confirm({
462
+ message: "Overwrite existing countries?",
463
+ initialValue: false
464
+ });
465
+ if (p.isCancel(overwrite) || !overwrite) {
466
+ const newCodes = codes.filter((c) => !existing.includes(c));
467
+ if (newCodes.length === 0) {
468
+ cancel2("Nothing to add");
469
+ return;
470
+ }
471
+ codes.length = 0;
472
+ codes.push(...newCodes);
473
+ }
474
+ }
475
+ if (options.dryRun) {
476
+ const list2 = codes.map((code) => formatCountryDisplay(code, registry.countries[code])).join("\n");
477
+ p.note(list2, "Would add (dry run)");
478
+ p.log.info(`Target: ${config.outputDir}/`);
479
+ console.log();
480
+ return;
481
+ }
482
+ const added = [];
483
+ const failed = [];
484
+ for (const code of codes) {
485
+ const info = registry.countries[code];
486
+ s.start(`${info.flag} ${info.name.en}`);
487
+ try {
488
+ const countryData = await fetchCountry(code, config);
489
+ if (!countryData) {
490
+ s.stop(`${info.flag} ${info.name.en} - failed: invalid data`);
491
+ failed.push(code);
492
+ continue;
493
+ }
494
+ const outputPath = path4.join(config.outputDir, `${code}.json`);
495
+ await fs5.ensureDir(config.outputDir);
496
+ await fs5.writeJson(outputPath, countryData, { spaces: 2 });
497
+ const size = (JSON.stringify(countryData).length / 1024).toFixed(1);
498
+ s.stop(`${info.flag} ${info.name.en} (${size} KB)`);
499
+ added.push(code);
500
+ } catch (error) {
501
+ s.stop(`${info.flag} ${info.name.en} - failed: ${errorMessage(error)}`);
502
+ failed.push(code);
503
+ }
504
+ }
505
+ if (added.length > 0) {
506
+ const ext = config.typescript ? "ts" : "js";
507
+ s.start(`Generating index.${ext}`);
508
+ await generateIndex(config);
509
+ s.stop(`Generated index.${ext}`);
510
+ }
511
+ if (failed.length > 0) {
512
+ process.exitCode = 1;
513
+ p.log.warn(`Failed: ${failed.map((c) => c.toUpperCase()).join(", ")}`);
514
+ }
515
+ if (added.length > 0) {
516
+ p.note(`import { getCities } from '${config.outputDir}';`, "Usage");
517
+ outro2(`Added ${added.length} ${added.length === 1 ? "country" : "countries"}`);
518
+ }
519
+ } catch (error) {
520
+ s.stop("Failed");
521
+ process.exitCode = 1;
522
+ p.log.error(errorMessage(error));
523
+ console.log();
524
+ }
525
+ }
526
+
527
+ // src/commands/cache.ts
528
+ async function cache(action = "info") {
529
+ if (action !== "info" && action !== "clear") {
530
+ intro2("Cache");
531
+ p.log.error(`Unknown action: "${action}". Use "info" or "clear".`);
532
+ console.log();
533
+ process.exitCode = 1;
534
+ return;
535
+ }
536
+ intro2("Cache");
537
+ if (action === "clear") {
538
+ const s = p.spinner();
539
+ s.start("Clearing cache");
540
+ try {
541
+ await clearCache();
542
+ s.stop("Cache cleared");
543
+ outro2("Done");
544
+ } catch (error) {
545
+ s.stop("Failed to clear cache");
546
+ process.exitCode = 1;
547
+ p.log.error(errorMessage(error));
548
+ console.log();
549
+ }
550
+ return;
551
+ }
552
+ const info = await getCacheInfo();
553
+ if (!info) {
554
+ p.log.info("No cached data");
555
+ console.log();
556
+ return;
557
+ }
558
+ const sizeKB = (info.size / 1024).toFixed(1);
559
+ const sizeMB = (info.size / 1024 / 1024).toFixed(2);
560
+ const sizeDisplay = info.size > 1024 * 1024 ? `${sizeMB} MB` : `${sizeKB} KB`;
561
+ p.note(`Files: ${info.files}
562
+ Size: ${sizeDisplay}
563
+ Location: ~/.cache/geo-data/`, "Cache info");
564
+ p.log.message("Run: npx geo-data cache clear");
565
+ console.log();
566
+ }
567
+
568
+ // src/commands/init.ts
569
+ import fs6 from "fs-extra";
570
+ import path5 from "path";
571
+ async function detectDefaults() {
572
+ const cwd = process.cwd();
573
+ const hasTsconfig = await fs6.pathExists(path5.resolve(cwd, "tsconfig.json"));
574
+ const hasSrc = await fs6.pathExists(path5.resolve(cwd, "src"));
575
+ return {
576
+ outputDir: hasSrc ? "./src/data/geo" : "./data/geo",
577
+ typescript: hasTsconfig,
578
+ languages: ["en"],
579
+ includeCoordinates: false,
580
+ hints: {
581
+ outputDir: hasSrc ? "(src/ detected)" : "",
582
+ typescript: hasTsconfig ? "(tsconfig.json found)" : ""
583
+ }
584
+ };
585
+ }
586
+ async function init() {
587
+ banner();
588
+ intro2("Initialize");
589
+ const existingConfig = await getConfig();
590
+ if (existingConfig) {
591
+ const overwrite = await p.confirm({
592
+ message: `${CONFIG_FILE} already exists. Overwrite?`,
593
+ initialValue: false
594
+ });
595
+ if (p.isCancel(overwrite) || !overwrite) {
596
+ cancel2();
597
+ return;
598
+ }
599
+ }
600
+ const defaults = await detectDefaults();
601
+ p.note(
602
+ [
603
+ `Output: ${defaults.outputDir} ${defaults.hints.outputDir}`,
604
+ `TypeScript: ${defaults.typescript ? "yes" : "no"} ${defaults.hints.typescript}`,
605
+ `Languages: ${defaults.languages.join(", ")}`,
606
+ `Coordinates: no`
607
+ ].join("\n"),
608
+ "Detected settings"
609
+ );
610
+ const confirm = await p.confirm({
611
+ message: "Look good?",
612
+ initialValue: true
613
+ });
614
+ if (p.isCancel(confirm)) {
615
+ cancel2();
616
+ return;
617
+ }
618
+ let { outputDir, languages, includeCoordinates, typescript } = defaults;
619
+ if (!confirm) {
620
+ const customOutputDir = await p.text({
621
+ message: "Where should geo data be stored?",
622
+ placeholder: defaults.outputDir,
623
+ defaultValue: defaults.outputDir
624
+ });
625
+ if (p.isCancel(customOutputDir)) {
626
+ cancel2();
627
+ return;
628
+ }
629
+ outputDir = customOutputDir;
630
+ const customLanguages = await p.multiselect({
631
+ message: "Which languages do you need?",
632
+ options: [
633
+ { value: "en", label: "English", hint: "recommended" },
634
+ { value: "ar", label: "Arabic (\u0627\u0644\u0639\u0631\u0628\u064A\u0629)" },
635
+ { value: "fr", label: "French (Fran\xE7ais)" },
636
+ { value: "es", label: "Spanish (Espa\xF1ol)" },
637
+ { value: "de", label: "German (Deutsch)" },
638
+ { value: "zh", label: "Chinese (\u4E2D\u6587)" },
639
+ { value: "ja", label: "Japanese (\u65E5\u672C\u8A9E)" },
640
+ { value: "pt", label: "Portuguese (Portugu\xEAs)" },
641
+ { value: "ru", label: "Russian (\u0420\u0443\u0441\u0441\u043A\u0438\u0439)" },
642
+ { value: "hi", label: "Hindi (\u0939\u093F\u0928\u094D\u0926\u0940)" },
643
+ { value: "it", label: "Italian (Italiano)" },
644
+ { value: "ko", label: "Korean (\uD55C\uAD6D\uC5B4)" },
645
+ { value: "nl", label: "Dutch (Nederlands)" },
646
+ { value: "pl", label: "Polish (Polski)" },
647
+ { value: "pt-BR", label: "Brazilian Portuguese (Portugu\xEAs do Brasil)" },
648
+ { value: "tr", label: "Turkish (T\xFCrk\xE7e)" },
649
+ { value: "uk", label: "Ukrainian (\u0423\u043A\u0440\u0430\u0457\u043D\u0441\u044C\u043A\u0430)" }
650
+ ],
651
+ initialValues: ["en"],
652
+ required: true
653
+ });
654
+ if (p.isCancel(customLanguages)) {
655
+ cancel2();
656
+ return;
657
+ }
658
+ languages = customLanguages;
659
+ const customCoordinates = await p.confirm({
660
+ message: "Include coordinates (latitude/longitude)?",
661
+ initialValue: false
662
+ });
663
+ if (p.isCancel(customCoordinates)) {
664
+ cancel2();
665
+ return;
666
+ }
667
+ includeCoordinates = customCoordinates;
668
+ const customTypescript = await p.confirm({
669
+ message: "Generate TypeScript types?",
670
+ initialValue: defaults.typescript
671
+ });
672
+ if (p.isCancel(customTypescript)) {
673
+ cancel2();
674
+ return;
675
+ }
676
+ typescript = customTypescript;
677
+ }
678
+ const config = {
679
+ $schema: "https://raw.githubusercontent.com/H4ck3r-x0/geo-data/main/schema.json",
680
+ outputDir,
681
+ languages,
682
+ includeCoordinates,
683
+ typescript
684
+ };
685
+ const s = p.spinner();
686
+ s.start("Creating configuration");
687
+ try {
688
+ await fs6.writeJson(CONFIG_FILE, config, { spaces: 2 });
689
+ await fs6.ensureDir(outputDir);
690
+ } catch (error) {
691
+ s.stop("Failed");
692
+ p.log.error(errorMessage(error));
693
+ console.log();
694
+ process.exitCode = 1;
695
+ return;
696
+ }
697
+ s.stop("Configuration created");
698
+ const firstCountry = await p.text({
699
+ message: "Add your first country? (code or name, Enter to skip)",
700
+ placeholder: "e.g. sa, us, fr",
701
+ defaultValue: ""
702
+ });
703
+ if (!p.isCancel(firstCountry) && firstCountry.trim()) {
704
+ console.log();
705
+ await add([firstCountry.trim()], {});
706
+ return;
707
+ }
708
+ p.note("npx geo-data add sa ae Add countries\nnpx geo-data pick Interactive picker", "Next steps");
709
+ outro2("Ready to go!");
710
+ }
711
+
712
+ // src/commands/list.ts
713
+ import chalk2 from "chalk";
714
+ async function list(options = {}) {
715
+ const config = await getConfig();
716
+ intro2(options.installed ? "Installed Countries" : "Available Countries");
717
+ const s = p.spinner();
718
+ s.start("Fetching countries");
719
+ try {
720
+ const registry = await fetchRegistry();
721
+ if (!registry) {
722
+ s.stop("Failed");
723
+ p.log.error("Could not load registry \u2014 invalid data from remote");
724
+ console.log();
725
+ process.exitCode = 1;
726
+ return;
727
+ }
728
+ let installedSet = /* @__PURE__ */ new Set();
729
+ if (config) {
730
+ const installed = await getInstalledCountries(config.outputDir);
731
+ installedSet = new Set(installed);
732
+ }
733
+ s.stop("Countries loaded");
734
+ let countries = Object.entries(registry.countries).sort((a, b) => a[1].name.en.localeCompare(b[1].name.en));
735
+ if (options.installed) {
736
+ if (installedSet.size === 0) {
737
+ p.log.warn("No countries installed yet");
738
+ p.log.message("Run: npx geo-data add sa ae qa");
739
+ console.log();
740
+ return;
741
+ }
742
+ countries = countries.filter(([code]) => installedSet.has(code));
743
+ }
744
+ console.log();
745
+ let currentLetter = "";
746
+ for (const [code, info] of countries) {
747
+ const firstLetter = info.name.en[0].toUpperCase();
748
+ if (firstLetter !== currentLetter && !options.installed) {
749
+ if (currentLetter !== "") console.log();
750
+ currentLetter = firstLetter;
751
+ }
752
+ const installed = installedSet.has(code);
753
+ const marker = installed ? chalk2.green("\u25CF") : chalk2.dim("\u25CB");
754
+ const codeStyled = installed ? chalk2.green.bold(code.toUpperCase()) : chalk2.white(code.toUpperCase());
755
+ console.log(` ${marker} ${info.flag} ${codeStyled.padEnd(installed ? 14 : 6)} ${chalk2.dim(info.name.en)}`);
756
+ }
757
+ console.log();
758
+ if (options.installed) {
759
+ outro2(`${installedSet.size} countries installed`);
760
+ } else {
761
+ if (installedSet.size > 0) {
762
+ p.log.info(`${installedSet.size} installed ${chalk2.green("\u25CF")}`);
763
+ }
764
+ outro2(`${countries.length} countries available`);
765
+ }
766
+ } catch (error) {
767
+ s.stop("Failed to fetch countries");
768
+ process.exitCode = 1;
769
+ p.log.error(errorMessage(error));
770
+ console.log();
771
+ }
772
+ }
773
+
774
+ // src/commands/pick.ts
775
+ import prompts from "prompts";
776
+ async function pick() {
777
+ banner();
778
+ const config = await requireConfig("Pick Countries");
779
+ if (!config) {
780
+ process.exitCode = 1;
781
+ return;
782
+ }
783
+ intro2("Pick Countries");
784
+ const s = p.spinner();
785
+ s.start("Loading countries");
786
+ try {
787
+ const registry = await fetchRegistry();
788
+ if (!registry) {
789
+ s.stop("Failed");
790
+ p.log.error("Could not load registry \u2014 invalid data from remote");
791
+ console.log();
792
+ process.exitCode = 1;
793
+ return;
794
+ }
795
+ const installed = await getInstalledCountries(config.outputDir);
796
+ const installedSet = new Set(installed);
797
+ s.stop("Countries loaded");
798
+ const countries = Object.entries(registry.countries).map(([code, info]) => ({
799
+ code,
800
+ ...info,
801
+ installed: installedSet.has(code)
802
+ })).sort((a, b) => a.name.en.localeCompare(b.name.en));
803
+ p.log.message("Type to search, Space to select, Enter to confirm");
804
+ console.log();
805
+ const { selected } = await prompts({
806
+ type: "autocompleteMultiselect",
807
+ name: "selected",
808
+ message: "Select countries",
809
+ choices: countries.map((c) => ({
810
+ title: `${c.flag} ${c.code.toUpperCase()} - ${c.name.en}`,
811
+ value: c.code,
812
+ selected: c.installed,
813
+ disabled: c.installed ? "(installed)" : false
814
+ })),
815
+ instructions: false
816
+ });
817
+ if (!selected || selected.length === 0) {
818
+ console.log();
819
+ cancel2("No countries selected");
820
+ return;
821
+ }
822
+ const toAdd = selected.filter((code) => !installedSet.has(code));
823
+ if (toAdd.length === 0) {
824
+ console.log();
825
+ cancel2("All selected countries are already installed");
826
+ return;
827
+ }
828
+ console.log();
829
+ await add(toAdd, {});
830
+ } catch (error) {
831
+ s.stop("Failed");
832
+ process.exitCode = 1;
833
+ p.log.error(errorMessage(error));
834
+ console.log();
835
+ }
836
+ }
837
+
838
+ // src/commands/remove.ts
839
+ import fs7 from "fs-extra";
840
+ import path6 from "path";
841
+ async function remove(countryCodes, options = {}) {
842
+ const config = await requireConfig("Remove Countries");
843
+ if (!config) {
844
+ process.exitCode = 1;
845
+ return;
846
+ }
847
+ intro2("Remove Countries");
848
+ const codes = countryCodes.map((c) => c.toLowerCase());
849
+ try {
850
+ const toRemove = [];
851
+ const notFound = [];
852
+ for (const code of codes) {
853
+ const filePath = path6.join(config.outputDir, `${code}.json`);
854
+ if (await fs7.pathExists(filePath)) {
855
+ toRemove.push(code);
856
+ } else {
857
+ notFound.push(code);
858
+ }
859
+ }
860
+ if (notFound.length > 0) {
861
+ for (const code of notFound) {
862
+ p.log.warn(`Not installed: ${code.toUpperCase()}`);
863
+ }
864
+ }
865
+ if (toRemove.length === 0) {
866
+ process.exitCode = 1;
867
+ cancel2("Nothing to remove");
868
+ return;
869
+ }
870
+ const registry = await fetchRegistry();
871
+ if (!registry) {
872
+ p.log.error("Could not load registry \u2014 invalid data from remote");
873
+ console.log();
874
+ process.exitCode = 1;
875
+ return;
876
+ }
877
+ const list2 = toRemove.map((code) => formatCountryDisplay(code, registry.countries[code])).join("\n");
878
+ if (options.dryRun) {
879
+ p.note(list2, "Would remove (dry run)");
880
+ console.log();
881
+ return;
882
+ }
883
+ p.note(list2, "Will remove");
884
+ if (!options.force) {
885
+ const confirm = await p.confirm({
886
+ message: `Remove ${toRemove.length} ${toRemove.length === 1 ? "country" : "countries"}?`,
887
+ initialValue: false
888
+ });
889
+ if (p.isCancel(confirm) || !confirm) {
890
+ cancel2();
891
+ return;
892
+ }
893
+ }
894
+ const s = p.spinner();
895
+ for (const code of toRemove) {
896
+ const info = registry.countries[code];
897
+ const label = `${info?.flag || "\u{1F3F3}\uFE0F"} ${info?.name.en || code}`;
898
+ s.start(label);
899
+ const filePath = path6.join(config.outputDir, `${code}.json`);
900
+ await fs7.remove(filePath);
901
+ s.stop(label);
902
+ }
903
+ const ext = config.typescript ? "ts" : "js";
904
+ s.start(`Regenerating index.${ext}`);
905
+ await generateIndex(config);
906
+ s.stop(`Regenerated index.${ext}`);
907
+ outro2(`Removed ${toRemove.length} ${toRemove.length === 1 ? "country" : "countries"}`);
908
+ } catch (error) {
909
+ process.exitCode = 1;
910
+ p.log.error(errorMessage(error));
911
+ console.log();
912
+ }
913
+ }
914
+
915
+ // src/commands/update.ts
916
+ import fs8 from "fs-extra";
917
+ import path7 from "path";
918
+ async function update(options = {}) {
919
+ const config = await requireConfig("Update Countries");
920
+ if (!config) {
921
+ process.exitCode = 1;
922
+ return;
923
+ }
924
+ intro2("Update Countries");
925
+ const s = p.spinner();
926
+ s.start("Checking installed countries");
927
+ try {
928
+ const installed = await getInstalledCountries(config.outputDir);
929
+ if (installed.length === 0) {
930
+ s.stop("No countries found");
931
+ p.log.warn("No countries installed");
932
+ p.log.message("Run: npx geo-data add sa ae");
933
+ console.log();
934
+ process.exitCode = 1;
935
+ return;
936
+ }
937
+ const registry = await fetchRegistry();
938
+ if (!registry) {
939
+ s.stop("Failed");
940
+ p.log.error("Could not load registry \u2014 invalid data from remote");
941
+ console.log();
942
+ process.exitCode = 1;
943
+ return;
944
+ }
945
+ s.stop(`Found ${installed.length} countries`);
946
+ if (options.dryRun) {
947
+ const list2 = installed.sort().map((code) => formatCountryDisplay(code, registry.countries[code])).join("\n");
948
+ p.note(list2, "Would update (dry run)");
949
+ console.log();
950
+ return;
951
+ }
952
+ let updated = 0;
953
+ for (const code of installed.sort()) {
954
+ const info = registry.countries[code];
955
+ if (!info) continue;
956
+ s.start(`${info.flag} ${info.name.en}`);
957
+ try {
958
+ const countryData = await fetchCountry(code, config);
959
+ if (!countryData) {
960
+ s.stop(`${info.flag} ${info.name.en} - failed: invalid data`);
961
+ continue;
962
+ }
963
+ const outputPath = path7.join(config.outputDir, `${code}.json`);
964
+ await fs8.writeJson(outputPath, countryData, { spaces: 2 });
965
+ const size = (JSON.stringify(countryData).length / 1024).toFixed(1);
966
+ s.stop(`${info.flag} ${info.name.en} (${size} KB)`);
967
+ updated++;
968
+ } catch (error) {
969
+ s.stop(`${info.flag} ${info.name.en} - failed: ${errorMessage(error)}`);
970
+ }
971
+ }
972
+ const ext = config.typescript ? "ts" : "js";
973
+ s.start(`Regenerating index.${ext}`);
974
+ await generateIndex(config);
975
+ s.stop(`Regenerated index.${ext}`);
976
+ outro2(`Updated ${updated} ${updated === 1 ? "country" : "countries"}`);
977
+ } catch (error) {
978
+ s.stop("Failed");
979
+ process.exitCode = 1;
980
+ p.log.error(errorMessage(error));
981
+ console.log();
982
+ }
983
+ }
984
+
985
+ // src/index.ts
986
+ var require2 = createRequire(import.meta.url);
987
+ var { version } = require2("../package.json");
988
+ var program = new Command();
989
+ program.configureOutput({
990
+ outputError: (str, write) => write(chalk3.red(str))
991
+ });
992
+ program.name("geo-data").description("Copy only the countries you need. No bloat.").version(version);
993
+ program.command("init").description("Initialize geo-data in your project").action(init);
994
+ program.command("add").description("Add countries to your project").argument("[countries...]", "Country codes (e.g., sa ae fr)").option("-f, --force", "Overwrite existing without asking").option("-n, --dry-run", "Preview changes without writing").action((countries, options) => {
995
+ if (!countries || countries.length === 0) {
996
+ pick();
997
+ } else {
998
+ add(countries, options);
999
+ }
1000
+ });
1001
+ program.command("list").alias("ls").description("List available countries").option("-i, --installed", "Show only installed countries").action((options) => list(options));
1002
+ program.command("update").description("Re-download installed countries").option("-f, --force", "Update without confirmation").option("-n, --dry-run", "Preview what would be updated").action((options) => update(options));
1003
+ program.command("remove").alias("rm").description("Remove countries from your project").argument("<countries...>", "Country codes to remove").option("-f, --force", "Remove without confirmation").option("-n, --dry-run", "Preview what would be removed").action((countries, options) => remove(countries, options));
1004
+ program.command("cache").description("Manage offline cache").argument("[action]", "info (default) or clear", "info").action((action) => cache(action));
1005
+ program.command("pick").alias("select").description("Interactive country picker").action(() => pick());
1006
+ program.showHelpAfterError(chalk3.dim("(use --help for available commands)"));
1007
+ program.showSuggestionAfterError(true);
1008
+ program.addHelpText(
1009
+ "after",
1010
+ `
1011
+ ${chalk3.bold("Examples:")}
1012
+ ${chalk3.dim("$")} geo-data init
1013
+ ${chalk3.dim("$")} geo-data pick
1014
+ ${chalk3.dim("$")} geo-data add sa ae
1015
+ ${chalk3.dim("$")} geo-data list -i
1016
+ `
1017
+ );
1018
+ program.parseAsync().catch((error) => {
1019
+ console.error(chalk3.red(error instanceof Error ? error.message : "An unexpected error occurred"));
1020
+ process.exitCode = 1;
1021
+ });
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "geo-data-cli",
3
+ "version": "0.1.0",
4
+ "description": "Copy only the countries you need. No bloat.",
5
+ "type": "module",
6
+ "files": [
7
+ "dist",
8
+ "LICENSE"
9
+ ],
10
+ "bin": {
11
+ "geo-data": "./dist/index.js"
12
+ },
13
+ "scripts": {
14
+ "build": "tsup src/index.ts --format esm",
15
+ "dev": "tsup src/index.ts --format esm --watch",
16
+ "start": "node dist/index.js",
17
+ "prepublishOnly": "npm run build",
18
+ "lint": "biome lint .",
19
+ "format": "biome format --write .",
20
+ "check": "biome check .",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "test:coverage": "vitest run --coverage"
24
+ },
25
+ "keywords": [
26
+ "geo",
27
+ "countries",
28
+ "cities",
29
+ "i18n",
30
+ "multilingual",
31
+ "cli",
32
+ "geography",
33
+ "typescript",
34
+ "codegen",
35
+ "states",
36
+ "regions",
37
+ "localization",
38
+ "l10n"
39
+ ],
40
+ "author": "Mohammed Fahad",
41
+ "license": "MIT",
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "git+https://github.com/H4ck3r-x0/geo-data.git",
45
+ "directory": "packages/cli"
46
+ },
47
+ "homepage": "https://github.com/H4ck3r-x0/geo-data#readme",
48
+ "bugs": {
49
+ "url": "https://github.com/H4ck3r-x0/geo-data/issues"
50
+ },
51
+ "engines": {
52
+ "node": ">=18"
53
+ },
54
+ "dependencies": {
55
+ "@clack/prompts": "^0.8.2",
56
+ "chalk": "^5.3.0",
57
+ "commander": "^12.1.0",
58
+ "figlet": "^1.8.0",
59
+ "fs-extra": "^11.2.0",
60
+ "gradient-string": "^3.0.0",
61
+ "prompts": "^2.4.2",
62
+ "string-similarity": "^4.0.4",
63
+ "zod": "^4.3.6"
64
+ },
65
+ "devDependencies": {
66
+ "@types/figlet": "^1.7.0",
67
+ "@types/fs-extra": "^11.0.4",
68
+ "@types/gradient-string": "^1.1.6",
69
+ "@types/node": "^20.11.0",
70
+ "@types/prompts": "^2.4.9",
71
+ "@types/string-similarity": "^4.0.2",
72
+ "tsup": "^8.0.1",
73
+ "typescript": "^5.3.3",
74
+ "vitest": "^4.0.18"
75
+ }
76
+ }