@adamlui/geolocate 2.2.1 → 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
@@ -8,12 +8,12 @@
8
8
  <img height=31 src="https://img.shields.io/npm/dm/@adamlui/geolocate?logo=npm&color=af68ff&logoColor=white&labelColor=464646&style=for-the-badge"></a>
9
9
  <a href="#%EF%B8%8F-mit-license">
10
10
  <img height=31 src="https://img.shields.io/badge/License-MIT-orange.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
11
- <a href="https://github.com/adamlui/js-utils/releases/tag/geolocate-2.2.1">
12
- <img height=31 src="https://img.shields.io/badge/Latest_Build-2.2.1-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
11
+ <a href="https://github.com/adamlui/js-utils/releases/tag/geolocate-2.3.0">
12
+ <img height=31 src="https://img.shields.io/badge/Latest_Build-2.3.0-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
13
13
  <a href="https://www.npmjs.com/package/@adamlui/geolocate?activeTab=code">
14
14
  <img height=31 src="https://img.shields.io/npm/unpacked-size/%40adamlui%2Fgeolocate?style=for-the-badge&logo=ebox&logoColor=white&labelColor=464646&color=blue"></a>
15
- <a href="https://github.com/adamlui/js-utils/blob/geolocate-2.2.1/geolocate/dist/geolocate.min.js">
16
- <img height=31 src="https://img.shields.io/github/size/adamlui/js-utils/geolocate/dist/geolocate.min.js?branch=geolocate-2.2.1&label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge"></a>
15
+ <a href="#">
16
+ <img height=31 src="https://img.shields.io/bundlejs/size/%40adamlui%2Fgeolocate%402.2.1?label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge"></a>
17
17
  <a href="https://sonarcloud.io/component_measures?metric=new_vulnerabilities&id=adamlui_js-utils:geolocate/src/geolocate.js">
18
18
  <img height=31 src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fsonarcloud.io%2Fapi%2Fmeasures%2Fcomponent%3Fcomponent%3Dadamlui_js-utils%3Ageolocate%2Fsrc%2Fgeolocate.js%26metricKeys%3Dvulnerabilities&query=%24.component.measures.0.value&style=for-the-badge&logo=sonarcloud&logoColor=white&labelColor=464646&label=Vulnerabilities&color=gold"></a>
19
19
  <a href="https://github.com/toolleeo/cli-apps#networking">
@@ -77,6 +77,8 @@ Commands:
77
77
  -i, --init Create config file (in project root).
78
78
  -h, --help Display help screen.
79
79
  -v, --version Show version number.
80
+ --stats Show npm stats.
81
+ --debug [targetKey] Show debug logs.
80
82
  ```
81
83
 
82
84
  #
@@ -124,14 +126,14 @@ const geo = require('@adamlui/geolocate')
124
126
  #### <> HTML script tag:
125
127
 
126
128
  ```html
127
- <script src="https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.2.1/dist/geolocate.min.js"></script>
129
+ <script src="https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.3.0/dist/geolocate.min.js"></script>
128
130
  ```
129
131
 
130
132
  #### ES6:
131
133
 
132
134
  ```js
133
135
  (async () => {
134
- await import('https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.2.1/dist/geolocate.min.js')
136
+ await import('https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.3.0/dist/geolocate.min.js')
135
137
  // Your code here...
136
138
  })()
137
139
  ```
@@ -140,7 +142,7 @@ const geo = require('@adamlui/geolocate')
140
142
 
141
143
  ```js
142
144
  ...
143
- // @require https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.2.1/dist/geolocate.min.js
145
+ // @require https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.3.0/dist/geolocate.min.js
144
146
  // ==/UserScript==
145
147
 
146
148
  // Your code here...
@@ -148,7 +150,7 @@ const geo = require('@adamlui/geolocate')
148
150
 
149
151
  <br>
150
152
 
151
- **📝 Note:** To always import the latest version (not recommended in production!) remove the `@2.2.1` version tag from the jsDelivr URL: `https://cdn.jsdelivr.net/npm/@adamlui/geolocate/dist/geolocate.min.js`
153
+ **📝 Note:** To always import the latest version (not recommended in production!) remove the `@2.3.0` version tag from the jsDelivr URL: `https://cdn.jsdelivr.net/npm/@adamlui/geolocate/dist/geolocate.min.js`
152
154
 
153
155
  <br>
154
156
 
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ (async () => {
4
+ 'use strict'
5
+
6
+ globalThis.env = {
7
+ args: process.argv.slice(2),
8
+ modes: { dev: /[\\/]src(?:[\\/]|$)/i.test(__dirname) },
9
+ paths: { lib: './lib' }
10
+ }
11
+ env.modes.debug = env.args.some(arg => /^--?(?:V|debug(?:[-_]?mode)?)$/.test(arg))
12
+
13
+ // Import LIBS
14
+ globalThis.log = require(`${env.paths.lib}/log`)
15
+ const clipboardy = require('node-clipboardy'),
16
+ geo = require(`../geolocate${ env.modes.dev ? '' : '.min' }.js`),
17
+ init = require(`${env.paths.lib}/init`)
18
+
19
+ await init.cli()
20
+
21
+ // Exec CMD arg if passed
22
+ if (cli.config.init) return init.configFile()
23
+ else if (cli.config.help) return log.help()
24
+ else if (cli.config.version) return log.version()
25
+ else if (cli.config.stats) return log.stats()
26
+
27
+ // Process IP args
28
+ const validIPs = []
29
+ for (const arg of env.args) if (!arg.startsWith('-')) // load IP from leading-dash-less arg
30
+ validIPs.push(arg.replace(/[[\]]/g, '')) // strip outer '[]' in case copied from docs
31
+
32
+ // Log/copy GEO result(s)
33
+ log.break()
34
+ const geoResults = await geo.locate(validIPs, { verbose: !cli.config.quietMode })
35
+ if (!geoResults) process.exit(1)
36
+ if (!cli.config.quietMode && geoResults.length == 1) log.geoData(geoResults[0])
37
+ log.ifNotQuiet(`${cli.msgs.info_copyingToClip}...`)
38
+ clipboardy.writeSync(JSON.stringify(geoResults, null, 2))
39
+
40
+ })()
@@ -0,0 +1,31 @@
1
+ const color = module.exports = {
2
+ nc: '\x1b[0m',
3
+ hex: {
4
+ br: '#ff0000', by: '#ffff00', bo: '#ffa500', bg: '#00ff00',
5
+ bw: '#ffffff', gry: '#808080', blk: '#000000', tlBG: '#008080'
6
+ },
7
+ schemes: {
8
+ get default() {
9
+ return [
10
+ '#00e5bc', '#18c8ae', '#30ac9f', '#488f91', '#607383',
11
+ '#775674', '#8f3966', '#a71d57', '#bf0049', '#9a1b5e'
12
+ ].map(color.hexToANSI)
13
+ },
14
+ get rainbow() {
15
+ return [
16
+ '#e41a1c', '#ff7f00', '#ffff33', '#4daf4a', '#377eb8',
17
+ '#984ea3', '#f781bf', '#999999', '#a65628', '#d95f02'
18
+ ].map(color.hexToANSI)
19
+ }
20
+ },
21
+
22
+ hexToANSI(hex) {
23
+ const r = parseInt(hex.slice(1,3), 16),
24
+ g = parseInt(hex.slice(3,5), 16),
25
+ b = parseInt(hex.slice(5,7), 16)
26
+ return `\x1b[38;2;${r};${g};${b}m`
27
+ }
28
+ }
29
+
30
+ for (const hexKey of Object.keys(color.hex)) // add color[hexKey] getters that return ANSI
31
+ Object.defineProperty(color, hexKey, { get: () => color.hexToANSI(color.hex[hexKey]) })
@@ -0,0 +1,30 @@
1
+ module.exports = {
2
+
3
+ atomicWrite(filePath, data, encoding = 'utf8') { // to prevent TOCTOU
4
+ const fs = require('fs'),
5
+ path = require('path'),
6
+ tmpPath = path.join(path.dirname(filePath), `.${path.basename(filePath)}.tmp`)
7
+ fs.writeFileSync(tmpPath, data, encoding) ; fs.renameSync(tmpPath, filePath)
8
+ },
9
+
10
+ fetch(url) { // to support Node.js < v21
11
+ return typeof fetch == 'undefined' ? new Promise((resolve, reject) => { // using https?.get()
12
+ const protocol = url.match(/^([^:]+):\/\//)[1]
13
+ if (!/^https?$/.test(protocol))
14
+ reject(new Error(`${cli.msgs.error_invalidURL}.`))
15
+ require(protocol).get(url, resp => {
16
+ let rawData = ''
17
+ resp.on('data', chunk => rawData += chunk)
18
+ resp.on('end', () => resolve({ json: () => JSON.parse(rawData), text: () => rawData }))
19
+ }).on('error', reject)
20
+ }) : fetch(url) // using Node.js fetch()
21
+ },
22
+
23
+ flatten(json, { key = 'message' } = {}) { // eliminate need to ref nested keys
24
+ const flatObj = {}
25
+ for (const jsonKey in json) flatObj[jsonKey] =
26
+ typeof json[jsonKey] == 'object' && key in json[jsonKey] ? json[jsonKey][key]
27
+ : json[jsonKey]
28
+ return flatObj
29
+ }
30
+ }
@@ -0,0 +1,43 @@
1
+ const language = require('./language'),
2
+ settings = require('./settings')
3
+
4
+ const dataPath = `../../${ env.modes.dev ? '../' : 'data/' }`
5
+
6
+ module.exports = {
7
+
8
+ async cli() {
9
+ Object.assign(globalThis.cli ??= {}, require(`${dataPath}package-data.json`))
10
+ cli.msgs = await language.getMsgs('en')
11
+ cli.msgs = await language.getMsgs(cli.lang = settings.load('uiLang') || (
12
+ env.modes.debug ? language.generateRandomLang({ excludes: ['en'] }) : language.getSysLang() ))
13
+ cli.urls.cliDocs ||= `${cli.urls.docs}/#-command-line-usage`
14
+ settings.load() // all keys to cli.config
15
+ },
16
+
17
+ async configFile(filename = settings.configFilename) {
18
+ const fs = require('fs'),
19
+ path = require('path'),
20
+ paths = { target: path.resolve(process.cwd(), filename) }
21
+
22
+ if (fs.existsSync(paths.target)) // use existing config file
23
+ return log.warn(`${cli.msgs.warn_configFileExists}:`, paths.target)
24
+ if (fs.existsSync(paths.src = path.resolve(__dirname, `${dataPath}${filename}`)))
25
+ fs.copyFileSync(paths.src, paths.target) // use found template
26
+
27
+ else { // use jsDelivr copy
28
+ const jsdURL = `${require('./jsdelivr').pkgVerURL()}/${filename}/`
29
+ log.data(`${cli.msgs.info_fetchingRemoteConfigFrom} ${jsdURL}...`)
30
+ try {
31
+ const data = require('./data'),
32
+ resp = await data.fetch(jsdURL)
33
+ if (resp.ok) data.atomicWrite(paths.target, await resp.text())
34
+ else return log.warn(`${cli.msgs.warn_remoteConfigNotFound}: ${jsdURL} (${resp.status})`)
35
+ } catch (err) {
36
+ return log.warn(`${cli.msgs.warn_remoteConfigFailed}: ${jsdURL} ${err.message}`) }
37
+ }
38
+
39
+ log.success(`${cli.msgs.info_configFileCreated}: ${paths.target}\n`)
40
+ log.tip(`${cli.msgs.tip_editToSetDefaults}.`)
41
+ log.tip(`${cli.msgs.tip_cliArgsPrioritized}.`)
42
+ }
43
+ }
@@ -0,0 +1,11 @@
1
+ module.exports = {
2
+
3
+ pkgVerURL(version) {
4
+ version ||= cli.version ||= require('./pkg').getVer('local') || 'none'
5
+ const pkgName = cli.name.split('/')[1],
6
+ verTag = !/^\d+\.\d+\.\d+$/.test(version) ? 'latest' : `${pkgName}-${version}`
7
+ return `${cli.urls.jsdelivr}@${verTag}/${pkgName}`
8
+ },
9
+
10
+ commitURL(hash = 'latest') { return `${cli.urls.jsdelivr}@${hash}/${cli.name.split('/')[1]}` }
11
+ }
@@ -0,0 +1,92 @@
1
+ const data = require('./data')
2
+
3
+ module.exports = {
4
+
5
+ formatCode(langCode) { // to match locale dir name
6
+ return langCode.replace(
7
+ /([a-z]{2,8})[-_]([a-z]{2})/i, (_, lang, region) =>`${lang.toLowerCase()}_${region.toUpperCase()}`) },
8
+
9
+ generateRandomLang({ includes = [], excludes = [] } = {}) {
10
+ const fs = require('fs'),
11
+ path = require('path')
12
+
13
+ let locales = includes.length ? includes : (() => {
14
+
15
+ // Read cache if found
16
+ const cacheDir = path.join(__dirname, '..', '.cache'),
17
+ localeCache = path.join(cacheDir, 'locales.json')
18
+ if (fs.existsSync(localeCache))
19
+ try { return JSON.parse(fs.readFileSync(localeCache, 'utf8')) } catch (err) {}
20
+
21
+ // Discover pkg _locales
22
+ const localesDir = path.resolve(process.cwd(), '_locales')
23
+ if (!fs.existsSync(localesDir)) return ['en']
24
+ const locales = fs.readdirSync(localesDir, { withFileTypes: true })
25
+ .filter(entry => entry.isDirectory()).map(entry => entry.name)
26
+ .filter(name => /^\w{2}[-_]?\w{0,2}$/.test(name))
27
+
28
+ // Cache result
29
+ fs.mkdirSync(cacheDir, { recursive: true })
30
+ data.atomicWrite(localeCache, JSON.stringify(locales, null, 2))
31
+
32
+ return locales
33
+ })()
34
+
35
+ // Filter out excludes
36
+ const excludeSet = new Set(excludes)
37
+ locales = locales.filter(locale => !excludeSet.has(locale))
38
+
39
+ // Get random language
40
+ let randomLang = 'en'
41
+ if (locales.length)
42
+ randomLang = locales[Math.floor(Math.random() * locales.length)]
43
+ log.debug(`Random language: ${randomLang}`)
44
+
45
+ return randomLang
46
+ },
47
+
48
+ async getMsgs(langCode = 'en') {
49
+ langCode = module.exports.formatCode(langCode)
50
+ if (env.msgs && langCode == cli.lang) return env.msgs // don't re-fetch same msgs
51
+
52
+ let msgs = data.flatten( // local ones
53
+ require(`../../${ env.modes.dev ? '../_locales/en/' : 'data/' }messages.json`))
54
+
55
+ if (!langCode.startsWith('en')) { // fetch non-English msgs from jsDelivr
56
+ const msgHostURL = `${require('./jsdelivr').commitURL(cli.commitHashes.locales)}/_locales/`
57
+ let msgHref = `${msgHostURL}${langCode}/messages.json`, msgFetchesTried = 0
58
+ while (msgFetchesTried < 3)
59
+ try { // fetch remote msgs
60
+ msgs = data.flatten(await (await data.fetch(msgHref)).json())
61
+ break
62
+ } catch (err) { // retry up to 2X (region-stripped + EN)
63
+ msgFetchesTried++ ; if (msgFetchesTried >= 3) break
64
+ log.debug(msgHref = langCode.includes('-') && msgFetchesTried == 1 ?
65
+ msgHref.replace(/([^_]*)_[^/]*(\/.*)/, '$1$2') // strip region before retrying
66
+ : `${msgHostURL}en/messages.json` // else use EN msgs
67
+ )
68
+ }
69
+ }
70
+
71
+ return msgs
72
+ },
73
+
74
+ getSysLang() {
75
+ try {
76
+ if (process.platform == 'win32')
77
+ return require('child_process').execSync(
78
+ '(Get-Culture).TwoLetterISOLanguageName', { shell: 'powershell', encoding: 'utf-8' }
79
+ ).trim()
80
+ else { // macOS/Linux
81
+ const pe = process.env
82
+ return (pe.LANG || pe.LANGUAGE || pe.LC_ALL || pe.LC_MESSAGES || pe.LC_NAME)
83
+ .split('.')[0] // strip encoding e.g. .UTF-8
84
+ }
85
+ } catch (err) {
86
+ log.error(`${cli.msgs.error_failedToFetchSysLang}:`, err.message)
87
+ return 'en'
88
+ }
89
+ },
90
+
91
+ validateLangCode(code) { return typeof code != 'string' ? false : /^[a-z]{2,8}(?:[-_][a-z]{2,3})?$/i.test(code) }
92
+ }
@@ -0,0 +1,172 @@
1
+ const colors = require('./color'),
2
+ { getDownloads, getVer } = require('./pkg'),
3
+ string = require('./string')
4
+
5
+ const nextMajVer = require('../../../package.json').version.replace(/^(\d+)\..*/, (_, major) => `${ +major +1 }.0.0`)
6
+
7
+ module.exports = {
8
+ colors,
9
+
10
+ configURL() { this.info(`${cli.msgs.info_exampleValidConfigFile}: ${cli.urls.config}`) },
11
+ configURLandExit(...args) { this.error(...args); this.configURL(); process.exit(1) },
12
+ data(msg) { console.log(`\n${colors.bw}${msg}${colors.nc}`) },
13
+ dim(msg) { console.log(`${colors.gry}${msg}${colors.nc}`) },
14
+ error(...args) { console.error(`\n${colors.br}ERROR:`, ...args, colors.nc) },
15
+ errorAndExit(...args) { this.error(...args); this.helpDocsCmdsDocsURL(); process.exit(1) },
16
+ ifNotQuiet(msg) { if (!cli.config.quietMode) this.info(msg) },
17
+ info(msg) { console.info(`\n${colors.schemes.default[0]}${msg}${colors.nc}`) },
18
+ break() { console.log() },
19
+ tip(msg) { console.info(`${colors.by}TIP: ${msg}${colors.nc}`) },
20
+ success(msg) { console.log(`\n${colors.bg}${msg}${colors.nc}`) },
21
+ warn(...args) { console.warn(`\n${colors.bo}WARNING:`, ...args, colors.nc) },
22
+
23
+ geoData(data) {
24
+ console.info([
25
+ `\nIP: ${this.colors.bw}${data.ip}${this.colors.nc}`,
26
+ `${cli.msgs.geoLabel_country}: ${this.colors.bw}${data.country}${this.colors.nc}`,
27
+ `${cli.msgs.geoLabel_region}: ${this.colors.bw}${data.regionName}${this.colors.nc}`,
28
+ `${cli.msgs.geoLabel_city}: ${this.colors.bw}${data.city}${this.colors.nc}`,
29
+ `${cli.msgs.geoLabel_zip}: ${this.colors.bw}${data.zip}${this.colors.nc}`,
30
+ `${cli.msgs.geoLabel_lat}: ${this.colors.bw}${data.lat}${this.colors.nc}`,
31
+ `${cli.msgs.geoLabel_lon}: ${this.colors.bw}${data.lon}${this.colors.nc}`,
32
+ `${cli.msgs.geoLabel_timeZone}: ${
33
+ this.colors.bw}${data.timezone.replace(/_/g, ' ').replace(/\//g, ' / ')}${this.colors.nc}`,
34
+ `ISP: ${this.colors.bw}${data.isp}${this.colors.nc}`
35
+ ].join('\n'))
36
+ },
37
+
38
+ argDoesNothing(arg) {
39
+ this.warn(`${cli.msgs.warn_option} ${arg} ${cli.msgs.warn_noLongerHasAnyEffect} ${
40
+ cli.msgs.warn_andWillBeRemoved} @ v${nextMajVer}`)
41
+ },
42
+
43
+ configKeyReplacedBy(oldKey, newKey, oldVal) {
44
+ if (!this[`${oldKey}Warned`]) {
45
+ this.warn(
46
+ `${cli.msgs.info_configFile} ${cli.msgs.warn_option.toLowerCase()} '${oldKey}: ${oldVal}' ${
47
+ cli.msgs.warn_hasBeenReplacedBy} '${
48
+ newKey}: ${ isNegKey(oldKey) != isNegKey(newKey) ? !oldVal : oldVal }' ${
49
+ cli.msgs.warn_andWillBeRemoved} @ v${nextMajVer}`
50
+ )
51
+ this[`${oldKey}Warned`] = true
52
+ function isNegKey(key) { return /^(?:no|disable|exclude)[A-Z]/.test(key) }
53
+ }
54
+ },
55
+
56
+ debug(msg) {
57
+ if (!env.modes.debug) return
58
+ this.argIdx ??= env.args.findIndex(arg => /^--?(?:V|debug(?:[-_]?mode)?)$/.test(arg))
59
+ if (this.argIdx +1 < env.args.length && !env.args[this.argIdx +1].startsWith('-')) // use --debug [targetKey]
60
+ this.key ??= env.args[this.argIdx +1].replace('-', '_')
61
+ if (this.key)
62
+ this.val = cli.config[this.key] || `cli.config key "${this.key}" ${cli.msgs.warn_notFound.toLowerCase()}`
63
+ else
64
+ this.val = cli.config
65
+ msg += `\n${colors.gry}${JSON.stringify(this.val)}${colors.nc}`
66
+ console.debug(`\n${colors.by}DEBUG:`, msg, colors.nc)
67
+ },
68
+
69
+ help(includeSections = ['header', 'usage', 'params', 'flags', 'cmds']) {
70
+ cli.prefix = `${this.colors.tlBG}${this.colors.blk} ${cli.name.replace(/^@[^/]+\//, '')} ${this.colors.nc} `
71
+ const helpSections = {
72
+ header: [
73
+ `\n├ ${cli.prefix}${cli.msgs.pkg_copyright}.`,
74
+ `${cli.prefix}${cli.msgs.prefix_source}: ${cli.urls.src}`
75
+ ],
76
+ usage: [
77
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_usage}:${this.colors.nc}`,
78
+ ` ${this.colors.bw}» ${this.colors.bg}${cli.cmdFormat}${this.colors.nc}`
79
+ ],
80
+ params: [
81
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_params}:${this.colors.nc}`,
82
+ ` --ui-lang="code" ${cli.msgs.optionDesc_uiLang}.`,
83
+ ` --config="path/to/file" ${cli.msgs.optionDesc_config}.`
84
+ ],
85
+ flags: [
86
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_flags}:${this.colors.nc}`,
87
+ ` -q, --quiet ${cli.msgs.optionDesc_quiet}.`
88
+ ],
89
+ cmds: [
90
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_cmds}:${this.colors.nc}`,
91
+ ` -i, --init ${cli.msgs.optionDesc_init}.`,
92
+ ` -h, --help ${cli.msgs.optionDesc_help}.`,
93
+ ` -v, --version ${cli.msgs.optionDesc_version}.`,
94
+ ` -v, --stats ${cli.msgs.optionDesc_stats}.`,
95
+ ` -V, --debug ${cli.msgs.optionDesc_debug}.`
96
+ ]
97
+ }
98
+ includeSections.forEach(section => // print valid arg elems
99
+ helpSections[section]?.forEach(line => printHelpMsg(line, /header|usage/.test(section) ? 1 : 29)))
100
+ console.info(`\n${cli.msgs.info_moreHelp}, ${
101
+ cli.msgs.info_visit}: ${this.colors.bw}${cli.urls.cliDocs}${this.colors.nc}`)
102
+
103
+ function printHelpMsg(msg, indent) { // wrap msg + indent 2nd+ lines
104
+ const terminalWidth = process.stdout.columns || 80,
105
+ words = msg.match(/\S+|\s+/g),
106
+ lines = [], prefix = '| '
107
+
108
+ // Split msg into lines of appropriate lengths
109
+ let currentLine = ''
110
+ words.forEach(word => {
111
+ const lineLength = terminalWidth -( !lines.length ? 0 : indent )
112
+ if (currentLine.length + prefix.length + word.length > lineLength) { // cap/store it
113
+ lines.push(!lines.length ? currentLine : currentLine.trimStart())
114
+ currentLine = ''
115
+ }
116
+ currentLine += word
117
+ })
118
+ lines.push(!lines.length ? currentLine : currentLine.trimStart())
119
+
120
+ // Print formatted msg
121
+ lines.forEach((line, idx) => console.info(prefix +(
122
+ idx == 0 ? line // print 1st line unindented
123
+ : ' '.repeat(indent) + line // print subsequent lines indented
124
+ )))
125
+ }
126
+ },
127
+
128
+ helpDocsCmdsDocsURL() {
129
+ console.info(`\n${
130
+ cli.msgs.info_moreHelp}, ${cli.msgs.info_type} ${
131
+ colors.bw}${cli.name.split('/')[1]} --<docs|help>${colors.nc} ${
132
+ cli.msgs.info_or} ${cli.msgs.info_visit}\n${colors.by}${cli.urls.docs}${colors.nc}`
133
+ )
134
+ },
135
+
136
+ initCmd(invalidKey) {
137
+ if (invalidKey)
138
+ this.warn(
139
+ `${cli.msgs.error_invalidKey} '${invalidKey}' ${cli.msgs.error_foundIn}\n`
140
+ + `${log.colors.gry}${cli.configPath}`
141
+ )
142
+ if (!this.initTipped) {
143
+ this.break()
144
+ this.tip(`${
145
+ string.toTitleCase(cli.msgs.info_type)} '${cli.name} init' ${
146
+ cli.msgs.info_toCreateDefaultConfig}`
147
+ )
148
+ this.initTipped = true
149
+ }
150
+ },
151
+
152
+ invalidConfigKey(key) { if (!this[`${key}Tipped`]) { this.initCmd(key) ; this[`${key}Tipped`] = true } },
153
+
154
+ async stats(pkgName = cli.name, options = { ecosystem: 'npm', maxDays: 8, maxVers: 5, scheme: 'default' }) {
155
+ const pkgStats = await getDownloads(pkgName, options),
156
+ schemeData = colors.schemes[options.scheme]
157
+ if (!schemeData) return this.error(`Scheme '${options.scheme}' not found!`)
158
+ const colorMap = Object.fromEntries(schemeData.map((hex, idx) => [`c${idx}`, hex])),
159
+ statsTable = new (require('console-table-printer').Table)({ colorMap })
160
+ pkgStats.forEach((row, idx) => // build colored rows
161
+ statsTable.addRow(row, { color: `c${Math.floor(idx / pkgStats.length * schemeData.length)}` }))
162
+ statsTable.printTable()
163
+ },
164
+
165
+ version() {
166
+ this.info(cli.name)
167
+ this.data(`${
168
+ cli.msgs.prefix_globalVer}: ${ getVer('global') || 'none' }\n${
169
+ cli.msgs.prefix_localVer }: ${ getVer('local') || 'none' }`
170
+ )
171
+ }
172
+ }
@@ -0,0 +1,78 @@
1
+ const data = require('./data')
2
+
3
+ const endpoints = {
4
+ npmjsDLs: 'https://api.npmjs.org/downloads',
5
+ pepyProjects: 'https://pepy.tech/projects'
6
+ }
7
+
8
+ module.exports = {
9
+
10
+ async getDownloads(
11
+ pkgName, // e.g. some-npm-pkg, npm:@adamlui/minify.js, pypi:translate-messages
12
+ { ecosystem = 'npm', maxDays = 10, maxVers = 10 } = {}
13
+ ) {
14
+ if (pkgName.includes(':')) [ecosystem, pkgName] = pkgName.split(':')
15
+
16
+ if (/npm|node/i.test(ecosystem)) { // fetch from endpoints.npmjsDLs
17
+ function formatDate(date) { return date.toISOString().split('T')[0] }
18
+ const dates = { end: new Date(), start: new Date() }
19
+ dates.start.setMonth(dates.end.getMonth() -3)
20
+ const npmjsURL = `${endpoints.npmjsDLs}/range/${formatDate(dates.start)}:${formatDate(dates.end)}/${pkgName}`
21
+ log.info(`Fetching npm stats for ${pkgName}${
22
+ env.modes.debug ? ` from\n${log.colors.bw}${npmjsURL}` : '' }...\n`)
23
+ return (await (await data.fetch(npmjsURL)).json()).downloads // { downloads: [{ day, downloads }] }
24
+ .sort((a, b) => new Date(b.day) - new Date(a.day)) // new ⇅ old
25
+ .slice(0, maxDays) // cap rows
26
+ .map(({ day: date, downloads }) => ({ date, downloads }))
27
+
28
+ } else if (/^py/i.test(ecosystem)) { // fetch from endpoints.pepyProjects
29
+ let rows = []
30
+ const pepyURL = `${endpoints.pepyProjects}/${pkgName}`
31
+ log.info(`Fetching PyPI/mirror stats for ${pkgName}${
32
+ env.modes.debug ? ` from\n${log.colors.bw}${pepyURL}` : '' }...\n`)
33
+ const respText = await (await data.fetch(pepyURL)).text(),
34
+ rePush = /self\.__next_f\.push\(\[\d+,\s*"((?:\\.|[^"\\])*)"\]\)/g
35
+ let downloads = {}, match
36
+ while ((match = rePush.exec(respText))) {
37
+ try { // extract project.downloads
38
+ const inner = JSON.parse(`"${match[1]}"`),
39
+ data = JSON.parse(inner.substring(inner.indexOf(':') +1))
40
+ if (data[3]?.project) downloads = data[3].project.downloads
41
+ } catch (err) {}
42
+ }
43
+ rows = Object.entries(downloads)
44
+ .sort(([a], [b]) => new Date(b) - new Date(a)) // new ⇅ old
45
+ .slice(0, maxDays) // cap rows
46
+ .map(([date, data]) => ({ date, ...data }))
47
+ const activeVers = new Set()
48
+ rows.forEach(row => Object.keys(row).forEach(key => {
49
+ if (key != 'date' && row[key] > 0) activeVers.add(key) }))
50
+ const finalVers = [...activeVers]
51
+ .sort((a, b) => b.localeCompare(a, undefined, { numeric: true })) // new ⇆ old
52
+ .slice(0, maxVers) // cap columns
53
+ return rows.map(row => {
54
+ const result = { date: row.date }
55
+ finalVers.forEach(ver => result[ver] = row[ver] || 0)
56
+ return result
57
+ })
58
+
59
+ } else return log.debug(`${ecosystem} not supported.`)
60
+ },
61
+
62
+ getVer(type = 'any') { // or <'global'|'local'>
63
+ let pkgVer
64
+ if (type != 'global')
65
+ try { // get local ver
66
+ const localManifestPath = require('path').resolve(
67
+ process.cwd(), 'node_modules', cli.name, 'package.json')
68
+ pkgVer = require(localManifestPath).version
69
+ } catch (err) { log.debug(`${cli.msgs.error_readingLocalPkgVer}: ${err.message}`) }
70
+ if (type == 'global' || type == 'all' && !pkgVer)
71
+ try { // get global ver
72
+ pkgVer = require('child_process').execSync(
73
+ `npm view ${JSON.stringify(cli.name)} version`
74
+ ).toString().trim()
75
+ } catch (err) { log.debug(`${cli.msgs.error_failedToFetchGlobalVer}: ${err.message}`) }
76
+ return pkgVer
77
+ }
78
+ }
@@ -0,0 +1,131 @@
1
+ const fs = require('fs'),
2
+ path = require('path')
3
+
4
+ ;(globalThis.cli ??= {}).config = {}
5
+
6
+ module.exports = {
7
+ configFilename: 'geolocate.config.mjs',
8
+
9
+ controls: {
10
+ uiLang: { type: 'param', valType: 'langCode', regex: /^--?ui[-_]?lang(?:[=\s].*|$)/ },
11
+ config: { type: 'param', valType: 'filepath', regex: /^--?config(?:[=\s].*|$)/ },
12
+ quietMode: { type: 'flag', regex: /^--?q(?:uiet)?(?:[-_]?mode)?$/ },
13
+ init: { type: 'cmd', regex: /^-{0,2}i(?:nit)?$/ },
14
+ help: { type: 'cmd', regex: /^--?h(?:elp)?$/ },
15
+ version: { type: 'cmd', regex: /^--?ve?r?s?i?o?n?$/ },
16
+ stats: { type: 'cmd', regex: /^--?stats?$/ }
17
+ },
18
+
19
+ load(ctrlKeys = Object.keys(this.controls)) {
20
+ const inputCtrlKeys = [].concat(ctrlKeys) // force array
21
+
22
+ if (!cli.defaultsSet && !arguments.length) { // init all defaults on arg-less load()
23
+ inputCtrlKeys.forEach(key => {
24
+ const ctrl = this.controls[key] ; if (ctrl.mode || ctrl.type == 'legacy') return
25
+ cli.config[key] ??= ctrl.defaultVal ?? ( ctrl.type == 'param' ? '' : false )
26
+ })
27
+ cli.defaultsSet = true
28
+ log.debug('All cli.config default vals set!')
29
+ }
30
+
31
+ if (!cli.configPathTried) { // init config file path
32
+ const configArg = env.args.find(arg => this.controls.config.regex.test(arg))
33
+
34
+ if (configArg) { // resolve input path, then validate
35
+ if (!/=/.test(configArg))
36
+ log.errorAndExit(`[${configArg}] ${cli.msgs.error_mustIncludePath}`)
37
+ const inputPath = configArg.split('=')[1]
38
+ cli.configPath = path.isAbsolute(inputPath) ? inputPath : path.resolve(process.cwd(), inputPath)
39
+ if (!fs.existsSync(cli.configPath))
40
+ log.configURLandExit(`${cli.msgs.error_configFileNotFound}:`, cli.configPath)
41
+
42
+ } else // auto-discover .config.[mc]?js file
43
+ for (const configExt of ['.mjs', '.cjs', '.js']) {
44
+ const autoPath = path.resolve(process.cwd(), this.configFilename.replace(/\.[^.]+$/, configExt))
45
+ if (fs.existsSync(autoPath)) { cli.configPath = autoPath ; break }
46
+ }
47
+
48
+ cli.configPathTried = true
49
+ }
50
+
51
+ if (cli.configPath) // load from config file
52
+ try {
53
+ const mod = require(cli.configPath), fileConfig = mod?.default ?? mod
54
+ if (!fileConfig || typeof fileConfig != 'object')
55
+ log.configURLandExit(`${cli.msgs.error_invalidConfigFile}.`)
56
+ ;(arguments.length ? inputCtrlKeys : Object.keys(fileConfig)).forEach(key => {
57
+ if (!(key in fileConfig)) return
58
+ const val = fileConfig[key], ctrl = this.controls[key]
59
+ if (!ctrl) {
60
+ if (this.configFileKeyWhitelist && !this.configFileKeyWhitelist.includes(key))
61
+ log.invalidConfigKey(key)
62
+ return
63
+ } else if (ctrl.type == 'legacy' && ctrl.replacedBy) {
64
+ if (key.toLowerCase().includes('no') != ctrl.replacedBy.toLowerCase().includes('no'))
65
+ cli.config[ctrl.replacedBy] = !val // assign opposite val to current key
66
+ else // assign direct val to current key
67
+ cli.config[ctrl.replacedBy] = val
68
+ return log.configKeyReplacedBy(key, ctrl.replacedBy, val)
69
+ }
70
+ cli.config[key] = val
71
+ })
72
+ if (!arguments.length) log.debug('Config file loaded!')
73
+ } catch (err) {
74
+ log.configURLandExit(`${cli.msgs.error_failedToLoadConfigFile}:`, cli.configPath, `\n${err.message}`) }
75
+
76
+ for (let i = 0 ; i < env.args.length ; i++) { // load from CLI arg (overriding config file loads)
77
+ const arg = env.args[i]
78
+ if (/^[^-]|--?(?:config|debug)/.test(arg) && arg != 'init') continue
79
+ const ctrlKey = Object.keys(this.controls).find(key => this.controls[key]?.regex?.test(arg))
80
+ if (!ctrlKey && cli.msgs) log.errorAndExit(`[${arg}] ${cli.msgs.error_notRecognized}.`)
81
+ if (!inputCtrlKeys.includes(ctrlKey)) return // don't process env.args when load() specific keys
82
+ const ctrl = this.controls[ctrlKey]
83
+ if (ctrl.type == 'legacy') { log.argDoesNothing(arg) ; continue }
84
+ if (ctrl.mode) // set cli.config.mode to mode name
85
+ cli.config.mode = ctrlKey.replace(/mode$/i, '').toLowerCase()
86
+ else { // init flag/param/cmd cli.config[ctrlKey] val
87
+ if (ctrl.type == 'param')
88
+ cli.config[ctrlKey] =
89
+ arg.includes('=') ? arg.split('=')[1]?.trim() || '' // =val
90
+ : (i +1 < env.args.length && !env.args[i +1].startsWith('-')) ? env.args[++i] // dashless val
91
+ : '' // val-less --param passed
92
+ else // flag/cmd
93
+ cli.config[ctrlKey] = true
94
+ }
95
+ }
96
+
97
+ if (!arguments.length) log.debug('Args parsed!')
98
+
99
+ this.parseValidateConfig(inputCtrlKeys)
100
+ if (!arguments.length) log.debug('All cli.config vals parsed/validated!')
101
+
102
+ return inputCtrlKeys.length == 1 ? cli.config[inputCtrlKeys[0]] : cli.config
103
+ },
104
+
105
+ parseValidateConfig(ctrlKeys = Object.keys(this.controls)) {
106
+ const language = require('./language')
107
+ for (const key of [].concat(ctrlKeys)) {
108
+ const ctrl = this.controls[key], configVal = cli.config[key]
109
+
110
+ if (ctrl.parser && !ctrl.parsed) {
111
+ cli.config[key] = ctrl.parser(configVal) ; ctrl.parsed = true }
112
+
113
+ if (ctrl.valType) ({
114
+ positiveInt() {
115
+ const numVal = parseInt(configVal, 10)
116
+ if (isNaN(numVal) || numVal < 1)
117
+ log.errorAndExit(`[${key}] ${cli.msgs.error_nonPositiveNum}: ${configVal}`)
118
+ cli.config[key] = numVal
119
+ },
120
+ filepath() {
121
+ if (configVal && !fs.existsSync(configVal))
122
+ log.errorAndExit(`[${key}] ${cli.msgs.error_invalidFilepath}: ${configVal}`)
123
+ },
124
+ langCode() {
125
+ if (configVal && !language.validateLangCode(configVal))
126
+ log.errorAndExit(`[${key}] ${cli.msgs.error_invalidLangCode}: ${configVal}`)
127
+ }
128
+ })[ctrl.valType]()
129
+ }
130
+ }
131
+ }
@@ -0,0 +1,3 @@
1
+ module.exports = {
2
+ toTitleCase(str) { return str.toLowerCase().replace(/\b\w/g, char => char.toUpperCase()) }
3
+ }
@@ -1,6 +1,5 @@
1
1
  {
2
- "appName": { "message": "geolocate" },
3
- "appCopyright": { "message": "© 2024–2026 Adam Lui under the MIT license" },
2
+ "pkg_copyright": { "message": "© 2024–2026 Adam Lui under the MIT license" },
4
3
  "prefix_globalVer": { "message": "Global version" },
5
4
  "prefix_localVer": { "message": "Local version" },
6
5
  "prefix_source": { "message": "Source" },
@@ -11,6 +10,8 @@
11
10
  "error_invalidLangCode": { "message": "is an invalid language code" },
12
11
  "error_invalidURL": { "message": "Invalid URL" },
13
12
  "error_invalidConfigFile": { "message": "Config file must export an object" },
13
+ "error_invalidKey": { "message": "Invalid key" },
14
+ "error_foundIn": { "message": "found in" },
14
15
  "error_configFileNotFound": { "message": "Config file not found" },
15
16
  "error_failedToLoadConfigFile": { "message": "Failed to load config file" },
16
17
  "error_failedToFetchGlobalVer": { "message": "Failed to fetch global version" },
@@ -18,13 +19,20 @@
18
19
  "error_mustIncludePath": { "message": "must include =path" },
19
20
  "warn_configFileExists": { "message": "Config file already exists" },
20
21
  "warn_remoteConfigNotFound": { "message": "Remote config file not found" },
21
- "warn_remoteConfigFailed": { "message": "Failed to fetch remote config file" },
22
- "info_exampleValidConfigFile": { "message": "Example valid config file" },
23
- "info_copyingToClip": { "message": "Copying to clipboard" },
24
- "info_moreHelp": { "message": "For more help" },
22
+ "warn_remoteConfigFailed": { "message": "Failed to fetch remote config file" },
23
+ "warn_option": { "message": "Option" },
24
+ "warn_noLongerHasAnyEffect": { "message": "no longer has any effect" },
25
+ "warn_hasBeenReplacedBy": { "message": "has been replaced by" },
26
+ "warn_andWillBeRemoved": { "message": "and will be removed" },
27
+ "warn_notFound": { "message": "Not found" },
28
+ "info_configFile": { "message": "Config file" },
29
+ "info_exampleValidConfigFile": { "message": "Example valid config file" },
30
+ "info_copyingToClip": { "message": "Copying to clipboard" },
31
+ "info_moreHelp": { "message": "For more help" },
25
32
  "info_type": { "message": "type" },
26
33
  "info_or": { "message": "or" },
27
34
  "info_visit": { "message": "visit" },
35
+ "info_toCreateDefaultConfig": { "message": "to create default config file" },
28
36
  "info_configFileCreated": { "message": "Config file created" },
29
37
  "info_fetchingRemoteConfigFrom": { "message": "Fetching remote config file from" },
30
38
  "tip_editToSetDefaults": { "message": "Edit this file to customize defaults" },
@@ -45,5 +53,7 @@
45
53
  "optionDesc_quiet": { "message": "Suppress all logging except errors" },
46
54
  "optionDesc_init": { "message": "Create config file (in project root)" },
47
55
  "optionDesc_help": { "message": "Display help screen" },
48
- "optionDesc_version": { "message": "Show version number" }
56
+ "optionDesc_version": { "message": "Show version number" },
57
+ "optionDesc_stats": { "message": "Show npm stats" },
58
+ "optionDesc_debug": { "message": "Show debug logs" }
49
59
  }
@@ -13,6 +13,6 @@
13
13
  "src": "https://github.com/adamlui/js-utils/tree/main/geolocate/src"
14
14
  },
15
15
  "commitHashes": {
16
- "locales": "69618f8"
16
+ "locales": "78c0809"
17
17
  }
18
18
  }
@@ -3,4 +3,4 @@
3
3
  * Source: https://github.com/adamlui/js-utils/tree/main/geolocate/src
4
4
  * Documentation: https://github.com/adamlui/js-utils/tree/main/geolocate/docs
5
5
  */
6
- async function geolocate(e,n={}){var t,o={verbose:!0};log.prefix="geolocate()",(e=[].concat(e))[0]||=await getOwnIP();for(t of e){n.verbose&&log.info(`Validating ${t}...`);let o;try{o=require("generate-ip").ipv4.validate}catch(e){await import("https://cdn.jsdelivr.net/npm/generate-ip/dist/generate-ip.min.js"),o=window.ipv4.validate}if(o&&!o(t,{verbose:!1}))return log.error(t+" is not a valid IPv4 address.")}if(validateOptions({options:n,defaultOptions:o,helpURL:"https://github.com/adamlui/js-utils/tree/main/geolocate/docs/#locateips-options",exampleCall:"geolocate('8.8.8.8', { verbose: false })"})){n={...o,...n};try{var l,s=[];for(l of e){n.verbose&&log.info(`Fetching geolocation data for ${l}...`);let e=await fetchData("http://ip-api.com/json/"+l),{status:o,org:t,as:r,query:a,...i}=await e.json();s.push({ip:l,...i})}return n.verbose&&"undefined"!=typeof window&&log.info("Success!","Check returned array."),s}catch(e){log.error(e.message)}}}function fetchData(r){return"undefined"==typeof fetch?new Promise((t,o)=>{try{var e=r.match(/^([^:]+):\/\//)[1];/^https?$/.test(e)||o(new Error("Invalid fetchData() URL.")),require(e).get(r,e=>{let o="";e.on("data",e=>o+=e),e.on("end",()=>t({json:()=>JSON.parse(o)}))}).on("error",e=>o(new Error(e.message)))}catch(e){o(new Error("Environment not supported."))}}):fetch(r)}async function getOwnIP(){return fetchData("https://ifconfig.me/ip").then(e=>e.text()).catch(()=>fetchData("http://ip-api.com/json/").then(e=>e.json()).then(e=>e.query)).catch(async()=>{try{var e=require("child_process").exec,o=require("util").promisify,{stdout:t,stderr:r}=await o(e)("curl -s ifconfig.me");return r?log.error(r):t.trim()}catch(e){log.error(e.message)}})}function validateOptions({options:e,defaultOptions:o,helpURL:t,exampleCall:r}){var a,i,n=Object.keys(o).filter(e=>"boolean"==typeof o[e]),l=Object.keys(o).filter(e=>Number.isInteger(o[e]));if("object"!=typeof e)return a=r.split(",").findIndex(e=>e.trim().startsWith("{"))+1,a+=["st","nd","rd"][a-1]||"th",log.error(`${"0th"==a?"[O":a+" arg [o"}ptions] can only be an object of key/vals.`),log.info("Example valid call:",r),log.validOptions(o),log.helpURL(t),!1;for(i in e){if(!Object.prototype.hasOwnProperty.call(o,i))return log.error(`\`${i}\` is an invalid option.`),log.validOptions(o),log.helpURL(t),!1;if(n.includes(i)&&"boolean"!=typeof e[i])return log.error(`[${i}] option can only be \`true\` or \`false\`.`),log.helpURL(t),!1;if(l.includes(i)&&(e[i]=parseInt(e[i],10),isNaN(e[i])||e[i]<1))return log.error(`[${i}] option can only be an integer > 0.`),log.helpURL(t),!1}return!0}Object.assign(globalThis.api??={},{name:"geolocate",aliases:{geolocate:["Geolocate","geoLocate","GeoLocate","locate","Locate"]}});let log={prefix:api.name,error(...e){console.error(this.prefix+" » ERROR:",...e)},helpURL(e=api.urls?.docs){this.info("For more help, please visit",e)},info(...e){console.info(this.prefix+" »",...e)},validOptions(e){var o=Object.keys(e).join(", "),e=JSON.stringify(e,null,2).replace(/"([^"]+)":/g,"$1:").replace(/"/g,"'").replace(/\n\s*/g," ");this.info(`Valid options: [${o}]`),this.info("If omitted, default settings are: "+e)}};try{module.exports={geolocate:geolocate}}catch(e){}try{window.geo={geolocate:geolocate}}catch(e){}for(let o in api.aliases){try{api.aliases[o].forEach(e=>module.exports[e]??=module.exports[o])}catch(e){}try{api.aliases[o].forEach(e=>window.geo[e]??=window.geo[o])}catch(e){}}
6
+ async function geolocate(e,n={}){var o,t={verbose:!0};_log.prefix="geolocate()",(e=[].concat(e))[0]||=await getOwnIP();for(o of e){n.verbose&&_log.info(`Validating ${o}...`);let t;try{t=require("generate-ip").ipv4.validate}catch(e){await import("https://cdn.jsdelivr.net/npm/generate-ip/dist/generate-ip.min.js"),t=window.ipv4.validate}if(t&&!t(o,{verbose:!1}))return _log.error(o+" is not a valid IPv4 address.")}if(_validateOptions({options:n,defaultOptions:t,helpURL:"https://github.com/adamlui/js-utils/tree/main/geolocate/docs/#locateips-options",exampleCall:"geolocate('8.8.8.8', { verbose: false })"})){n={...t,...n};try{var s,l=[];for(s of e){n.verbose&&_log.info(`Fetching geolocation data for ${s}...`);let e=await fetchData("http://ip-api.com/json/"+s),{status:t,org:o,as:r,query:i,...a}=await e.json();l.push({ip:s,...a})}return n.verbose&&"undefined"!=typeof window&&_log.info("Success!","Check returned array."),l}catch(e){_log.error(e.message)}}}function fetchData(r){return"undefined"==typeof fetch?new Promise((o,t)=>{try{var e=r.match(/^([^:]+):\/\//)[1];/^https?$/.test(e)||t(new Error("Invalid fetchData() URL.")),require(e).get(r,e=>{let t="";e.on("data",e=>t+=e),e.on("end",()=>o({json:()=>JSON.parse(t),text:()=>t}))}).on("error",e=>t(new Error(e.message)))}catch(e){t(new Error("Environment not supported."))}}):fetch(r)}async function getOwnIP(){return fetchData("https://ifconfig.me/ip").then(e=>e.text()).catch(()=>fetchData("http://ip-api.com/json/").then(e=>e.json()).then(e=>e.query)).catch(async()=>{try{var e=require("child_process").exec,t=require("util").promisify,{stdout:o,stderr:r}=await t(e)("curl -s ifconfig.me");return r?_log.error(r):o.trim()}catch(e){_log.error(e.message)}})}function _validateOptions({options:e,defaultOptions:t,helpURL:o,exampleCall:r}){var i,a,n=Object.keys(t).filter(e=>"boolean"==typeof t[e]),s=Object.keys(t).filter(e=>Number.isInteger(t[e]));if("object"!=typeof e)return i=r.split(",").findIndex(e=>e.trim().startsWith("{"))+1,i+=["st","nd","rd"][i-1]||"th",_log.error(`${"0th"==i?"[O":i+" arg [o"}ptions] can only be an object of key/vals.`),_log.info("Example valid call:",r),_log.validOptions(t),_log.helpURL(o),!1;for(a in e){if(!Object.prototype.hasOwnProperty.call(t,a))return _log.error(`\`${a}\` is an invalid option.`),_log.validOptions(t),_log.helpURL(o),!1;if(n.includes(a)&&"boolean"!=typeof e[a])return _log.error(`[${a}] option can only be \`true\` or \`false\`.`),_log.helpURL(o),!1;if(s.includes(a)&&(e[a]=parseInt(e[a],10),isNaN(e[a])||e[a]<1))return _log.error(`[${a}] option can only be an integer > 0.`),_log.helpURL(o),!1}return!0}Object.assign(globalThis.api??={},{name:"geolocate",regex:{geolocate:/^(?:geo)?locate$/i}});let _log={prefix:api.name,error(...e){console.error(this.prefix+" » ERROR:",...e)},helpURL(e=api.urls?.docs){this.info("For more help, please visit",e)},info(...e){console.info(this.prefix+" »",...e)},validOptions(e){var t=Object.keys(e).join(", "),e=JSON.stringify(e,null,2).replace(/"([^"]+)":/g,"$1:").replace(/"/g,"'").replace(/\n\s*/g," ");this.info(`Valid options: [${t}]`),this.info("If omitted, default settings are: "+e)}};api.exports=new Proxy({geolocate:geolocate},{get(e,t){for(var[o,r]of Object.entries(api.regex))if(r.test(t))return e[o]}});try{module.exports=api.exports}catch(e){}try{Object.assign(window,api.exports)}catch(e){}
package/docs/README.md CHANGED
@@ -8,12 +8,12 @@
8
8
  <img height=31 src="https://img.shields.io/npm/dm/@adamlui/geolocate?logo=npm&color=af68ff&logoColor=white&labelColor=464646&style=for-the-badge"></a>
9
9
  <a href="#%EF%B8%8F-mit-license">
10
10
  <img height=31 src="https://img.shields.io/badge/License-MIT-orange.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
11
- <a href="https://github.com/adamlui/js-utils/releases/tag/geolocate-2.2.1">
12
- <img height=31 src="https://img.shields.io/badge/Latest_Build-2.2.1-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
11
+ <a href="https://github.com/adamlui/js-utils/releases/tag/geolocate-2.3.0">
12
+ <img height=31 src="https://img.shields.io/badge/Latest_Build-2.3.0-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
13
13
  <a href="https://www.npmjs.com/package/@adamlui/geolocate?activeTab=code">
14
14
  <img height=31 src="https://img.shields.io/npm/unpacked-size/%40adamlui%2Fgeolocate?style=for-the-badge&logo=ebox&logoColor=white&labelColor=464646&color=blue"></a>
15
- <a href="https://github.com/adamlui/js-utils/blob/geolocate-2.2.1/geolocate/dist/geolocate.min.js">
16
- <img height=31 src="https://img.shields.io/github/size/adamlui/js-utils/geolocate/dist/geolocate.min.js?branch=geolocate-2.2.1&label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge"></a>
15
+ <a href="#">
16
+ <img height=31 src="https://img.shields.io/bundlejs/size/%40adamlui%2Fgeolocate%402.2.1?label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge"></a>
17
17
  <a href="https://sonarcloud.io/component_measures?metric=new_vulnerabilities&id=adamlui_js-utils:geolocate/src/geolocate.js">
18
18
  <img height=31 src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fsonarcloud.io%2Fapi%2Fmeasures%2Fcomponent%3Fcomponent%3Dadamlui_js-utils%3Ageolocate%2Fsrc%2Fgeolocate.js%26metricKeys%3Dvulnerabilities&query=%24.component.measures.0.value&style=for-the-badge&logo=sonarcloud&logoColor=white&labelColor=464646&label=Vulnerabilities&color=gold"></a>
19
19
  <a href="https://github.com/toolleeo/cli-apps#networking">
@@ -77,6 +77,8 @@ Commands:
77
77
  -i, --init Create config file (in project root).
78
78
  -h, --help Display help screen.
79
79
  -v, --version Show version number.
80
+ --stats Show npm stats.
81
+ --debug [targetKey] Show debug logs.
80
82
  ```
81
83
 
82
84
  #
@@ -124,14 +126,14 @@ const geo = require('@adamlui/geolocate')
124
126
  #### <> HTML script tag:
125
127
 
126
128
  ```html
127
- <script src="https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.2.1/dist/geolocate.min.js"></script>
129
+ <script src="https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.3.0/dist/geolocate.min.js"></script>
128
130
  ```
129
131
 
130
132
  #### ES6:
131
133
 
132
134
  ```js
133
135
  (async () => {
134
- await import('https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.2.1/dist/geolocate.min.js')
136
+ await import('https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.3.0/dist/geolocate.min.js')
135
137
  // Your code here...
136
138
  })()
137
139
  ```
@@ -140,7 +142,7 @@ const geo = require('@adamlui/geolocate')
140
142
 
141
143
  ```js
142
144
  ...
143
- // @require https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.2.1/dist/geolocate.min.js
145
+ // @require https://cdn.jsdelivr.net/npm/@adamlui/geolocate@2.3.0/dist/geolocate.min.js
144
146
  // ==/UserScript==
145
147
 
146
148
  // Your code here...
@@ -148,7 +150,7 @@ const geo = require('@adamlui/geolocate')
148
150
 
149
151
  <br>
150
152
 
151
- **📝 Note:** To always import the latest version (not recommended in production!) remove the `@2.2.1` version tag from the jsDelivr URL: `https://cdn.jsdelivr.net/npm/@adamlui/geolocate/dist/geolocate.min.js`
153
+ **📝 Note:** To always import the latest version (not recommended in production!) remove the `@2.3.0` version tag from the jsDelivr URL: `https://cdn.jsdelivr.net/npm/@adamlui/geolocate/dist/geolocate.min.js`
152
154
 
153
155
  <br>
154
156
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adamlui/geolocate",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Fetch IP geolocation data from the CLI.",
5
5
  "author": {
6
6
  "name": "Adam Lui",
@@ -33,13 +33,14 @@
33
33
  "docs/"
34
34
  ],
35
35
  "bin": {
36
- "geolocate": "dist/cli/index.min.js"
36
+ "geolocate": "dist/cli/index.js"
37
37
  },
38
38
  "directories": {
39
39
  "lib": "./dist",
40
40
  "doc": "./docs"
41
41
  },
42
42
  "scripts": {
43
+ "dev": "npm run build && npm i -g && geolocate --help",
43
44
  "build": "node utils/build",
44
45
  "build:js": "node utils/build --js",
45
46
  "build:data": "node utils/build --data",
@@ -48,6 +49,7 @@
48
49
  "translate": "translate-messages",
49
50
  "bump:patch": "bash utils/bump.sh patch",
50
51
  "bump:minor": "bash utils/bump.sh minor",
52
+ "bump:feat": "npm run bump:minor",
51
53
  "bump:major": "bash utils/bump.sh major"
52
54
  },
53
55
  "repository": {
@@ -72,7 +74,9 @@
72
74
  "node-clipboardy": "^1.0.3"
73
75
  },
74
76
  "devDependencies": {
75
- "@adamlui/minify.js": "^2.3.0",
76
- "generate-ip": "^2.6.0"
77
+ "@adamlui/minify.js": "^2.3.1",
78
+ "console-table-printer": "^2.15.0",
79
+ "copyfiles": "^2.4.1",
80
+ "generate-ip": "^2.8.2"
77
81
  }
78
82
  }
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * © 2024–2026 Adam Lui & contributors under the MIT license.
4
- * Source: https://github.com/adamlui/js-utils/tree/main/geolocate/src
5
- * Documentation: https://github.com/adamlui/js-utils/tree/main/geolocate/docs
6
- */
7
- (async()=>{globalThis.env={args:process.argv.slice(2),devMode:/[\\/]src(?:[\\/]|$)/i.test(__dirname)},env.debugMode=env.args.some(e=>/^--?debug(?:-?mode)?$/.test(e)),env.modExt=`${env.devMode?"":".min"}.js`;var e=require("node-clipboardy"),i=require("../geolocate"+env.modExt),o=require("./lib/init"+env.modExt),r=require("./lib/log"+env.modExt);if(await o.cli(),cli.config.init)return o.configFile();if(cli.config.help)return r.help();if(cli.config.version)return r.version();var n,t=[];for(n of env.args)n.startsWith("-")||t.push(n.replace(/[[\]]/g,""));o=await i.locate(t,{verbose:!cli.config.quietMode});o||process.exit(1),cli.config.quietMode||1!=o.length||r.geoData(o[0]),r.ifNotQuiet(`
8
- ${cli.msgs.info_copyingToClip}...`),e.writeSync(JSON.stringify(o,null,2))})();
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/geolocate/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/geolocate/docs
5
- */
6
- module.exports={atomicWrite(e,r,t="utf8"){var n=require("fs"),a=require("path"),a=a.join(a.dirname(e),`.${a.basename(e)}.tmp`);n.writeFileSync(a,r,t),n.renameSync(a,e)},fetch(n){return"undefined"==typeof fetch?new Promise((t,e)=>{var r=n.match(/^([^:]+):\/\//)[1];/^https?$/.test(r)||e(new Error(cli.msgs.error_invalidURL+".")),require(r).get(n,e=>{let r="";e.on("data",e=>r+=e),e.on("end",()=>t({json:()=>JSON.parse(r)}))}).on("error",e)}):fetch(n)},flatten(e,{key:r="message"}={}){var t,n={};for(t in e)n[t]="object"==typeof e[t]&&r in e[t]?e[t][r]:e[t];return n}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/geolocate/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/geolocate/docs
5
- */
6
- let language=require("./language"+env.modExt),log=require("./log"+env.modExt),settings=require("./settings"+env.modExt),dataPath="../../"+(env.devMode?"../":"data/");module.exports={async cli(){Object.assign(globalThis.cli??={},require(dataPath+"package-data.json")),cli.lang=settings.load("uiLang")||(env.debugMode?language.generateRandomLang({excludes:["en"]}):language.getSysLang()),cli.msgs=await language.getMsgs(cli.lang),cli.urls.cliDocs||=cli.urls.docs+"/#-command-line-usage",settings.load()},async configFile(e=settings.configFilename){var t=require("fs"),a=require("path"),i={target:a.resolve(process.cwd(),e)};if(t.existsSync(i.target))return log.warn(cli.msgs.warn_configFileExists+":",i.target);if(t.existsSync(i.src=a.resolve(__dirname,""+dataPath+e)))t.copyFileSync(i.src,i.target);else{a=require("./jsdelivr"+env.modExt).pkgVerURL+`/${e}/`;log.data(cli.msgs.info_fetchingRemoteConfigFrom+` ${a}...`);try{var s=require("./data"+env.modExt),g=await s.fetch(a);if(!g.ok)return log.warn(`${cli.msgs.warn_remoteConfigNotFound}: ${a} (${g.status})`);s.atomicWrite(i.target,await g.text())}catch(e){return log.warn(cli.msgs.warn_remoteConfigFailed+`: ${a} `+e.message)}}log.success(`${cli.msgs.info_configFileCreated}: ${i.target}\n`),log.tip(cli.msgs.tip_editToSetDefaults+"."),log.tip(cli.msgs.tip_cliArgsPrioritized+".")}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/geolocate/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/geolocate/docs
5
- */
6
- module.exports={pkgVerURL(e){e||=cli.version||=require("./pkg"+env.modExt).getVer("local")||"none";var l=cli.name.split("/")[1],e=/^\d+\.\d+\.\d+$/.test(e)?l+"-"+e:"latest";return cli.urls.jsdelivr+`@${e}/`+l},commitURL(e="latest"){return cli.urls.jsdelivr+`@${e}/`+cli.name.split("/")[1]}};
@@ -1,7 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/geolocate/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/geolocate/docs
5
- */
6
- let data=require("./data"+env.modExt),log=require("./log"+env.modExt);module.exports={formatCode(e){return e.replace(/([a-z]{2,8})[-_]([a-z]{2})/i,(e,r,t)=>r.toLowerCase()+"_"+t.toUpperCase())},generateRandomLang({includes:e=[],excludes:r=[]}={}){let a=require("fs"),s=require("path"),t=e.length?e:(()=>{var e=s.join(__dirname,"..",".cache"),r=s.join(e,"locales.json");if(a.existsSync(r))try{return JSON.parse(a.readFileSync(r,"utf8"))}catch(e){}var t=s.resolve(process.cwd(),"_locales");return a.existsSync(t)?(t=a.readdirSync(t,{withFileTypes:!0}).filter(e=>e.isDirectory()).map(e=>e.name).filter(e=>/^\w{2}[-_]?\w{0,2}$/.test(e)),a.mkdirSync(e,{recursive:!0}),data.atomicWrite(r,JSON.stringify(t,null,2)),t):["en"]})(),n=new Set(r),l="en";return(t=t.filter(e=>!n.has(e))).length&&(l=t[Math.floor(Math.random()*t.length)]),log.debug(`Random language: ${l}
7
- `),l},async getMsgs(a="en"){if(a=module.exports.formatCode(a),env.msgs&&a==cli.lang)return env.msgs;let e=data.flatten(require(`../../${env.devMode?"../_locales/en/":"data/"}messages.json`));if(!a.startsWith("en")){var s=require("./jsdelivr"+env.modExt).commitURL(cli.commitHashes.locales)+"/_locales/";let r=s+a+"/messages.json",t=0;for(;t<3;)try{e=data.flatten(await(await data.fetch(r)).json());break}catch(e){if(3<=++t)break;log.debug(r=a.includes("-")&&1==t?r.replace(/([^_]*)_[^/]*(\/.*)/,"$1$2"):s+"en/messages.json")}}return e},getSysLang(){try{var e;return"win32"==process.platform?require("child_process").execSync("(Get-Culture).TwoLetterISOLanguageName",{shell:"powershell",encoding:"utf-8"}).trim():((e=process.env).LANG||e.LANGUAGE||e.LC_ALL||e.LC_MESSAGES||e.LC_NAME).split(".")[0]}catch(e){return log.error(cli.msgs.error_failedToFetchSysLang+":",e.message),"en"}},validateLangCode(e){return"string"==typeof e&&/^[a-z]{2,8}(?:[-_][a-z]{2,3})?$/i.test(e)}};
@@ -1,23 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/geolocate/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/geolocate/docs
5
- */
6
- module.exports={colors:{nc:"",br:"",by:"",bo:"",bg:"",bw:"",gry:"",blk:"",tlBG:""},configURL(){this.info(`
7
- ${cli.msgs.info_exampleValidConfigFile}: `+cli.urls.config)},configURLandExit(...o){this.error(...o),this.configURL(),process.exit(1)},data(o){console.log(`
8
- `+this.colors.bw+o+this.colors.nc)},debug(o){env.debugMode&&console.debug(`
9
- ${this.colors.bo}DEBUG:`,o,this.colors.nc,"\n")},dim(o){console.log(""+this.colors.gry+o+this.colors.nc)},error(...o){console.error(`
10
- ${this.colors.br}ERROR:`,...o,this.colors.nc)},errorAndExit(...o){this.error(...o),this.helpCmdAndDocURL(),process.exit(1)},ifNotQuiet(o){cli.config.quietMode||console.info(o)},info(o){console.info(`
11
- `+this.colors.by+o+this.colors.nc)},tip(o){console.info(this.colors.by+"TIP: "+o+this.colors.nc)},success(o){console.log(`
12
- `+this.colors.bg+o+this.colors.nc)},warn(...o){console.warn(`
13
- ${this.colors.bo}WARNING:`,...o,this.colors.nc)},geoData(o){console.info([`
14
- IP: `+this.colors.bw+o.ip+this.colors.nc,cli.msgs.geoLabel_country+": "+this.colors.bw+o.country+this.colors.nc,cli.msgs.geoLabel_region+": "+this.colors.bw+o.regionName+this.colors.nc,cli.msgs.geoLabel_city+": "+this.colors.bw+o.city+this.colors.nc,cli.msgs.geoLabel_zip+": "+this.colors.bw+o.zip+this.colors.nc,cli.msgs.geoLabel_lat+": "+this.colors.bw+o.lat+this.colors.nc,cli.msgs.geoLabel_lon+": "+this.colors.bw+o.lon+this.colors.nc,cli.msgs.geoLabel_timeZone+": "+this.colors.bw+o.timezone.replace(/_/g," ").replace(/\//g," / ")+this.colors.nc,"ISP: "+this.colors.bw+o.isp+this.colors.nc].join("\n"))},help(o=["header","usage","params","flags","cmds"]){cli.prefix=""+this.colors.tlBG+this.colors.blk+` ${cli.name.replace(/^@[^/]+\//,"")} ${this.colors.nc} `;let s={header:[`
15
- ├ ${cli.prefix}${cli.msgs.appCopyright}.`,""+cli.prefix+cli.msgs.prefix_source+": "+cli.urls.src],usage:[`
16
- ${this.colors.bw}o ${cli.msgs.helpSection_usage}:`+this.colors.nc,` ${this.colors.bw}» `+this.colors.bg+cli.cmdFormat+this.colors.nc],params:[`
17
- ${this.colors.bw}o ${cli.msgs.helpSection_params}:`+this.colors.nc,` --ui-lang="code" ${cli.msgs.optionDesc_uiLang}.`,` --config="path/to/file" ${cli.msgs.optionDesc_config}.`],flags:[`
18
- ${this.colors.bw}o ${cli.msgs.helpSection_flags}:`+this.colors.nc,` -q, --quiet ${cli.msgs.optionDesc_quiet}.`],cmds:[`
19
- ${this.colors.bw}o ${cli.msgs.helpSection_cmds}:`+this.colors.nc,` -i, --init ${cli.msgs.optionDesc_init}.`,` -h, --help ${cli.msgs.optionDesc_help}.`,` -v, --version ${cli.msgs.optionDesc_version}.`]};o.forEach(r=>s[r]?.forEach(s=>{{var e=/header|usage/.test(r)?1:29;let i=process.stdout.columns||80,o=s.match(/\S+|\s+/g),c=[],l="";o.forEach(o=>{var s=i-(c.length?e:0);l.length+"| ".length+o.length>s&&(c.push(c.length?l.trimStart():l),l=""),l+=o}),c.push(c.length?l.trimStart():l),c.forEach((o,s)=>console.info("| "+(0==s?o:" ".repeat(e)+o)))}})),console.info(`
20
- ${cli.msgs.info_moreHelp}, ${cli.msgs.info_visit}: `+this.colors.bw+cli.urls.cliDocs+this.colors.nc)},helpCmdAndDocURL(){console.info(`
21
- ${cli.msgs.info_moreHelp}, ${cli.msgs.info_type} ${cli.name.split("/")[1]} --help' ${cli.msgs.info_or} ${cli.msgs.info_visit}
22
- `+this.colors.bw+cli.urls.docs+this.colors.nc)},version(){var o=require("./pkg"+env.modExt).getVer;this.info(cli.name),this.data(`${cli.msgs.prefix_globalVer}: ${o("global")||"none"}
23
- ${cli.msgs.prefix_localVer}: `+(o("local")||"none"))}};
@@ -1,6 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/geolocate/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/geolocate/docs
5
- */
6
- let log=require("./log"+env.modExt);module.exports={getVer(e="any"){let r;if("global"!=e)try{var l=require("path").resolve(process.cwd(),"node_modules",cli.name,"package.json");r=require(l).version}catch(e){log.debug(cli.msgs.error_readingLocalPkgVer+": "+e.message)}if("global"==e||"all"==e&&!r)try{r=require("child_process").execSync(`npm view ${JSON.stringify(cli.name)} version`).toString().trim()}catch(e){log.debug(cli.msgs.error_failedToFetchGlobalVer+": "+e.message)}return r}};
@@ -1,7 +0,0 @@
1
- /**
2
- * © 2024–2026 Adam Lui & contributors under the MIT license.
3
- * Source: https://github.com/adamlui/js-utils/tree/main/geolocate/src
4
- * Documentation: https://github.com/adamlui/js-utils/tree/main/geolocate/docs
5
- */
6
- let fs=require("fs"),log=require("./log"+env.modExt),path=require("path");(globalThis.cli??={}).config={},module.exports={configFilename:"geolocate.config.mjs",controls:{uiLang:{type:"param",valType:"langCode",regex:/^--?ui-?lang(?:=.*|$)/},config:{type:"param",valType:"filepath",regex:/^--?config(?:=.*|$)/},quietMode:{type:"flag",regex:/^--?q(?:uiet)?(?:-?mode)?$/},init:{type:"cmd",regex:/^-{0,2}i(?:nit)?$/},help:{type:"cmd",regex:/^--?h(?:elp)?$/},version:{type:"cmd",regex:/^--?ve?r?s?i?o?n?$/}},load(e=Object.keys(this.controls)){let t=[].concat(e);if(cli.defaultsSet||arguments.length||(t.forEach(e=>{var i=this.controls[e];i.mode||(cli.config[e]??=i.defaultVal??("param"==i.type&&""))}),cli.defaultsSet=!0),!cli.configPathTried){e=env.args.find(e=>this.controls.config.regex.test(e));if(e){/=/.test(e)||log.errorAndExit(`[${e}] `+cli.msgs.error_mustIncludePath);e=e.split("=")[1];cli.configPath=path.isAbsolute(e)?e:path.resolve(process.cwd(),e),fs.existsSync(cli.configPath)||log.configURLandExit(cli.msgs.error_configFileNotFound+":",cli.configPath)}else for(var i of[".mjs",".cjs",".js"]){i=path.resolve(process.cwd(),this.configFilename.replace(/\.[^.]+$/,i));if(fs.existsSync(i)){cli.configPath=i;break}}cli.configPathTried=!0}if(cli.configPath)try{let e=require(cli.configPath),o=e?.default??e;o&&"object"==typeof o||log.configURLandExit(cli.msgs.error_invalidConfigFile+"."),Object.assign(cli.config,arguments.length?t.reduce((e,i)=>o[i]?{...e,[i]:o[i]}:e,{}):o)}catch(e){log.configURLandExit(cli.msgs.error_failedToLoadConfigFile+":",cli.configPath,`
7
- `+e.message)}return env.args.forEach(i=>{var e,o;/^[^-]|--?(?:config|debug)/.test(i)&&"init"!=i||(!(e=Object.keys(this.controls).find(e=>this.controls[e]?.regex?.test(i)))&&cli.msgs&&log.errorAndExit(`[${i}] ${cli.msgs.error_notRecognized}.`),t.includes(e)&&((o=this.controls[e]).mode?cli.config.mode=e.replace(/mode$/i,"").toLowerCase():cli.config[e]="param"!=o.type||(i.split("=")[1]?.trim()??"")))}),this.parseValidateConfig(t),1==t.length?cli.config[t[0]]:cli.config},parseValidateConfig(e=Object.keys(this.controls)){let t=require("./language"+env.modExt);for(let o of[].concat(e)){let e=this.controls[o],i=cli.config[o];e.parser&&!e.parsed&&(cli.config[o]=e.parser(i),e.parsed=!0),e.valType&&{positiveInt(){var e=parseInt(i,10);(isNaN(e)||e<1)&&log.errorAndExit(`[${o}] ${cli.msgs.error_nonPositiveNum}: `+i),cli.config[o]=e},filepath(){i&&!fs.existsSync(i)&&log.errorAndExit(`[${o}] ${cli.msgs.error_invalidFilepath}: `+i)},langCode(){i&&!t.validateLangCode(i)&&log.errorAndExit(`[${o}] ${cli.msgs.error_invalidLangCode}: `+i)}}[e.valType]()}}};