generate-pw 2.1.2 โ†’ 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
@@ -21,16 +21,14 @@
21
21
 
22
22
  ### Randomly generate, strengthen, and validate cryptographically-secure passwords.
23
23
 
24
- <a href="https://www.npmjs.com/package/generate-pw">
24
+ <a href="https://npmstar.com/compare/generate-pw">
25
25
  <img height=31 src="https://img.shields.io/npm/dm/generate-pw?logo=npm&color=af68ff&logoColor=white&labelColor=464646&style=for-the-badge"></a>
26
26
  <a href="#%EF%B8%8F-mit-license">
27
27
  <img height=31 src="https://img.shields.io/badge/License-MIT-orange.svg?logo=internetarchive&logoColor=white&labelColor=464646&style=for-the-badge"></a>
28
- <a href="https://github.com/adamlui/js-utils/releases/tag/generate-pw-2.1.2">
29
- <img height=31 src="https://img.shields.io/badge/Latest_Build-2.1.2-44cc11.svg?logo=icinga&logoColor=white&labelColor=464646&style=for-the-badge"></a>
28
+ <a href="https://github.com/adamlui/js-utils/releases/tag/generate-pw-2.3.0">
29
+ <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>
30
30
  <a href="https://www.npmjs.com/package/generate-pw?activeTab=code">
31
31
  <img height=31 src="https://img.shields.io/npm/unpacked-size/generate-pw?style=for-the-badge&logo=ebox&logoColor=white&labelColor=464646&color=blue"></a>
32
- <a href="https://github.com/adamlui/js-utils/blob/generate-pw-2.1.2/generate-pw/dist/generate-pw.min.js">
33
- <img height=31 src="https://img.shields.io/github/size/adamlui/js-utils/generate-pw/dist/generate-pw.min.js?branch=generate-pw-2.1.2&label=Minified%20Size&logo=databricks&logoColor=white&labelColor=464646&color=ff69b4&style=for-the-badge"></a>
34
32
  <a href="https://sonarcloud.io/component_measures?metric=new_vulnerabilities&id=adamlui_js-utils:generate-pw/src/generate-pw.js">
35
33
  <img height=31 src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fsonarcloud.io%2Fapi%2Fmeasures%2Fcomponent%3Fcomponent%3Dadamlui_js-utils%3Agenerate-pw%2Fsrc%2Fgenerate-pw.js%26metricKeys%3Dvulnerabilities&query=%24.component.measures.0.value&style=for-the-badge&logo=sonarcloud&logoColor=white&labelColor=464646&label=Vulnerabilities&color=gold"></a>
36
34
  <a href="https://github.com/toolleeo/cli-apps#password-managers">
@@ -94,14 +92,14 @@ const pw = require('generate-pw')
94
92
  #### <> HTML script tag:
95
93
 
96
94
  ```html
97
- <script src="https://cdn.jsdelivr.net/npm/generate-pw@2.1.2/dist/generate-pw.min.js"></script>
95
+ <script src="https://cdn.jsdelivr.net/npm/generate-pw@2.3.0/dist/generate-pw.min.js"></script>
98
96
  ```
99
97
 
100
98
  #### ES6:
101
99
 
102
100
  ```js
103
101
  (async () => {
104
- await import('https://cdn.jsdelivr.net/npm/generate-pw@2.1.2/dist/generate-pw.min.js')
102
+ await import('https://cdn.jsdelivr.net/npm/generate-pw@2.3.0/dist/generate-pw.min.js')
105
103
  // Your code here...
106
104
  })()
107
105
  ```
@@ -110,7 +108,7 @@ const pw = require('generate-pw')
110
108
 
111
109
  ```js
112
110
  ...
113
- // @require https://cdn.jsdelivr.net/npm/generate-pw@2.1.2/dist/generate-pw.min.js
111
+ // @require https://cdn.jsdelivr.net/npm/generate-pw@2.3.0/dist/generate-pw.min.js
114
112
  // ==/UserScript==
115
113
 
116
114
  // Your code here...
@@ -118,7 +116,7 @@ const pw = require('generate-pw')
118
116
 
119
117
  <br>
120
118
 
121
- **๐Ÿ’ก Note:** To always import the latest version (not recommended in production!) remove the `@2.1.2` version tag from the jsDelivr URL: `https://cdn.jsdelivr.net/npm/generate-pw/dist/generate-pw.min.js`
119
+ **๐Ÿ’ก 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/generate-pw/dist/generate-pw.min.js`
122
120
 
123
121
  <br>
124
122
 
@@ -255,7 +253,7 @@ Name | Type | Description
255
253
  `uppercase` | Boolean | Allow uppercase letters in password(s). | `true`
256
254
  `similarChars` | Boolean | Include similar characters (e.g. o,0,O,i,l,1,\|) in password(s). | `false`
257
255
  `strict` | Boolean | Require at least one character from each allowed character set in password(s). | `true`
258
- `entropy` | Boolean | Calculate/log estimated entropy. | `false`
256
+ `entropy` | Boolean | Calculate/log estimated entropy. | `true`
259
257
 
260
258
  ##### _*Only available in [`generatePassword([options])`](#generatepasswordoptions) since [`generatePasswords(qty[, options])`](#generatepasswordsqty-options) takes a `qty` argument_
261
259
 
@@ -265,13 +263,13 @@ Name | Type | Description
265
263
 
266
264
  ## ๐Ÿ’ป Command line usage
267
265
 
268
- When installed [globally](#-installation), **generate-pw** can also be used from the command line. The basic command is:
266
+ **generate-pw** can also be used directly from the command line. The basic command is:
269
267
 
270
268
  ```
271
269
  $ generate-pw
272
270
  ```
273
271
 
274
- <img src="https://media.generatepw.org/images/screenshots/cli/generate-pw-cmd-output.png?0d36e26">
272
+ <img src="https://cdn.jsdelivr.net/gh/adamlui/js-utils@0d3424a/generate-pw/assets/images/screenshots/cli/generate-pw-cmd-output.png">
275
273
 
276
274
  #
277
275
 
@@ -297,13 +295,15 @@ Boolean options:
297
295
  -S, --similar-chars Include similar characters in password(s).
298
296
  -S, --unstrict Don't require at least one character from
299
297
  each allowed character set in password(s).
300
- -e, --entropy Calculate/log estimated entropy.
298
+ -E, --no-entropy Don't calculate/log estimated entropy.
301
299
  -q, --quiet Suppress all logging except errors.
302
300
 
303
301
  Commands:
304
302
  -i, --init Create config file (in project root).
305
303
  -h, --help Display help screen.
306
304
  -v, --version Show version number.
305
+ --stats Show npm stats.
306
+ --debug [targetKey] Show debug logs.
307
307
  ```
308
308
 
309
309
  #
@@ -327,7 +327,7 @@ export default {
327
327
  excludeUpperChars: false, // disallow uppercase letters in password(s)
328
328
  similarChars: false, // include similar chars in password(s)
329
329
  unstrict: false, // don't require 1+ char from each allowed charset in password(s)
330
- entropy: false, // calculate/log estimated entropy
330
+ noEntropy: false, // don't calculate/log estimated entropy
331
331
  quietMode: false // suppress all logging except errors
332
332
  }
333
333
  ```
@@ -354,7 +354,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
354
354
 
355
355
  ## ๐Ÿ› ๏ธ Related utilities
356
356
 
357
- ### <picture><source media="(prefers-color-scheme: dark)" srcset="https://media.generate-ip.org/images/icons/node-graph/white/icon55x49.png?b4eb06e"><img height=21 src="https://media.generate-ip.org/images/icons/node-graph/black/icon55x49.png?b4eb06e"></picture> [generate-ip](https://js-utils.org/generate-ip) &nbsp;<a href="https://github.com/toolleeo/cli-apps#networking"><img height=18 src="https://assets.js-utils.org/images/badges/awesome/badge.svg?v=0d36e26"></a>
357
+ ### <picture><source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/adamlui/js-utils@5c34563/generate-ip/assets/images/icons/node-graph/white/icon55x49.png"><img height=21 src="https://cdn.jsdelivr.net/gh/adamlui/js-utils@5c34563/generate-ip/assets/images/icons/node-graph/black/icon55x49.png"></picture> [generate-ip](https://js-utils.org/generate-ip) &nbsp;<a href="https://github.com/toolleeo/cli-apps#networking"><img height=18 src="https://assets.js-utils.org/images/badges/awesome/badge.svg?v=0d36e26"></a>
358
358
 
359
359
  > Randomly generate, format, and validate IPv4 + IPv6 + MAC addresses.
360
360
  <br>[Install](https://docs.generate-ip.org/#-installation) /
@@ -363,7 +363,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
363
363
  [CLI usage](https://docs.generate-ip.org/#-command-line-usage) /
364
364
  [Discuss](https://github.com/adamlui/js-utils/discussions)
365
365
 
366
- ### <picture><source media="(prefers-color-scheme: dark)" srcset="https://media.geolocatejs.org/images/icons/wire-globe/white/icon32.png?0d36e26"><img height=22 src="https://media.geolocatejs.org/images/icons/wire-globe/black/icon32.png?0d36e26"></picture> [geolocate](https://js-utils.org/geolocate) &nbsp;<a href="https://github.com/toolleeo/cli-apps#networking"><img height=18 src="https://assets.js-utils.org/images/badges/awesome/badge.svg?v=0d36e26"></a>
366
+ ### <picture><source media="(prefers-color-scheme: dark)" srcset="https://cdn.jsdelivr.net/gh/adamlui/js-utils@0d3424a/geolocate/assets/images/icons/wire-globe/white/icon32.png"><img height=22 src="https://cdn.jsdelivr.net/gh/adamlui/js-utils@0d3424a/geolocate/assets/images/icons/wire-globe/black/icon32.png"></picture> [geolocate](https://js-utils.org/geolocate) &nbsp;<a href="https://github.com/toolleeo/cli-apps#networking"><img height=18 src="https://assets.js-utils.org/images/badges/awesome/badge.svg?v=0d36e26"></a>
367
367
 
368
368
  > Fetch IP geolocation data from the CLI.
369
369
  <br>[Install](https://docs.geolocatejs.org/#-installation) /
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+
3
+ (async () => {
4
+ 'use strict'
5
+
6
+ // Init ENV
7
+ const init = require('./lib/init')
8
+ init.env()
9
+
10
+ // Import LIBS
11
+ globalThis.log = require('./lib/log')
12
+ const clipboardy = require('node-clipboardy'),
13
+ { generatePassword } = require(`../generate-pw${ env.modes.dev ? '' : '.min' }.js`)
14
+
15
+ await init.cli()
16
+
17
+ // Exec CMD arg if passed
18
+ if (cli.config.init) return init.configFile()
19
+ else if (cli.config.help) return log.help()
20
+ else if (cli.config.version) return log.version()
21
+ else if (cli.config.stats) return log.stats()
22
+
23
+ // Copy random PASSWORD(s)
24
+ const genOptions = {
25
+ length: cli.config.length,
26
+ qty: cli.config.qty,
27
+ strength: cli.config.mode || cli.config.strength,
28
+ charset: cli.config.charset,
29
+ exclude: cli.config.excludeChars,
30
+ numbers: !cli.config.excludeNums,
31
+ symbols: !cli.config.excludeSymbols,
32
+ lowercase: !cli.config.excludeLowerChars,
33
+ uppercase: !cli.config.excludeUpperChars,
34
+ similarChars: cli.config.similarChars,
35
+ strict: !cli.config.unstrict,
36
+ entropy: !cli.config.noEntropy,
37
+ verbose: !cli.config.quietMode
38
+ }
39
+ log.break()
40
+ clipboardy.writeSync([].concat(generatePassword(genOptions)).join('\n'))
41
+ log.ifNotQuiet(`${cli.msgs.info_copyingToClip}...`)
42
+
43
+ })()
@@ -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,57 @@
1
+ const language = require('./language'),
2
+ settings = require('./settings')
3
+
4
+ module.exports = {
5
+
6
+ async cli() {
7
+ Object.assign(globalThis.cli ??= {}, require(`${env.paths.libData}/package-data.json`))
8
+ cli.msgs = await language.getMsgs('en')
9
+ cli.msgs = await language.getMsgs(cli.lang = settings.load('uiLang') || (
10
+ env.modes.debug ? language.generateRandomLang({ excludes: ['en'] }) : language.getSysLang() ))
11
+ cli.urls.cliDocs = `${cli.urls.docs}/#-command-line-usage`
12
+ if (!cli.lang.startsWith('en')) { // localize cli.urls.cliDocs
13
+ cli.docLocale = cli.lang.replace('_', '-').toLowerCase()
14
+ cli.docLocales ??= await language.getDocLocales()
15
+ if (cli.docLocales?.includes(cli.docLocale))
16
+ log.debug(cli.urls.cliDocs = `${cli.urls.docs}/${cli.docLocale}#readme`)
17
+ }
18
+ settings.load() // all keys to cli.config
19
+ },
20
+
21
+ async configFile(filename = settings.configFilename) {
22
+ const fs = require('fs'),
23
+ path = require('path'),
24
+ paths = { target: path.resolve(process.cwd(), filename) }
25
+
26
+ if (fs.existsSync(paths.target)) // use existing config file
27
+ return log.warn(`${cli.msgs.warn_configFileExists}:`, paths.target)
28
+ if (fs.existsSync(paths.src = path.resolve(__dirname, `${env.paths.libData}/${filename}`)))
29
+ fs.copyFileSync(paths.src, paths.target) // use found template
30
+
31
+ else { // use jsDelivr copy
32
+ const jsdURL = `${require('./jsdelivr').getPkgVerURL()}/${filename}`
33
+ log.data(`${cli.msgs.info_fetchingRemoteConfigFrom} ${jsdURL}...`)
34
+ try {
35
+ const data = require('./data'),
36
+ resp = await data.fetch(jsdURL)
37
+ if (resp.ok) data.atomicWrite(paths.target, await resp.text())
38
+ else return log.warn(`${cli.msgs.warn_remoteConfigNotFound}: ${jsdURL} (${resp.status})`)
39
+ } catch (err) {
40
+ return log.warn(`${cli.msgs.warn_remoteConfigFailed}: ${jsdURL} ${err.message}`) }
41
+ }
42
+
43
+ log.success(`${cli.msgs.info_configFileCreated}: ${paths.target}\n`)
44
+ log.tip(`${cli.msgs.tip_editToSetDefaults}.`)
45
+ log.tip(`${cli.msgs.tip_cliArgsPrioritized}.`)
46
+ },
47
+
48
+ env() {
49
+ Object.assign(globalThis.env ??= {}, {
50
+ args: process.argv.slice(2),
51
+ modes: { dev: /[\\/]src(?:[\\/]|$)/i.test(__dirname) },
52
+ supports: { unicode: require('is-unicode-supported').default() }
53
+ })
54
+ env.modes.debug = env.args.some(arg => /^--?(?:V|debug(?:[-_]?mode)?)$/.test(arg))
55
+ env.paths = { libData: `../../${ env.modes.dev ? '..' : 'data' }` }
56
+ }
57
+ }
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+
3
+ getPkgVerURL(version) {
4
+ version ||= cli.version ||= require('./pkg').getVer('local') || 'none'
5
+ const verTag = !/^\d+\.\d+\.\d+$/.test(version) ? 'latest' : `${cli.name}-${version}`
6
+ return `${cli.urls.jsdelivr}@${verTag}/${cli.name}`
7
+ },
8
+
9
+ getCommitURL(hash = 'latest') { return `${cli.urls.jsdelivr}@${hash}/${cli.name}` }
10
+ }
@@ -0,0 +1,113 @@
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
+ async getDocLocales() {
10
+ cli.version ||= require('./pkg').getVer('local') || 'none'
11
+ const jsdURL = `${require('./jsdelivr').getPkgVerURL()}/docs/`,
12
+ locales = []
13
+ try {
14
+ const respText = await (await data.fetch(jsdURL)).text(),
15
+ reLocale = /href=".*\/docs\/([^/]+)\/"/g
16
+ let match ; while ((match = reLocale.exec(respText))) locales.push(match[1]) // store locale dir names
17
+ } catch (err) {
18
+ log.warn(`${cli.msgs.warn_docLocalesFetchFailed}:`, err.message)
19
+ }
20
+ return locales
21
+ },
22
+
23
+ generateRandomLang({ includes = [], excludes = [] } = {}) {
24
+ const fs = require('fs'),
25
+ path = require('path')
26
+
27
+ let locales = includes.length ? includes : (() => {
28
+
29
+ // Read cache if found
30
+ const cacheDir = path.join(__dirname, '../../.cache'),
31
+ localeCache = path.join(cacheDir, 'locales.json')
32
+ if (fs.existsSync(localeCache))
33
+ try { return JSON.parse(fs.readFileSync(localeCache, 'utf8')) } catch (err) {}
34
+
35
+ // Discover pkg _locales
36
+ const localesDir = path.resolve(__dirname, '../../../_locales')
37
+ if (!fs.existsSync(localesDir)) return ['en']
38
+ const locales = fs.readdirSync(localesDir, { withFileTypes: true })
39
+ .filter(entry => entry.isDirectory()).map(entry => entry.name)
40
+ .filter(name => /^\w{2}[-_]?\w{0,2}$/.test(name))
41
+
42
+ // Cache result
43
+ fs.mkdirSync(cacheDir, { recursive: true })
44
+ data.atomicWrite(localeCache, JSON.stringify(locales, null, 2))
45
+
46
+ return locales
47
+ })()
48
+
49
+ // Filter out excludes
50
+ const excludeSet = new Set(excludes)
51
+ locales = locales.filter(locale => !excludeSet.has(locale))
52
+
53
+ // Get random language
54
+ const randomLang = locales.length ? locales[Math.floor(Math.random() * locales.length)] : 'en'
55
+ log.debug(`Random language: ${randomLang}`)
56
+
57
+ return randomLang
58
+ },
59
+
60
+ async getMsgs(langCode = 'en') {
61
+ langCode = module.exports.formatCode(langCode)
62
+ if (env.msgs && langCode == cli.lang) return env.msgs // don't re-fetch same msgs
63
+
64
+ let msgs = data.flatten( // local ones
65
+ require(`../../${ env.modes.dev ? '../_locales/en' : 'data' }/messages.json`))
66
+
67
+ if (!langCode.startsWith('en')) { // fetch non-English msgs from jsDelivr
68
+ try { // check if terminal supports non-Latin scripts
69
+ const nonLatinLocales = await (await data.fetch(
70
+ `${cli.urls.jsdelivr}@${cli.commitHashes.data}/assets/data/non-latin-locales.json`
71
+ )).json()
72
+ if (nonLatinLocales.includes(langCode.split('-')[0]) && !env.supports.unicode)
73
+ return msgs // en ones
74
+ } catch (err) {
75
+ log.debug(`Failed to fetch non-Latin locales: ${err}`)
76
+ }
77
+ const msgBaseURL = `${require('./jsdelivr').getCommitURL(cli.commitHashes.locales)}/_locales`
78
+ let msgURL = `${msgBaseURL}/${langCode}/messages.json`, msgFetchesTried = 0
79
+ while (msgFetchesTried < 3)
80
+ try { // fetch remote msgs
81
+ msgs = data.flatten(await (await data.fetch(msgURL)).json())
82
+ break
83
+ } catch (err) { // retry up to 2X (region-stripped + EN)
84
+ msgFetchesTried++ ; if (msgFetchesTried >= 3) break
85
+ log.debug(msgURL = langCode.includes('-') && msgFetchesTried == 1 ?
86
+ msgURL.replace(/([^_]*)_[^/]*(\/.*)/, '$1$2') // strip region before retrying
87
+ : `${msgBaseURL}/en/messages.json` // else use EN msgs
88
+ )
89
+ }
90
+ }
91
+
92
+ return msgs
93
+ },
94
+
95
+ getSysLang() {
96
+ try {
97
+ if (process.platform == 'win32')
98
+ return require('child_process').execSync(
99
+ '(Get-Culture).TwoLetterISOLanguageName', { shell: 'powershell', encoding: 'utf-8' }
100
+ ).trim()
101
+ else { // macOS/Linux
102
+ const pe = process.env
103
+ return (pe.LANG || pe.LANGUAGE || pe.LC_ALL || pe.LC_MESSAGES || pe.LC_NAME)
104
+ .split('.')[0] // strip encoding e.g. .UTF-8
105
+ }
106
+ } catch (err) {
107
+ log.error(`${cli.msgs.error_failedToFetchSysLang}:`, err.message)
108
+ return 'en'
109
+ }
110
+ },
111
+
112
+ validateLangCode(code) { return typeof code != 'string' ? false : /^[a-z]{2,8}(?:[-_][a-z]{2,3})?$/i.test(code) }
113
+ }
@@ -0,0 +1,171 @@
1
+ const colors = require('./color'),
2
+ { getDownloads, getVer } = require('./pkg'),
3
+ settings = require('./settings'),
4
+ string = require('./string')
5
+
6
+ const nextMajVer = require('../../../package.json').version.replace(/^(\d+)\..*/, (_, major) => `${ +major +1 }.0.0`)
7
+
8
+ module.exports = {
9
+ colors,
10
+
11
+ break() { console.log() },
12
+ configURL() { this.info(`${cli.msgs.info_exampleValidConfigFile}: ${cli.urls.config}`) },
13
+ configURLandExit(...args) { this.error(...args); this.configURL(); process.exit(1) },
14
+ data(msg) { console.log(`\n${colors.bw}${msg}${colors.nc}`) },
15
+ dim(msg) { console.log(`${colors.gry}${msg}${colors.nc}`) },
16
+ error(...args) { console.error(`\n${colors.br}ERROR:`, ...args, colors.nc) },
17
+ errorAndExit(...args) { this.error(...args); this.helpDocsCmdsDocsURL(); process.exit(1) },
18
+ ifNotQuiet(msg) { if (!cli.config.quietMode) this.info(msg) },
19
+ info(msg) { console.info(`\n${colors.schemes.default[0]}${msg}${colors.nc}`) },
20
+ success(msg) { console.log(`\n${colors.bg}${msg}${colors.nc}`) },
21
+ tip(msg) { console.info(`${colors.by}TIP: ${msg}${colors.nc}`) },
22
+ warn(...args) { console.warn(`\n${colors.bo}WARNING:`, ...args, colors.nc) },
23
+
24
+ argDoesNothing(arg) {
25
+ this.warn(`${cli.msgs.warn_option} ${arg} ${cli.msgs.warn_noLongerHasAnyEffect} ${
26
+ cli.msgs.warn_andWillBeRemoved} @ v${nextMajVer}`)
27
+ },
28
+
29
+ configKeyReplacedBy(oldKey, newKey, oldVal) {
30
+ if (!this[`${oldKey}Warned`]) {
31
+ this.warn(
32
+ `${cli.msgs.info_configFile} ${cli.msgs.warn_option.toLowerCase()} '${oldKey}: ${oldVal}' ${
33
+ cli.msgs.warn_hasBeenReplacedBy} '${
34
+ newKey}: ${ settings.isNegKey(oldKey) != settings.isNegKey(newKey) ? !oldVal : oldVal }' ${
35
+ cli.msgs.warn_andWillBeRemoved} @ v${nextMajVer}`
36
+ )
37
+ this[`${oldKey}Warned`] = true
38
+ }
39
+ },
40
+
41
+ debug(msg) {
42
+ if (!env.modes.debug) return
43
+ this.argIdx ??= env.args.findIndex(arg => /^--?(?:V|debug(?:[-_]?mode)?)$/.test(arg))
44
+ if (this.argIdx +1 < env.args.length && !env.args[this.argIdx +1].startsWith('-')) // use --debug [targetKey]
45
+ this.key ??= env.args[this.argIdx +1].replace('-', '_')
46
+ if (this.key)
47
+ this.val = cli.config[this.key] || `cli.config key "${this.key}" ${cli.msgs.warn_notFound.toLowerCase()}`
48
+ else
49
+ this.val = cli.config
50
+ msg += `\n${colors.gry}${JSON.stringify(this.val)}${colors.nc}`
51
+ console.debug(`\n${colors.by}DEBUG:`, msg, colors.nc)
52
+ },
53
+
54
+ help(includeSections = ['header', 'usage', 'params', 'flags', 'cmds']) {
55
+ cli.prefix = `${this.colors.tlBG}${this.colors.blk}\x1b[30m ${cli.name} ${this.colors.nc} `
56
+ const helpSections = {
57
+ header: [
58
+ `\nโ”œ ${cli.prefix}${cli.msgs.pkg_copyright}.`,
59
+ `${cli.prefix}${cli.msgs.prefix_source}: ${cli.urls.src}`
60
+ ],
61
+ usage: [
62
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_usage}:${this.colors.nc}`,
63
+ ` ${this.colors.bw}ยป ${this.colors.bg}${cli.cmdFormat}${this.colors.nc}`
64
+ ],
65
+ params: [
66
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_params}:${this.colors.nc}`,
67
+ ` --length=n ${cli.msgs.optionDesc_length}.`,
68
+ ` --qty=n ${cli.msgs.optionDesc_qty}.`,
69
+ ` --charset=chars ${cli.msgs.optionDesc_charset}.`,
70
+ ` --exclude=chars ${cli.msgs.optionDesc_exclude}.`,
71
+ ` --ui-lang="code" ${cli.msgs.optionDesc_uiLang}.`,
72
+ ` --config="path/to/file" ${cli.msgs.optionDesc_config}.`
73
+ ],
74
+ flags: [
75
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_flags}:${this.colors.nc}`,
76
+ ` -w, --weak ${cli.msgs.optionDesc_weak}.`,
77
+ ` -b, --basic ${cli.msgs.optionDesc_basic}.`,
78
+ ` -t, --strong ${cli.msgs.optionDesc_strong}.`,
79
+ ` -N, --no-numbers ${cli.msgs.optionDesc_excludeNums}.`,
80
+ ` -Y, --no-symbols ${cli.msgs.optionDesc_excludeSymbols}.`,
81
+ ` -L, --no-lowercase ${cli.msgs.optionDesc_noLower}.`,
82
+ ` -U, --no-uppercase ${cli.msgs.optionDesc_noUpper}.`,
83
+ ` -s, --similar-chars ${cli.msgs.optionDesc_similarChars}.`,
84
+ ` -S, --unstrict ${cli.msgs.optionDesc_unstrict}.`,
85
+ ` -E, --no-entropy ${cli.msgs.optionDesc_noEntropy}.`,
86
+ ` -q, --quiet ${cli.msgs.optionDesc_quiet}.`
87
+ ],
88
+ cmds: [
89
+ `\n${this.colors.bw}o ${cli.msgs.helpSection_cmds}:${this.colors.nc}`,
90
+ ` -i, --init ${cli.msgs.optionDesc_init}.`,
91
+ ` -h, --help ${cli.msgs.optionDesc_help}.`,
92
+ ` -v, --version ${cli.msgs.optionDesc_version}.`,
93
+ ` -v, --stats ${cli.msgs.optionDesc_stats}.`,
94
+ ` -V, --debug ${cli.msgs.optionDesc_debug}.`
95
+ ]
96
+ }
97
+ includeSections.forEach(section => // print valid arg elems
98
+ helpSections[section]?.forEach(line => printHelpMsg(line, /header|usage/.test(section) ? 1 : 29)))
99
+ console.info(`\n${cli.msgs.info_moreHelp}, ${
100
+ cli.msgs.info_visit}: ${this.colors.bw}${cli.urls.cliDocs}${this.colors.nc}`)
101
+
102
+ function printHelpMsg(msg, indent) { // wrap msg + indent 2nd+ lines
103
+ const terminalWidth = process.stdout.columns || 80,
104
+ words = msg.match(/\S+|\s+/g),
105
+ lines = [], prefix = '| '
106
+
107
+ // Split msg into lines of appropriate lengths
108
+ let currentLine = ''
109
+ words.forEach(word => {
110
+ const lineLength = terminalWidth -( !lines.length ? 0 : indent )
111
+ if (currentLine.length + prefix.length + word.length > lineLength) { // cap/store it
112
+ lines.push(!lines.length ? currentLine : currentLine.trimStart())
113
+ currentLine = ''
114
+ }
115
+ currentLine += word
116
+ })
117
+ lines.push(!lines.length ? currentLine : currentLine.trimStart())
118
+
119
+ // Print formatted msg
120
+ lines.forEach((line, idx) => console.info(prefix +(
121
+ idx == 0 ? line // print 1st line unindented
122
+ : ' '.repeat(indent) + line // print subsequent lines indented
123
+ )))
124
+ }
125
+ },
126
+
127
+ helpDocsCmdsDocsURL() {
128
+ console.info(`\n${
129
+ cli.msgs.info_moreHelp}, ${cli.msgs.info_type} ${
130
+ colors.bw}${cli.name} --<docs|help>${colors.nc} ${
131
+ cli.msgs.info_or} ${cli.msgs.info_visit}\n${colors.by}${cli.urls.docs}${colors.nc}`
132
+ )
133
+ },
134
+
135
+ initCmd(invalidKey) {
136
+ if (invalidKey)
137
+ this.warn(
138
+ `${cli.msgs.error_invalidKey} '${invalidKey}' ${cli.msgs.error_foundIn}\n`
139
+ + `${log.colors.gry}${cli.configPath}`
140
+ )
141
+ if (!this.initTipped) {
142
+ this.break()
143
+ this.tip(`${
144
+ string.toTitleCase(cli.msgs.info_type)} '${cli.name} init' ${
145
+ cli.msgs.info_toCreateDefaultConfig}`
146
+ )
147
+ this.initTipped = true
148
+ }
149
+ },
150
+
151
+ invalidConfigKey(key) { if (!this[`${key}Tipped`]) { this.initCmd(key) ; this[`${key}Tipped`] = true } },
152
+
153
+ async stats(pkgName = cli.name, options = { ecosystem: 'npm', maxDays: 8, maxVers: 5, scheme: 'default' }) {
154
+ const pkgStats = await getDownloads(pkgName, options),
155
+ schemeData = colors.schemes[options.scheme]
156
+ if (!schemeData) return this.error(`Scheme '${options.scheme}' not found!`)
157
+ const colorMap = Object.fromEntries(schemeData.map((hex, idx) => [`c${idx}`, hex])),
158
+ statsTable = new (require('console-table-printer').Table)({ colorMap })
159
+ pkgStats.forEach((row, idx) => // build colored rows
160
+ statsTable.addRow(row, { color: `c${Math.floor(idx / pkgStats.length * schemeData.length)}` }))
161
+ statsTable.printTable()
162
+ },
163
+
164
+ version() {
165
+ this.info(cli.name)
166
+ this.data(`${
167
+ cli.msgs.prefix_globalVer}: ${ getVer('global') || 'none' }\n${
168
+ cli.msgs.prefix_localVer }: ${ getVer('local') || 'none' }`
169
+ )
170
+ }
171
+ }