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 +16 -16
- package/dist/cli/index.js +43 -0
- package/dist/cli/lib/color.js +31 -0
- package/dist/cli/lib/data.js +30 -0
- package/dist/cli/lib/init.js +57 -0
- package/dist/cli/lib/jsdelivr.js +10 -0
- package/dist/cli/lib/language.js +113 -0
- package/dist/cli/lib/log.js +171 -0
- package/dist/cli/lib/pkg.js +78 -0
- package/dist/cli/lib/settings.js +155 -0
- package/dist/cli/lib/string.js +3 -0
- package/dist/data/generate-pw.config.mjs +2 -2
- package/dist/data/messages.json +14 -4
- package/dist/data/package-data.json +2 -1
- package/dist/generate-pw.min.js +1 -1
- package/docs/README.md +16 -16
- package/package.json +11 -4
- package/dist/cli/index.min.js +0 -8
- package/dist/cli/lib/data.min.js +0 -6
- package/dist/cli/lib/init.min.js +0 -6
- package/dist/cli/lib/jsdelivr.min.js +0 -6
- package/dist/cli/lib/language.min.js +0 -7
- package/dist/cli/lib/log.min.js +0 -22
- package/dist/cli/lib/pkg.min.js +0 -6
- package/dist/cli/lib/settings.min.js +0 -7
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://
|
|
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.
|
|
29
|
-
<img height=31 src="https://img.shields.io/badge/Latest_Build-2.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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. | `
|
|
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
|
-
|
|
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://
|
|
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
|
-
-
|
|
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
|
-
|
|
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://
|
|
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) <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://
|
|
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) <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
|
+
}
|