@k03mad/ip2geo 2.1.0 → 2.3.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/README.md CHANGED
@@ -4,6 +4,28 @@
4
4
  — Runtime cache \
5
5
  — Filesystem infinity cache
6
6
 
7
+ ## Global
8
+
9
+ ```bash
10
+ npm i @k03mad/ip2geo -g
11
+
12
+ ip2geo 1.1.1.1
13
+ # {
14
+ # ip: '1.1.1.1',
15
+ # emoji: '🇺🇸',
16
+ # country: 'United States',
17
+ # countryA2: 'US',
18
+ # region: 'District of Columbia',
19
+ # city: 'Washington',
20
+ # org: 'APNIC and Cloudflare DNS Resolver project',
21
+ # isp: 'Cloudflare, Inc.',
22
+ # ispDomain: 'cloudflare.com'
23
+ # }
24
+
25
+ ip2geo 1.1.1.1 --json
26
+ # {"ip":"1.1.1.1","emoji":"🇺🇸","country":"United States","countryA2":"US","region":"District of Columbia","city":"Washington","org":"APNIC and Cloudflare DNS Resolver project","isp":"Cloudflare, Inc.","ispDomain":"cloudflare.com"}
27
+ ```
28
+
7
29
  ## API
8
30
 
9
31
  ```bash
@@ -14,14 +36,7 @@ echo .geoip >> .gitignore
14
36
  ```js
15
37
  import {ip2geo} from '@k03mad/ip2geo';
16
38
 
17
- const {
18
- ip,
19
- emoji,
20
- country,
21
- countryA2,
22
- city,
23
- isp,
24
- } = await ip2geo('1.1.1.1', {
39
+ const info = await ip2geo('1.1.1.1', {
25
40
  // defaults
26
41
  cacheDir: '.geoip',
27
42
  cacheFileName: 'ips.log',
@@ -29,10 +44,15 @@ const {
29
44
  cacheFileNewline: '\n',
30
45
  });
31
46
 
32
- // ip: "1.1.1.1"
33
- // emoji: "🇺🇸"
34
- // country: "United States"
35
- // countryA2: "US"
36
- // city: "Washington"
37
- // isp: "Cloudflare, Inc."
47
+ // info {
48
+ // ip: '1.1.1.1',
49
+ // emoji: '🇺🇸',
50
+ // country: 'United States',
51
+ // countryA2: 'US',
52
+ // region: 'District of Columbia',
53
+ // city: 'Washington',
54
+ // org: 'APNIC and Cloudflare DNS Resolver project',
55
+ // isp: 'Cloudflare, Inc.',
56
+ // ispDomain: 'cloudflare.com'
57
+ // }
38
58
  ```
package/app/cli.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ import {codeText, errorText, nameText} from './helpers/colors.js';
7
+ import {log, throwError} from './helpers/logging.js';
8
+
9
+ import {ip2geo} from './index.js';
10
+
11
+ const jsonParam = '--json';
12
+
13
+ let args = process.argv.slice(2);
14
+
15
+ if (args.length === 0) {
16
+ const prefix = codeText('$');
17
+ const name = nameText('ip2geo');
18
+
19
+ throwError([
20
+ errorText('IP(s) should be passed as args'),
21
+ '',
22
+ `${prefix} ${name} 1.1.1.1`,
23
+ `${prefix} ${name} 1.1.1.1 8.8.8.8`,
24
+ ]);
25
+ }
26
+
27
+ let json;
28
+
29
+ if (args.includes(jsonParam)) {
30
+ json = true;
31
+ args = args.filter(elem => elem !== jsonParam);
32
+ }
33
+
34
+ await Promise.all(args.map(async arg => {
35
+ const output = await ip2geo(arg, {
36
+ cacheDir: path.join(os.tmpdir(), '.ip2geo'),
37
+ });
38
+
39
+ if (json) {
40
+ return log(JSON.stringify(output));
41
+ }
42
+
43
+ log(output);
44
+ }));
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @param {any|any[]} elem
3
+ * @returns {any[]}
4
+ */
5
+ export const convertToArray = elem => Array.isArray(elem) ? elem : [elem];
@@ -0,0 +1,6 @@
1
+ import chalk from 'chalk';
2
+
3
+ export const errorText = chalk.red.bold.bgGray;
4
+ export const codeText = chalk.gray;
5
+ export const numText = chalk.blue;
6
+ export const nameText = chalk.magenta;
@@ -0,0 +1,26 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import {convertToArray} from './array.js';
4
+
5
+ /**
6
+ * @param {any|any[]} msg
7
+ * @returns {void}
8
+ */
9
+ export const log = msg => convertToArray(msg)
10
+ .forEach(elem => console.log(elem));
11
+
12
+ /**
13
+ * @param {any|any[]} msg
14
+ * @returns {void}
15
+ */
16
+ export const logError = msg => convertToArray(msg)
17
+ .forEach(elem => console.error(elem));
18
+
19
+ /**
20
+ * @param {any|any[]} msg
21
+ * @returns {void}
22
+ */
23
+ export const throwError = msg => {
24
+ logError(msg);
25
+ process.exit(1);
26
+ };
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@k03mad/ip2geo",
3
- "version": "2.1.0",
3
+ "version": "2.3.0",
4
4
  "description": "GeoIP library",
5
5
  "maintainers": [
6
6
  "Kirill Molchanov <k03.mad@gmail.com"
7
7
  ],
8
+ "bin": {
9
+ "ip2geo": "app/cli.js"
10
+ },
8
11
  "main": "app/index.js",
9
12
  "repository": {
10
13
  "type": "git",
@@ -17,17 +20,19 @@
17
20
  },
18
21
  "dependencies": {
19
22
  "@k03mad/request": "5.4.1",
23
+ "chalk": "5.3.0",
20
24
  "debug": "4.3.4"
21
25
  },
22
26
  "devDependencies": {
23
27
  "@k03mad/eslint-config": "19.2.0",
24
28
  "eslint": "8.56.0",
25
- "husky": "8.0.3"
29
+ "husky": "8.0.3",
30
+ "mocha": "10.2.0"
26
31
  },
27
32
  "scripts": {
28
33
  "lint": "npm run lint:eslint",
29
34
  "lint:eslint": "eslint ./ --cache",
30
- "test": "node --test tests/*",
35
+ "test": "mocha tests",
31
36
  "clean": "npm run clean:modules && npm run clean:eslint:cache",
32
37
  "clean:modules": "rm -rf ./node_modules || true",
33
38
  "clean:eslint:cache": "rm -rf .eslintcache || true",
@@ -5,3 +5,9 @@ import path from 'node:path';
5
5
  * @returns {string}
6
6
  */
7
7
  export const getCurrentFilename = file => path.basename(file, '.js');
8
+
9
+ /**
10
+ * @param {string} file
11
+ * @returns {string}
12
+ */
13
+ export const getTestFolder = file => path.join('.geoipCustom', file);
@@ -0,0 +1,51 @@
1
+ import assert from 'node:assert/strict';
2
+
3
+ import {describe, it} from 'mocha';
4
+
5
+ import {ip2geo} from '../app/index.js';
6
+
7
+ import {getCurrentFilename, getTestFolder} from './helpers/path.js';
8
+ import {checkCacheFile, removeCacheFolder} from './shared/fs.js';
9
+
10
+ const testName = getCurrentFilename(import.meta.url);
11
+
12
+ describe(testName, () => {
13
+ const CACHE_FILE_DIR = getTestFolder(testName);
14
+ const CACHE_FILE_NAME = 'ips.log';
15
+ const CACHE_FILE_SEPARATOR = ';;';
16
+ const CACHE_FILE_NEWLINE = '\n';
17
+
18
+ const REQUEST_IP = '2a00:dd80:40:100::';
19
+
20
+ const cacheFile = `${REQUEST_IP.split(/\.|:/)[0]}_${CACHE_FILE_NAME}`;
21
+
22
+ const response = {
23
+ ip: REQUEST_IP,
24
+ emoji: '🇳🇱',
25
+ country: 'Netherlands',
26
+ countryA2: 'NL',
27
+ region: 'North Holland',
28
+ city: 'Amsterdam',
29
+ org: '',
30
+ isp: 'NetActuate Inc',
31
+ ispDomain: '',
32
+ };
33
+
34
+ it('should remove fs cache dir if exist', () => removeCacheFolder(CACHE_FILE_DIR));
35
+
36
+ it(`should return correct response for IP: "${REQUEST_IP}"`, async () => {
37
+ const data = await ip2geo(REQUEST_IP, {
38
+ cacheDir: CACHE_FILE_DIR,
39
+ });
40
+
41
+ assert.deepEqual(data, response);
42
+ });
43
+
44
+ it('should have cache file', () => checkCacheFile(
45
+ CACHE_FILE_DIR,
46
+ cacheFile,
47
+ CACHE_FILE_SEPARATOR,
48
+ CACHE_FILE_NEWLINE,
49
+ response,
50
+ ));
51
+ });
@@ -0,0 +1,58 @@
1
+ import assert from 'node:assert/strict';
2
+ import path from 'node:path';
3
+
4
+ import {describe, it} from 'mocha';
5
+
6
+ import {ip2geo} from '../app/index.js';
7
+
8
+ import {getCurrentFilename, getTestFolder} from './helpers/path.js';
9
+ import {checkCacheFile, removeCacheFolder} from './shared/fs.js';
10
+
11
+ const testName = getCurrentFilename(import.meta.url);
12
+ const SUBFOLDERS = 5;
13
+
14
+ describe(testName, () => {
15
+ const CACHE_FILE_DIR = getTestFolder(
16
+ path.join(
17
+ ...Array.from({length: SUBFOLDERS}, () => testName),
18
+ ),
19
+ );
20
+
21
+ const CACHE_FILE_NAME = 'ips.log';
22
+ const CACHE_FILE_SEPARATOR = ';;';
23
+ const CACHE_FILE_NEWLINE = '\n';
24
+
25
+ const REQUEST_IP = '9.9.9.9';
26
+
27
+ const cacheFile = `${REQUEST_IP.split(/\.|:/)[0]}_${CACHE_FILE_NAME}`;
28
+
29
+ const response = {
30
+ ip: REQUEST_IP,
31
+ emoji: '🇨🇭',
32
+ country: 'Switzerland',
33
+ countryA2: 'CH',
34
+ region: 'Zurich',
35
+ city: 'Zürich',
36
+ org: 'Quad9',
37
+ isp: 'Quad9',
38
+ ispDomain: 'quad9.net',
39
+ };
40
+
41
+ it('should remove fs cache dir if exist', () => removeCacheFolder(CACHE_FILE_DIR));
42
+
43
+ it(`should return correct response for IP: "${REQUEST_IP}"`, async () => {
44
+ const data = await ip2geo(REQUEST_IP, {
45
+ cacheDir: CACHE_FILE_DIR,
46
+ });
47
+
48
+ assert.deepEqual(data, response);
49
+ });
50
+
51
+ it('should have cache file', () => checkCacheFile(
52
+ CACHE_FILE_DIR,
53
+ cacheFile,
54
+ CACHE_FILE_SEPARATOR,
55
+ CACHE_FILE_NEWLINE,
56
+ response,
57
+ ));
58
+ });
@@ -0,0 +1,54 @@
1
+ import assert from 'node:assert/strict';
2
+
3
+ import {describe, it} from 'mocha';
4
+
5
+ import {ip2geo} from '../app/index.js';
6
+
7
+ import {getCurrentFilename, getTestFolder} from './helpers/path.js';
8
+ import {checkCacheFile, removeCacheFolder} from './shared/fs.js';
9
+
10
+ const testName = getCurrentFilename(import.meta.url);
11
+
12
+ describe(testName, () => {
13
+ const CACHE_FILE_DIR = getTestFolder(testName);
14
+ const CACHE_FILE_NAME = 'ips.md';
15
+ const CACHE_FILE_SEPARATOR = '-_-';
16
+ const CACHE_FILE_NEWLINE = '%%%';
17
+
18
+ const REQUEST_IP = '8.8.8.8';
19
+
20
+ const cacheFile = `${REQUEST_IP.split(/\.|:/)[0]}_${CACHE_FILE_NAME}`;
21
+
22
+ const response = {
23
+ ip: REQUEST_IP,
24
+ emoji: '🇺🇸',
25
+ country: 'United States',
26
+ countryA2: 'US',
27
+ region: 'California',
28
+ city: 'Mountain View',
29
+ org: 'Google LLC',
30
+ isp: 'Google LLC',
31
+ ispDomain: 'google.com',
32
+ };
33
+
34
+ it('should remove fs cache dir if exist', () => removeCacheFolder(CACHE_FILE_DIR));
35
+
36
+ it(`should return correct response for IP: "${REQUEST_IP}"`, async () => {
37
+ const data = await ip2geo(REQUEST_IP, {
38
+ cacheDir: CACHE_FILE_DIR,
39
+ cacheFileName: CACHE_FILE_NAME,
40
+ cacheFileSeparator: CACHE_FILE_SEPARATOR,
41
+ cacheFileNewline: CACHE_FILE_NEWLINE,
42
+ });
43
+
44
+ assert.deepEqual(data, response);
45
+ });
46
+
47
+ it('should have cache file', () => checkCacheFile(
48
+ CACHE_FILE_DIR,
49
+ cacheFile,
50
+ CACHE_FILE_SEPARATOR,
51
+ CACHE_FILE_NEWLINE,
52
+ response,
53
+ ));
54
+ });
@@ -0,0 +1,78 @@
1
+ import assert from 'node:assert/strict';
2
+
3
+ import {describe, it} from 'mocha';
4
+
5
+ import {ip2geo} from '../app/index.js';
6
+
7
+ import {checkCacheFile, removeCacheFolder} from './shared/fs.js';
8
+
9
+ describe('opts-default', () => {
10
+ const CACHE_FILE_DIR = '.geoip';
11
+ const CACHE_FILE_NAME = 'ips.log';
12
+ const CACHE_FILE_SEPARATOR = ';;';
13
+ const CACHE_FILE_NEWLINE = '\n';
14
+
15
+ const REQUEST_IP = '1.1.1.1';
16
+
17
+ const cacheFile = `${REQUEST_IP.split(/\.|:/)[0]}_${CACHE_FILE_NAME}`;
18
+
19
+ const response = {
20
+ ip: REQUEST_IP,
21
+ emoji: '🇺🇸',
22
+ country: 'United States',
23
+ countryA2: 'US',
24
+ region: 'District of Columbia',
25
+ city: 'Washington',
26
+ org: 'APNIC and Cloudflare DNS Resolver project',
27
+ isp: 'Cloudflare, Inc.',
28
+ ispDomain: 'cloudflare.com',
29
+ };
30
+
31
+ const outputKeys = [
32
+ 'ip',
33
+ 'emoji',
34
+ 'country',
35
+ 'countryA2',
36
+ 'region',
37
+ 'city',
38
+ 'org',
39
+ 'isp',
40
+ 'ispDomain',
41
+ ];
42
+
43
+ removeCacheFolder(CACHE_FILE_DIR);
44
+
45
+ describe('with ip arg', () => {
46
+ it(`should return correct response for IP: "${REQUEST_IP}"`, async () => {
47
+ const data = await ip2geo(REQUEST_IP);
48
+
49
+ assert.deepEqual(data, response);
50
+ });
51
+
52
+ it('should have cache file', () => checkCacheFile(
53
+ CACHE_FILE_DIR,
54
+ cacheFile,
55
+ CACHE_FILE_SEPARATOR,
56
+ CACHE_FILE_NEWLINE,
57
+ response,
58
+ ));
59
+ });
60
+
61
+ describe('without ip arg', () => {
62
+ let data;
63
+
64
+ it('should request geoip without ip arg', async () => {
65
+ data = await ip2geo();
66
+ });
67
+
68
+ outputKeys.forEach(key => {
69
+ it(`should have "${key}" in request response`, () => {
70
+ assert.ok(data[key]);
71
+ });
72
+ });
73
+
74
+ it('should not have extra keys in request response', () => {
75
+ assert.deepEqual(Object.keys(data), outputKeys);
76
+ });
77
+ });
78
+ });
@@ -1,21 +1,18 @@
1
1
  import assert from 'node:assert/strict';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
- import {it} from 'node:test';
5
4
 
6
5
  /**
7
6
  * @param {string} cacheDir
8
7
  */
9
- export const removeCacheFolder = cacheDir => {
10
- it('should remove fs cache dir if exist', async () => {
11
- try {
12
- await fs.rm(cacheDir, {recursive: true, force: true});
13
- } catch (err) {
14
- if (err.code !== 'ENOENT') {
15
- throw err;
16
- }
8
+ export const removeCacheFolder = async cacheDir => {
9
+ try {
10
+ await fs.rm(cacheDir, {recursive: true, force: true});
11
+ } catch (err) {
12
+ if (err.code !== 'ENOENT') {
13
+ throw err;
17
14
  }
18
- });
15
+ }
19
16
  };
20
17
 
21
18
  /**
@@ -25,10 +22,8 @@ export const removeCacheFolder = cacheDir => {
25
22
  * @param {string} cacheFileNewline
26
23
  * @param {object} response
27
24
  */
28
- export const checkCacheFile = (cacheDir, cacheFileName, cacheFileSeparator, cacheFileNewline, response) => {
29
- it('should have cache file', async () => {
30
- const data = await fs.readFile(path.join(cacheDir, cacheFileName), {encoding: 'utf8'});
25
+ export const checkCacheFile = async (cacheDir, cacheFileName, cacheFileSeparator, cacheFileNewline, response) => {
26
+ const data = await fs.readFile(path.join(cacheDir, cacheFileName), {encoding: 'utf8'});
31
27
 
32
- assert.equal(data, Object.values(response).join(cacheFileSeparator) + cacheFileNewline);
33
- });
28
+ assert.equal(data, Object.values(response).join(cacheFileSeparator) + cacheFileNewline);
34
29
  };